diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000000..a93ec32a975 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,54 @@ +os: Visual Studio 2017 + +version: ci-{build} + +environment: + matrix: + - BUILD_SYSTEM: CMAKE + - BUILD_SYSTEM: MSVC + +matrix: + allow_failures: + - BUILD_SYSTEM: CMAKE + +init: + - git config --global core.eol native + - git config --global core.autocrlf true + +clone_depth: 50 + +skip_commits: + files: + - default/ + - '**/*.md' + - '**/*.xml' + - '**/COPYING' + message: /.*\[(skip appveyor)|(appveyor skip)\].*/ + +install: + - cd .. + - ps: Start-FileDownload https://github.com/freeorion/freeorion-sdk/releases/download/v11/FreeOrionSDK_11_MSVC-v141-Win32.zip -FileName FreeOrionSDK.zip + - unzip -q FreeOrionSDK.zip + - cp bin/* freeorion/ + - cd freeorion + +before_build: + - if "%BUILD_SYSTEM%" == "MSVC" ( + cd msvc2017 + ) + - if "%BUILD_SYSTEM%" == "CMAKE" ( + mkdir build + ) + +build_script: + - if "%BUILD_SYSTEM%" == "MSVC" ( + msbuild FreeOrion.sln /maxcpucount /property:BuildInParallel=true /property:CL_MPCount=2 /property:PlatformToolset=v141 /property:Configuration=Release /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" /verbosity:minimal + ) + - if "%BUILD_SYSTEM%" == "CMAKE" ( + pushd build && + cmake -G "Visual Studio 15 2017" -T v141 -A Win32 -DBUILD_TESTING=On .. && + cmake --build . --config "Release" -- /maxcpucount /property:BuildInParallel=true /property:CL_MPCount=2 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" /verbosity:minimal && + popd + ) + +test: off diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..40d0fd206ac --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Code owners for translation stringtables +ru.txt @Cjkjvfnby @o01eg diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug.md similarity index 64% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug.md index 797048e643c..b1292b099f7 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,24 +1,30 @@ +--- +name: Report bug +about: Report problems and bugs with FreeOrion. +labels: "category:bug" +--- +Bug Report +========== -### Environment +Environment +----------- -### Expected Result +Expected Result +--------------- -### Steps to reproduce +Steps to reproduce +------------------ * First step. * Second step. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..d58038ed51e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: FreeOrion Forum + url: https://freeorion.org/forum/ + about: Get in touch with the FreeOrion developers and community for gameplay support diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 00000000000..e21d65b98c5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,31 @@ +--- +name: Feature request +about: Propose a new or improved functionality to FreeOrion. +labels: "category:feature" +--- +Feature request +=============== + +Idea +---- + + + +Gain +---- + + + +Discussion +---------- + + diff --git a/.github/RELEASE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/release.md similarity index 91% rename from .github/RELEASE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/release.md index 3d17d14514a..07fd726feba 100644 --- a/.github/RELEASE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -1,6 +1,12 @@ +--- +name: Publish release +about: "Team only: Create a tracking issue for a FreeOrion release." +title: Release X.Y.Z preparation +assignees: Vezzra +--- index; ) + for (int i = index + node->GetWeight(); i-->index; ) m_column[i] = node; node->m_row = index; @@ -77,7 +77,7 @@ bool TechTreeLayout::Column::Place(int index, TechTreeLayout::Node* node) { bool TechTreeLayout::Column::Move(int to, TechTreeLayout::Node* node) { if (Fit(to, node)) { - for ( int i = node->m_row + node->GetWeight(); i-->node->m_row; ) + for (int i = node->m_row + node->GetWeight(); i-->node->m_row; ) m_column[i] = nullptr; Place(to, node); @@ -148,14 +148,14 @@ const std::string& TechTreeLayout::Edge::GetTechTo() const void TechTreeLayout::Edge::AddPoint(double x, double y) { m_points.push_back(std::pair(x, y)); } -void TechTreeLayout::Edge::ReadPoints(std::vector> & points) const { - for(const std::pair& p : m_points) +void TechTreeLayout::Edge::ReadPoints(std::vector>& points) const { + for (const auto& p : m_points) points.push_back(p); } void TechTreeLayout::Edge::Debug() const { DebugLogger() << "Edge " << m_from << "-> " << m_to << ": "; - for(const std::pair& p : m_points) + for (const auto& p : m_points) DebugLogger() << "(" << p.first << "," << p.second << ") "; DebugLogger() << "\n"; } @@ -217,7 +217,7 @@ void TechTreeLayout::DoLayout(double column_width, double row_height, double x_m nodes_at_each_depth[node->GetDepth()].push_back(node); } // sort within each depth column - for (std::vector& level : nodes_at_each_depth) + for (auto& level : nodes_at_each_depth) { std::sort(level.begin(), level.end(), NodePointerCmp()); } @@ -297,7 +297,7 @@ const GG::Y TechTreeLayout::GetHeight() const { return GG::Y(static_cast(m_height)); } const TechTreeLayout::Node* TechTreeLayout::GetNode(const std::string & name) const { - std::map< std::string, TechTreeLayout::Node*>::const_iterator item = m_node_map.find(name); + auto item = m_node_map.find(name); if (item == m_node_map.end()) { DebugLogger() << "TechTreeLayout::getNode: missing node " << name << "\n"; Debug(); @@ -316,14 +316,14 @@ void TechTreeLayout::AddNode(const std::string& tech, GG::X width, GG::Y height) } void TechTreeLayout::AddEdge(const std::string& parent, const std::string& child) { - std::map::iterator p = m_node_map.find(parent); - std::map::iterator c = m_node_map.find(child); + auto p = m_node_map.find(parent); + auto c = m_node_map.find(child); assert(p != m_node_map.end() && c != m_node_map.end()); p->second->AddChild(c->second); } const std::vector& TechTreeLayout::GetOutEdges(const std::string& name) const { - std::map< std::string, TechTreeLayout::Node*>::const_iterator item = m_node_map.find(name); + auto item = m_node_map.find(name); if (item == m_node_map.end()) { DebugLogger() << "TechTreeLayout::getNode: missing node " << name << "\n"; Debug(); @@ -657,14 +657,14 @@ void TechTreeLayout::Node::DoLayout(std::vector& row_index, bool cat) { int index = 0; int count = 0; //check children - for(int i = m_children.size(); i --> 0;) { + for (int i = m_children.size(); i --> 0;) { if (m_children[i]->m_row != -1) { index += m_children[i]->m_row; count++; } } //check parents - for(int i = m_parents.size(); i --> 0;) { + for (int i = m_parents.size(); i --> 0;) { if (m_parents[i]->m_row != -1) { index += m_parents[i]->m_row; count++; diff --git a/UI/TechTreeWnd.cpp b/UI/TechTreeWnd.cpp index f0b30f249cf..be0571c9f86 100644 --- a/UI/TechTreeWnd.cpp +++ b/UI/TechTreeWnd.cpp @@ -12,16 +12,17 @@ #include "../util/Logger.h" #include "../util/OptionsDB.h" #include "../util/Directories.h" +#include "../util/ScopedTimer.h" +#include "../universe/Effects.h" +#include "../universe/Enums.h" #include "../universe/Tech.h" -#include "../universe/Effect.h" +#include "../universe/UnlockableItem.h" #include "../universe/ValueRef.h" -#include "../universe/Enums.h" #include "../Empire/Empire.h" #include "TechTreeLayout.h" #include "TechTreeArcs.h" #include "Hotkeys.h" -#include #include #include #include @@ -29,49 +30,42 @@ #include -#include -#include - namespace { const std::string RES_PEDIA_WND_NAME = "research.pedia"; - const std::string RES_CONTROLS_WND_NAME = "research.tech-controls"; + const std::string RES_CONTROLS_WND_NAME = "research.control"; // command-line options void AddOptions(OptionsDB& db) { - db.Add("UI.tech-layout-horz-spacing", UserStringNop("OPTIONS_DB_UI_TECH_LAYOUT_HORZ_SPACING"), 0.25, RangedStepValidator(0.25, 0.25, 4.0)); - db.Add("UI.tech-layout-vert-spacing", UserStringNop("OPTIONS_DB_UI_TECH_LAYOUT_VERT_SPACING"), 0.75, RangedStepValidator(0.25, 0.25, 4.0)); - db.Add("UI.tech-layout-zoom-scale", UserStringNop("OPTIONS_DB_UI_TECH_LAYOUT_ZOOM_SCALE"), 1.0, RangedStepValidator(1.0, -25.0, 10.0)); - db.Add("UI.tech-controls-graphic-size", UserStringNop("OPTIONS_DB_UI_TECH_CTRL_ICON_SIZE"), 3.0, RangedStepValidator(0.25, 0.5, 12.0)); - db.Add("UI.windows." + RES_PEDIA_WND_NAME + ".persistently-hidden", UserStringNop("OPTIONS_DB_RESEARCH_PEDIA_HIDDEN"), false, Validator()); + db.Add("ui.research.tree.spacing.horizontal", UserStringNop("OPTIONS_DB_UI_TECH_LAYOUT_HORZ_SPACING"), + 0.25, RangedStepValidator(0.25, 0.25, 4.0)); + db.Add("ui.research.tree.spacing.vertical", UserStringNop("OPTIONS_DB_UI_TECH_LAYOUT_VERT_SPACING"), + 0.75, RangedStepValidator(0.25, 0.25, 4.0)); + db.Add("ui.research.tree.zoom.scale", UserStringNop("OPTIONS_DB_UI_TECH_LAYOUT_ZOOM_SCALE"), + 1.0, RangedStepValidator(1.0, -25.0, 10.0)); + db.Add("ui.research.control.graphic.size", UserStringNop("OPTIONS_DB_UI_TECH_CTRL_ICON_SIZE"), + 3.0, RangedStepValidator(0.25, 0.5, 12.0)); + db.Add("ui." + RES_PEDIA_WND_NAME + ".hidden.enabled", UserStringNop("OPTIONS_DB_RESEARCH_PEDIA_HIDDEN"), false); // TechListBox::TechRow column widths int default_pts = 16; - db.Add("UI.research.listbox.column-widths.graphic", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_GRAPHIC"), - default_pts * 2, StepValidator(1)); - db.Add("UI.research.listbox.column-widths.name", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_NAME"), + db.Add("ui.research.list.column.graphic.width", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_GRAPHIC"), + default_pts * 3, StepValidator(1)); + db.Add("ui.research.list.column.name.width", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_NAME"), default_pts * 18, StepValidator(1)); - db.Add("UI.research.listbox.column-widths.cost", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_COST"), + db.Add("ui.research.list.column.cost.width", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_COST"), default_pts * 8, StepValidator(1)); - db.Add("UI.research.listbox.column-widths.time", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_TIME"), + db.Add("ui.research.list.column.time.width", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_TIME"), default_pts * 6, StepValidator(1)); - db.Add("UI.research.listbox.column-widths.category", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_CATEGORY"), + db.Add("ui.research.list.column.category.width", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_CATEGORY"), default_pts * 12, StepValidator(1)); - db.Add("UI.research.listbox.column-widths.description", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_DESCRIPTION"), + db.Add("ui.research.list.column.description.width", UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_DESCRIPTION"), default_pts * 18, StepValidator(1)); // Default status for TechTreeControl filter. - db.Add("UI.research.tech-tree-control.status.unresearchable", - UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_UNRESEARCHABLE"), - false, Validator()); - db.Add("UI.research.tech-tree-control.status.has_researched_prereq", - UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_HAS_RESEARCHED_PREREQ"), - true, Validator()); - db.Add("UI.research.tech-tree-control.status.researchable", - UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_RESEARCHABLE"), - true, Validator()); - db.Add("UI.research.tech-tree-control.status.completed", - UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_COMPLETED"), - true, Validator()); + db.Add("ui.research.status.unresearchable.shown", UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_UNRESEARCHABLE"), false); + db.Add("ui.research.status.partial.shown", UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_HAS_RESEARCHED_PREREQ"), true); + db.Add("ui.research.status.researchable.shown", UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_RESEARCHABLE"), true); + db.Add("ui.research.status.completed.shown", UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_COMPLETED"), true); } bool temp_bool = RegisterOptions(&AddOptions); @@ -100,32 +94,25 @@ namespace { return false; // check that category is visible - if (categories_shown.find(tech->Category()) == categories_shown.end()) + if (!categories_shown.count(tech->Category())) return false; // check tech status const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID()); if (!empire) return true; // if no empire, techs have no status, so just return true - if (statuses_shown.find(empire->GetTechStatus(tech_name)) == statuses_shown.end()) + if (!statuses_shown.count(empire->GetTechStatus(tech_name))) return false; // all tests pass, so tech is visible return true; } - - // Duplicated in MapWnd.cpp/ObjectListWnd.cpp - const std::locale& GetLocale() { - static boost::locale::generator gen; - static const std::locale& loc = gen("en_US.UTF-8"); // manually sets locale - return loc; - } } /////////////////////////// -// TechPanelRowBrowseWnd // +// TechRowBrowseWnd // /////////////////////////// -std::shared_ptr TechPanelRowBrowseWnd(const std::string& tech_name, int empire_id) { +std::shared_ptr TechRowBrowseWnd(const std::string& tech_name, int empire_id) { const Empire* empire = GetEmpire(empire_id); const Tech* tech = GetTech(tech_name); if (!tech) @@ -133,7 +120,7 @@ std::shared_ptr TechPanelRowBrowseWnd(const std::string& tech std::string main_text; - main_text += UserString(tech->Category()) + " "; + main_text += UserString(tech->Category()) + " - "; main_text += UserString(tech->ShortDescription()) + "\n"; if (empire) { @@ -168,7 +155,7 @@ std::shared_ptr TechPanelRowBrowseWnd(const std::string& tech } const ResearchQueue& queue = empire->GetResearchQueue(); - ResearchQueue::const_iterator queue_it = queue.find(tech_name); + auto queue_it = queue.find(tech_name); if (queue_it != queue.end()) { main_text += UserString("TECH_WND_ENQUEUED") + "\n"; @@ -274,7 +261,7 @@ TechTreeWnd::TechTreeControls::TechTreeControls(const std::string& config_name) {} void TechTreeWnd::TechTreeControls::CompleteConstruction() { - const int tooltip_delay = GetOptionsDB().Get("UI.tooltip-delay"); + const int tooltip_delay = GetOptionsDB().Get("ui.tooltip.delay"); const boost::filesystem::path icon_dir = ClientUI::ArtDir() / "icons" / "tech" / "controls"; // create a button for each tech category... @@ -303,7 +290,7 @@ void TechTreeWnd::TechTreeControls::CompleteConstruction() { m_status_buttons[TS_UNRESEARCHABLE]->SetBrowseInfoWnd(GG::Wnd::Create(UserString("TECH_WND_STATUS_LOCKED"), "")); m_status_buttons[TS_UNRESEARCHABLE]->SetBrowseModeTime(tooltip_delay); m_status_buttons[TS_UNRESEARCHABLE]->SetCheck( - GetOptionsDB().Get("UI.research.tech-tree-control.status.unresearchable")); + GetOptionsDB().Get("ui.research.status.unresearchable.shown")); AttachChild(m_status_buttons[TS_UNRESEARCHABLE]); m_status_buttons[TS_HAS_RESEARCHED_PREREQ] = GG::Wnd::Create("", ClientUI::GetFont(), GG::FORMAT_NONE, GG::CLR_ZERO, @@ -311,7 +298,7 @@ void TechTreeWnd::TechTreeControls::CompleteConstruction() { m_status_buttons[TS_HAS_RESEARCHED_PREREQ]->SetBrowseInfoWnd(GG::Wnd::Create(UserString("TECH_WND_STATUS_PARTIAL_UNLOCK"), "")); m_status_buttons[TS_HAS_RESEARCHED_PREREQ]->SetBrowseModeTime(tooltip_delay); m_status_buttons[TS_HAS_RESEARCHED_PREREQ]->SetCheck( - GetOptionsDB().Get("UI.research.tech-tree-control.status.has_researched_prereq")); + GetOptionsDB().Get("ui.research.status.partial.shown")); AttachChild(m_status_buttons[TS_HAS_RESEARCHED_PREREQ]); m_status_buttons[TS_RESEARCHABLE] = GG::Wnd::Create("", ClientUI::GetFont(), GG::FORMAT_NONE, GG::CLR_ZERO, @@ -319,7 +306,7 @@ void TechTreeWnd::TechTreeControls::CompleteConstruction() { m_status_buttons[TS_RESEARCHABLE]->SetBrowseInfoWnd(GG::Wnd::Create(UserString("TECH_WND_STATUS_RESEARCHABLE"), "")); m_status_buttons[TS_RESEARCHABLE]->SetBrowseModeTime(tooltip_delay); m_status_buttons[TS_RESEARCHABLE]->SetCheck( - GetOptionsDB().Get("UI.research.tech-tree-control.status.researchable")); + GetOptionsDB().Get("ui.research.status.researchable.shown")); AttachChild(m_status_buttons[TS_RESEARCHABLE]); m_status_buttons[TS_COMPLETE] = GG::Wnd::Create("", ClientUI::GetFont(), GG::FORMAT_NONE, GG::CLR_ZERO, @@ -327,7 +314,7 @@ void TechTreeWnd::TechTreeControls::CompleteConstruction() { m_status_buttons[TS_COMPLETE]->SetBrowseInfoWnd(GG::Wnd::Create(UserString("TECH_WND_STATUS_COMPLETED"), "")); m_status_buttons[TS_COMPLETE]->SetBrowseModeTime(tooltip_delay); m_status_buttons[TS_COMPLETE]->SetCheck( - GetOptionsDB().Get("UI.research.tech-tree-control.status.completed")); + GetOptionsDB().Get("ui.research.status.completed.shown")); AttachChild(m_status_buttons[TS_COMPLETE]); // create button to switch between tree and list views @@ -345,13 +332,15 @@ void TechTreeWnd::TechTreeControls::CompleteConstruction() { CUIWnd::CompleteConstruction(); DoButtonLayout(); + SaveDefaultedOptions(); + SaveOptions(); } void TechTreeWnd::TechTreeControls::DoButtonLayout() { const int PTS = ClientUI::Pts(); const GG::X RIGHT_EDGE_PAD(PTS / 3); const GG::X USABLE_WIDTH = std::max(ClientWidth() - RIGHT_EDGE_PAD, GG::X1); // space in which to do layout - const GG::X BUTTON_WIDTH = GG::X(PTS * std::max(GetOptionsDB().Get("UI.tech-controls-graphic-size"), 0.5)); + const GG::X BUTTON_WIDTH = GG::X(PTS * std::max(GetOptionsDB().Get("ui.research.control.graphic.size"), 0.5)); const GG::Y BUTTON_HEIGHT = GG::Y(Value(BUTTON_WIDTH)); m_col_offset = BUTTON_WIDTH + BUTTON_SEPARATION; // horizontal distance between each column of buttons @@ -512,7 +501,7 @@ class TechTreeWnd::LayoutPanel : public GG::Wnd { std::set GetCategoriesShown() const; std::set GetTechStatusesShown() const; - mutable TechTreeWnd::TechClickSignalType TechLeftClickedSignal; + mutable TechTreeWnd::TechClickSignalType TechSelectedSignal; mutable TechTreeWnd::TechClickSignalType TechDoubleClickedSignal; mutable TechTreeWnd::TechSignalType TechPediaDisplaySignal; //@} @@ -531,7 +520,7 @@ class TechTreeWnd::LayoutPanel : public GG::Wnd { void HideAllCategories(); void ShowStatus(TechStatus status); void HideStatus(TechStatus status); - void ShowTech(const std::string& tech_name); + void SelectTech(const std::string& tech_name); void CenterOnTech(const std::string& tech_name); void DoZoom(const GG::Pt &pt) const; void UndoZoom() const; @@ -575,9 +564,6 @@ class TechTreeWnd::LayoutPanel : public GG::Wnd { void ScrolledSlot(int, int, int, int); - void TechLeftClickedSlot(const std::string& tech_name, - const GG::Flags& modkeys); - void TreeDraggedSlot(const GG::Pt& move); void TreeDragBegin(const GG::Pt& move); void TreeDragEnd(const GG::Pt& move); @@ -586,25 +572,25 @@ class TechTreeWnd::LayoutPanel : public GG::Wnd { bool TreeZoomOutKeyboard(); void ConnectKeyboardAcceleratorSignals(); - double m_scale; + double m_scale = INITIAL_SCALE; std::set m_categories_shown; std::set m_tech_statuses_shown; std::string m_selected_tech_name; std::string m_browsed_tech_name; TechTreeLayout m_graph; - std::map> m_techs; - TechTreeArcs m_dependency_arcs; - - std::shared_ptr m_layout_surface; - std::shared_ptr m_vscroll; - std::shared_ptr m_hscroll; - double m_scroll_position_x; //actual scroll position - double m_scroll_position_y; - double m_drag_scroll_position_x;//position when drag started - double m_drag_scroll_position_y; - std::shared_ptr m_zoom_in_button; - std::shared_ptr m_zoom_out_button; + std::map> m_techs; + TechTreeArcs m_dependency_arcs; + + std::shared_ptr m_layout_surface; + std::shared_ptr m_vscroll; + std::shared_ptr m_hscroll; + double m_scroll_position_x = 0.0; //actual scroll position + double m_scroll_position_y = 0.0; + double m_drag_scroll_position_x = 0.0; //position when drag started + double m_drag_scroll_position_y = 0.0; + std::shared_ptr m_zoom_in_button; + std::shared_ptr m_zoom_out_button; }; @@ -683,15 +669,7 @@ class TechTreeWnd::LayoutPanel::TechPanel : public GG::Wnd { TechTreeWnd::LayoutPanel::TechPanel::TechPanel(const std::string& tech_name, const LayoutPanel* panel) : GG::Wnd(GG::X0, GG::Y0, TechPanelWidth(), TechPanelHeight(), GG::INTERACTIVE), m_tech_name(tech_name), - m_name_text(), - m_cost_and_duration_text(), - m_eta_text(), m_layout_panel(panel), - m_icon(nullptr), - m_unlock_icons(), - m_name_label(nullptr), - m_cost_and_duration_label(nullptr), - m_eta_label(nullptr), m_colour(GG::CLR_GRAY), m_status(TS_RESEARCHABLE), m_browse_highlight(false), @@ -709,11 +687,12 @@ TechTreeWnd::LayoutPanel::TechPanel::TechPanel(const std::string& tech_name, con // intentionally not attaching as child; TechPanel::Render the child Render() function instead. - SetBrowseModeTime(GetOptionsDB().Get("UI.tooltip-delay")); + SetBrowseModeTime(GetOptionsDB().Get("ui.tooltip.delay")); } void TechTreeWnd::LayoutPanel::TechPanel::CompleteConstruction() { GG::Wnd::CompleteConstruction(); + SetBrowseModeTime(GetOptionsDB().Get("ui.tooltip.delay")); Update(); } @@ -906,12 +885,16 @@ void TechTreeWnd::LayoutPanel::TechPanel::LClick(const GG::Pt& pt, GG::Flags mod_keys) { - auto dclick_action = [this, pt, mod_keys]() { LDoubleClick(pt, mod_keys); }; + auto dclick_action = [this, pt]() { LDoubleClick(pt, GG::Flags()); }; + auto ctrl_dclick_action = [this, pt]() { LDoubleClick(pt, GG::MOD_KEY_CTRL); }; auto pedia_display_action = [this]() { TechPediaDisplaySignal(m_tech_name); }; auto popup = GG::Wnd::Create(pt.x, pt.y); - if (!(m_enqueued) && !(m_status == TS_COMPLETE)) + if (!(m_enqueued) && !(m_status == TS_COMPLETE)) { popup->AddMenuItem(GG::MenuItem(UserString("PRODUCTION_DETAIL_ADD_TO_QUEUE"), false, false, dclick_action)); + popup->AddMenuItem(GG::MenuItem(UserString("PRODUCTION_DETAIL_ADD_TO_TOP_OF_QUEUE"), false, false, + ctrl_dclick_action)); + } std::string popup_label = boost::io::str(FlexibleFormat(UserString("ENC_LOOKUP")) % UserString(m_tech_name)); popup->AddMenuItem(GG::MenuItem(popup_label, false, false, pedia_display_action)); @@ -940,7 +923,7 @@ void TechTreeWnd::LayoutPanel::TechPanel::Update() { m_enqueued = empire->GetResearchQueue().InQueue(m_tech_name); const ResearchQueue& queue = empire->GetResearchQueue(); - ResearchQueue::const_iterator queue_it = queue.find(m_tech_name); + auto queue_it = queue.find(m_tech_name); if (queue_it != queue.end()) { m_eta = queue_it->turns_left; if (m_eta != -1) @@ -974,7 +957,7 @@ void TechTreeWnd::LayoutPanel::TechPanel::Update() { GG::Y icon_top = icon_bottom - icon_height; // add icons for unlocked items - for (const ItemSpec& item : tech->UnlockedItems()) { + for (const UnlockableItem& item : tech->UnlockedItems()) { std::shared_ptr texture; switch (item.type) { case UIT_BUILDING: texture = ClientUI::BuildingIcon(item.name); break; @@ -994,18 +977,18 @@ void TechTreeWnd::LayoutPanel::TechPanel::Update() { std::set meters_affected; std::set specials_affected; std::set parts_whose_meters_are_affected; - for (std::shared_ptr effects_group : tech->Effects()) { - for (Effect::EffectBase* effect : effects_group->EffectsList()) { + for (auto& effects_group : tech->Effects()) { + for (Effect::Effect* effect : effects_group->EffectsList()) { if (const Effect::SetMeter* set_meter_effect = dynamic_cast(effect)) { meters_affected.insert(set_meter_effect->GetMeterType()); } else if (const Effect::SetShipPartMeter* set_ship_part_meter_effect = dynamic_cast(effect)) { - const ValueRef::ValueRefBase* part_name = set_ship_part_meter_effect->GetPartName(); + const ValueRef::ValueRef* part_name = set_ship_part_meter_effect->GetPartName(); if (part_name && part_name->ConstantExpr()) parts_whose_meters_are_affected.insert(part_name->Eval()); } else if (const Effect::AddSpecial* add_special_effect = dynamic_cast(effect)) { - const ValueRef::ValueRefBase* special_name = add_special_effect->GetSpecialName(); + const ValueRef::ValueRef* special_name = add_special_effect->GetSpecialName(); if (special_name && special_name->ConstantExpr()) specials_affected.insert(special_name->Eval()); } @@ -1056,7 +1039,7 @@ void TechTreeWnd::LayoutPanel::TechPanel::Update() { m_eta_label->SetText("" + m_eta_text + ""); ClearBrowseInfoWnd(); - SetBrowseInfoWnd(TechPanelRowBrowseWnd(m_tech_name, client_empire_id)); + SetBrowseInfoWnd(TechRowBrowseWnd(m_tech_name, client_empire_id)); RequirePreRender(); } @@ -1066,18 +1049,7 @@ void TechTreeWnd::LayoutPanel::TechPanel::Update() { // TechTreeWnd::LayoutPanel // ////////////////////////////////////////////////// TechTreeWnd::LayoutPanel::LayoutPanel(GG::X w, GG::Y h) : - GG::Wnd(GG::X0, GG::Y0, w, h, GG::INTERACTIVE), - m_scale(INITIAL_SCALE), - m_categories_shown(), - m_tech_statuses_shown(), - m_selected_tech_name(), - m_browsed_tech_name(), - m_graph(), - m_layout_surface(nullptr), - m_vscroll(nullptr), - m_hscroll(nullptr), - m_zoom_in_button(nullptr), - m_zoom_out_button(nullptr) + GG::Wnd(GG::X0, GG::Y0, w, h, GG::INTERACTIVE) {} void TechTreeWnd::LayoutPanel::CompleteConstruction() { @@ -1085,7 +1057,7 @@ void TechTreeWnd::LayoutPanel::CompleteConstruction() { SetChildClippingMode(ClipToClient); - m_scale = std::pow(ZOOM_STEP_SIZE, GetOptionsDB().Get("UI.tech-layout-zoom-scale")); //(LATHANDA) Initialise Fullzoom and do real zooming using GL. TODO: Check best size + m_scale = std::pow(ZOOM_STEP_SIZE, GetOptionsDB().Get("ui.research.tree.zoom.scale")); // (LATHANDA) Initialise Fullzoom and do real zooming using GL. TODO: Check best size m_layout_surface = GG::Wnd::Create(); @@ -1139,13 +1111,13 @@ void TechTreeWnd::LayoutPanel::CompleteConstruction() { void TechTreeWnd::LayoutPanel::ConnectKeyboardAcceleratorSignals() { HotkeyManager* hkm = HotkeyManager::GetManager(); - hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomInKeyboard, this), "map.zoom_in", + hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomInKeyboard, this), "ui.zoom.in", AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition})); - hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomInKeyboard, this), "map.zoom_in_alt", + hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomInKeyboard, this), "ui.zoom.in.alt", AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition})); - hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomOutKeyboard, this), "map.zoom_out", + hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomOutKeyboard, this), "ui.zoom.out", AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition})); - hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomOutKeyboard, this), "map.zoom_out_alt", + hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomOutKeyboard, this), "ui.zoom.out.alt", AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition})); hkm->RebuildShortcuts(); @@ -1242,14 +1214,14 @@ void TechTreeWnd::LayoutPanel::SetScale(double scale) { if (MAX_SCALE < scale) scale = MAX_SCALE; m_scale = scale; - GetOptionsDB().Set("UI.tech-layout-zoom-scale", std::floor(0.1 + (std::log(m_scale) / std::log(ZOOM_STEP_SIZE)))); + GetOptionsDB().Set("ui.research.tree.zoom.scale", std::floor(0.1 + (std::log(m_scale) / std::log(ZOOM_STEP_SIZE)))); for (auto& entry : m_techs) entry.second->RequirePreRender(); } void TechTreeWnd::LayoutPanel::ShowCategory(const std::string& category) { - if (m_categories_shown.find(category) == m_categories_shown.end()) { + if (!m_categories_shown.count(category)) { m_categories_shown.insert(category); Layout(true); } @@ -1280,7 +1252,7 @@ void TechTreeWnd::LayoutPanel::HideAllCategories() { } void TechTreeWnd::LayoutPanel::ShowStatus(TechStatus status) { - if (m_tech_statuses_shown.find(status) == m_tech_statuses_shown.end()) { + if (!m_tech_statuses_shown.count(status)) { m_tech_statuses_shown.insert(status); Layout(true); } @@ -1294,9 +1266,6 @@ void TechTreeWnd::LayoutPanel::HideStatus(TechStatus status) { } } -void TechTreeWnd::LayoutPanel::ShowTech(const std::string& tech_name) -{ TechLeftClickedSlot(tech_name, GG::Flags()); } - void TechTreeWnd::LayoutPanel::CenterOnTech(const std::string& tech_name) { const auto& it = m_techs.find(tech_name); if (it == m_techs.end()) { @@ -1354,8 +1323,8 @@ GG::Pt TechTreeWnd::LayoutPanel::ConvertPtZoomedToScreen(const GG::Pt& pt) const void TechTreeWnd::LayoutPanel::Layout(bool keep_position) { const GG::X TECH_PANEL_MARGIN_X(ClientUI::Pts()*16); const GG::Y TECH_PANEL_MARGIN_Y(ClientUI::Pts()*16 + 100); - const double RANK_SEP = Value(TechPanelWidth()) * GetOptionsDB().Get("UI.tech-layout-horz-spacing"); - const double NODE_SEP = Value(TechPanelHeight()) * GetOptionsDB().Get("UI.tech-layout-vert-spacing"); + const double RANK_SEP = Value(TechPanelWidth()) * GetOptionsDB().Get("ui.research.tree.spacing.horizontal"); + const double NODE_SEP = Value(TechPanelHeight()) * GetOptionsDB().Get("ui.research.tree.spacing.vertical"); const double WIDTH = Value(TechPanelWidth()); const double HEIGHT = Value(TechPanelHeight()); const double X_MARGIN(12); @@ -1374,7 +1343,7 @@ void TechTreeWnd::LayoutPanel::Layout(bool keep_position) { // create a node for every tech TechManager& manager = GetTechManager(); - for (const Tech* tech : manager) { + for (const auto& tech : manager) { if (!tech) continue; const std::string& tech_name = tech->Name(); if (!TechVisible(tech_name, m_categories_shown, m_tech_statuses_shown)) continue; @@ -1383,7 +1352,7 @@ void TechTreeWnd::LayoutPanel::Layout(bool keep_position) { } // create an edge for every prerequisite - for (const Tech* tech : manager) { + for (const auto& tech : manager) { if (!tech) continue; const std::string& tech_name = tech->Name(); if (!TechVisible(tech_name, m_categories_shown, m_tech_statuses_shown)) continue; @@ -1405,7 +1374,7 @@ void TechTreeWnd::LayoutPanel::Layout(bool keep_position) { std::set visible_techs; // create new tech panels and new dependency arcs - for (const Tech* tech : manager) { + for (const auto& tech : manager) { if (!tech) continue; const std::string& tech_name = tech->Name(); if (!TechVisible(tech_name, m_categories_shown, m_tech_statuses_shown)) continue; @@ -1416,7 +1385,7 @@ void TechTreeWnd::LayoutPanel::Layout(bool keep_position) { tech_panel->MoveTo(GG::Pt(node->GetX(), node->GetY())); m_layout_surface->AttachChild(tech_panel); tech_panel->TechLeftClickedSignal.connect( - boost::bind(&TechTreeWnd::LayoutPanel::TechLeftClickedSlot, this, _1, _2)); + boost::bind(&TechTreeWnd::LayoutPanel::SelectTech, this, _1)); tech_panel->TechDoubleClickedSignal.connect( TechDoubleClickedSignal); tech_panel->TechPediaDisplaySignal.connect( @@ -1441,7 +1410,7 @@ void TechTreeWnd::LayoutPanel::Layout(bool keep_position) { if (keep_position) { m_selected_tech_name = selected_tech; // select clicked on tech - if (m_techs.find(m_selected_tech_name) != m_techs.end()) + if (m_techs.count(m_selected_tech_name)) m_techs[m_selected_tech_name]->Select(true); double hscroll_page_size_ratio = m_hscroll->PageSize() / initial_hscroll_page_size; double vscroll_page_size_ratio = m_vscroll->PageSize() / initial_vscroll_page_size; @@ -1452,7 +1421,7 @@ void TechTreeWnd::LayoutPanel::Layout(bool keep_position) { } else { m_selected_tech_name.clear(); // find a tech to centre view on - for (const Tech* tech : manager) { + for (const auto& tech : manager) { const std::string& tech_name = tech->Name(); if (TechVisible(tech_name, m_categories_shown, m_tech_statuses_shown)) { CenterOnTech(tech_name); @@ -1471,17 +1440,16 @@ void TechTreeWnd::LayoutPanel::ScrolledSlot(int, int, int, int) { m_scroll_position_y = m_vscroll->PosnRange().first; } -void TechTreeWnd::LayoutPanel::TechLeftClickedSlot(const std::string& tech_name, - const GG::Flags& modkeys) +void TechTreeWnd::LayoutPanel::SelectTech(const std::string& tech_name) { // deselect previously-selected tech panel - if (m_techs.find(m_selected_tech_name) != m_techs.end()) + if (m_techs.count(m_selected_tech_name)) m_techs[m_selected_tech_name]->Select(false); // select clicked on tech - if (m_techs.find(tech_name) != m_techs.end()) + if (m_techs.count(tech_name)) m_techs[tech_name]->Select(true); m_selected_tech_name = tech_name; - TechLeftClickedSignal(tech_name, modkeys); + TechSelectedSignal(tech_name, GG::Flags()); } void TechTreeWnd::LayoutPanel::TreeDraggedSlot(const GG::Pt& move) { @@ -1533,14 +1501,12 @@ class TechTreeWnd::TechListBox : public CUIListBox { void CompleteConstruction() override; /** \name Accessors */ //@{ - std::set GetCategoriesShown() const; - std::set GetTechStatusesShown() const; bool TechRowCmp(const GG::ListBox::Row& lhs, const GG::ListBox::Row& rhs, std::size_t column); //@} //! \name Mutators //@{ void Reset(); - void Update(); + void Update(bool populate = true); void ShowCategory(const std::string& category); void ShowAllCategories(); @@ -1563,45 +1529,52 @@ class TechTreeWnd::TechListBox : public CUIListBox { void Render() override; - const std::string& GetTech() { return m_tech; } - static std::vector ColWidths(GG::X total_width); - static std::vector ColAlignments(); - void Update(); + const std::string& GetTech() { return m_tech; } + static std::vector ColWidths(GG::X total_width); + static std::vector ColAlignments(); + void Update(); private: std::string m_tech; + GG::Clr m_background_color; + bool m_enqueued; }; - void Populate(); - void TechDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags& modkeys); - void TechLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags& modkeys); - void TechRightClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags& modkeys); - void TechPediaDisplay(const std::string& tech_name); - void ToggleSortCol(unsigned int col); - - std::set m_categories_shown; - std::set m_tech_statuses_shown; - std::multimap> m_all_tech_rows; - std::shared_ptr m_header_row; - size_t m_previous_sort_col; + void Populate(bool update = true); + void TechDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags& modkeys); + void TechLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags& modkeys); + void TechRightClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags& modkeys); + void ToggleSortCol(unsigned int col); + + std::set m_categories_shown; + std::set m_tech_statuses_shown; + std::unordered_map> m_tech_row_cache; + std::shared_ptr m_header_row; + size_t m_previous_sort_col; }; void TechTreeWnd::TechListBox::TechRow::Render() { GG::Pt ul = UpperLeft(); GG::Pt lr = LowerRight(); - GG::FlatRectangle(ul, lr, ClientUI::WndColor(), GG::CLR_WHITE, 1); + GG::Pt offset{GG::X(3), GG::Y(3)}; + if (m_enqueued) { + GG::FlatRectangle(ul, lr, ClientUI::WndColor(), GG::CLR_WHITE, 1); + GG::FlatRoundedRectangle(ul + offset, lr - offset, m_background_color, GG::CLR_WHITE); + } else { + GG::FlatRectangle(ul, lr, m_background_color, GG::CLR_WHITE, 1); + } } std::vector TechTreeWnd::TechListBox::TechRow::ColWidths(GG::X total_width) { - GG::X graphic_width( GetOptionsDB().Get("UI.research.listbox.column-widths.graphic")); - GG::X name_width( GetOptionsDB().Get("UI.research.listbox.column-widths.name")); - GG::X cost_width( GetOptionsDB().Get("UI.research.listbox.column-widths.cost")); - GG::X time_width( GetOptionsDB().Get("UI.research.listbox.column-widths.time")); - GG::X category_width( GetOptionsDB().Get("UI.research.listbox.column-widths.category")); + GG::X graphic_width(GetOptionsDB().Get("ui.research.list.column.graphic.width")); + GG::X name_width(GetOptionsDB().Get("ui.research.list.column.name.width")); + GG::X cost_width(GetOptionsDB().Get("ui.research.list.column.cost.width")); + GG::X time_width(GetOptionsDB().Get("ui.research.list.column.time.width")); + GG::X category_width(GetOptionsDB().Get("ui.research.list.column.category.width")); GG::X cols_width_sum = graphic_width + name_width + cost_width + time_width + category_width; - GG::X desc_width(std::max(GetOptionsDB().Get("UI.research.listbox.column-widths.description"), + GG::X desc_width(std::max(GetOptionsDB().Get("ui.research.list.column.description.width"), Value(total_width - cols_width_sum))); return {graphic_width, name_width, cost_width, time_width, category_width, desc_width}; @@ -1624,7 +1597,7 @@ bool TechTreeWnd::TechListBox::TechRowCmp(const GG::ListBox::Row& lhs, const GG: try { // attempt compare by int retval = boost::lexical_cast(lhs_key) < boost::lexical_cast(rhs_key); } catch (const boost::bad_lexical_cast& e) { - retval = GetLocale().operator()(lhs_key, rhs_key); + retval = GetLocale("en_US.UTF-8").operator()(lhs_key, rhs_key); } } @@ -1632,9 +1605,11 @@ bool TechTreeWnd::TechListBox::TechRowCmp(const GG::ListBox::Row& lhs, const GG: } TechTreeWnd::TechListBox::TechRow::TechRow(GG::X w, const std::string& tech_name) : - CUIListBox::Row(w, GG::Y(ClientUI::Pts() * 2 + 5), "TechListBox::TechRow"), - m_tech(tech_name) -{} + CUIListBox::Row(w, GG::Y(ClientUI::Pts() * 2 + 5)), + m_tech(tech_name), + m_background_color(ClientUI::WndColor()), + m_enqueued(false) +{ SetDragDropDataType("TechListBox::TechRow"); } void TechTreeWnd::TechListBox::TechRow::CompleteConstruction() { @@ -1646,7 +1621,7 @@ void TechTreeWnd::TechListBox::TechRow::CompleteConstruction() { std::vector col_widths = ColWidths(Width()); const GG::X GRAPHIC_WIDTH = col_widths[0]; - const GG::Y ICON_HEIGHT(std::max(ClientUI::Pts(), Value(GRAPHIC_WIDTH) - 6)); + const GG::Y ICON_HEIGHT(std::min(Value(Height()) - 12, std::max(ClientUI::Pts(), Value(GRAPHIC_WIDTH) - 6))); // TODO replace string padding with new TextFormat flag std::string just_pad = " "; @@ -1695,13 +1670,41 @@ void TechTreeWnd::TechListBox::TechRow::Update() { // TODO replace string padding with new TextFormat flag std::string just_pad = " "; - std::string cost_str = std::to_string(std::lround(this_row_tech->ResearchCost(HumanClientApp::GetApp()->EmpireID()))); + auto client_empire_id = HumanClientApp::GetApp()->EmpireID(); + auto empire = GetEmpire(client_empire_id); + + std::string cost_str = std::to_string(std::lround(this_row_tech->ResearchCost(client_empire_id))); if (GG::Button* cost_btn = dynamic_cast((size() >= 3) ? at(2) : nullptr)) cost_btn->SetText(cost_str + just_pad + just_pad); - std::string time_str = std::to_string(this_row_tech->ResearchTime(HumanClientApp::GetApp()->EmpireID())); + std::string time_str = std::to_string(this_row_tech->ResearchTime(client_empire_id)); if (GG::Button* time_btn = dynamic_cast((size() >= 4) ? at(3) : nullptr)) time_btn->SetText(time_str + just_pad + just_pad); + + // Adjust colors for tech status + auto foreground_color = ClientUI::CategoryColor(this_row_tech->Category()); + auto this_row_status = empire ? empire->GetTechStatus(m_tech) : TS_RESEARCHABLE; + if (this_row_status == TS_COMPLETE) { + foreground_color.a = m_background_color.a; // preserve users 'wnd-color' trasparency + AdjustBrightness(foreground_color, 0.3); + m_background_color = foreground_color; + foreground_color = ClientUI::TextColor(); + } else if (this_row_status == TS_UNRESEARCHABLE || this_row_status == TS_HAS_RESEARCHED_PREREQ) { + foreground_color.a = 96; + } + + for (std::size_t i = 0; i < size(); ++i) + at(i)->SetColor(foreground_color); + + if (empire) { + const ResearchQueue& rq = empire->GetResearchQueue(); + m_enqueued = rq.InQueue(m_tech); + } else { + m_enqueued = false; + } + + ClearBrowseInfoWnd(); + SetBrowseInfoWnd(TechRowBrowseWnd(m_tech, client_empire_id)); } void TechTreeWnd::TechListBox::ToggleSortCol(unsigned int col) { @@ -1745,7 +1748,7 @@ void TechTreeWnd::TechListBox::CompleteConstruction() { GG::X row_width = Width() - ClientUI::ScrollWidth() - ClientUI::Pts(); std::vector col_widths = TechRow::ColWidths(row_width); const GG::Y HEIGHT(Value(col_widths[0])); - m_header_row = GG::Wnd::Create(row_width, HEIGHT, ""); + m_header_row = GG::Wnd::Create(row_width, HEIGHT); auto graphic_col = GG::Wnd::Create(""); // graphic graphic_col->Resize(GG::Pt(col_widths[0], HEIGHT)); @@ -1806,63 +1809,52 @@ void TechTreeWnd::TechListBox::CompleteConstruction() { TechTreeWnd::TechListBox::~TechListBox() {} -std::set TechTreeWnd::TechListBox::GetCategoriesShown() const -{ return m_categories_shown; } - -std::set TechTreeWnd::TechListBox::GetTechStatusesShown() const -{ return m_tech_statuses_shown; } - void TechTreeWnd::TechListBox::Reset() -{ Populate(); } - -void TechTreeWnd::TechListBox::Update() { - for (auto& row : m_all_tech_rows) { - auto& tech_row = row.second; - if (TechVisible(tech_row->GetTech(), m_categories_shown, m_tech_statuses_shown)) - tech_row->Update(); - } +{ + m_tech_row_cache.clear(); + Populate(); } -void TechTreeWnd::TechListBox::Populate() { - // abort of not visible to see results - if (!Visible()) - return; +void TechTreeWnd::TechListBox::Update(bool populate /* = true */) { + if (populate) + Populate(false); - DebugLogger() << "Tech List Box Populating"; + DebugLogger() << "Tech List Box Updating"; - double creation_elapsed = 0.0; double insertion_elapsed = 0.0; - boost::timer creation_timer; - boost::timer insertion_timer; - + ScopedTimer insertion_timer; GG::X row_width = Width() - ClientUI::ScrollWidth() - ClientUI::Pts(); - // HACK! This caching of TechRows works only if there are no "hidden" techs - // that are added to the manager mid-game. - TechManager& manager = GetTechManager(); - if (m_all_tech_rows.empty()) { - for (const Tech* tech : manager) { - const std::string& tech_name = UserString(tech->Name()); - creation_timer.restart(); - m_all_tech_rows.insert(std::make_pair(tech_name, - GG::Wnd::Create(row_width, tech->Name()))); - creation_elapsed += creation_timer.elapsed(); - } - } + + // Try to preserve the first row, only works if a row for the tech is still visible + std::string first_tech_shown; + if (FirstRowShown() != end()) + if (auto first_row = FirstRowShown()->get()) + if (auto first_row_shown = dynamic_cast(first_row)) + first_tech_shown = first_row_shown->GetTech(); + + // Skip setting first row during insertion + bool first_tech_set = first_tech_shown.empty(); // remove techs in listbox, then reset the rest of its state for (iterator it = begin(); it != end(); ) { iterator temp_it = it++; Erase(temp_it); } + Clear(); - for (auto& row : m_all_tech_rows) { + // Add rows from cache + for (auto& row : m_tech_row_cache) { auto& tech_row = row.second; if (TechVisible(tech_row->GetTech(), m_categories_shown, m_tech_statuses_shown)) { tech_row->Update(); insertion_timer.restart(); - Insert(tech_row); - insertion_elapsed += insertion_timer.elapsed(); + auto listbox_row_it = Insert(tech_row); + insertion_elapsed += insertion_timer.duration(); + if (!first_tech_set && row.first == first_tech_shown) { + first_tech_set = true; + SetFirstRowShown(listbox_row_it); + } } } @@ -1876,17 +1868,35 @@ void TechTreeWnd::TechListBox::Populate() { } if (SortCol() < 1) SetSortCol(2); - // TODO workaround for header rendering excessively high and overlapping some rows - if (begin() != end()) + + if (!first_tech_set || first_tech_shown.empty()) BringRowIntoView(begin()); - DebugLogger() << "Tech List Box Done Populating"; - DebugLogger() << " Creation time=" << (creation_elapsed * 1000) << "ms"; - DebugLogger() << " Insertion time=" << (insertion_elapsed * 1000) << "ms"; + DebugLogger() << "Tech List Box Updating Done, Insertion time = " << (insertion_elapsed * 1000) << " ms"; +} + +void TechTreeWnd::TechListBox::Populate(bool update /* = true*/) { + DebugLogger() << "Tech List Box Populating"; + + GG::X row_width = Width() - ClientUI::ScrollWidth() - ClientUI::Pts(); + + ScopedTimer creation_timer; + + // Skip lookup check when starting with empty cache + bool new_cache = m_tech_row_cache.empty(); + for (const auto& tech : GetTechManager()) { + if (new_cache || !m_tech_row_cache.count(tech->Name())) + m_tech_row_cache.emplace(tech->Name(), GG::Wnd::Create(row_width, tech->Name())); + } + + DebugLogger() << "Tech List Box Populating Done, Creation time = " << creation_timer.DurationString(); + + if (update) + Update(false); } void TechTreeWnd::TechListBox::ShowCategory(const std::string& category) { - if (m_categories_shown.find(category) == m_categories_shown.end()) { + if (!m_categories_shown.count(category)) { m_categories_shown.insert(category); Populate(); } @@ -1917,7 +1927,7 @@ void TechTreeWnd::TechListBox::HideAllCategories() { } void TechTreeWnd::TechListBox::ShowStatus(TechStatus status) { - if (m_tech_statuses_shown.find(status) == m_tech_statuses_shown.end()) { + if (!m_tech_statuses_shown.count(status)) { m_tech_statuses_shown.insert(status); Populate(); } @@ -1949,31 +1959,32 @@ void TechTreeWnd::TechListBox::TechRightClicked(GG::ListBox::iterator it, const return; const std::string& tech_name = tech_row->GetTech(); - const ResearchQueue& rq = empire->GetResearchQueue(); - if (rq.find(tech_name) != rq.end()) - return; - - auto tech_dclick_action = [this, it, pt]() { TechDoubleClicked(it, pt, GG::Flags()); }; - auto pedia_display_action = [this, &tech_name]() { TechPediaDisplay(tech_name); }; - auto popup = GG::Wnd::Create(pt.x, pt.y); - if (!empire->TechResearched(tech_name)) - popup->AddMenuItem(GG::MenuItem(UserString("PRODUCTION_DETAIL_ADD_TO_QUEUE"), false, false, tech_dclick_action)); + const ResearchQueue& rq = empire->GetResearchQueue(); + if (!rq.InQueue(tech_name)) { + auto tech_dclick_action = [this, it, pt]() { TechDoubleClicked(it, pt, GG::Flags()); }; + auto tech_ctrl_dclick_action = [this, it, pt]() { TechDoubleClicked(it, pt, GG::MOD_KEY_CTRL); }; + + if (!empire->TechResearched(tech_name)) { + popup->AddMenuItem(GG::MenuItem(UserString("PRODUCTION_DETAIL_ADD_TO_QUEUE"), false, false, + tech_dclick_action)); + popup->AddMenuItem(GG::MenuItem(UserString("PRODUCTION_DETAIL_ADD_TO_TOP_OF_QUEUE"), false, false, + tech_ctrl_dclick_action)); + } + } + auto pedia_display_action = [this, &tech_name]() { TechPediaDisplaySignal(tech_name); }; std::string popup_label = boost::io::str(FlexibleFormat(UserString("ENC_LOOKUP")) % UserString(tech_name)); popup->AddMenuItem(GG::MenuItem(popup_label, false, false, pedia_display_action)); - popup->Run(); -} -void TechTreeWnd::TechListBox::TechPediaDisplay(const std::string& tech_name) { - TechPediaDisplaySignal(tech_name); + popup->Run(); } void TechTreeWnd::TechListBox::TechDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags& modkeys) { // determine type of row that was clicked, and emit appropriate signal TechRow* tech_row = dynamic_cast(it->get()); if (tech_row) - TechDoubleClickedSignal(tech_row->GetTech(), GG::Flags()); + TechDoubleClickedSignal(tech_row->GetTech(), modkeys); } @@ -1982,13 +1993,8 @@ void TechTreeWnd::TechListBox::TechDoubleClicked(GG::ListBox::iterator it, const ////////////////////////////////////////////////// TechTreeWnd::TechTreeWnd(GG::X w, GG::Y h, bool initially_hidden /*= true*/) : GG::Wnd(GG::X0, GG::Y0, w, h, GG::INTERACTIVE), - m_tech_tree_controls(nullptr), - m_enc_detail_panel(nullptr), - m_layout_panel(nullptr), - m_tech_list(nullptr), m_init_flag(initially_hidden) -{ -} +{} void TechTreeWnd::CompleteConstruction() { GG::Wnd::CompleteConstruction(); @@ -1996,10 +2002,10 @@ void TechTreeWnd::CompleteConstruction() { Sound::TempUISoundDisabler sound_disabler; m_layout_panel = GG::Wnd::Create(Width(), Height()); - m_layout_panel->TechLeftClickedSignal.connect( + m_layout_panel->TechSelectedSignal.connect( boost::bind(&TechTreeWnd::TechLeftClickedSlot, this, _1, _2)); m_layout_panel->TechDoubleClickedSignal.connect( - boost::bind(&TechTreeWnd::TechDoubleClickedSlot, this, _1, _2)); + [this](const std::string& tech_name, GG::Flags modkeys){ this->AddTechToResearchQueue(tech_name, modkeys & GG::MOD_KEY_CTRL); }); m_layout_panel->TechPediaDisplaySignal.connect( boost::bind(&TechTreeWnd::TechPediaDisplaySlot, this, _1)); AttachChild(m_layout_panel); @@ -2008,7 +2014,7 @@ void TechTreeWnd::CompleteConstruction() { m_tech_list->TechLeftClickedSignal.connect( boost::bind(&TechTreeWnd::TechLeftClickedSlot, this, _1, _2)); m_tech_list->TechDoubleClickedSignal.connect( - boost::bind(&TechTreeWnd::TechDoubleClickedSlot, this, _1, _2)); + [this](const std::string& tech_name, GG::Flags modkeys){ this->AddTechToResearchQueue(tech_name, modkeys & GG::MOD_KEY_CTRL); }); m_tech_list->TechPediaDisplaySignal.connect( boost::bind(&TechTreeWnd::TechPediaDisplaySlot, this, _1)); @@ -2023,6 +2029,7 @@ void TechTreeWnd::CompleteConstruction() { if (m_tech_tree_controls->Bottom() > m_layout_panel->Bottom() - ClientUI::ScrollWidth()) { m_tech_tree_controls->MoveTo(GG::Pt(m_tech_tree_controls->Left(), m_layout_panel->Bottom() - ClientUI::ScrollWidth() - m_tech_tree_controls->Height())); + m_tech_tree_controls->SaveDefaultedOptions(); } HumanClientApp::GetApp()->RepositionWindowsSignal.connect( @@ -2072,10 +2079,10 @@ void TechTreeWnd::CompleteConstruction() { //long time and generates errors, but is never seen by the user. if (!m_init_flag) { ShowAllCategories(); - SetTechStatus(TS_COMPLETE, GetOptionsDB().Get("UI.research.tech-tree-control.status.completed")); - SetTechStatus(TS_UNRESEARCHABLE, GetOptionsDB().Get("UI.research.tech-tree-control.status.unresearchable")); - SetTechStatus(TS_HAS_RESEARCHED_PREREQ, GetOptionsDB().Get("UI.research.tech-tree-control.status.has_researched_prereq")); - SetTechStatus(TS_RESEARCHABLE, GetOptionsDB().Get("UI.research.tech-tree-control.status.researchable")); + SetTechStatus(TS_COMPLETE, GetOptionsDB().Get("ui.research.status.completed.shown")); + SetTechStatus(TS_UNRESEARCHABLE, GetOptionsDB().Get("ui.research.status.unresearchable.shown")); + SetTechStatus(TS_HAS_RESEARCHED_PREREQ, GetOptionsDB().Get("ui.research.status.partial.shown")); + SetTechStatus(TS_RESEARCHABLE, GetOptionsDB().Get("ui.research.status.researchable.shown")); } ShowTreeView(); @@ -2098,12 +2105,6 @@ void TechTreeWnd::SizeMove(const GG::Pt& ul, const GG::Pt& lr) { double TechTreeWnd::Scale() const { return m_layout_panel->Scale(); } -std::set TechTreeWnd::GetCategoriesShown() const -{ return m_layout_panel->GetCategoriesShown(); } - -std::set TechTreeWnd::GetTechStatusesShown() const -{ return m_layout_panel->GetTechStatusesShown(); } - void TechTreeWnd::Update() { m_layout_panel->Update(); m_tech_list->Update(); @@ -2141,10 +2142,10 @@ void TechTreeWnd::Show() { if (m_init_flag) { m_init_flag = false; ShowAllCategories(); - SetTechStatus(TS_COMPLETE, GetOptionsDB().Get("UI.research.tech-tree-control.status.completed")); - SetTechStatus(TS_UNRESEARCHABLE, GetOptionsDB().Get("UI.research.tech-tree-control.status.unresearchable")); - SetTechStatus(TS_HAS_RESEARCHED_PREREQ, GetOptionsDB().Get("UI.research.tech-tree-control.status.has_researched_prereq")); - SetTechStatus(TS_RESEARCHABLE, GetOptionsDB().Get("UI.research.tech-tree-control.status.researchable")); + SetTechStatus(TS_COMPLETE, GetOptionsDB().Get("ui.research.status.completed.shown")); + SetTechStatus(TS_UNRESEARCHABLE, GetOptionsDB().Get("ui.research.status.unresearchable.shown")); + SetTechStatus(TS_HAS_RESEARCHED_PREREQ, GetOptionsDB().Get("ui.research.status.partial.shown")); + SetTechStatus(TS_RESEARCHABLE, GetOptionsDB().Get("ui.research.status.researchable.shown")); } } @@ -2195,16 +2196,16 @@ void TechTreeWnd::ToggleAllCategories() { void TechTreeWnd::SetTechStatus(const TechStatus status, const bool state) { switch (status) { case TS_UNRESEARCHABLE: - GetOptionsDB().Set("UI.research.tech-tree-control.status.unresearchable", state); + GetOptionsDB().Set("ui.research.status.unresearchable.shown", state); break; case TS_HAS_RESEARCHED_PREREQ: - GetOptionsDB().Set("UI.research.tech-tree-control.status.has_researched_prereq", state); + GetOptionsDB().Set("ui.research.status.partial.shown", state); break; case TS_RESEARCHABLE: - GetOptionsDB().Set("UI.research.tech-tree-control.status.researchable", state); + GetOptionsDB().Set("ui.research.status.researchable.shown", state); break; case TS_COMPLETE: - GetOptionsDB().Set("UI.research.tech-tree-control.status.completed", state); + GetOptionsDB().Set("ui.research.status.completed.shown", state); break; default: ; // do nothing @@ -2240,9 +2241,6 @@ void TechTreeWnd::ShowListView() { MoveChildUp(m_tech_tree_controls); } -void TechTreeWnd::SetScale(double scale) -{ m_layout_panel->SetScale(scale); } - void TechTreeWnd::CenterOnTech(const std::string& tech_name) { // ensure tech exists and is visible const Tech* tech = ::GetTech(tech_name); @@ -2260,21 +2258,21 @@ void TechTreeWnd::SetEncyclopediaTech(const std::string& tech_name) { m_enc_detail_panel->SetTech(tech_name); } void TechTreeWnd::SelectTech(const std::string& tech_name) -{ m_layout_panel->ShowTech(tech_name); } +{ m_layout_panel->SelectTech(tech_name); } void TechTreeWnd::ShowPedia() { m_enc_detail_panel->Refresh(); m_enc_detail_panel->Show(); OptionsDB& db = GetOptionsDB(); - db.Set("UI.windows." + RES_PEDIA_WND_NAME + ".persistently-hidden", false); + db.Set("ui." + RES_PEDIA_WND_NAME + ".hidden.enabled", false); } void TechTreeWnd::HidePedia() { m_enc_detail_panel->Hide(); OptionsDB& db = GetOptionsDB(); - db.Set("UI.windows." + RES_PEDIA_WND_NAME + ".persistently-hidden", true); + db.Set("ui." + RES_PEDIA_WND_NAME + ".hidden.enabled", true); } void TechTreeWnd::TogglePedia() { @@ -2288,21 +2286,21 @@ bool TechTreeWnd::PediaVisible() { return m_enc_detail_panel->Visible(); } bool TechTreeWnd::TechIsVisible(const std::string& tech_name) const -{ return TechVisible(tech_name, GetCategoriesShown(), GetTechStatusesShown()); } +{ return TechVisible(tech_name, m_layout_panel->GetCategoriesShown(), m_layout_panel->GetTechStatusesShown()); } void TechTreeWnd::TechLeftClickedSlot(const std::string& tech_name, const GG::Flags& modkeys) { if (modkeys & GG::MOD_KEY_SHIFT) { - TechDoubleClickedSlot(tech_name, modkeys); + AddTechToResearchQueue(tech_name, modkeys & GG::MOD_KEY_CTRL); } else { SetEncyclopediaTech(tech_name); TechSelectedSignal(tech_name); } } -void TechTreeWnd::TechDoubleClickedSlot(const std::string& tech_name, - const GG::Flags& modkeys) +void TechTreeWnd::AddTechToResearchQueue(const std::string& tech_name, + bool to_front) { const Tech* tech = GetTech(tech_name); if (!tech) return; @@ -2312,7 +2310,7 @@ void TechTreeWnd::TechDoubleClickedSlot(const std::string& tech_name, tech_status = empire->GetTechStatus(tech_name); int queue_pos = -1; - if (modkeys & GG::MOD_KEY_CTRL) + if (to_front) queue_pos = 0; // if tech can be researched already, just add it diff --git a/UI/TechTreeWnd.h b/UI/TechTreeWnd.h index 0afbd356391..7a836a03120 100644 --- a/UI/TechTreeWnd.h +++ b/UI/TechTreeWnd.h @@ -10,8 +10,8 @@ class Tech; class EncyclopediaDetailPanel; -/** Returns a browse wnd for tech panels */ -std::shared_ptr TechPanelRowBrowseWnd(const std::string& tech_name, int empire_id); +/** Returns a browse wnd for tech entries */ +std::shared_ptr TechRowBrowseWnd(const std::string& tech_name, int empire_id); /** Contains the tech graph layout, some controls to control what is visible in * the tech layout, the tech navigator, and the tech detail window. */ @@ -35,8 +35,6 @@ class TechTreeWnd : public GG::Wnd { /** \name Accessors */ //@{ double Scale() const; - std::set GetCategoriesShown() const; - std::set GetTechStatusesShown() const; bool PediaVisible(); /** If tech @p tech_name is currently visible */ bool TechIsVisible(const std::string& tech_name) const; @@ -50,7 +48,6 @@ class TechTreeWnd : public GG::Wnd { void Update(); void Clear(); void Reset(); - void SetScale(double scale); void ShowCategory(const std::string& category); void ShowAllCategories(); @@ -83,8 +80,8 @@ class TechTreeWnd : public GG::Wnd { void TechLeftClickedSlot(const std::string& tech_name, const GG::Flags& modkeys); - void TechDoubleClickedSlot(const std::string& tech_name, - const GG::Flags& modkeys); + void AddTechToResearchQueue(const std::string& tech_name, + bool to_front); void TechPediaDisplaySlot(const std::string& tech_name); void InitializeWindows(); diff --git a/Xcode/FreeOrion.xcodeproj/project.pbxproj b/Xcode/FreeOrion.xcodeproj/project.pbxproj deleted file mode 100644 index 72e818bf304..00000000000 --- a/Xcode/FreeOrion.xcodeproj/project.pbxproj +++ /dev/null @@ -1,3508 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXAggregateTarget section */ - 829C76351689EFD4005881B4 /* Configure */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 829C763B1689EFF3005881B4 /* Build configuration list for PBXAggregateTarget "Configure" */; - buildPhases = ( - 82728DEB1A49A58D0034F4FE /* Delete existing app bundle */, - 829C76341689EFD4005881B4 /* Version.cpp */, - ); - dependencies = ( - ); - name = Configure; - productName = INIT; - }; - 82EC621C18C6109D000237C2 /* MakeDMG */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 82EC622518C610C9000237C2 /* Build configuration list for PBXAggregateTarget "MakeDMG" */; - buildPhases = ( - 82EC621B18C6109D000237C2 /* make_dmg */, - ); - dependencies = ( - 82EC622418C610AB000237C2 /* PBXTargetDependency */, - ); - name = MakeDMG; - productName = MakeDMG; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 2F22EC0612F7F4CF00456CDE /* TechTreeLayout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2F22EC0412F7F4CF00456CDE /* TechTreeLayout.cpp */; }; - 2F60966512EEAD2200F58913 /* PlayerListWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2F60966312EEAD2200F58913 /* PlayerListWnd.cpp */; }; - 2F60966B12EEAF0200F58913 /* GroupBox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2F60966A12EEAF0200F58913 /* GroupBox.cpp */; }; - 2F60967E12EEB1AE00F58913 /* MessageQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 47102FA40CEF565700A7DF2B /* MessageQueue.cpp */; }; - 2F6096CE12EF046500F58913 /* ClientApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C5D0A98A3F900DA9C21 /* ClientApp.cpp */; }; - 2F6096CF12EF046B00F58913 /* ClientNetworking.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 47102FAA0CEF566200A7DF2B /* ClientNetworking.cpp */; }; - 3402E26A0F5A31BB00DF6FE7 /* FreeOrion.icns in Resources */ = {isa = PBXBuildFile; fileRef = 471033FE0CEF7BCB00A7DF2B /* FreeOrion.icns */; }; - 3402E5360F5A339900DF6FE7 /* vorbis.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 4710323F0CEF6A8B00A7DF2B /* vorbis.framework */; }; - 3402E5370F5A339900DF6FE7 /* Ogg.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 471032380CEF6A7600A7DF2B /* Ogg.framework */; }; - 3402E53D0F5A33EF00DF6FE7 /* FreeOrion in Copy Main Executable */ = {isa = PBXBuildFile; fileRef = 8DD76F6C0486A84900D96B5E /* FreeOrion */; }; - 3402F0FB0F5A355D00DF6FE7 /* freeoriond in Copy Server and AI client */ = {isa = PBXBuildFile; fileRef = 471D5D440A98A46000DA9C21 /* freeoriond */; }; - 3402F0FC0F5A355D00DF6FE7 /* freeorionca in Copy Server and AI client */ = {isa = PBXBuildFile; fileRef = 471FEFD70A9A629800C36AA3 /* freeorionca */; }; - 343EC6340F3F513700782AD3 /* UnicodeCharsets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6330F3F513700782AD3 /* UnicodeCharsets.cpp */; }; - 343EC6630F3F5C7C00782AD3 /* EncyclopediaDetailPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC65D0F3F5C7C00782AD3 /* EncyclopediaDetailPanel.cpp */; }; - 343EC6650F3F5C7C00782AD3 /* QueueListBox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6610F3F5C7C00782AD3 /* QueueListBox.cpp */; }; - 343EC66F0F3F60F000782AD3 /* DesignWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6590F3F5B5300782AD3 /* DesignWnd.cpp */; }; - 343EC67A0F3F61D000782AD3 /* EmpireWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6740F3F61D000782AD3 /* EmpireWrapper.cpp */; }; - 343EC67B0F3F61D000782AD3 /* EnumWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6750F3F61D000782AD3 /* EnumWrapper.cpp */; }; - 343EC67C0F3F61D000782AD3 /* LoggingWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6760F3F61D000782AD3 /* LoggingWrapper.cpp */; }; - 343EC67D0F3F61D000782AD3 /* UniverseWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6780F3F61D000782AD3 /* UniverseWrapper.cpp */; }; - 346D87EF0FC5F6080095593E /* ShaderProgram.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 346D87ED0FC5F6080095593E /* ShaderProgram.cpp */; }; - 34972BEA0F49CBE80015C864 /* ChatWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34972BE60F49CBE80015C864 /* ChatWnd.cpp */; }; - 34972BEE0F49CBF90015C864 /* Sound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34972BEC0F49CBF90015C864 /* Sound.cpp */; }; - 34ACA4C10FFFD2DB00500F40 /* chmain.mm in Sources */ = {isa = PBXBuildFile; fileRef = 34ACA4BE0FFFD2DB00500F40 /* chmain.mm */; }; - 34ACA4C70FFFDBC000500F40 /* chmain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C620A98A3F900DA9C21 /* chmain.cpp */; }; - 34C4495F1182716E0071E09A /* SerializeEmpire.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34C44957118271590071E09A /* SerializeEmpire.cpp */; }; - 34C449601182716E0071E09A /* SerializeMultiplayerCommon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34C44958118271590071E09A /* SerializeMultiplayerCommon.cpp */; }; - 34C449611182716E0071E09A /* SerializeOrderSet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34C44959118271590071E09A /* SerializeOrderSet.cpp */; }; - 34C449631182716E0071E09A /* SerializeUniverse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34C4495B118271590071E09A /* SerializeUniverse.cpp */; }; - 34C9B34511DA2EA0003695F4 /* ClrConstants.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34C9B34311DA2E95003695F4 /* ClrConstants.cpp */; }; - 34D3CE8C11D7DA11007C1E78 /* Species.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34D3CE8A11D7DA03007C1E78 /* Species.cpp */; }; - 34F0E429113F497500A10EED /* Python.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F0E427113F491C00A10EED /* Python.framework */; }; - 34F0E455113F49E900A10EED /* Python.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 34F0E427113F491C00A10EED /* Python.framework */; }; - 3A01E1E31749F93500EC9110 /* AppInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D180A98A3F900DA9C21 /* AppInterface.cpp */; }; - 3A1BF3EB1748875300812237 /* Version.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3A1BF3EA1748875200812237 /* Version.cpp */; }; - 3A5105541748D68B00DC258B /* i18n.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3A5105521748D68B00DC258B /* i18n.cpp */; }; - 3A5105571748D6A700DC258B /* Logger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3A5105551748D6A600DC258B /* Logger.cpp */; }; - 3A5105571748D6A700DC258C /* LoggerWithOptionsDB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3A5105551748D6A600DC258C /* LoggerWithOptionsDB.cpp */; }; - 3A76046217499F3000363653 /* PlanetEnvironmentValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936F5147E3B1B00B840A5 /* PlanetEnvironmentValueRefParser.cpp */; }; - 3A76046317499F3D00363653 /* PlanetSizeValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936F6147E3B1B00B840A5 /* PlanetSizeValueRefParser.cpp */; }; - 3A76046417499F4C00363653 /* PlanetTypeValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936F7147E3B1B00B840A5 /* PlanetTypeValueRefParser.cpp */; }; - 3A76046517499F7D00363653 /* ReportParseError.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936F8147E3B1B00B840A5 /* ReportParseError.cpp */; }; - 3A76046617499F8800363653 /* ShipDesignsParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936FA147E3B1B00B840A5 /* ShipDesignsParser.cpp */; }; - 3A76046717499F9600363653 /* ShipHullsParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936FB147E3B1B00B840A5 /* ShipHullsParser.cpp */; }; - 3A76046817499FA400363653 /* ShipPartsParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936FC147E3B1B00B840A5 /* ShipPartsParser.cpp */; }; - 3A76046A17499FBD00363653 /* SpecialsParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936FD147E3B1B00B840A5 /* SpecialsParser.cpp */; }; - 3A76046B1749A01600363653 /* SpeciesParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936FE147E3B1B00B840A5 /* SpeciesParser.cpp */; }; - 3A76046E1749A03E00363653 /* StarTypeValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936FF147E3B1B00B840A5 /* StarTypeValueRefParser.cpp */; }; - 3A76049C1749A09400363653 /* StringValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82593700147E3B1B00B840A5 /* StringValueRefParser.cpp */; }; - 3A76049E1749A0A300363653 /* TechsParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82593701147E3B1B00B840A5 /* TechsParser.cpp */; }; - 3A76049F1749A0AF00363653 /* UniverseObjectTypeValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8259376A147E3B1C00B840A5 /* UniverseObjectTypeValueRefParser.cpp */; }; - 3A7604A01749A0BF00363653 /* MonsterFleetPlansParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936F0147E3B1B00B840A5 /* MonsterFleetPlansParser.cpp */; }; - 3A7604A11749A0CB00363653 /* Lexer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936EE147E3B1B00B840A5 /* Lexer.cpp */; }; - 3A7604A21749A0D700363653 /* KeymapParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82D68C6C15F4F720006B772D /* KeymapParser.cpp */; }; - 3A7604A31749A0E600363653 /* ItemsParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936EB147E3B1B00B840A5 /* ItemsParser.cpp */; }; - 3A7604A41749A0F200363653 /* IntValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936EA147E3B1B00B840A5 /* IntValueRefParser.cpp */; }; - 3A7604A51749A10100363653 /* FleetPlansParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936E6147E3B1B00B840A5 /* FleetPlansParser.cpp */; }; - 3A7604A61749A10D00363653 /* FieldsParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8250FDE215FCEFC400523C1C /* FieldsParser.cpp */; }; - 3A7604A71749A11800363653 /* EnumParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936E4147E3B1B00B840A5 /* EnumParser.cpp */; }; - 3A7604A81749A12200363653 /* EncyclopediaParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82BB838215AAD20A006F635F /* EncyclopediaParser.cpp */; }; - 3A7604A91749A13400363653 /* EffectParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936E2147E3B1B00B840A5 /* EffectParser.cpp */; }; - 3A7604AA1749A13400363653 /* EffectParser1.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8201E5F215E2C0770037D453 /* EffectParser1.cpp */; }; - 3A7604AB1749A13400363653 /* EffectParser2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8201E5F315E2C0770037D453 /* EffectParser2.cpp */; }; - 3A7604AC1749A13400363653 /* EffectParser3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 822B16B51642A6F8000ADE58 /* EffectParser3.cpp */; }; - 3A7604AD1749A13400363653 /* EffectParser4.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 822B16B61642A6F8000ADE58 /* EffectParser4.cpp */; }; - 3A7604AE1749A14600363653 /* DoubleValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936E1147E3B1B00B840A5 /* DoubleValueRefParser.cpp */; }; - 3A7604AF1749A15500363653 /* ConditionParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936D9147E3B1B00B840A5 /* ConditionParser.cpp */; }; - 3A7604B01749A15500363653 /* ConditionParser1.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936DB147E3B1B00B840A5 /* ConditionParser1.cpp */; }; - 3A7604B11749A15500363653 /* ConditionParser2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936DC147E3B1B00B840A5 /* ConditionParser2.cpp */; }; - 3A7604B21749A15500363653 /* ConditionParser3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936DD147E3B1B00B840A5 /* ConditionParser3.cpp */; }; - 3A7604B31749A15500363653 /* ConditionParser4.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8250FDE115FCEFC400523C1C /* ConditionParser4.cpp */; }; - 3A7604B41749A16200363653 /* BuildingsParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936D7147E3B1B00B840A5 /* BuildingsParser.cpp */; }; - 3ABEAB3B1749BFED00E34912 /* freeorioncommon.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 47103BE20CF04D8800A7DF2B /* freeorioncommon.dylib */; }; - 3ABEAB3C1749C21B00E34912 /* libboost_serialization.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7613ACB91A0085B1A0 /* libboost_serialization.a */; }; - 3ABEAB3F1749C27700E34912 /* libboost_filesystem.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7313ACB91A0085B1A0 /* libboost_filesystem.a */; }; - 3ABEAB711749C2F400E34912 /* libboost_signals.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7713ACB91A0085B1A0 /* libboost_signals.a */; }; - 3ABEAB741749C36200E34912 /* libboost_system.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7813ACB91A0085B1A0 /* libboost_system.a */; }; - 3ABEAB801749C51B00E34912 /* libboost_thread.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7913ACB91A0085B1A0 /* libboost_thread.a */; }; - 3ABEAB871749C72000E34912 /* Universe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D0F0A98A3F900DA9C21 /* Universe.cpp */; }; - 3ABEAB901749C8F500E34912 /* libboost_thread.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7913ACB91A0085B1A0 /* libboost_thread.a */; }; - 3ABEAB971749C93400E34912 /* libboost_signals.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7713ACB91A0085B1A0 /* libboost_signals.a */; }; - 3ABEAB9C1749C96400E34912 /* libboost_signals.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7713ACB91A0085B1A0 /* libboost_signals.a */; }; - 3ABEABA11749C9CA00E34912 /* libboost_thread.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7913ACB91A0085B1A0 /* libboost_thread.a */; }; - 3ABEABA41749D05700E34912 /* freeorionparse.dylib in Copy Shared Libraries */ = {isa = PBXBuildFile; fileRef = 82C07438149DE46200E76876 /* freeorionparse.dylib */; }; - 3ABEABA51749D05E00E34912 /* freeorioncommon.dylib in Copy Shared Libraries */ = {isa = PBXBuildFile; fileRef = 47103BE20CF04D8800A7DF2B /* freeorioncommon.dylib */; }; - 470DF9A10A9CE53500A88AD6 /* HumanClientApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C640A98A3F900DA9C21 /* HumanClientApp.cpp */; }; - 4710240A0CEF3AAC00A7DF2B /* AlignmentFlags.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023920CEF3AAC00A7DF2B /* AlignmentFlags.cpp */; }; - 4710240B0CEF3AAC00A7DF2B /* Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023930CEF3AAC00A7DF2B /* Base.cpp */; }; - 4710240C0CEF3AAC00A7DF2B /* BrowseInfoWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023940CEF3AAC00A7DF2B /* BrowseInfoWnd.cpp */; }; - 4710240D0CEF3AAC00A7DF2B /* Button.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023950CEF3AAC00A7DF2B /* Button.cpp */; }; - 4710240F0CEF3AAC00A7DF2B /* Control.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023970CEF3AAC00A7DF2B /* Control.cpp */; }; - 471024100CEF3AAC00A7DF2B /* Cursor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023980CEF3AAC00A7DF2B /* Cursor.cpp */; }; - 471024110CEF3AAC00A7DF2B /* ColorDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4710239A0CEF3AAC00A7DF2B /* ColorDlg.cpp */; }; - 471024120CEF3AAC00A7DF2B /* FileDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4710239B0CEF3AAC00A7DF2B /* FileDlg.cpp */; }; - 471024130CEF3AAC00A7DF2B /* ThreeButtonDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4710239D0CEF3AAC00A7DF2B /* ThreeButtonDlg.cpp */; }; - 471024140CEF3AAC00A7DF2B /* DrawUtil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4710239E0CEF3AAC00A7DF2B /* DrawUtil.cpp */; }; - 471024150CEF3AAC00A7DF2B /* DropDownList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4710239F0CEF3AAC00A7DF2B /* DropDownList.cpp */; }; - 471024160CEF3AAC00A7DF2B /* DynamicGraphic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023A00CEF3AAC00A7DF2B /* DynamicGraphic.cpp */; }; - 471024170CEF3AAC00A7DF2B /* Edit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023A10CEF3AAC00A7DF2B /* Edit.cpp */; }; - 471024180CEF3AAC00A7DF2B /* EventPump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023A20CEF3AAC00A7DF2B /* EventPump.cpp */; }; - 471024190CEF3AAC00A7DF2B /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023A30CEF3AAC00A7DF2B /* Font.cpp */; }; - 4710241A0CEF3AAC00A7DF2B /* GUI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023A40CEF3AAC00A7DF2B /* GUI.cpp */; }; - 4710241B0CEF3AAC00A7DF2B /* Layout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023A50CEF3AAC00A7DF2B /* Layout.cpp */; }; - 4710241C0CEF3AAC00A7DF2B /* ListBox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023A60CEF3AAC00A7DF2B /* ListBox.cpp */; }; - 4710241D0CEF3AAC00A7DF2B /* Menu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023A70CEF3AAC00A7DF2B /* Menu.cpp */; }; - 4710241E0CEF3AAC00A7DF2B /* MultiEdit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023A80CEF3AAC00A7DF2B /* MultiEdit.cpp */; }; - 471024260CEF3AAC00A7DF2B /* PtRect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023B50CEF3AAC00A7DF2B /* PtRect.cpp */; }; - 471024270CEF3AAC00A7DF2B /* Scroll.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023B70CEF3AAC00A7DF2B /* Scroll.cpp */; }; - 4710242A0CEF3AAC00A7DF2B /* StaticGraphic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023BC0CEF3AAC00A7DF2B /* StaticGraphic.cpp */; }; - 4710242B0CEF3AAC00A7DF2B /* StyleFactory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023BD0CEF3AAC00A7DF2B /* StyleFactory.cpp */; }; - 4710242C0CEF3AAC00A7DF2B /* TabWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023BE0CEF3AAC00A7DF2B /* TabWnd.cpp */; }; - 4710242D0CEF3AAC00A7DF2B /* TextControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023BF0CEF3AAC00A7DF2B /* TextControl.cpp */; }; - 4710242E0CEF3AAC00A7DF2B /* Texture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023C00CEF3AAC00A7DF2B /* Texture.cpp */; }; - 4710242F0CEF3AAC00A7DF2B /* Timer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023C10CEF3AAC00A7DF2B /* Timer.cpp */; }; - 471024300CEF3AAC00A7DF2B /* Wnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023C20CEF3AAC00A7DF2B /* Wnd.cpp */; }; - 471024320CEF3AAC00A7DF2B /* WndEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023C40CEF3AAC00A7DF2B /* WndEvent.cpp */; }; - 471024330CEF3AAC00A7DF2B /* ZList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023C50CEF3AAC00A7DF2B /* ZList.cpp */; }; - 471031380CEF617C00A7DF2B /* ClientFSMEvents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 47102F890CEF55F700A7DF2B /* ClientFSMEvents.cpp */; }; - 471031390CEF618C00A7DF2B /* HumanClientFSM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 47102F870CEF55E700A7DF2B /* HumanClientFSM.cpp */; }; - 4710323E0CEF6A7700A7DF2B /* Ogg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 471032380CEF6A7600A7DF2B /* Ogg.framework */; }; - 471032460CEF6A8B00A7DF2B /* vorbis.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4710323F0CEF6A8B00A7DF2B /* vorbis.framework */; }; - 471032540CEF6C9700A7DF2B /* ServerNetworking.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 47102FA80CEF565700A7DF2B /* ServerNetworking.cpp */; }; - 471032600CEF6D0D00A7DF2B /* ServerFSM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471030470CEF569200A7DF2B /* ServerFSM.cpp */; }; - 4710338A0CEF723900A7DF2B /* AIInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 47102F9E0CEF562700A7DF2B /* AIInterface.cpp */; }; - 47103BF20CF04E5900A7DF2B /* MultiplayerCommon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D270A98A3F900DA9C21 /* MultiplayerCommon.cpp */; }; - 47103BF30CF04E5900A7DF2B /* OptionsDB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D290A98A3F900DA9C21 /* OptionsDB.cpp */; }; - 47103BF40CF04E5900A7DF2B /* Order.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D2C0A98A3F900DA9C21 /* Order.cpp */; }; - 47103BF50CF04E5900A7DF2B /* OrderSet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D2E0A98A3F900DA9C21 /* OrderSet.cpp */; }; - 47103BF60CF04E5900A7DF2B /* Random.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D320A98A3F900DA9C21 /* Random.cpp */; }; - 47103BF70CF04E5900A7DF2B /* SitRepEntry.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D360A98A3F900DA9C21 /* SitRepEntry.cpp */; }; - 47103BF80CF04E5900A7DF2B /* VarText.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D380A98A3F900DA9C21 /* VarText.cpp */; }; - 47103BF90CF04E5900A7DF2B /* XMLDoc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D3C0A98A3F900DA9C21 /* XMLDoc.cpp */; }; - 47103BFA0CF04E5900A7DF2B /* Building.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CE50A98A3F900DA9C21 /* Building.cpp */; }; - 47103BFB0CF04E5900A7DF2B /* Condition.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CE70A98A3F900DA9C21 /* Condition.cpp */; }; - 47103BFF0CF04E5900A7DF2B /* Effect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CEF0A98A3F900DA9C21 /* Effect.cpp */; }; - 47103C010CF04E5900A7DF2B /* Enums.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CF20A98A3F900DA9C21 /* Enums.cpp */; }; - 47103C020CF04E5900A7DF2B /* Fleet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CF40A98A3F900DA9C21 /* Fleet.cpp */; }; - 47103C030CF04E5900A7DF2B /* Meter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CF70A98A3F900DA9C21 /* Meter.cpp */; }; - 47103C050CF04E5900A7DF2B /* Planet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CFC0A98A3F900DA9C21 /* Planet.cpp */; }; - 47103C060CF04E5900A7DF2B /* PopCenter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CFE0A98A3F900DA9C21 /* PopCenter.cpp */; }; - 47103C070CF04E5900A7DF2B /* Predicates.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D000A98A3F900DA9C21 /* Predicates.cpp */; }; - 47103C080CF04E5900A7DF2B /* ResourceCenter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D020A98A3F900DA9C21 /* ResourceCenter.cpp */; }; - 47103C090CF04E5900A7DF2B /* Ship.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D040A98A3F900DA9C21 /* Ship.cpp */; }; - 47103C0A0CF04E5900A7DF2B /* ShipDesign.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D060A98A3F900DA9C21 /* ShipDesign.cpp */; }; - 47103C0B0CF04E5900A7DF2B /* Special.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D080A98A3F900DA9C21 /* Special.cpp */; }; - 47103C0C0CF04E5900A7DF2B /* System.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D0A0A98A3F900DA9C21 /* System.cpp */; }; - 47103C0D0CF04E5900A7DF2B /* Tech.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D0C0A98A3F900DA9C21 /* Tech.cpp */; }; - 47103C100CF04E5900A7DF2B /* UniverseObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D110A98A3F900DA9C21 /* UniverseObject.cpp */; }; - 47103C110CF04E5900A7DF2B /* ValueRef.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D130A98A3F900DA9C21 /* ValueRef.cpp */; }; - 47103C150CF04E5900A7DF2B /* Message.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C830A98A3F900DA9C21 /* Message.cpp */; }; - 47103C160CF04E5900A7DF2B /* Empire.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C750A98A3F900DA9C21 /* Empire.cpp */; }; - 47103C170CF04E5900A7DF2B /* EmpireManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C770A98A3F900DA9C21 /* EmpireManager.cpp */; }; - 47103C180CF04E5900A7DF2B /* ResourcePool.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C790A98A3F900DA9C21 /* ResourcePool.cpp */; }; - 47103C190CF04E5900A7DF2B /* StringTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CDC0A98A3F900DA9C21 /* StringTable.cpp */; }; - 47103C1B0CF04E5900A7DF2B /* Networking.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 47102FA60CEF565700A7DF2B /* Networking.cpp */; }; - 47103C3B0CF051B000A7DF2B /* Directories.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D1E0A98A3F900DA9C21 /* Directories.cpp */; }; - 471D5D3F0A98A40900DA9C21 /* libGG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 471D5A300A988D5700DA9C21 /* libGG.a */; }; - 471D5E0E0A98A96700DA9C21 /* About.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CA50A98A3F900DA9C21 /* About.cpp */; }; - 471D5E0F0A98A96D00DA9C21 /* BuildDesignatorWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CA70A98A3F900DA9C21 /* BuildDesignatorWnd.cpp */; }; - 471D5E100A98A97300DA9C21 /* ClientUI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CA90A98A3F900DA9C21 /* ClientUI.cpp */; }; - 471D5E150A98A97D00DA9C21 /* CUIControls.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CAD0A98A3F900DA9C21 /* CUIControls.cpp */; }; - 471D5E160A98A98700DA9C21 /* CUIDrawUtil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CAF0A98A3F900DA9C21 /* CUIDrawUtil.cpp */; }; - 471D5E180A98A98B00DA9C21 /* CUIStyle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CB20A98A3F900DA9C21 /* CUIStyle.cpp */; }; - 471D5E1A0A98A99600DA9C21 /* CUIWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CB60A98A3F900DA9C21 /* CUIWnd.cpp */; }; - 471D5E1B0A98A99D00DA9C21 /* FleetButton.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CBA0A98A3F900DA9C21 /* FleetButton.cpp */; }; - 471D5E1C0A98A9A300DA9C21 /* FleetWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CBC0A98A3F900DA9C21 /* FleetWnd.cpp */; }; - 471D5E1E0A98A9AE00DA9C21 /* GalaxySetupWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CC00A98A3F900DA9C21 /* GalaxySetupWnd.cpp */; }; - 471D5E1F0A98A9B000DA9C21 /* InGameMenu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CC20A98A3F900DA9C21 /* InGameMenu.cpp */; }; - 471D5E200A98A9B200DA9C21 /* IntroScreen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CC40A98A3F900DA9C21 /* IntroScreen.cpp */; }; - 471D5E210A98A9C000DA9C21 /* LinkText.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CC60A98A3F900DA9C21 /* LinkText.cpp */; }; - 471D5E220A98A9C500DA9C21 /* MapWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CC80A98A3F900DA9C21 /* MapWnd.cpp */; }; - 471D5E230A98A9C700DA9C21 /* MultiplayerLobbyWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CCA0A98A3F900DA9C21 /* MultiplayerLobbyWnd.cpp */; }; - 471D5E240A98A9C900DA9C21 /* OptionsWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CCC0A98A3F900DA9C21 /* OptionsWnd.cpp */; }; - 471D5E260A98A9CD00DA9C21 /* ProductionWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CD00A98A3F900DA9C21 /* ProductionWnd.cpp */; }; - 471D5E270A98A9CF00DA9C21 /* ResearchWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CD20A98A3F900DA9C21 /* ResearchWnd.cpp */; }; - 471D5E280A98A9D100DA9C21 /* ServerConnectWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CD40A98A3F900DA9C21 /* ServerConnectWnd.cpp */; }; - 471D5E290A98A9D700DA9C21 /* SidePanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CD60A98A3F900DA9C21 /* SidePanel.cpp */; }; - 471D5E2A0A98A9D900DA9C21 /* SitRepPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CD80A98A3F900DA9C21 /* SitRepPanel.cpp */; }; - 471D5E2D0A98AA1700DA9C21 /* SystemIcon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CDE0A98A3F900DA9C21 /* SystemIcon.cpp */; }; - 471D5E2E0A98AA1A00DA9C21 /* TechTreeWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CE00A98A3F900DA9C21 /* TechTreeWnd.cpp */; }; - 471FEFDE0A9A631C00C36AA3 /* AIClientApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C5A0A98A3F900DA9C21 /* AIClientApp.cpp */; }; - 471FEFDF0A9A632000C36AA3 /* camain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C5C0A98A3F900DA9C21 /* camain.cpp */; }; - 478417700CF0581A00BE4710 /* freeorioncommon.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 47103BE20CF04D8800A7DF2B /* freeorioncommon.dylib */; }; - 478417730CF0589200BE4710 /* freeorioncommon.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 47103BE20CF04D8800A7DF2B /* freeorioncommon.dylib */; }; - 478417760CF058B100BE4710 /* Process.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5D300A98A3F900DA9C21 /* Process.cpp */; }; - 4784177A0CF058F000BE4710 /* freeorioncommon.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 47103BE20CF04D8800A7DF2B /* freeorioncommon.dylib */; }; - 478418DF0CF059D700BE4710 /* ClientNetworking.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 47102FAA0CEF566200A7DF2B /* ClientNetworking.cpp */; }; - 478418E00CF059DC00BE4710 /* ClientApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C5D0A98A3F900DA9C21 /* ClientApp.cpp */; }; - 478418EE0CF05A5000BE4710 /* libClientCommon.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 478417B10CF0592E00BE4710 /* libClientCommon.a */; }; - 478418EF0CF05A5600BE4710 /* libClientCommon.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 478417B10CF0592E00BE4710 /* libClientCommon.a */; }; - 478418F70CF05AAA00BE4710 /* MessageQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 47102FA40CEF565700A7DF2B /* MessageQueue.cpp */; }; - 47FAB6770A98D89F00F0AF3F /* dmain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C9F0A98A3F900DA9C21 /* dmain.cpp */; }; - 47FAB6780A98D8A600F0AF3F /* ServerApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5CA20A98A3F900DA9C21 /* ServerApp.cpp */; }; - 820BDECD1848A93E009BC457 /* Hotkeys.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 820BDECB1848A93E009BC457 /* Hotkeys.cpp */; }; - 82132C2F15DA2BBC00F5B537 /* ObjectListWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82132C2D15DA2BBC00F5B537 /* ObjectListWnd.cpp */; }; - 82182CFA185B506200D70FF2 /* UniverseWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6780F3F61D000782AD3 /* UniverseWrapper.cpp */; }; - 822E325C19BEEE5E00A7B707 /* libz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 822E325B19BEED6C00A7B707 /* libz.a */; }; - 822EB0FF170832240083EB38 /* CombatLogManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 822EB0FD170832240083EB38 /* CombatLogManager.cpp */; }; - 8242C81C176B0C8E001E1CF2 /* ScopedTimer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8242C81A176B0C8E001E1CF2 /* ScopedTimer.cpp */; }; - 8246FED0194D8F83008C07B5 /* IntComplexValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8246FECF194D8F83008C07B5 /* IntComplexValueRefParser.cpp */; }; - 8250FDE515FCEFDE00523C1C /* Field.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8250FDE315FCEFDD00523C1C /* Field.cpp */; }; - 8257A8D71A90BA610006777D /* GLClientAndServerBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8257A8D61A90BA610006777D /* GLClientAndServerBuffer.cpp */; }; - 82592EF0147E381B00B840A5 /* EffectAccounting.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82592EEF147E381B00B840A5 /* EffectAccounting.cpp */; }; - 82592EF6147E387100B840A5 /* ObjectMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82592EF3147E387100B840A5 /* ObjectMap.cpp */; }; - 825E7259196EC28A00F8114D /* default in Resources */ = {isa = PBXBuildFile; fileRef = 825E7258196EC28A00F8114D /* default */; }; - 825EA76016DB9DB10002A797 /* ModeratorAction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825EA75E16DB9DB10002A797 /* ModeratorAction.cpp */; }; - 82655A401716D9240007EEDD /* CombatSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471D5C6D0A98A3F900DA9C21 /* CombatSystem.cpp */; }; - 82682D8219C24DEC0041C465 /* libGLEW.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E250AD19C2317700F0E2F2 /* libGLEW.a */; }; - 82714D771ADABE680071A329 /* libboost_log.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 82714D761ADABE680071A329 /* libboost_log.a */; }; - 8275F3F91AD31381003568FF /* AccordionPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3DD1AD31381003568FF /* AccordionPanel.cpp */; }; - 8275F3FA1AD31381003568FF /* BuildingsPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3DF1AD31381003568FF /* BuildingsPanel.cpp */; }; - 8275F3FB1AD31381003568FF /* CensusBrowseWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3E11AD31381003568FF /* CensusBrowseWnd.cpp */; }; - 8275F3FC1AD31381003568FF /* IconTextBrowseWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3E31AD31381003568FF /* IconTextBrowseWnd.cpp */; }; - 8275F3FD1AD31381003568FF /* MeterBrowseWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3E51AD31381003568FF /* MeterBrowseWnd.cpp */; }; - 8275F3FE1AD31381003568FF /* MilitaryPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3E71AD31381003568FF /* MilitaryPanel.cpp */; }; - 8275F3FF1AD31381003568FF /* MultiIconValueIndicator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3E91AD31381003568FF /* MultiIconValueIndicator.cpp */; }; - 8275F4001AD31381003568FF /* MultiMeterStatusBar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3EB1AD31381003568FF /* MultiMeterStatusBar.cpp */; }; - 8275F4011AD31381003568FF /* PopulationPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3ED1AD31381003568FF /* PopulationPanel.cpp */; }; - 8275F4021AD31381003568FF /* ResourcePanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3EF1AD31381003568FF /* ResourcePanel.cpp */; }; - 8275F4041AD31381003568FF /* SpecialsPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3F31AD31381003568FF /* SpecialsPanel.cpp */; }; - 8275F4051AD31381003568FF /* SystemResourceSummaryBrowseWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3F51AD31381003568FF /* SystemResourceSummaryBrowseWnd.cpp */; }; - 8275F4061AD31381003568FF /* TextBrowseWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8275F3F71AD31381003568FF /* TextBrowseWnd.cpp */; }; - 827902381737C6EA008AF126 /* ModeratorActionsWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 827902361737C6EA008AF126 /* ModeratorActionsWnd.cpp */; }; - 8280AC5D19C0741B00ADDB65 /* libboost_iostreams.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8280AC5C19C0741B00ADDB65 /* libboost_iostreams.a */; }; - 8280AC5E19C0741B00ADDB65 /* libboost_iostreams.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8280AC5C19C0741B00ADDB65 /* libboost_iostreams.a */; }; - 8280AC5F19C0741C00ADDB65 /* libboost_iostreams.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8280AC5C19C0741B00ADDB65 /* libboost_iostreams.a */; }; - 8280AC6019C0741C00ADDB65 /* libboost_iostreams.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8280AC5C19C0741B00ADDB65 /* libboost_iostreams.a */; }; - 8288432E1850A1770023C4D7 /* Python.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F0E427113F491C00A10EED /* Python.framework */; }; - 8288432F1850A1960023C4D7 /* libboost_python.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7413ACB91A0085B1A0 /* libboost_python.a */; }; - 828843341850A2550023C4D7 /* LoggingWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6760F3F61D000782AD3 /* LoggingWrapper.cpp */; }; - 82884C4B1850B0E60023C4D7 /* EnumWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6750F3F61D000782AD3 /* EnumWrapper.cpp */; }; - 8289A72919BDB307001256C1 /* SDLGUI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471023BA0CEF3AAC00A7DF2B /* SDLGUI.cpp */; }; - 8289A72B19BDB3EF001256C1 /* SDL2.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 8289A72A19BDB3D8001256C1 /* SDL2.framework */; }; - 8289A72C19BDB45C001256C1 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8289A72A19BDB3D8001256C1 /* SDL2.framework */; }; - 828AB7D415F7BCDD0075BE24 /* libpng.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 828AB7D315F7BCDD0075BE24 /* libpng.a */; }; - 828C793F160778AA0075F220 /* FieldIcon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 828C793D160778AA0075F220 /* FieldIcon.cpp */; }; - 8295CEC71A88FFF8000EE624 /* StringComplexValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8295CEC61A88FFF8000EE624 /* StringComplexValueRefParser.cpp */; }; - 82970BEF1A0CF53A008B37EF /* TechTreeArcs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82970BED1A0CF53A008B37EF /* TechTreeArcs.cpp */; }; - 8298A1AE18F9BB980001D3DA /* SaveFileDialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8298A1AC18F9BB980001D3DA /* SaveFileDialog.cpp */; }; - 829BBB6217D09017009D3735 /* ValueRefParserImpl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 829BBB6117D09017009D3735 /* ValueRefParserImpl.cpp */; }; - 829E16B11811181F00F7F52D /* EmpireStatsParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 829E16B01811181F00F7F52D /* EmpireStatsParser.cpp */; }; - 82AB1D13194D97A1009C8220 /* libboost_regex.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7513ACB91A0085B1A0 /* libboost_regex.a */; }; - 82B3265319B603C1005082AC /* ConditionParser7.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82B3265219B603C1005082AC /* ConditionParser7.cpp */; }; - 82B4313414A74E07003EEE42 /* UniverseGenerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82B4313314A74E07003EEE42 /* UniverseGenerator.cpp */; }; - 82B9AEA11AA71CCC00486B2A /* CombatLogWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82B9AE991AA71CCC00486B2A /* CombatLogWnd.cpp */; }; - 82B9AEA21AA71CCC00486B2A /* CombatReportData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82B9AE9B1AA71CCC00486B2A /* CombatReportData.cpp */; }; - 82B9AEA31AA71CCC00486B2A /* CombatReportWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82B9AE9D1AA71CCC00486B2A /* CombatReportWnd.cpp */; }; - 82B9AEA41AA71CCC00486B2A /* GraphicalSummary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82B9AE9F1AA71CCC00486B2A /* GraphicalSummary.cpp */; }; - 82B9D447180C53FE0032F86D /* GraphControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82B9D445180C53FE0032F86D /* GraphControl.cpp */; }; - 82BB623E19BEDF1700A0E281 /* freeorionparse.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 82C07438149DE46200E76876 /* freeorionparse.dylib */; }; - 82BB623F19BEDF6F00A0E281 /* freeorionparse.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 82C07438149DE46200E76876 /* freeorionparse.dylib */; }; - 82BB624019BEDF9F00A0E281 /* freeorionparse.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 82C07438149DE46200E76876 /* freeorionparse.dylib */; }; - 82BEB659159853B300B024CB /* Diplomacy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82BEB657159853B300B024CB /* Diplomacy.cpp */; }; - 82C07470149DE72D00E76876 /* Parse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 825936F1147E3B1B00B840A5 /* Parse.cpp */; }; - 82C560D317D0CC2F00FE3B35 /* Tokens.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82C560D117D0CC2F00FE3B35 /* Tokens.cpp */; }; - 82C96D0B16E4C1F900579E56 /* SerializeModeratorAction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82C96D0A16E4C1F900579E56 /* SerializeModeratorAction.cpp */; }; - 82CE867F19BF27D0004DF6DF /* GUIController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 82CE867E19BF27D0004DF6DF /* GUIController.mm */; }; - 82D2D9A31838C8340011C212 /* ConditionParser5.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82D2D9A11838C8340011C212 /* ConditionParser5.cpp */; }; - 82D2D9A41838C8340011C212 /* ConditionParser6.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82D2D9A21838C8340011C212 /* ConditionParser6.cpp */; }; - 82D563D71A6AEBD6007E1131 /* DoubleComplexValueRefParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82D563D51A6AEBD6007E1131 /* DoubleComplexValueRefParser.cpp */; }; - 82D7B5A01AA3666F00A85842 /* ServerWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82D7B59E1AA3666F00A85842 /* ServerWrapper.cpp */; }; - 82D7B5A31AA37C9000A85842 /* ServerFramework.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82D7B5A11AA37C9000A85842 /* ServerFramework.cpp */; }; - 82E5238F192FE8CE001E2374 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E5238E192FE8CE001E2374 /* AppKit.framework */; }; - 82E52391192FE90F001E2374 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E52390192FE90F001E2374 /* IOKit.framework */; }; - 82E52398192FE99D001E2374 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E52392192FE99C001E2374 /* Carbon.framework */; }; - 82E52399192FE99D001E2374 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E52393192FE99C001E2374 /* Cocoa.framework */; }; - 82E5239A192FE99D001E2374 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E52394192FE99C001E2374 /* CoreData.framework */; }; - 82E5239B192FE99D001E2374 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E52395192FE99C001E2374 /* Foundation.framework */; }; - 82E5239C192FE99D001E2374 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E52396192FE99C001E2374 /* OpenAL.framework */; }; - 82E5239D192FE99D001E2374 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E52397192FE99D001E2374 /* OpenGL.framework */; }; - 82E5239F192FEAA6001E2374 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E5239E192FEAA6001E2374 /* CoreFoundation.framework */; }; - 82E5716115F63D8000BD29C8 /* libfreetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 471D654D0A98B7F800DA9C21 /* libfreetype.a */; }; - 82E68F63190ECB8400BB1AD9 /* EnumText.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82E68F5F190ECB8400BB1AD9 /* EnumText.cpp */; }; - 82E68F64190ECB8400BB1AD9 /* SaveGamePreviewUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82E68F61190ECB8400BB1AD9 /* SaveGamePreviewUtils.cpp */; }; - 82E944E91B04C726004F3343 /* EffectParser5.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82E944E81B04C726004F3343 /* EffectParser5.cpp */; }; - 82E958D3188EC86A00479B77 /* SaveLoad.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 471030490CEF569900A7DF2B /* SaveLoad.cpp */; }; - 82E9DDF119530E5D007E681B /* CombatEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82E9DDED19530E5D007E681B /* CombatEvent.cpp */; }; - 82E9DDF219530E5D007E681B /* CombatEvents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82E9DDEF19530E5D007E681B /* CombatEvents.cpp */; }; - 82EC868B1B40504700D4584D /* Encyclopedia.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 82EC86891B40504700D4584D /* Encyclopedia.cpp */; }; - 82F55DE818B0FF0A00FA9E11 /* libboost_date_time.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 82483B7A15F4F24100D27614 /* libboost_date_time.a */; }; - 9E632AEC13AD24D1003D1874 /* libboost_python.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7413ACB91A0085B1A0 /* libboost_python.a */; }; - 9E632AED13AD24D1003D1874 /* libboost_regex.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7513ACB91A0085B1A0 /* libboost_regex.a */; }; - B7A7D5371ECF2D5000E086C2 /* libboost_date_time.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 82483B7A15F4F24100D27614 /* libboost_date_time.a */; }; - B7A7D5381ECF2D6E00E086C2 /* libboost_regex.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEEEA7513ACB91A0085B1A0 /* libboost_regex.a */; }; - B7A7D5391ECF2D9D00E086C2 /* libz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 822E325B19BEED6C00A7B707 /* libz.a */; }; - CE0ED5F91BCFE16E00375001 /* AIWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE0ED5F71BCFE16E00375001 /* AIWrapper.cpp */; }; - CE13B48F1C8CB0CF0085A4DC /* CommonParamsParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE13B48E1C8CB0CF0085A4DC /* CommonParamsParser.cpp */; }; - CE1939C61F31AD4C0085A4DC /* IDAllocator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE1939C41F31AD4C0085A4DC /* IDAllocator.cpp */; }; - CE2BAD8F1E3E43E40085A4DC /* Pathfinder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE2BAD8D1E3E43E40085A4DC /* Pathfinder.cpp */; }; - CE2C454D1C65036F0085A4DC /* ResourceBrowseWnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE2C454B1C65036F0085A4DC /* ResourceBrowseWnd.cpp */; }; - CE2C454E1C6503990085A4DC /* libz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 822E325B19BEED6C00A7B707 /* libz.a */; }; - CE3102B61DBFD0730085A4DC /* DeferredLayout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE3102B51DBFD0730085A4DC /* DeferredLayout.cpp */; }; - CE3656A91F22244E0085A4DC /* GameRulesParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE3656A81F22244E0085A4DC /* GameRulesParser.cpp */; }; - CE43BB2B1EC456A20085A4DC /* CheckSums.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE43BB291EC456A20085A4DC /* CheckSums.cpp */; }; - CE5C92C91B81FE9C002346C0 /* main.xib in Sources */ = {isa = PBXBuildFile; fileRef = 34ACA40C0FFFABE600500F40 /* main.xib */; }; - CE6183A61BB80D2E00952BEA /* BlockControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE61839D1BB80D2E00952BEA /* BlockControl.cpp */; }; - CE6183A71BB80D2E00952BEA /* ImageBlock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE61839E1BB80D2E00952BEA /* ImageBlock.cpp */; }; - CE6183A81BB80D2E00952BEA /* RichText.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE61839F1BB80D2E00952BEA /* RichText.cpp */; }; - CE6183A91BB80D2E00952BEA /* TagParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE6183A11BB80D2E00952BEA /* TagParser.cpp */; }; - CE6183AA1BB80D2E00952BEA /* TextBlock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE6183A31BB80D2E00952BEA /* TextBlock.cpp */; }; - CE6183AB1BB80D2E00952BEA /* ScrollPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE6183A51BB80D2E00952BEA /* ScrollPanel.cpp */; }; - CE6183AE1BB80D4900952BEA /* CUILinkTextBlock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE6183AC1BB80D4900952BEA /* CUILinkTextBlock.cpp */; }; - CE6730B01BC29F23004179D1 /* CommonFramework.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CEC4ADC21BC15195000DFA9B /* CommonFramework.cpp */; }; - CE6730B11BC29F25004179D1 /* CommonFramework.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CEC4ADC21BC15195000DFA9B /* CommonFramework.cpp */; }; - CE71ED7A1DADAB120085A4DC /* DependencyVersions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE97F70A1BC7D74D0002988B /* DependencyVersions.cpp */; }; - CE71ED7B1DADAB130085A4DC /* DependencyVersions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE97F70A1BC7D74D0002988B /* DependencyVersions.cpp */; }; - CE71ED7C1DADAB140085A4DC /* DependencyVersions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE97F70A1BC7D74D0002988B /* DependencyVersions.cpp */; }; - CE76B9CF1CBD7ACC0085A4DC /* ChangeLog.md in Resources */ = {isa = PBXBuildFile; fileRef = CE76B9CD1CBD7A990085A4DC /* ChangeLog.md */; }; - CEC4ADC11BC13741000DFA9B /* AIFramework.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CEC4ADBF1BC13741000DFA9B /* AIFramework.cpp */; }; - CED22D4D1EB8F15B0085A4DC /* libboost_log.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 82714D761ADABE680071A329 /* libboost_log.a */; }; - CED22D4F1EB8F15B0085A4DC /* libboost_log.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 82714D761ADABE680071A329 /* libboost_log.a */; }; - CED22D511EB8F15B0085A4DC /* libboost_log.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 82714D761ADABE680071A329 /* libboost_log.a */; }; - CED22D541EB8F2B50085A4DC /* libboost_log_setup.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CED22D531EB8F2B50085A4DC /* libboost_log_setup.a */; }; - CED22D551EB8F2B50085A4DC /* libboost_log_setup.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CED22D531EB8F2B50085A4DC /* libboost_log_setup.a */; }; - CED22D561EB8F2B50085A4DC /* libboost_log_setup.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CED22D531EB8F2B50085A4DC /* libboost_log_setup.a */; }; - CED22D571EB8F2B50085A4DC /* libboost_log_setup.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CED22D531EB8F2B50085A4DC /* libboost_log_setup.a */; }; - CED6E5521C6DDC0F0085A4DC /* Fighter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CED6E5501C6DDC0F0085A4DC /* Fighter.cpp */; }; - CEDABCF01C688D050085A4DC /* libboost_locale.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CEDABCEF1C688D050085A4DC /* libboost_locale.a */; }; - CEDABCF31C688E290085A4DC /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CEDABCF21C688E290085A4DC /* libiconv.dylib */; }; - CEDC4AEF1BD3E361009BB38D /* EmpireWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343EC6740F3F61D000782AD3 /* EmpireWrapper.cpp */; }; - CEED195F1C79BA2F0085A4DC /* Supply.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CEED195D1C79BA2F0085A4DC /* Supply.cpp */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 3402E2620F5A319B00DF6FE7 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 471FEF870A9A629800C36AA3; - remoteInfo = FreeOrionAI; - }; - 3402E2640F5A319B00DF6FE7 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 471D5D430A98A46000DA9C21; - remoteInfo = FreeOrionServer; - }; - 3402E2660F5A319B00DF6FE7 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8DD76F620486A84900D96B5E; - remoteInfo = FreeOrionClient; - }; - 3ABEAB1E1749BDF100E34912 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 47103BE10CF04D8800A7DF2B; - remoteInfo = Common; - }; - 471D65480A98B73C00DA9C21 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 471D5A2F0A988D5700DA9C21; - remoteInfo = GG; - }; - 478417640CF057B800BE4710 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 47103BE10CF04D8800A7DF2B; - remoteInfo = Common; - }; - 478417660CF057BF00BE4710 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 47103BE10CF04D8800A7DF2B; - remoteInfo = Common; - }; - 478417680CF057C300BE4710 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 47103BE10CF04D8800A7DF2B; - remoteInfo = Common; - }; - 478418E90CF05A2800BE4710 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4784177C0CF0592E00BE4710; - remoteInfo = ClientCommon; - }; - 478418EB0CF05A2E00BE4710 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4784177C0CF0592E00BE4710; - remoteInfo = ClientCommon; - }; - 8237404A19BDA4120046BFD1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 82C073D8149DE46200E76876; - remoteInfo = Parse; - }; - 8257A8D51A90B7EF0006777D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 829C76351689EFD4005881B4; - remoteInfo = Configure; - }; - 829C763E1689F0EA005881B4 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 829C76351689EFD4005881B4; - remoteInfo = INIT; - }; - 82A1B47019240B3100A3C137 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 829C76351689EFD4005881B4; - remoteInfo = Configure; - }; - 82C07477149DE7AC00E76876 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 82C073D8149DE46200E76876; - remoteInfo = Parse; - }; - 82C0747A149DE7EB00E76876 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 82C073D8149DE46200E76876; - remoteInfo = Parse; - }; - 82EC622318C610AB000237C2 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 3402E25C0F5A317400DF6FE7; - remoteInfo = FreeOrion; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 3402E3DA0F5A31FA00DF6FE7 /* Copy Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 8289A72B19BDB3EF001256C1 /* SDL2.framework in Copy Frameworks */, - 34F0E455113F49E900A10EED /* Python.framework in Copy Frameworks */, - 3402E5360F5A339900DF6FE7 /* vorbis.framework in Copy Frameworks */, - 3402E5370F5A339900DF6FE7 /* Ogg.framework in Copy Frameworks */, - ); - name = "Copy Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - 3402E53C0F5A33E300DF6FE7 /* Copy Main Executable */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 6; - files = ( - 3402E53D0F5A33EF00DF6FE7 /* FreeOrion in Copy Main Executable */, - ); - name = "Copy Main Executable"; - runOnlyForDeploymentPostprocessing = 0; - }; - 3402EB620F5A345D00DF6FE7 /* Copy Shared Libraries */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 12; - files = ( - 3ABEABA51749D05E00E34912 /* freeorioncommon.dylib in Copy Shared Libraries */, - 3ABEABA41749D05700E34912 /* freeorionparse.dylib in Copy Shared Libraries */, - ); - name = "Copy Shared Libraries"; - runOnlyForDeploymentPostprocessing = 0; - }; - 3402EB750F5A34FC00DF6FE7 /* Copy Server and AI client */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}/Contents/Executables"; - dstSubfolderSpec = 0; - files = ( - 3402F0FB0F5A355D00DF6FE7 /* freeoriond in Copy Server and AI client */, - 3402F0FC0F5A355D00DF6FE7 /* freeorionca in Copy Server and AI client */, - ); - name = "Copy Server and AI client"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 2F22EC0412F7F4CF00456CDE /* TechTreeLayout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TechTreeLayout.cpp; sourceTree = ""; }; - 2F22EC0512F7F4CF00456CDE /* TechTreeLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TechTreeLayout.h; sourceTree = ""; }; - 2F60966312EEAD2200F58913 /* PlayerListWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlayerListWnd.cpp; sourceTree = ""; }; - 2F60966412EEAD2200F58913 /* PlayerListWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayerListWnd.h; sourceTree = ""; }; - 2F60966A12EEAF0200F58913 /* GroupBox.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GroupBox.cpp; sourceTree = ""; }; - 2F60966C12EEAF2C00F58913 /* GroupBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GroupBox.h; sourceTree = ""; }; - 3402E25D0F5A317400DF6FE7 /* FreeOrion.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FreeOrion.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 343EC6330F3F513700782AD3 /* UnicodeCharsets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UnicodeCharsets.cpp; sourceTree = ""; }; - 343EC64F0F3F528E00782AD3 /* checked.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = checked.h; sourceTree = ""; }; - 343EC6500F3F528E00782AD3 /* core.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = core.h; sourceTree = ""; }; - 343EC6510F3F528E00782AD3 /* unchecked.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = unchecked.h; sourceTree = ""; }; - 343EC6520F3F52B200782AD3 /* UnicodeCharsets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnicodeCharsets.h; sourceTree = ""; }; - 343EC6590F3F5B5300782AD3 /* DesignWnd.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DesignWnd.cpp; sourceTree = ""; }; - 343EC65A0F3F5B5300782AD3 /* DesignWnd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DesignWnd.h; sourceTree = ""; }; - 343EC65D0F3F5C7C00782AD3 /* EncyclopediaDetailPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EncyclopediaDetailPanel.cpp; sourceTree = ""; }; - 343EC65E0F3F5C7C00782AD3 /* EncyclopediaDetailPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EncyclopediaDetailPanel.h; sourceTree = ""; }; - 343EC6610F3F5C7C00782AD3 /* QueueListBox.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = QueueListBox.cpp; sourceTree = ""; }; - 343EC6620F3F5C7C00782AD3 /* QueueListBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QueueListBox.h; sourceTree = ""; }; - 343EC66D0F3F600800782AD3 /* StrongTypedef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StrongTypedef.h; sourceTree = ""; }; - 343EC6740F3F61D000782AD3 /* EmpireWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EmpireWrapper.cpp; sourceTree = ""; }; - 343EC6750F3F61D000782AD3 /* EnumWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EnumWrapper.cpp; sourceTree = ""; }; - 343EC6760F3F61D000782AD3 /* LoggingWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LoggingWrapper.cpp; sourceTree = ""; }; - 343EC6770F3F61D000782AD3 /* SetWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SetWrapper.h; sourceTree = ""; }; - 343EC6780F3F61D000782AD3 /* UniverseWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UniverseWrapper.cpp; sourceTree = ""; }; - 343EC6790F3F61D000782AD3 /* CommonWrappers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommonWrappers.h; sourceTree = ""; }; - 346D87ED0FC5F6080095593E /* ShaderProgram.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ShaderProgram.cpp; sourceTree = ""; }; - 346D87EE0FC5F6080095593E /* ShaderProgram.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShaderProgram.h; sourceTree = ""; }; - 34972BE60F49CBE80015C864 /* ChatWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ChatWnd.cpp; sourceTree = ""; }; - 34972BE70F49CBE80015C864 /* ChatWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChatWnd.h; sourceTree = ""; }; - 34972BEC0F49CBF90015C864 /* Sound.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Sound.cpp; sourceTree = ""; }; - 34972BED0F49CBF90015C864 /* Sound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Sound.h; sourceTree = ""; }; - 34ACA40C0FFFABE600500F40 /* main.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = main.xib; sourceTree = ""; }; - 34ACA4BE0FFFD2DB00500F40 /* chmain.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = chmain.mm; sourceTree = ""; }; - 34ACA4C50FFFD50100500F40 /* chmain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = chmain.h; sourceTree = ""; }; - 34B9EBEE0F5E1F7F005BF6A4 /* Config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Config.h; path = dep/include/GG/Config.h; sourceTree = ""; }; - 34BAB0BE0FC5CD7300EF792E /* DefaultFont.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DefaultFont.h; sourceTree = ""; }; - 34BAB0C20FC5CDC400EF792E /* FontFwd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FontFwd.h; sourceTree = ""; }; - 34BAB0C30FC5CDC400EF792E /* MultiEditFwd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiEditFwd.h; sourceTree = ""; }; - 34C44956118271590071E09A /* Serialize.ipp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Serialize.ipp; sourceTree = ""; }; - 34C44957118271590071E09A /* SerializeEmpire.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerializeEmpire.cpp; sourceTree = ""; }; - 34C44958118271590071E09A /* SerializeMultiplayerCommon.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerializeMultiplayerCommon.cpp; sourceTree = ""; }; - 34C44959118271590071E09A /* SerializeOrderSet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerializeOrderSet.cpp; sourceTree = ""; }; - 34C4495B118271590071E09A /* SerializeUniverse.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerializeUniverse.cpp; sourceTree = ""; }; - 34C4495D118271590071E09A /* Version.cpp.in */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp; fileEncoding = 4; path = Version.cpp.in; sourceTree = ""; }; - 34C9B34311DA2E95003695F4 /* ClrConstants.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ClrConstants.cpp; sourceTree = ""; }; - 34C9B34711DA2ECA003695F4 /* ClrConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClrConstants.h; sourceTree = ""; }; - 34D3CE8A11D7DA03007C1E78 /* Species.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Species.cpp; sourceTree = ""; }; - 34D3CE8B11D7DA03007C1E78 /* Species.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Species.h; sourceTree = ""; }; - 34F0E427113F491C00A10EED /* Python.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Python.framework; sourceTree = ""; }; - 3A1BF3EA1748875200812237 /* Version.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Version.cpp; path = ../util/Version.cpp; sourceTree = SOURCE_ROOT; }; - 3A5105521748D68B00DC258B /* i18n.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = i18n.cpp; sourceTree = ""; }; - 3A5105531748D68B00DC258B /* i18n.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = i18n.h; sourceTree = ""; }; - 3A5105551748D6A600DC258B /* Logger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Logger.cpp; sourceTree = ""; }; - 3A5105551748D6A600DC258C /* LoggerWithOptionsDB.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LoggerWithOptionsDB.cpp; sourceTree = ""; }; - 3A5105561748D6A700DC258B /* Logger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logger.h; sourceTree = ""; }; - 3A5105561748D6A700DC258C /* LoggerWithOptionsDB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoggerWithOptionsDB.h; sourceTree = ""; }; - 4710233D0CEF3AAB00A7DF2B /* GGDoc.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GGDoc.txt; sourceTree = ""; }; - 4710233F0CEF3AAB00A7DF2B /* AlignmentFlags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AlignmentFlags.h; sourceTree = ""; }; - 471023400CEF3AAB00A7DF2B /* Base.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Base.h; sourceTree = ""; }; - 471023410CEF3AAB00A7DF2B /* BrowseInfoWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BrowseInfoWnd.h; sourceTree = ""; }; - 471023420CEF3AAB00A7DF2B /* Button.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Button.h; sourceTree = ""; }; - 471023430CEF3AAB00A7DF2B /* Clr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Clr.h; sourceTree = ""; }; - 471023440CEF3AAB00A7DF2B /* Control.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Control.h; sourceTree = ""; }; - 471023450CEF3AAB00A7DF2B /* Cursor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Cursor.h; sourceTree = ""; }; - 471023470CEF3AAB00A7DF2B /* ColorDlg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ColorDlg.h; sourceTree = ""; }; - 471023480CEF3AAB00A7DF2B /* FileDlg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileDlg.h; sourceTree = ""; }; - 471023490CEF3AAB00A7DF2B /* ThreeButtonDlg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreeButtonDlg.h; sourceTree = ""; }; - 4710234A0CEF3AAB00A7DF2B /* DrawUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DrawUtil.h; sourceTree = ""; }; - 4710234B0CEF3AAB00A7DF2B /* DropDownList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropDownList.h; sourceTree = ""; }; - 4710234C0CEF3AAB00A7DF2B /* DynamicGraphic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DynamicGraphic.h; sourceTree = ""; }; - 4710234D0CEF3AAB00A7DF2B /* Edit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Edit.h; sourceTree = ""; }; - 4710234E0CEF3AAB00A7DF2B /* Enum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Enum.h; sourceTree = ""; }; - 4710234F0CEF3AAB00A7DF2B /* EventPump.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EventPump.h; sourceTree = ""; }; - 471023500CEF3AAB00A7DF2B /* Exception.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Exception.h; sourceTree = ""; }; - 471023510CEF3AAB00A7DF2B /* Flags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Flags.h; sourceTree = ""; }; - 471023520CEF3AAB00A7DF2B /* Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Font.h; sourceTree = ""; }; - 471023550CEF3AAB00A7DF2B /* GUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUI.h; sourceTree = ""; }; - 471023560CEF3AAB00A7DF2B /* Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Layout.h; sourceTree = ""; }; - 471023570CEF3AAB00A7DF2B /* ListBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ListBox.h; sourceTree = ""; }; - 471023580CEF3AAB00A7DF2B /* Menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Menu.h; sourceTree = ""; }; - 471023590CEF3AAB00A7DF2B /* MultiEdit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiEdit.h; sourceTree = ""; }; - 4710235D0CEF3AAB00A7DF2B /* PtRect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PtRect.h; sourceTree = ""; }; - 4710235E0CEF3AAB00A7DF2B /* Scroll.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Scroll.h; sourceTree = ""; }; - 471023600CEF3AAB00A7DF2B /* SDLGUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLGUI.h; sourceTree = ""; }; - 4710236C0CEF3AAB00A7DF2B /* Slider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Slider.h; sourceTree = ""; }; - 4710236D0CEF3AAB00A7DF2B /* Spin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Spin.h; sourceTree = ""; }; - 4710236E0CEF3AAB00A7DF2B /* StaticGraphic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StaticGraphic.h; sourceTree = ""; }; - 4710236F0CEF3AAB00A7DF2B /* StyleFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StyleFactory.h; sourceTree = ""; }; - 471023700CEF3AAC00A7DF2B /* TabWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TabWnd.h; sourceTree = ""; }; - 471023710CEF3AAC00A7DF2B /* TextControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextControl.h; sourceTree = ""; }; - 471023720CEF3AAC00A7DF2B /* Texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Texture.h; sourceTree = ""; }; - 471023730CEF3AAC00A7DF2B /* Timer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Timer.h; sourceTree = ""; }; - 471023740CEF3AAC00A7DF2B /* Wnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Wnd.h; sourceTree = ""; }; - 471023760CEF3AAC00A7DF2B /* WndEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WndEvent.h; sourceTree = ""; }; - 471023770CEF3AAC00A7DF2B /* ZList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZList.h; sourceTree = ""; }; - 471023920CEF3AAC00A7DF2B /* AlignmentFlags.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AlignmentFlags.cpp; sourceTree = ""; }; - 471023930CEF3AAC00A7DF2B /* Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Base.cpp; sourceTree = ""; }; - 471023940CEF3AAC00A7DF2B /* BrowseInfoWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BrowseInfoWnd.cpp; sourceTree = ""; }; - 471023950CEF3AAC00A7DF2B /* Button.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Button.cpp; sourceTree = ""; }; - 471023970CEF3AAC00A7DF2B /* Control.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Control.cpp; sourceTree = ""; }; - 471023980CEF3AAC00A7DF2B /* Cursor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cursor.cpp; sourceTree = ""; }; - 4710239A0CEF3AAC00A7DF2B /* ColorDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ColorDlg.cpp; sourceTree = ""; }; - 4710239B0CEF3AAC00A7DF2B /* FileDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileDlg.cpp; sourceTree = ""; }; - 4710239D0CEF3AAC00A7DF2B /* ThreeButtonDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ThreeButtonDlg.cpp; sourceTree = ""; }; - 4710239E0CEF3AAC00A7DF2B /* DrawUtil.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DrawUtil.cpp; sourceTree = ""; }; - 4710239F0CEF3AAC00A7DF2B /* DropDownList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DropDownList.cpp; sourceTree = ""; }; - 471023A00CEF3AAC00A7DF2B /* DynamicGraphic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DynamicGraphic.cpp; sourceTree = ""; }; - 471023A10CEF3AAC00A7DF2B /* Edit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Edit.cpp; sourceTree = ""; }; - 471023A20CEF3AAC00A7DF2B /* EventPump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EventPump.cpp; sourceTree = ""; }; - 471023A30CEF3AAC00A7DF2B /* Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Font.cpp; sourceTree = ""; }; - 471023A40CEF3AAC00A7DF2B /* GUI.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUI.cpp; sourceTree = ""; }; - 471023A50CEF3AAC00A7DF2B /* Layout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Layout.cpp; sourceTree = ""; }; - 471023A60CEF3AAC00A7DF2B /* ListBox.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ListBox.cpp; sourceTree = ""; }; - 471023A70CEF3AAC00A7DF2B /* Menu.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Menu.cpp; sourceTree = ""; }; - 471023A80CEF3AAC00A7DF2B /* MultiEdit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiEdit.cpp; sourceTree = ""; }; - 471023B50CEF3AAC00A7DF2B /* PtRect.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PtRect.cpp; sourceTree = ""; }; - 471023B70CEF3AAC00A7DF2B /* Scroll.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Scroll.cpp; sourceTree = ""; }; - 471023BA0CEF3AAC00A7DF2B /* SDLGUI.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SDLGUI.cpp; sourceTree = ""; }; - 471023BC0CEF3AAC00A7DF2B /* StaticGraphic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticGraphic.cpp; sourceTree = ""; }; - 471023BD0CEF3AAC00A7DF2B /* StyleFactory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StyleFactory.cpp; sourceTree = ""; }; - 471023BE0CEF3AAC00A7DF2B /* TabWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TabWnd.cpp; sourceTree = ""; }; - 471023BF0CEF3AAC00A7DF2B /* TextControl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextControl.cpp; sourceTree = ""; }; - 471023C00CEF3AAC00A7DF2B /* Texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Texture.cpp; sourceTree = ""; }; - 471023C10CEF3AAC00A7DF2B /* Timer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Timer.cpp; sourceTree = ""; }; - 471023C20CEF3AAC00A7DF2B /* Wnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Wnd.cpp; sourceTree = ""; }; - 471023C40CEF3AAC00A7DF2B /* WndEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WndEvent.cpp; sourceTree = ""; }; - 471023C50CEF3AAC00A7DF2B /* ZList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZList.cpp; sourceTree = ""; }; - 47102F870CEF55E700A7DF2B /* HumanClientFSM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HumanClientFSM.cpp; sourceTree = ""; }; - 47102F880CEF55E700A7DF2B /* HumanClientFSM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HumanClientFSM.h; sourceTree = ""; }; - 47102F890CEF55F700A7DF2B /* ClientFSMEvents.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ClientFSMEvents.cpp; sourceTree = ""; }; - 47102F8A0CEF55F700A7DF2B /* ClientFSMEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClientFSMEvents.h; sourceTree = ""; }; - 47102F9E0CEF562700A7DF2B /* AIInterface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AIInterface.cpp; sourceTree = ""; }; - 47102F9F0CEF562700A7DF2B /* AIInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIInterface.h; sourceTree = ""; }; - 47102FA40CEF565700A7DF2B /* MessageQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MessageQueue.cpp; sourceTree = ""; }; - 47102FA50CEF565700A7DF2B /* MessageQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MessageQueue.h; sourceTree = ""; }; - 47102FA60CEF565700A7DF2B /* Networking.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Networking.cpp; sourceTree = ""; }; - 47102FA70CEF565700A7DF2B /* Networking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Networking.h; sourceTree = ""; }; - 47102FA80CEF565700A7DF2B /* ServerNetworking.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ServerNetworking.cpp; sourceTree = ""; }; - 47102FA90CEF565700A7DF2B /* ServerNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServerNetworking.h; sourceTree = ""; }; - 47102FAA0CEF566200A7DF2B /* ClientNetworking.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ClientNetworking.cpp; sourceTree = ""; }; - 47102FAB0CEF566200A7DF2B /* ClientNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClientNetworking.h; sourceTree = ""; }; - 471030470CEF569200A7DF2B /* ServerFSM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ServerFSM.cpp; sourceTree = ""; }; - 471030480CEF569200A7DF2B /* ServerFSM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServerFSM.h; sourceTree = ""; }; - 471030490CEF569900A7DF2B /* SaveLoad.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SaveLoad.cpp; sourceTree = ""; }; - 4710304A0CEF569900A7DF2B /* SaveLoad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveLoad.h; sourceTree = ""; }; - 471032380CEF6A7600A7DF2B /* Ogg.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Ogg.framework; sourceTree = ""; }; - 4710323F0CEF6A8B00A7DF2B /* vorbis.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = vorbis.framework; sourceTree = ""; }; - 471033FE0CEF7BCB00A7DF2B /* FreeOrion.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = FreeOrion.icns; sourceTree = ""; }; - 47103BE20CF04D8800A7DF2B /* freeorioncommon.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = freeorioncommon.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; - 471D5A300A988D5700DA9C21 /* libGG.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libGG.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 471D5C5A0A98A3F900DA9C21 /* AIClientApp.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AIClientApp.cpp; sourceTree = ""; }; - 471D5C5B0A98A3F900DA9C21 /* AIClientApp.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AIClientApp.h; sourceTree = ""; }; - 471D5C5C0A98A3F900DA9C21 /* camain.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = camain.cpp; sourceTree = ""; }; - 471D5C5D0A98A3F900DA9C21 /* ClientApp.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientApp.cpp; sourceTree = ""; }; - 471D5C5E0A98A3F900DA9C21 /* ClientApp.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientApp.h; sourceTree = ""; }; - 471D5C600A98A3F900DA9C21 /* Doxyfile */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = Doxyfile; sourceTree = ""; }; - 471D5C620A98A3F900DA9C21 /* chmain.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = chmain.cpp; sourceTree = ""; }; - 471D5C640A98A3F900DA9C21 /* HumanClientApp.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = HumanClientApp.cpp; sourceTree = ""; }; - 471D5C650A98A3F900DA9C21 /* HumanClientApp.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = HumanClientApp.h; sourceTree = ""; }; - 471D5C6D0A98A3F900DA9C21 /* CombatSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CombatSystem.cpp; sourceTree = ""; }; - 471D5C6E0A98A3F900DA9C21 /* CombatSystem.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CombatSystem.h; sourceTree = ""; }; - 471D5C730A98A3F900DA9C21 /* DoxyFile */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = DoxyFile; sourceTree = ""; }; - 471D5C740A98A3F900DA9C21 /* EmpireDoc.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = EmpireDoc.txt; sourceTree = ""; }; - 471D5C750A98A3F900DA9C21 /* Empire.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Empire.cpp; sourceTree = ""; }; - 471D5C760A98A3F900DA9C21 /* Empire.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Empire.h; sourceTree = ""; }; - 471D5C770A98A3F900DA9C21 /* EmpireManager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = EmpireManager.cpp; sourceTree = ""; }; - 471D5C780A98A3F900DA9C21 /* EmpireManager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = EmpireManager.h; sourceTree = ""; }; - 471D5C790A98A3F900DA9C21 /* ResourcePool.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ResourcePool.cpp; sourceTree = ""; }; - 471D5C7A0A98A3F900DA9C21 /* ResourcePool.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ResourcePool.h; sourceTree = ""; }; - 471D5C810A98A3F900DA9C21 /* Doxyfile */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = Doxyfile; sourceTree = ""; }; - 471D5C820A98A3F900DA9C21 /* NetworkDoc.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = NetworkDoc.txt; sourceTree = ""; }; - 471D5C830A98A3F900DA9C21 /* Message.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Message.cpp; sourceTree = ""; }; - 471D5C840A98A3F900DA9C21 /* Message.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Message.h; sourceTree = ""; }; - 471D5C9F0A98A3F900DA9C21 /* dmain.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = dmain.cpp; sourceTree = ""; }; - 471D5CA10A98A3F900DA9C21 /* Doxyfile */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = Doxyfile; sourceTree = ""; }; - 471D5CA20A98A3F900DA9C21 /* ServerApp.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ServerApp.cpp; sourceTree = ""; }; - 471D5CA30A98A3F900DA9C21 /* ServerApp.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ServerApp.h; sourceTree = ""; }; - 471D5CA50A98A3F900DA9C21 /* About.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = About.cpp; sourceTree = ""; }; - 471D5CA60A98A3F900DA9C21 /* About.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = About.h; sourceTree = ""; }; - 471D5CA70A98A3F900DA9C21 /* BuildDesignatorWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = BuildDesignatorWnd.cpp; sourceTree = ""; }; - 471D5CA80A98A3F900DA9C21 /* BuildDesignatorWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BuildDesignatorWnd.h; sourceTree = ""; }; - 471D5CA90A98A3F900DA9C21 /* ClientUI.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientUI.cpp; sourceTree = ""; }; - 471D5CAA0A98A3F900DA9C21 /* ClientUI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientUI.h; sourceTree = ""; }; - 471D5CAD0A98A3F900DA9C21 /* CUIControls.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CUIControls.cpp; sourceTree = ""; }; - 471D5CAE0A98A3F900DA9C21 /* CUIControls.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CUIControls.h; sourceTree = ""; }; - 471D5CAF0A98A3F900DA9C21 /* CUIDrawUtil.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CUIDrawUtil.cpp; sourceTree = ""; }; - 471D5CB00A98A3F900DA9C21 /* CUIDrawUtil.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CUIDrawUtil.h; sourceTree = ""; }; - 471D5CB10A98A3F900DA9C21 /* CUISpin.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CUISpin.h; sourceTree = ""; }; - 471D5CB20A98A3F900DA9C21 /* CUIStyle.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CUIStyle.cpp; sourceTree = ""; }; - 471D5CB30A98A3F900DA9C21 /* CUIStyle.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CUIStyle.h; sourceTree = ""; }; - 471D5CB60A98A3F900DA9C21 /* CUIWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CUIWnd.cpp; sourceTree = ""; }; - 471D5CB70A98A3F900DA9C21 /* CUIWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CUIWnd.h; sourceTree = ""; }; - 471D5CBA0A98A3F900DA9C21 /* FleetButton.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = FleetButton.cpp; sourceTree = ""; }; - 471D5CBB0A98A3F900DA9C21 /* FleetButton.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FleetButton.h; sourceTree = ""; }; - 471D5CBC0A98A3F900DA9C21 /* FleetWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = FleetWnd.cpp; sourceTree = ""; }; - 471D5CBD0A98A3F900DA9C21 /* FleetWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FleetWnd.h; sourceTree = ""; }; - 471D5CC00A98A3F900DA9C21 /* GalaxySetupWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = GalaxySetupWnd.cpp; sourceTree = ""; }; - 471D5CC10A98A3F900DA9C21 /* GalaxySetupWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GalaxySetupWnd.h; sourceTree = ""; }; - 471D5CC20A98A3F900DA9C21 /* InGameMenu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = InGameMenu.cpp; sourceTree = ""; }; - 471D5CC30A98A3F900DA9C21 /* InGameMenu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = InGameMenu.h; sourceTree = ""; }; - 471D5CC40A98A3F900DA9C21 /* IntroScreen.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IntroScreen.cpp; sourceTree = ""; }; - 471D5CC50A98A3F900DA9C21 /* IntroScreen.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = IntroScreen.h; sourceTree = ""; }; - 471D5CC60A98A3F900DA9C21 /* LinkText.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = LinkText.cpp; sourceTree = ""; }; - 471D5CC70A98A3F900DA9C21 /* LinkText.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LinkText.h; sourceTree = ""; }; - 471D5CC80A98A3F900DA9C21 /* MapWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MapWnd.cpp; sourceTree = ""; }; - 471D5CC90A98A3F900DA9C21 /* MapWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MapWnd.h; sourceTree = ""; }; - 471D5CCA0A98A3F900DA9C21 /* MultiplayerLobbyWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MultiplayerLobbyWnd.cpp; sourceTree = ""; }; - 471D5CCB0A98A3F900DA9C21 /* MultiplayerLobbyWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MultiplayerLobbyWnd.h; sourceTree = ""; }; - 471D5CCC0A98A3F900DA9C21 /* OptionsWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = OptionsWnd.cpp; sourceTree = ""; }; - 471D5CCD0A98A3F900DA9C21 /* OptionsWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OptionsWnd.h; sourceTree = ""; }; - 471D5CD00A98A3F900DA9C21 /* ProductionWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ProductionWnd.cpp; sourceTree = ""; }; - 471D5CD10A98A3F900DA9C21 /* ProductionWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ProductionWnd.h; sourceTree = ""; }; - 471D5CD20A98A3F900DA9C21 /* ResearchWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ResearchWnd.cpp; sourceTree = ""; }; - 471D5CD30A98A3F900DA9C21 /* ResearchWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ResearchWnd.h; sourceTree = ""; }; - 471D5CD40A98A3F900DA9C21 /* ServerConnectWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ServerConnectWnd.cpp; sourceTree = ""; }; - 471D5CD50A98A3F900DA9C21 /* ServerConnectWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ServerConnectWnd.h; sourceTree = ""; }; - 471D5CD60A98A3F900DA9C21 /* SidePanel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SidePanel.cpp; sourceTree = ""; }; - 471D5CD70A98A3F900DA9C21 /* SidePanel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SidePanel.h; sourceTree = ""; }; - 471D5CD80A98A3F900DA9C21 /* SitRepPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SitRepPanel.cpp; sourceTree = ""; }; - 471D5CD90A98A3F900DA9C21 /* SitRepPanel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SitRepPanel.h; sourceTree = ""; }; - 471D5CDC0A98A3F900DA9C21 /* StringTable.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StringTable.cpp; sourceTree = ""; }; - 471D5CDD0A98A3F900DA9C21 /* StringTable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StringTable.h; sourceTree = ""; }; - 471D5CDE0A98A3F900DA9C21 /* SystemIcon.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SystemIcon.cpp; sourceTree = ""; }; - 471D5CDF0A98A3F900DA9C21 /* SystemIcon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SystemIcon.h; sourceTree = ""; }; - 471D5CE00A98A3F900DA9C21 /* TechTreeWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TechTreeWnd.cpp; sourceTree = ""; }; - 471D5CE10A98A3F900DA9C21 /* TechTreeWnd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TechTreeWnd.h; sourceTree = ""; }; - 471D5CE50A98A3F900DA9C21 /* Building.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Building.cpp; sourceTree = ""; }; - 471D5CE60A98A3F900DA9C21 /* Building.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Building.h; sourceTree = ""; }; - 471D5CE70A98A3F900DA9C21 /* Condition.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Condition.cpp; sourceTree = ""; }; - 471D5CE80A98A3F900DA9C21 /* Condition.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Condition.h; sourceTree = ""; }; - 471D5CEE0A98A3F900DA9C21 /* Doxyfile */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = Doxyfile; sourceTree = ""; }; - 471D5CEF0A98A3F900DA9C21 /* Effect.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Effect.cpp; sourceTree = ""; }; - 471D5CF00A98A3F900DA9C21 /* Effect.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Effect.h; sourceTree = ""; }; - 471D5CF20A98A3F900DA9C21 /* Enums.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Enums.cpp; sourceTree = ""; }; - 471D5CF30A98A3F900DA9C21 /* Enums.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Enums.h; sourceTree = ""; }; - 471D5CF40A98A3F900DA9C21 /* Fleet.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Fleet.cpp; sourceTree = ""; }; - 471D5CF50A98A3F900DA9C21 /* Fleet.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Fleet.h; sourceTree = ""; }; - 471D5CF60A98A3F900DA9C21 /* blocking_combiner.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = blocking_combiner.h; sourceTree = ""; }; - 471D5CF70A98A3F900DA9C21 /* Meter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Meter.cpp; sourceTree = ""; }; - 471D5CF80A98A3F900DA9C21 /* Meter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Meter.h; sourceTree = ""; }; - 471D5CFC0A98A3F900DA9C21 /* Planet.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Planet.cpp; sourceTree = ""; }; - 471D5CFD0A98A3F900DA9C21 /* Planet.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Planet.h; sourceTree = ""; }; - 471D5CFE0A98A3F900DA9C21 /* PopCenter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = PopCenter.cpp; sourceTree = ""; }; - 471D5CFF0A98A3F900DA9C21 /* PopCenter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PopCenter.h; sourceTree = ""; }; - 471D5D000A98A3F900DA9C21 /* Predicates.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Predicates.cpp; sourceTree = ""; }; - 471D5D010A98A3F900DA9C21 /* Predicates.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Predicates.h; sourceTree = ""; }; - 471D5D020A98A3F900DA9C21 /* ResourceCenter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ResourceCenter.cpp; sourceTree = ""; }; - 471D5D030A98A3F900DA9C21 /* ResourceCenter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ResourceCenter.h; sourceTree = ""; }; - 471D5D040A98A3F900DA9C21 /* Ship.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Ship.cpp; sourceTree = ""; }; - 471D5D050A98A3F900DA9C21 /* Ship.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Ship.h; sourceTree = ""; }; - 471D5D060A98A3F900DA9C21 /* ShipDesign.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ShipDesign.cpp; sourceTree = ""; }; - 471D5D070A98A3F900DA9C21 /* ShipDesign.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ShipDesign.h; sourceTree = ""; }; - 471D5D080A98A3F900DA9C21 /* Special.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Special.cpp; sourceTree = ""; }; - 471D5D090A98A3F900DA9C21 /* Special.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Special.h; sourceTree = ""; }; - 471D5D0A0A98A3F900DA9C21 /* System.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = System.cpp; sourceTree = ""; }; - 471D5D0B0A98A3F900DA9C21 /* System.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = System.h; sourceTree = ""; }; - 471D5D0C0A98A3F900DA9C21 /* Tech.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Tech.cpp; sourceTree = ""; }; - 471D5D0D0A98A3F900DA9C21 /* Tech.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Tech.h; sourceTree = ""; }; - 471D5D0F0A98A3F900DA9C21 /* Universe.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Universe.cpp; sourceTree = ""; }; - 471D5D100A98A3F900DA9C21 /* Universe.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Universe.h; sourceTree = ""; }; - 471D5D110A98A3F900DA9C21 /* UniverseObject.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = UniverseObject.cpp; sourceTree = ""; }; - 471D5D120A98A3F900DA9C21 /* UniverseObject.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = UniverseObject.h; sourceTree = ""; }; - 471D5D130A98A3F900DA9C21 /* ValueRef.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ValueRef.cpp; sourceTree = ""; }; - 471D5D140A98A3F900DA9C21 /* ValueRef.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ValueRef.h; sourceTree = ""; }; - 471D5D180A98A3F900DA9C21 /* AppInterface.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AppInterface.cpp; sourceTree = ""; }; - 471D5D190A98A3F900DA9C21 /* AppInterface.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AppInterface.h; sourceTree = ""; }; - 471D5D1A0A98A3F900DA9C21 /* binreloc.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = binreloc.c; sourceTree = ""; }; - 471D5D1B0A98A3F900DA9C21 /* binreloc.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = binreloc.h; sourceTree = ""; }; - 471D5D1E0A98A3F900DA9C21 /* Directories.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Directories.cpp; sourceTree = ""; }; - 471D5D1F0A98A3F900DA9C21 /* Directories.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Directories.h; sourceTree = ""; }; - 471D5D210A98A3F900DA9C21 /* Doxyfile */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = Doxyfile; sourceTree = ""; }; - 471D5D270A98A3F900DA9C21 /* MultiplayerCommon.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MultiplayerCommon.cpp; sourceTree = ""; }; - 471D5D280A98A3F900DA9C21 /* MultiplayerCommon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MultiplayerCommon.h; sourceTree = ""; }; - 471D5D290A98A3F900DA9C21 /* OptionsDB.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = OptionsDB.cpp; sourceTree = ""; }; - 471D5D2A0A98A3F900DA9C21 /* OptionsDB.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OptionsDB.h; sourceTree = ""; }; - 471D5D2B0A98A3F900DA9C21 /* OptionValidators.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OptionValidators.h; sourceTree = ""; }; - 471D5D2C0A98A3F900DA9C21 /* Order.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Order.cpp; sourceTree = ""; }; - 471D5D2D0A98A3F900DA9C21 /* Order.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Order.h; sourceTree = ""; }; - 471D5D2E0A98A3F900DA9C21 /* OrderSet.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = OrderSet.cpp; sourceTree = ""; }; - 471D5D2F0A98A3F900DA9C21 /* OrderSet.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OrderSet.h; sourceTree = ""; }; - 471D5D300A98A3F900DA9C21 /* Process.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Process.cpp; sourceTree = ""; }; - 471D5D310A98A3F900DA9C21 /* Process.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Process.h; sourceTree = ""; }; - 471D5D320A98A3F900DA9C21 /* Random.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Random.cpp; sourceTree = ""; }; - 471D5D330A98A3F900DA9C21 /* Random.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Random.h; sourceTree = ""; }; - 471D5D350A98A3F900DA9C21 /* Serialize.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Serialize.h; sourceTree = ""; }; - 471D5D360A98A3F900DA9C21 /* SitRepEntry.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SitRepEntry.cpp; sourceTree = ""; }; - 471D5D370A98A3F900DA9C21 /* SitRepEntry.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SitRepEntry.h; sourceTree = ""; }; - 471D5D380A98A3F900DA9C21 /* VarText.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VarText.cpp; sourceTree = ""; }; - 471D5D390A98A3F900DA9C21 /* VarText.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VarText.h; sourceTree = ""; }; - 471D5D3B0A98A3F900DA9C21 /* Version.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Version.h; sourceTree = ""; }; - 471D5D3C0A98A3F900DA9C21 /* XMLDoc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = XMLDoc.cpp; sourceTree = ""; }; - 471D5D3D0A98A3F900DA9C21 /* XMLDoc.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = XMLDoc.h; sourceTree = ""; }; - 471D5D440A98A46000DA9C21 /* freeoriond */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = freeoriond; sourceTree = BUILT_PRODUCTS_DIR; }; - 471D654D0A98B7F800DA9C21 /* libfreetype.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libfreetype.a; sourceTree = ""; }; - 471FEFD70A9A629800C36AA3 /* freeorionca */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = freeorionca; sourceTree = BUILT_PRODUCTS_DIR; }; - 471FF2560A9A7E6400C36AA3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 478417B10CF0592E00BE4710 /* libClientCommon.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libClientCommon.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 8201E5F215E2C0770037D453 /* EffectParser1.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EffectParser1.cpp; sourceTree = ""; }; - 8201E5F315E2C0770037D453 /* EffectParser2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EffectParser2.cpp; sourceTree = ""; }; - 8201E5F415E2C0770037D453 /* EffectParserImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EffectParserImpl.h; sourceTree = ""; }; - 820BDECB1848A93E009BC457 /* Hotkeys.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Hotkeys.cpp; sourceTree = ""; }; - 820BDECC1848A93E009BC457 /* Hotkeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Hotkeys.h; sourceTree = ""; }; - 82132C2D15DA2BBC00F5B537 /* ObjectListWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjectListWnd.cpp; sourceTree = ""; }; - 82132C2E15DA2BBC00F5B537 /* ObjectListWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectListWnd.h; sourceTree = ""; }; - 822B16B51642A6F8000ADE58 /* EffectParser3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EffectParser3.cpp; sourceTree = ""; }; - 822B16B61642A6F8000ADE58 /* EffectParser4.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EffectParser4.cpp; sourceTree = ""; }; - 822E325B19BEED6C00A7B707 /* libz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libz.a; sourceTree = ""; }; - 822EB0FD170832240083EB38 /* CombatLogManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CombatLogManager.cpp; sourceTree = ""; }; - 822EB0FE170832240083EB38 /* CombatLogManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CombatLogManager.h; sourceTree = ""; }; - 8242C81A176B0C8E001E1CF2 /* ScopedTimer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScopedTimer.cpp; sourceTree = ""; }; - 8242C81B176B0C8E001E1CF2 /* ScopedTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScopedTimer.h; sourceTree = ""; }; - 8246FECF194D8F83008C07B5 /* IntComplexValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntComplexValueRefParser.cpp; sourceTree = ""; }; - 82483B7A15F4F24100D27614 /* libboost_date_time.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_date_time.a; sourceTree = ""; }; - 82483B7B15F4F26200D27614 /* libFreeImage.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libFreeImage.a; sourceTree = ""; }; - 8250FDE115FCEFC400523C1C /* ConditionParser4.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConditionParser4.cpp; sourceTree = ""; }; - 8250FDE215FCEFC400523C1C /* FieldsParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FieldsParser.cpp; sourceTree = ""; }; - 8250FDE315FCEFDD00523C1C /* Field.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Field.cpp; sourceTree = ""; }; - 8250FDE415FCEFDE00523C1C /* Field.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Field.h; sourceTree = ""; }; - 8257A8D61A90BA610006777D /* GLClientAndServerBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GLClientAndServerBuffer.cpp; sourceTree = ""; }; - 8257A8D81A90BA8C0006777D /* Export.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Export.h; sourceTree = ""; }; - 8257A8D91A90BA8C0006777D /* GLClientAndServerBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLClientAndServerBuffer.h; sourceTree = ""; }; - 82592EEB147E365700B840A5 /* CUISlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CUISlider.h; sourceTree = ""; }; - 82592EEE147E37FB00B840A5 /* EffectAccounting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EffectAccounting.h; sourceTree = ""; }; - 82592EEF147E381B00B840A5 /* EffectAccounting.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EffectAccounting.cpp; sourceTree = ""; }; - 82592EF3147E387100B840A5 /* ObjectMap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjectMap.cpp; sourceTree = ""; }; - 82592EF4147E387100B840A5 /* ObjectMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectMap.h; sourceTree = ""; }; - 82592EF7147E38ED00B840A5 /* ValueRefFwd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ValueRefFwd.h; sourceTree = ""; }; - 825936D7147E3B1B00B840A5 /* BuildingsParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BuildingsParser.cpp; sourceTree = ""; }; - 825936D9147E3B1B00B840A5 /* ConditionParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConditionParser.cpp; sourceTree = ""; }; - 825936DA147E3B1B00B840A5 /* ConditionParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConditionParser.h; sourceTree = ""; }; - 825936DB147E3B1B00B840A5 /* ConditionParser1.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConditionParser1.cpp; sourceTree = ""; }; - 825936DC147E3B1B00B840A5 /* ConditionParser2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConditionParser2.cpp; sourceTree = ""; }; - 825936DD147E3B1B00B840A5 /* ConditionParser3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConditionParser3.cpp; sourceTree = ""; }; - 825936DE147E3B1B00B840A5 /* ConditionParserImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConditionParserImpl.h; sourceTree = ""; }; - 825936E1147E3B1B00B840A5 /* DoubleValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DoubleValueRefParser.cpp; sourceTree = ""; }; - 825936E2147E3B1B00B840A5 /* EffectParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EffectParser.cpp; sourceTree = ""; }; - 825936E3147E3B1B00B840A5 /* EffectParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EffectParser.h; sourceTree = ""; }; - 825936E4147E3B1B00B840A5 /* EnumParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EnumParser.cpp; sourceTree = ""; }; - 825936E5147E3B1B00B840A5 /* EnumParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EnumParser.h; sourceTree = ""; }; - 825936E6147E3B1B00B840A5 /* FleetPlansParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FleetPlansParser.cpp; sourceTree = ""; }; - 825936EA147E3B1B00B840A5 /* IntValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntValueRefParser.cpp; sourceTree = ""; }; - 825936EB147E3B1B00B840A5 /* ItemsParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ItemsParser.cpp; sourceTree = ""; }; - 825936EE147E3B1B00B840A5 /* Lexer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Lexer.cpp; sourceTree = ""; }; - 825936EF147E3B1B00B840A5 /* Lexer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Lexer.h; sourceTree = ""; }; - 825936F0147E3B1B00B840A5 /* MonsterFleetPlansParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MonsterFleetPlansParser.cpp; sourceTree = ""; }; - 825936F1147E3B1B00B840A5 /* Parse.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Parse.cpp; sourceTree = ""; }; - 825936F2147E3B1B00B840A5 /* Parse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Parse.h; sourceTree = ""; }; - 825936F3147E3B1B00B840A5 /* ParseImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParseImpl.h; sourceTree = ""; }; - 825936F5147E3B1B00B840A5 /* PlanetEnvironmentValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlanetEnvironmentValueRefParser.cpp; sourceTree = ""; }; - 825936F6147E3B1B00B840A5 /* PlanetSizeValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlanetSizeValueRefParser.cpp; sourceTree = ""; }; - 825936F7147E3B1B00B840A5 /* PlanetTypeValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlanetTypeValueRefParser.cpp; sourceTree = ""; }; - 825936F8147E3B1B00B840A5 /* ReportParseError.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ReportParseError.cpp; sourceTree = ""; }; - 825936F9147E3B1B00B840A5 /* ReportParseError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReportParseError.h; sourceTree = ""; }; - 825936FA147E3B1B00B840A5 /* ShipDesignsParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ShipDesignsParser.cpp; sourceTree = ""; }; - 825936FB147E3B1B00B840A5 /* ShipHullsParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ShipHullsParser.cpp; sourceTree = ""; }; - 825936FC147E3B1B00B840A5 /* ShipPartsParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ShipPartsParser.cpp; sourceTree = ""; }; - 825936FD147E3B1B00B840A5 /* SpecialsParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpecialsParser.cpp; sourceTree = ""; }; - 825936FE147E3B1B00B840A5 /* SpeciesParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpeciesParser.cpp; sourceTree = ""; }; - 825936FF147E3B1B00B840A5 /* StarTypeValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StarTypeValueRefParser.cpp; sourceTree = ""; }; - 82593700147E3B1B00B840A5 /* StringValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StringValueRefParser.cpp; sourceTree = ""; }; - 82593701147E3B1B00B840A5 /* TechsParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TechsParser.cpp; sourceTree = ""; }; - 82593704147E3B1C00B840A5 /* buildings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = buildings; sourceTree = ""; }; - 82593705147E3B1C00B840A5 /* buildings_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = buildings_errors; sourceTree = ""; }; - 82593706147E3B1C00B840A5 /* buildings_permutations */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = buildings_permutations; sourceTree = ""; }; - 8259370A147E3B1C00B840A5 /* condition_parser_1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = condition_parser_1; sourceTree = ""; }; - 8259370B147E3B1C00B840A5 /* condition_parser_1_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = condition_parser_1_errors; sourceTree = ""; }; - 8259370C147E3B1C00B840A5 /* condition_parser_2 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = condition_parser_2; sourceTree = ""; }; - 8259370D147E3B1C00B840A5 /* condition_parser_2_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = condition_parser_2_errors; sourceTree = ""; }; - 8259370E147E3B1C00B840A5 /* condition_parser_3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = condition_parser_3; sourceTree = ""; }; - 8259370F147E3B1C00B840A5 /* condition_parser_3_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = condition_parser_3_errors; sourceTree = ""; }; - 82593713147E3B1C00B840A5 /* double_statistic */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = double_statistic; sourceTree = ""; }; - 82593714147E3B1C00B840A5 /* double_statistic_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = double_statistic_errors; sourceTree = ""; }; - 82593715147E3B1C00B840A5 /* double_variable */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = double_variable; sourceTree = ""; }; - 82593716147E3B1C00B840A5 /* double_variable_arithmetic */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = double_variable_arithmetic; sourceTree = ""; }; - 82593717147E3B1C00B840A5 /* double_variable_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = double_variable_errors; sourceTree = ""; }; - 82593718147E3B1C00B840A5 /* effect_parser */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = effect_parser; sourceTree = ""; }; - 82593719147E3B1C00B840A5 /* effect_parser_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = effect_parser_errors; sourceTree = ""; }; - 8259371B147E3B1C00B840A5 /* fleet_plans */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fleet_plans; sourceTree = ""; }; - 8259371C147E3B1C00B840A5 /* fleet_plans_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fleet_plans_errors; sourceTree = ""; }; - 8259371D147E3B1C00B840A5 /* fleet_plans_permutations */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fleet_plans_permutations; sourceTree = ""; }; - 82593726147E3B1C00B840A5 /* items */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = items; sourceTree = ""; }; - 82593727147E3B1C00B840A5 /* items_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = items_errors; sourceTree = ""; }; - 82593728147E3B1C00B840A5 /* items_permutations */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = items_permutations; sourceTree = ""; }; - 82593729147E3B1C00B840A5 /* lexer_test_rules.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lexer_test_rules.cpp; sourceTree = ""; }; - 8259372A147E3B1C00B840A5 /* lexer_tokens */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = lexer_tokens; sourceTree = ""; }; - 8259372B147E3B1C00B840A5 /* monster_fleet_plans */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = monster_fleet_plans; sourceTree = ""; }; - 8259372C147E3B1C00B840A5 /* monster_fleet_plans_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = monster_fleet_plans_errors; sourceTree = ""; }; - 8259372D147E3B1C00B840A5 /* monster_fleet_plans_permutations */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = monster_fleet_plans_permutations; sourceTree = ""; }; - 8259372F147E3B1C00B840A5 /* planet_environment_constant */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_environment_constant; sourceTree = ""; }; - 82593731147E3B1C00B840A5 /* planet_environment_statistic */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_environment_statistic; sourceTree = ""; }; - 82593732147E3B1C00B840A5 /* planet_environment_statistic_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_environment_statistic_errors; sourceTree = ""; }; - 82593733147E3B1C00B840A5 /* planet_environment_variable */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_environment_variable; sourceTree = ""; }; - 82593734147E3B1C00B840A5 /* planet_environment_variable_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_environment_variable_errors; sourceTree = ""; }; - 82593735147E3B1C00B840A5 /* planet_size_constant */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_size_constant; sourceTree = ""; }; - 82593737147E3B1C00B840A5 /* planet_size_statistic */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_size_statistic; sourceTree = ""; }; - 82593738147E3B1C00B840A5 /* planet_size_statistic_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_size_statistic_errors; sourceTree = ""; }; - 82593739147E3B1C00B840A5 /* planet_size_variable */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_size_variable; sourceTree = ""; }; - 8259373A147E3B1C00B840A5 /* planet_size_variable_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_size_variable_errors; sourceTree = ""; }; - 8259373B147E3B1C00B840A5 /* planet_type_constant */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_type_constant; sourceTree = ""; }; - 8259373D147E3B1C00B840A5 /* planet_type_statistic */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_type_statistic; sourceTree = ""; }; - 8259373E147E3B1C00B840A5 /* planet_type_statistic_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_type_statistic_errors; sourceTree = ""; }; - 8259373F147E3B1C00B840A5 /* planet_type_variable */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_type_variable; sourceTree = ""; }; - 82593740147E3B1C00B840A5 /* planet_type_variable_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = planet_type_variable_errors; sourceTree = ""; }; - 82593741147E3B1C00B840A5 /* ship_designs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ship_designs; sourceTree = ""; }; - 82593742147E3B1C00B840A5 /* ship_designs_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ship_designs_errors; sourceTree = ""; }; - 82593743147E3B1C00B840A5 /* ship_designs_permutations */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ship_designs_permutations; sourceTree = ""; }; - 82593744147E3B1C00B840A5 /* ship_hulls */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ship_hulls; sourceTree = ""; }; - 82593745147E3B1C00B840A5 /* ship_hulls_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ship_hulls_errors; sourceTree = ""; }; - 82593747147E3B1C00B840A5 /* ship_parts */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ship_parts; sourceTree = ""; }; - 82593748147E3B1C00B840A5 /* ship_parts_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ship_parts_errors; sourceTree = ""; }; - 8259374A147E3B1C00B840A5 /* specials */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = specials; sourceTree = ""; }; - 8259374B147E3B1C00B840A5 /* specials_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = specials_errors; sourceTree = ""; }; - 8259374C147E3B1C00B840A5 /* specials_permutations */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = specials_permutations; sourceTree = ""; }; - 8259374D147E3B1C00B840A5 /* species */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = species; sourceTree = ""; }; - 8259374E147E3B1C00B840A5 /* species_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = species_errors; sourceTree = ""; }; - 8259374F147E3B1C00B840A5 /* species_permutations */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = species_permutations; sourceTree = ""; }; - 82593750147E3B1C00B840A5 /* star_type_constant */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = star_type_constant; sourceTree = ""; }; - 82593752147E3B1C00B840A5 /* star_type_statistic */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = star_type_statistic; sourceTree = ""; }; - 82593753147E3B1C00B840A5 /* star_type_statistic_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = star_type_statistic_errors; sourceTree = ""; }; - 82593754147E3B1C00B840A5 /* star_type_variable */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = star_type_variable; sourceTree = ""; }; - 82593755147E3B1C00B840A5 /* star_type_variable_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = star_type_variable_errors; sourceTree = ""; }; - 82593759147E3B1C00B840A5 /* string_statistic */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = string_statistic; sourceTree = ""; }; - 8259375A147E3B1C00B840A5 /* string_statistic_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = string_statistic_errors; sourceTree = ""; }; - 8259375B147E3B1C00B840A5 /* string_variable */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = string_variable; sourceTree = ""; }; - 8259375C147E3B1C00B840A5 /* string_variable_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = string_variable_errors; sourceTree = ""; }; - 8259375E147E3B1C00B840A5 /* techs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = techs; sourceTree = ""; }; - 8259375F147E3B1C00B840A5 /* techs_errors */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = techs_errors; sourceTree = ""; }; - 82593760147E3B1C00B840A5 /* techs_permutations */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = techs_permutations; sourceTree = ""; }; - 82593761147E3B1C00B840A5 /* test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = test.cpp; sourceTree = ""; }; - 82593762147E3B1C00B840A5 /* test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = test.h; sourceTree = ""; }; - 8259376A147E3B1C00B840A5 /* UniverseObjectTypeValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UniverseObjectTypeValueRefParser.cpp; sourceTree = ""; }; - 8259376B147E3B1C00B840A5 /* ValueRefParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ValueRefParser.h; sourceTree = ""; }; - 8259376C147E3B1C00B840A5 /* ValueRefParserImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ValueRefParserImpl.h; sourceTree = ""; }; - 825E7258196EC28A00F8114D /* default */ = {isa = PBXFileReference; lastKnownFileType = folder; name = default; path = ../default; sourceTree = ""; }; - 825EA75E16DB9DB10002A797 /* ModeratorAction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ModeratorAction.cpp; sourceTree = ""; }; - 825EA75F16DB9DB10002A797 /* ModeratorAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModeratorAction.h; sourceTree = ""; }; - 82714D761ADABE680071A329 /* libboost_log.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_log.a; sourceTree = ""; }; - 8275F3DD1AD31381003568FF /* AccordionPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AccordionPanel.cpp; sourceTree = ""; }; - 8275F3DE1AD31381003568FF /* AccordionPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AccordionPanel.h; sourceTree = ""; }; - 8275F3DF1AD31381003568FF /* BuildingsPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BuildingsPanel.cpp; sourceTree = ""; }; - 8275F3E01AD31381003568FF /* BuildingsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BuildingsPanel.h; sourceTree = ""; }; - 8275F3E11AD31381003568FF /* CensusBrowseWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CensusBrowseWnd.cpp; sourceTree = ""; }; - 8275F3E21AD31381003568FF /* CensusBrowseWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CensusBrowseWnd.h; sourceTree = ""; }; - 8275F3E31AD31381003568FF /* IconTextBrowseWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IconTextBrowseWnd.cpp; sourceTree = ""; }; - 8275F3E41AD31381003568FF /* IconTextBrowseWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IconTextBrowseWnd.h; sourceTree = ""; }; - 8275F3E51AD31381003568FF /* MeterBrowseWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MeterBrowseWnd.cpp; sourceTree = ""; }; - 8275F3E61AD31381003568FF /* MeterBrowseWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MeterBrowseWnd.h; sourceTree = ""; }; - 8275F3E71AD31381003568FF /* MilitaryPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MilitaryPanel.cpp; sourceTree = ""; }; - 8275F3E81AD31381003568FF /* MilitaryPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MilitaryPanel.h; sourceTree = ""; }; - 8275F3E91AD31381003568FF /* MultiIconValueIndicator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiIconValueIndicator.cpp; sourceTree = ""; }; - 8275F3EA1AD31381003568FF /* MultiIconValueIndicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiIconValueIndicator.h; sourceTree = ""; }; - 8275F3EB1AD31381003568FF /* MultiMeterStatusBar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMeterStatusBar.cpp; sourceTree = ""; }; - 8275F3EC1AD31381003568FF /* MultiMeterStatusBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiMeterStatusBar.h; sourceTree = ""; }; - 8275F3ED1AD31381003568FF /* PopulationPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PopulationPanel.cpp; sourceTree = ""; }; - 8275F3EE1AD31381003568FF /* PopulationPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopulationPanel.h; sourceTree = ""; }; - 8275F3EF1AD31381003568FF /* ResourcePanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ResourcePanel.cpp; sourceTree = ""; }; - 8275F3F01AD31381003568FF /* ResourcePanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResourcePanel.h; sourceTree = ""; }; - 8275F3F31AD31381003568FF /* SpecialsPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpecialsPanel.cpp; sourceTree = ""; }; - 8275F3F41AD31381003568FF /* SpecialsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpecialsPanel.h; sourceTree = ""; }; - 8275F3F51AD31381003568FF /* SystemResourceSummaryBrowseWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SystemResourceSummaryBrowseWnd.cpp; sourceTree = ""; }; - 8275F3F61AD31381003568FF /* SystemResourceSummaryBrowseWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SystemResourceSummaryBrowseWnd.h; sourceTree = ""; }; - 8275F3F71AD31381003568FF /* TextBrowseWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextBrowseWnd.cpp; sourceTree = ""; }; - 8275F3F81AD31381003568FF /* TextBrowseWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextBrowseWnd.h; sourceTree = ""; }; - 827902361737C6EA008AF126 /* ModeratorActionsWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ModeratorActionsWnd.cpp; sourceTree = ""; }; - 827902371737C6EA008AF126 /* ModeratorActionsWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModeratorActionsWnd.h; sourceTree = ""; }; - 827AF376185B26AE001CD79C /* UniverseGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UniverseGenerator.h; sourceTree = ""; }; - 827AF39F185B2FB9001CD79C /* Export.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Export.h; sourceTree = ""; }; - 8280AC5C19C0741B00ADDB65 /* libboost_iostreams.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_iostreams.a; sourceTree = ""; }; - 8289A72A19BDB3D8001256C1 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SDL2.framework; sourceTree = ""; }; - 828AB7D315F7BCDD0075BE24 /* libpng.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libpng.a; sourceTree = ""; }; - 828C793D160778AA0075F220 /* FieldIcon.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FieldIcon.cpp; sourceTree = ""; }; - 828C793E160778AA0075F220 /* FieldIcon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FieldIcon.h; sourceTree = ""; }; - 8295CEC61A88FFF8000EE624 /* StringComplexValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StringComplexValueRefParser.cpp; sourceTree = ""; }; - 82970BED1A0CF53A008B37EF /* TechTreeArcs.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TechTreeArcs.cpp; sourceTree = ""; }; - 82970BEE1A0CF53A008B37EF /* TechTreeArcs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TechTreeArcs.h; sourceTree = ""; }; - 8298A1AC18F9BB980001D3DA /* SaveFileDialog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SaveFileDialog.cpp; sourceTree = ""; }; - 8298A1AD18F9BB980001D3DA /* SaveFileDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveFileDialog.h; sourceTree = ""; }; - 829BBB6117D09017009D3735 /* ValueRefParserImpl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ValueRefParserImpl.cpp; sourceTree = ""; }; - 829E16B01811181F00F7F52D /* EmpireStatsParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EmpireStatsParser.cpp; sourceTree = ""; }; - 82B3265219B603C1005082AC /* ConditionParser7.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConditionParser7.cpp; sourceTree = ""; }; - 82B4313314A74E07003EEE42 /* UniverseGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UniverseGenerator.cpp; sourceTree = ""; }; - 82B9AE991AA71CCC00486B2A /* CombatLogWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CombatLogWnd.cpp; sourceTree = ""; }; - 82B9AE9A1AA71CCC00486B2A /* CombatLogWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CombatLogWnd.h; sourceTree = ""; }; - 82B9AE9B1AA71CCC00486B2A /* CombatReportData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CombatReportData.cpp; sourceTree = ""; }; - 82B9AE9C1AA71CCC00486B2A /* CombatReportData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CombatReportData.h; sourceTree = ""; }; - 82B9AE9D1AA71CCC00486B2A /* CombatReportWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CombatReportWnd.cpp; sourceTree = ""; }; - 82B9AE9E1AA71CCC00486B2A /* CombatReportWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CombatReportWnd.h; sourceTree = ""; }; - 82B9AE9F1AA71CCC00486B2A /* GraphicalSummary.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GraphicalSummary.cpp; sourceTree = ""; }; - 82B9AEA01AA71CCC00486B2A /* GraphicalSummary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GraphicalSummary.h; sourceTree = ""; }; - 82B9D445180C53FE0032F86D /* GraphControl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GraphControl.cpp; sourceTree = ""; }; - 82B9D446180C53FE0032F86D /* GraphControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GraphControl.h; sourceTree = ""; }; - 82BB838215AAD20A006F635F /* EncyclopediaParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EncyclopediaParser.cpp; sourceTree = ""; }; - 82BEB657159853B300B024CB /* Diplomacy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Diplomacy.cpp; sourceTree = ""; }; - 82BEB658159853B300B024CB /* Diplomacy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Diplomacy.h; sourceTree = ""; }; - 82C07438149DE46200E76876 /* freeorionparse.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = freeorionparse.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; - 82C560D117D0CC2F00FE3B35 /* Tokens.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tokens.cpp; sourceTree = ""; }; - 82C560D217D0CC2F00FE3B35 /* Tokens.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Tokens.h; sourceTree = ""; }; - 82C96D0A16E4C1F900579E56 /* SerializeModeratorAction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerializeModeratorAction.cpp; sourceTree = ""; }; - 82CE867E19BF27D0004DF6DF /* GUIController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GUIController.mm; sourceTree = ""; }; - 82D2D9A11838C8340011C212 /* ConditionParser5.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConditionParser5.cpp; sourceTree = ""; }; - 82D2D9A21838C8340011C212 /* ConditionParser6.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConditionParser6.cpp; sourceTree = ""; }; - 82D563D51A6AEBD6007E1131 /* DoubleComplexValueRefParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DoubleComplexValueRefParser.cpp; sourceTree = ""; }; - 82D68C6C15F4F720006B772D /* KeymapParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeymapParser.cpp; sourceTree = ""; }; - 82D7B59E1AA3666F00A85842 /* ServerWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ServerWrapper.cpp; sourceTree = ""; }; - 82D7B59F1AA3666F00A85842 /* ServerWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServerWrapper.h; sourceTree = ""; }; - 82D7B5A11AA37C9000A85842 /* ServerFramework.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ServerFramework.cpp; sourceTree = ""; }; - 82D7B5A21AA37C9000A85842 /* ServerFramework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServerFramework.h; sourceTree = ""; }; - 82E250AD19C2317700F0E2F2 /* libGLEW.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libGLEW.a; sourceTree = ""; }; - 82E5238E192FE8CE001E2374 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; - 82E52390192FE90F001E2374 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; - 82E52392192FE99C001E2374 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; - 82E52393192FE99C001E2374 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; - 82E52394192FE99C001E2374 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; - 82E52395192FE99C001E2374 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 82E52396192FE99C001E2374 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = System/Library/Frameworks/OpenAL.framework; sourceTree = SDKROOT; }; - 82E52397192FE99D001E2374 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; - 82E5239E192FEAA6001E2374 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; - 82E68F5F190ECB8400BB1AD9 /* EnumText.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EnumText.cpp; sourceTree = ""; }; - 82E68F60190ECB8400BB1AD9 /* EnumText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EnumText.h; sourceTree = ""; }; - 82E68F61190ECB8400BB1AD9 /* SaveGamePreviewUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SaveGamePreviewUtils.cpp; sourceTree = ""; }; - 82E68F62190ECB8400BB1AD9 /* SaveGamePreviewUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveGamePreviewUtils.h; sourceTree = ""; }; - 82E944E81B04C726004F3343 /* EffectParser5.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EffectParser5.cpp; sourceTree = ""; }; - 82E9DDED19530E5D007E681B /* CombatEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CombatEvent.cpp; sourceTree = ""; }; - 82E9DDEE19530E5D007E681B /* CombatEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CombatEvent.h; sourceTree = ""; }; - 82E9DDEF19530E5D007E681B /* CombatEvents.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CombatEvents.cpp; sourceTree = ""; }; - 82E9DDF019530E5D007E681B /* CombatEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CombatEvents.h; sourceTree = ""; }; - 82EC86891B40504700D4584D /* Encyclopedia.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Encyclopedia.cpp; sourceTree = ""; }; - 82EC868A1B40504700D4584D /* Encyclopedia.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Encyclopedia.h; sourceTree = ""; }; - 82ED913E194F5C85002F0A4A /* make_dmg.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = make_dmg.py; sourceTree = ""; }; - 82ED9141194F5CDD002F0A4A /* make_versioncpp.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; name = make_versioncpp.py; path = ../cmake/make_versioncpp.py; sourceTree = ""; }; - 8DD76F6C0486A84900D96B5E /* FreeOrion */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = FreeOrion; sourceTree = BUILT_PRODUCTS_DIR; }; - 9EEEEA7313ACB91A0085B1A0 /* libboost_filesystem.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_filesystem.a; sourceTree = ""; }; - 9EEEEA7413ACB91A0085B1A0 /* libboost_python.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_python.a; sourceTree = ""; }; - 9EEEEA7513ACB91A0085B1A0 /* libboost_regex.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_regex.a; sourceTree = ""; }; - 9EEEEA7613ACB91A0085B1A0 /* libboost_serialization.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_serialization.a; sourceTree = ""; }; - 9EEEEA7713ACB91A0085B1A0 /* libboost_signals.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_signals.a; sourceTree = ""; }; - 9EEEEA7813ACB91A0085B1A0 /* libboost_system.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_system.a; sourceTree = ""; }; - 9EEEEA7913ACB91A0085B1A0 /* libboost_thread.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_thread.a; sourceTree = ""; }; - CE0ED5F71BCFE16E00375001 /* AIWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AIWrapper.cpp; sourceTree = ""; }; - CE0ED5F81BCFE16E00375001 /* AIWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIWrapper.h; sourceTree = ""; }; - CE13B48D1C8CB0CF0085A4DC /* CommonParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommonParams.h; sourceTree = ""; }; - CE13B48E1C8CB0CF0085A4DC /* CommonParamsParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommonParamsParser.cpp; sourceTree = ""; }; - CE1939C41F31AD4C0085A4DC /* IDAllocator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDAllocator.cpp; sourceTree = ""; }; - CE1939C51F31AD4C0085A4DC /* IDAllocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDAllocator.h; sourceTree = ""; }; - CE2227D41EA971A10085A4DC /* EnumsFwd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EnumsFwd.h; sourceTree = ""; }; - CE2BAD8D1E3E43E40085A4DC /* Pathfinder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Pathfinder.cpp; sourceTree = ""; }; - CE2BAD8E1E3E43E40085A4DC /* Pathfinder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pathfinder.h; sourceTree = ""; }; - CE2C454B1C65036F0085A4DC /* ResourceBrowseWnd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ResourceBrowseWnd.cpp; sourceTree = ""; }; - CE2C454C1C65036F0085A4DC /* ResourceBrowseWnd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResourceBrowseWnd.h; sourceTree = ""; }; - CE3102B41DBFD05B0085A4DC /* DeferredLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeferredLayout.h; sourceTree = ""; }; - CE3102B51DBFD0730085A4DC /* DeferredLayout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DeferredLayout.cpp; sourceTree = ""; }; - CE3656A81F22244E0085A4DC /* GameRulesParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GameRulesParser.cpp; sourceTree = ""; }; - CE43BB291EC456A20085A4DC /* CheckSums.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CheckSums.cpp; sourceTree = ""; }; - CE43BB2A1EC456A20085A4DC /* CheckSums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CheckSums.h; sourceTree = ""; }; - CE594BAD1CB2D1360085A4DC /* color_convert.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = color_convert.hpp; sourceTree = ""; }; - CE594BC21CB2D1360085A4DC /* png_dynamic_io.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = png_dynamic_io.hpp; sourceTree = ""; }; - CE594BC31CB2D1360085A4DC /* png_io.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = png_io.hpp; sourceTree = ""; }; - CE594BC41CB2D1360085A4DC /* png_io_private.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = png_io_private.hpp; sourceTree = ""; }; - CE594BCB1CB2D1360085A4DC /* gray_alpha.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = gray_alpha.hpp; sourceTree = ""; }; - CE594BDC1CB2D1360085A4DC /* typedefs.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = typedefs.hpp; sourceTree = ""; }; - CE6183961BB80D1300952BEA /* GGFwd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GGFwd.h; sourceTree = ""; }; - CE6183981BB80D1300952BEA /* BlockControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlockControl.h; sourceTree = ""; }; - CE6183991BB80D1300952BEA /* ImageBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageBlock.h; sourceTree = ""; }; - CE61839A1BB80D1300952BEA /* RichText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RichText.h; sourceTree = ""; }; - CE61839B1BB80D1300952BEA /* ScrollPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollPanel.h; sourceTree = ""; }; - CE61839D1BB80D2E00952BEA /* BlockControl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BlockControl.cpp; sourceTree = ""; }; - CE61839E1BB80D2E00952BEA /* ImageBlock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImageBlock.cpp; sourceTree = ""; }; - CE61839F1BB80D2E00952BEA /* RichText.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RichText.cpp; sourceTree = ""; }; - CE6183A11BB80D2E00952BEA /* TagParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TagParser.cpp; sourceTree = ""; }; - CE6183A21BB80D2E00952BEA /* TagParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TagParser.h; sourceTree = ""; }; - CE6183A31BB80D2E00952BEA /* TextBlock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextBlock.cpp; sourceTree = ""; }; - CE6183A41BB80D2E00952BEA /* TextBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextBlock.h; sourceTree = ""; }; - CE6183A51BB80D2E00952BEA /* ScrollPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScrollPanel.cpp; sourceTree = ""; }; - CE6183AC1BB80D4900952BEA /* CUILinkTextBlock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CUILinkTextBlock.cpp; sourceTree = ""; }; - CE6183AD1BB80D4900952BEA /* CUILinkTextBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CUILinkTextBlock.h; sourceTree = ""; }; - CE76B9CD1CBD7A990085A4DC /* ChangeLog.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = ChangeLog.md; path = ../ChangeLog.md; sourceTree = ""; }; - CE97F70A1BC7D74D0002988B /* DependencyVersions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DependencyVersions.cpp; sourceTree = ""; }; - CEC4ADBF1BC13741000DFA9B /* AIFramework.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AIFramework.cpp; sourceTree = ""; }; - CEC4ADC01BC13741000DFA9B /* AIFramework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIFramework.h; sourceTree = ""; }; - CEC4ADC21BC15195000DFA9B /* CommonFramework.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommonFramework.cpp; sourceTree = ""; }; - CEC4ADC31BC15195000DFA9B /* CommonFramework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommonFramework.h; sourceTree = ""; }; - CED22D531EB8F2B50085A4DC /* libboost_log_setup.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_log_setup.a; sourceTree = ""; }; - CED6E5501C6DDC0F0085A4DC /* Fighter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Fighter.cpp; sourceTree = ""; }; - CED6E5511C6DDC0F0085A4DC /* Fighter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Fighter.h; sourceTree = ""; }; - CEDABCEF1C688D050085A4DC /* libboost_locale.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libboost_locale.a; sourceTree = ""; }; - CEDABCF21C688E290085A4DC /* libiconv.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.dylib; path = usr/lib/libiconv.dylib; sourceTree = SDKROOT; }; - CEED195D1C79BA2F0085A4DC /* Supply.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Supply.cpp; sourceTree = ""; }; - CEED195E1C79BA2F0085A4DC /* Supply.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Supply.h; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 3ABEAB361749BF8A00E34912 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3ABEAB3B1749BFED00E34912 /* freeorioncommon.dylib in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3ABEAB391749BFDB00E34912 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 82E5239F192FEAA6001E2374 /* CoreFoundation.framework in Frameworks */, - B7A7D5371ECF2D5000E086C2 /* libboost_date_time.a in Frameworks */, - 3ABEAB3F1749C27700E34912 /* libboost_filesystem.a in Frameworks */, - 8280AC5D19C0741B00ADDB65 /* libboost_iostreams.a in Frameworks */, - 82714D771ADABE680071A329 /* libboost_log.a in Frameworks */, - CED22D541EB8F2B50085A4DC /* libboost_log_setup.a in Frameworks */, - B7A7D5381ECF2D6E00E086C2 /* libboost_regex.a in Frameworks */, - 3ABEAB3C1749C21B00E34912 /* libboost_serialization.a in Frameworks */, - 3ABEAB711749C2F400E34912 /* libboost_signals.a in Frameworks */, - 3ABEAB741749C36200E34912 /* libboost_system.a in Frameworks */, - 3ABEAB801749C51B00E34912 /* libboost_thread.a in Frameworks */, - B7A7D5391ECF2D9D00E086C2 /* libz.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 471D5D420A98A46000DA9C21 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 8288432E1850A1770023C4D7 /* Python.framework in Frameworks */, - 82F55DE818B0FF0A00FA9E11 /* libboost_date_time.a in Frameworks */, - 8280AC5E19C0741B00ADDB65 /* libboost_iostreams.a in Frameworks */, - CED22D4D1EB8F15B0085A4DC /* libboost_log.a in Frameworks */, - CED22D551EB8F2B50085A4DC /* libboost_log_setup.a in Frameworks */, - 8288432F1850A1960023C4D7 /* libboost_python.a in Frameworks */, - 3ABEAB971749C93400E34912 /* libboost_signals.a in Frameworks */, - 3ABEAB901749C8F500E34912 /* libboost_thread.a in Frameworks */, - 478417730CF0589200BE4710 /* freeorioncommon.dylib in Frameworks */, - 82BB623E19BEDF1700A0E281 /* freeorionparse.dylib in Frameworks */, - CE2C454E1C6503990085A4DC /* libz.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 471FEFC10A9A629800C36AA3 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 34F0E429113F497500A10EED /* Python.framework in Frameworks */, - 8280AC6019C0741C00ADDB65 /* libboost_iostreams.a in Frameworks */, - CED22D511EB8F15B0085A4DC /* libboost_log.a in Frameworks */, - CED22D571EB8F2B50085A4DC /* libboost_log_setup.a in Frameworks */, - 9E632AEC13AD24D1003D1874 /* libboost_python.a in Frameworks */, - 9E632AED13AD24D1003D1874 /* libboost_regex.a in Frameworks */, - 3ABEABA11749C9CA00E34912 /* libboost_thread.a in Frameworks */, - 478418EF0CF05A5600BE4710 /* libClientCommon.a in Frameworks */, - 4784177A0CF058F000BE4710 /* freeorioncommon.dylib in Frameworks */, - 82BB624019BEDF9F00A0E281 /* freeorionparse.dylib in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 8DD76F660486A84900D96B5E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - CEDABCF31C688E290085A4DC /* libiconv.dylib in Frameworks */, - 822E325C19BEEE5E00A7B707 /* libz.a in Frameworks */, - 828AB7D415F7BCDD0075BE24 /* libpng.a in Frameworks */, - 82E5716115F63D8000BD29C8 /* libfreetype.a in Frameworks */, - 82682D8219C24DEC0041C465 /* libGLEW.a in Frameworks */, - 8280AC5F19C0741C00ADDB65 /* libboost_iostreams.a in Frameworks */, - CEDABCF01C688D050085A4DC /* libboost_locale.a in Frameworks */, - CED22D4F1EB8F15B0085A4DC /* libboost_log.a in Frameworks */, - CED22D561EB8F2B50085A4DC /* libboost_log_setup.a in Frameworks */, - 82AB1D13194D97A1009C8220 /* libboost_regex.a in Frameworks */, - 3ABEAB9C1749C96400E34912 /* libboost_signals.a in Frameworks */, - 82E5238F192FE8CE001E2374 /* AppKit.framework in Frameworks */, - 82E52398192FE99D001E2374 /* Carbon.framework in Frameworks */, - 82E52399192FE99D001E2374 /* Cocoa.framework in Frameworks */, - 82E5239A192FE99D001E2374 /* CoreData.framework in Frameworks */, - 82E5239B192FE99D001E2374 /* Foundation.framework in Frameworks */, - 82E52391192FE90F001E2374 /* IOKit.framework in Frameworks */, - 82E5239C192FE99D001E2374 /* OpenAL.framework in Frameworks */, - 82E5239D192FE99D001E2374 /* OpenGL.framework in Frameworks */, - 4710323E0CEF6A7700A7DF2B /* Ogg.framework in Frameworks */, - 8289A72C19BDB45C001256C1 /* SDL2.framework in Frameworks */, - 471032460CEF6A8B00A7DF2B /* vorbis.framework in Frameworks */, - 471D5D3F0A98A40900DA9C21 /* libGG.a in Frameworks */, - 478418EE0CF05A5000BE4710 /* libClientCommon.a in Frameworks */, - 478417700CF0581A00BE4710 /* freeorioncommon.dylib in Frameworks */, - 82BB623F19BEDF6F00A0E281 /* freeorionparse.dylib in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 08FB7794FE84155DC02AAC07 /* FreeOrion */ = { - isa = PBXGroup; - children = ( - 34B9EBEF0F5E208F005BF6A4 /* Resources */, - 47960BAF0A9CBF2900D0C556 /* Dependencies */, - 08FB7795FE84155DC02AAC07 /* Source */, - 1AB674ADFE9D54B511CA2CBB /* Products */, - 82ED913F194F5C96002F0A4A /* Utils */, - 471FF2560A9A7E6400C36AA3 /* Info.plist */, - ); - name = FreeOrion; - sourceTree = ""; - }; - 08FB7795FE84155DC02AAC07 /* Source */ = { - isa = PBXGroup; - children = ( - 471D59B00A988A2A00DA9C21 /* FreeOrion */, - 34B9EBEE0F5E1F7F005BF6A4 /* Config.h */, - ); - name = Source; - sourceTree = ""; - }; - 1AB674ADFE9D54B511CA2CBB /* Products */ = { - isa = PBXGroup; - children = ( - 8DD76F6C0486A84900D96B5E /* FreeOrion */, - 471D5A300A988D5700DA9C21 /* libGG.a */, - 471D5D440A98A46000DA9C21 /* freeoriond */, - 471FEFD70A9A629800C36AA3 /* freeorionca */, - 47103BE20CF04D8800A7DF2B /* freeorioncommon.dylib */, - 478417B10CF0592E00BE4710 /* libClientCommon.a */, - 3402E25D0F5A317400DF6FE7 /* FreeOrion.app */, - 82C07438149DE46200E76876 /* freeorionparse.dylib */, - ); - name = Products; - sourceTree = ""; - }; - 343EC64E0F3F528E00782AD3 /* utf8 */ = { - isa = PBXGroup; - children = ( - 343EC64F0F3F528E00782AD3 /* checked.h */, - 343EC6500F3F528E00782AD3 /* core.h */, - 343EC6510F3F528E00782AD3 /* unchecked.h */, - ); - path = utf8; - sourceTree = ""; - }; - 343EC6730F3F61D000782AD3 /* python */ = { - isa = PBXGroup; - children = ( - CEC4ADBE1BC13741000DFA9B /* AI */, - CEC4ADC21BC15195000DFA9B /* CommonFramework.cpp */, - CEC4ADC31BC15195000DFA9B /* CommonFramework.h */, - 343EC6790F3F61D000782AD3 /* CommonWrappers.h */, - 343EC6740F3F61D000782AD3 /* EmpireWrapper.cpp */, - 343EC6750F3F61D000782AD3 /* EnumWrapper.cpp */, - 343EC6760F3F61D000782AD3 /* LoggingWrapper.cpp */, - 824CE19D1AA0C28A00111CD9 /* server */, - 343EC6770F3F61D000782AD3 /* SetWrapper.h */, - 343EC6780F3F61D000782AD3 /* UniverseWrapper.cpp */, - ); - path = python; - sourceTree = ""; - }; - 34B9EBEF0F5E208F005BF6A4 /* Resources */ = { - isa = PBXGroup; - children = ( - CE76B9CD1CBD7A990085A4DC /* ChangeLog.md */, - 825E7258196EC28A00F8114D /* default */, - 471033FE0CEF7BCB00A7DF2B /* FreeOrion.icns */, - 34ACA40C0FFFABE600500F40 /* main.xib */, - ); - name = Resources; - sourceTree = ""; - }; - 471023380CEF3AAB00A7DF2B /* GG */ = { - isa = PBXGroup; - children = ( - 4710233B0CEF3AAB00A7DF2B /* doc */, - 4710233E0CEF3AAB00A7DF2B /* GG */, - 471023900CEF3AAC00A7DF2B /* src */, - ); - path = GG; - sourceTree = ""; - }; - 4710233B0CEF3AAB00A7DF2B /* doc */ = { - isa = PBXGroup; - children = ( - 4710233D0CEF3AAB00A7DF2B /* GGDoc.txt */, - ); - path = doc; - sourceTree = ""; - }; - 4710233E0CEF3AAB00A7DF2B /* GG */ = { - isa = PBXGroup; - children = ( - 4710233F0CEF3AAB00A7DF2B /* AlignmentFlags.h */, - 471023400CEF3AAB00A7DF2B /* Base.h */, - 471023410CEF3AAB00A7DF2B /* BrowseInfoWnd.h */, - 471023420CEF3AAB00A7DF2B /* Button.h */, - 471023430CEF3AAB00A7DF2B /* Clr.h */, - 34C9B34711DA2ECA003695F4 /* ClrConstants.h */, - 471023440CEF3AAB00A7DF2B /* Control.h */, - 471023450CEF3AAB00A7DF2B /* Cursor.h */, - CE3102B41DBFD05B0085A4DC /* DeferredLayout.h */, - 471023460CEF3AAB00A7DF2B /* dialogs */, - 4710234A0CEF3AAB00A7DF2B /* DrawUtil.h */, - 4710234B0CEF3AAB00A7DF2B /* DropDownList.h */, - 4710234C0CEF3AAB00A7DF2B /* DynamicGraphic.h */, - 4710234D0CEF3AAB00A7DF2B /* Edit.h */, - 4710234E0CEF3AAB00A7DF2B /* Enum.h */, - 4710234F0CEF3AAB00A7DF2B /* EventPump.h */, - 471023500CEF3AAB00A7DF2B /* Exception.h */, - 8257A8D81A90BA8C0006777D /* Export.h */, - 471023510CEF3AAB00A7DF2B /* Flags.h */, - 471023520CEF3AAB00A7DF2B /* Font.h */, - 34BAB0C20FC5CDC400EF792E /* FontFwd.h */, - CE6183961BB80D1300952BEA /* GGFwd.h */, - 8257A8D91A90BA8C0006777D /* GLClientAndServerBuffer.h */, - 2F60966C12EEAF2C00F58913 /* GroupBox.h */, - 471023550CEF3AAB00A7DF2B /* GUI.h */, - 471023560CEF3AAB00A7DF2B /* Layout.h */, - 471023570CEF3AAB00A7DF2B /* ListBox.h */, - 471023580CEF3AAB00A7DF2B /* Menu.h */, - 471023590CEF3AAB00A7DF2B /* MultiEdit.h */, - 34BAB0C30FC5CDC400EF792E /* MultiEditFwd.h */, - 4710235D0CEF3AAB00A7DF2B /* PtRect.h */, - CE6183971BB80D1300952BEA /* RichText */, - 4710235E0CEF3AAB00A7DF2B /* Scroll.h */, - CE61839B1BB80D1300952BEA /* ScrollPanel.h */, - 4710235F0CEF3AAB00A7DF2B /* SDL */, - 4710236C0CEF3AAB00A7DF2B /* Slider.h */, - 4710236D0CEF3AAB00A7DF2B /* Spin.h */, - 4710236E0CEF3AAB00A7DF2B /* StaticGraphic.h */, - 343EC66D0F3F600800782AD3 /* StrongTypedef.h */, - 4710236F0CEF3AAB00A7DF2B /* StyleFactory.h */, - 471023700CEF3AAC00A7DF2B /* TabWnd.h */, - 471023710CEF3AAC00A7DF2B /* TextControl.h */, - 471023720CEF3AAC00A7DF2B /* Texture.h */, - 471023730CEF3AAC00A7DF2B /* Timer.h */, - 343EC6520F3F52B200782AD3 /* UnicodeCharsets.h */, - 343EC64E0F3F528E00782AD3 /* utf8 */, - 471023740CEF3AAC00A7DF2B /* Wnd.h */, - 471023760CEF3AAC00A7DF2B /* WndEvent.h */, - 471023770CEF3AAC00A7DF2B /* ZList.h */, - ); - path = GG; - sourceTree = ""; - }; - 471023460CEF3AAB00A7DF2B /* dialogs */ = { - isa = PBXGroup; - children = ( - 471023470CEF3AAB00A7DF2B /* ColorDlg.h */, - 471023480CEF3AAB00A7DF2B /* FileDlg.h */, - 471023490CEF3AAB00A7DF2B /* ThreeButtonDlg.h */, - ); - path = dialogs; - sourceTree = ""; - }; - 4710235F0CEF3AAB00A7DF2B /* SDL */ = { - isa = PBXGroup; - children = ( - 471023600CEF3AAB00A7DF2B /* SDLGUI.h */, - ); - path = SDL; - sourceTree = ""; - }; - 471023900CEF3AAC00A7DF2B /* src */ = { - isa = PBXGroup; - children = ( - 471023920CEF3AAC00A7DF2B /* AlignmentFlags.cpp */, - 471023930CEF3AAC00A7DF2B /* Base.cpp */, - 471023940CEF3AAC00A7DF2B /* BrowseInfoWnd.cpp */, - 471023950CEF3AAC00A7DF2B /* Button.cpp */, - 34C9B34311DA2E95003695F4 /* ClrConstants.cpp */, - 471023970CEF3AAC00A7DF2B /* Control.cpp */, - 471023980CEF3AAC00A7DF2B /* Cursor.cpp */, - 34BAB0BE0FC5CD7300EF792E /* DefaultFont.h */, - CE3102B51DBFD0730085A4DC /* DeferredLayout.cpp */, - 471023990CEF3AAC00A7DF2B /* dialogs */, - 4710239E0CEF3AAC00A7DF2B /* DrawUtil.cpp */, - 4710239F0CEF3AAC00A7DF2B /* DropDownList.cpp */, - 471023A00CEF3AAC00A7DF2B /* DynamicGraphic.cpp */, - 471023A10CEF3AAC00A7DF2B /* Edit.cpp */, - 471023A20CEF3AAC00A7DF2B /* EventPump.cpp */, - 471023A30CEF3AAC00A7DF2B /* Font.cpp */, - CE594BA41CB2D1360085A4DC /* gilext */, - 8257A8D61A90BA610006777D /* GLClientAndServerBuffer.cpp */, - 2F60966A12EEAF0200F58913 /* GroupBox.cpp */, - 471023A40CEF3AAC00A7DF2B /* GUI.cpp */, - 471023A50CEF3AAC00A7DF2B /* Layout.cpp */, - 471023A60CEF3AAC00A7DF2B /* ListBox.cpp */, - 471023A70CEF3AAC00A7DF2B /* Menu.cpp */, - 471023A80CEF3AAC00A7DF2B /* MultiEdit.cpp */, - 471023B50CEF3AAC00A7DF2B /* PtRect.cpp */, - CE61839C1BB80D2E00952BEA /* RichText */, - 471023B70CEF3AAC00A7DF2B /* Scroll.cpp */, - CE6183A51BB80D2E00952BEA /* ScrollPanel.cpp */, - 471023B80CEF3AAC00A7DF2B /* SDL */, - 471023BC0CEF3AAC00A7DF2B /* StaticGraphic.cpp */, - 471023BD0CEF3AAC00A7DF2B /* StyleFactory.cpp */, - 471023BE0CEF3AAC00A7DF2B /* TabWnd.cpp */, - 471023BF0CEF3AAC00A7DF2B /* TextControl.cpp */, - 471023C00CEF3AAC00A7DF2B /* Texture.cpp */, - 471023C10CEF3AAC00A7DF2B /* Timer.cpp */, - 343EC6330F3F513700782AD3 /* UnicodeCharsets.cpp */, - 471023C20CEF3AAC00A7DF2B /* Wnd.cpp */, - 471023C40CEF3AAC00A7DF2B /* WndEvent.cpp */, - 471023C50CEF3AAC00A7DF2B /* ZList.cpp */, - ); - path = src; - sourceTree = ""; - }; - 471023990CEF3AAC00A7DF2B /* dialogs */ = { - isa = PBXGroup; - children = ( - 4710239A0CEF3AAC00A7DF2B /* ColorDlg.cpp */, - 4710239B0CEF3AAC00A7DF2B /* FileDlg.cpp */, - 4710239D0CEF3AAC00A7DF2B /* ThreeButtonDlg.cpp */, - ); - path = dialogs; - sourceTree = ""; - }; - 471023B80CEF3AAC00A7DF2B /* SDL */ = { - isa = PBXGroup; - children = ( - 471023BA0CEF3AAC00A7DF2B /* SDLGUI.cpp */, - ); - path = SDL; - sourceTree = ""; - }; - 47102F9D0CEF562700A7DF2B /* AI */ = { - isa = PBXGroup; - children = ( - 47102F9E0CEF562700A7DF2B /* AIInterface.cpp */, - 47102F9F0CEF562700A7DF2B /* AIInterface.h */, - ); - path = AI; - sourceTree = ""; - }; - 471D59B00A988A2A00DA9C21 /* FreeOrion */ = { - isa = PBXGroup; - children = ( - 47102F9D0CEF562700A7DF2B /* AI */, - 471D5C580A98A3F900DA9C21 /* client */, - 471D5C6A0A98A3F900DA9C21 /* combat */, - 471D5C6F0A98A3F900DA9C21 /* Empire */, - 471023380CEF3AAB00A7DF2B /* GG */, - 471D5C7D0A98A3F900DA9C21 /* network */, - 825936D5147E3B1B00B840A5 /* parse */, - 343EC6730F3F61D000782AD3 /* python */, - 471D5C9E0A98A3F900DA9C21 /* server */, - 471D5CA40A98A3F900DA9C21 /* UI */, - 471D5CE40A98A3F900DA9C21 /* universe */, - 471D5D170A98A3F900DA9C21 /* util */, - ); - name = FreeOrion; - path = ..; - sourceTree = ""; - }; - 471D5C580A98A3F900DA9C21 /* client */ = { - isa = PBXGroup; - children = ( - 471D5C590A98A3F900DA9C21 /* AI */, - 471D5C5D0A98A3F900DA9C21 /* ClientApp.cpp */, - 471D5C5E0A98A3F900DA9C21 /* ClientApp.h */, - 47102F890CEF55F700A7DF2B /* ClientFSMEvents.cpp */, - 47102F8A0CEF55F700A7DF2B /* ClientFSMEvents.h */, - 471D5C5F0A98A3F900DA9C21 /* doc */, - 471D5C610A98A3F900DA9C21 /* human */, - ); - path = client; - sourceTree = ""; - }; - 471D5C590A98A3F900DA9C21 /* AI */ = { - isa = PBXGroup; - children = ( - 471D5C5A0A98A3F900DA9C21 /* AIClientApp.cpp */, - 471D5C5B0A98A3F900DA9C21 /* AIClientApp.h */, - 471D5C5C0A98A3F900DA9C21 /* camain.cpp */, - ); - path = AI; - sourceTree = ""; - }; - 471D5C5F0A98A3F900DA9C21 /* doc */ = { - isa = PBXGroup; - children = ( - 471D5C600A98A3F900DA9C21 /* Doxyfile */, - ); - path = doc; - sourceTree = ""; - }; - 471D5C610A98A3F900DA9C21 /* human */ = { - isa = PBXGroup; - children = ( - 34ACA4BE0FFFD2DB00500F40 /* chmain.mm */, - 82CE867E19BF27D0004DF6DF /* GUIController.mm */, - 47102F870CEF55E700A7DF2B /* HumanClientFSM.cpp */, - 47102F880CEF55E700A7DF2B /* HumanClientFSM.h */, - 471D5C620A98A3F900DA9C21 /* chmain.cpp */, - 471D5C640A98A3F900DA9C21 /* HumanClientApp.cpp */, - 471D5C650A98A3F900DA9C21 /* HumanClientApp.h */, - 34ACA4C50FFFD50100500F40 /* chmain.h */, - ); - path = human; - sourceTree = ""; - }; - 471D5C6A0A98A3F900DA9C21 /* combat */ = { - isa = PBXGroup; - children = ( - 82E9DDED19530E5D007E681B /* CombatEvent.cpp */, - 82E9DDEE19530E5D007E681B /* CombatEvent.h */, - 82E9DDEF19530E5D007E681B /* CombatEvents.cpp */, - 82E9DDF019530E5D007E681B /* CombatEvents.h */, - 822EB0FD170832240083EB38 /* CombatLogManager.cpp */, - 822EB0FE170832240083EB38 /* CombatLogManager.h */, - 471D5C6D0A98A3F900DA9C21 /* CombatSystem.cpp */, - 471D5C6E0A98A3F900DA9C21 /* CombatSystem.h */, - ); - path = combat; - sourceTree = ""; - }; - 471D5C6F0A98A3F900DA9C21 /* Empire */ = { - isa = PBXGroup; - children = ( - 82BEB657159853B300B024CB /* Diplomacy.cpp */, - 82BEB658159853B300B024CB /* Diplomacy.h */, - 471D5C720A98A3F900DA9C21 /* doc */, - 471D5C750A98A3F900DA9C21 /* Empire.cpp */, - 471D5C760A98A3F900DA9C21 /* Empire.h */, - 471D5C770A98A3F900DA9C21 /* EmpireManager.cpp */, - 471D5C780A98A3F900DA9C21 /* EmpireManager.h */, - 471D5C790A98A3F900DA9C21 /* ResourcePool.cpp */, - 471D5C7A0A98A3F900DA9C21 /* ResourcePool.h */, - CEED195D1C79BA2F0085A4DC /* Supply.cpp */, - CEED195E1C79BA2F0085A4DC /* Supply.h */, - ); - path = Empire; - sourceTree = ""; - }; - 471D5C720A98A3F900DA9C21 /* doc */ = { - isa = PBXGroup; - children = ( - 471D5C730A98A3F900DA9C21 /* DoxyFile */, - 471D5C740A98A3F900DA9C21 /* EmpireDoc.txt */, - ); - path = doc; - sourceTree = ""; - }; - 471D5C7D0A98A3F900DA9C21 /* network */ = { - isa = PBXGroup; - children = ( - 47102FAA0CEF566200A7DF2B /* ClientNetworking.cpp */, - 47102FAB0CEF566200A7DF2B /* ClientNetworking.h */, - 471D5C800A98A3F900DA9C21 /* doc */, - 471D5C830A98A3F900DA9C21 /* Message.cpp */, - 471D5C840A98A3F900DA9C21 /* Message.h */, - 47102FA40CEF565700A7DF2B /* MessageQueue.cpp */, - 47102FA50CEF565700A7DF2B /* MessageQueue.h */, - 47102FA60CEF565700A7DF2B /* Networking.cpp */, - 47102FA70CEF565700A7DF2B /* Networking.h */, - 47102FA80CEF565700A7DF2B /* ServerNetworking.cpp */, - 47102FA90CEF565700A7DF2B /* ServerNetworking.h */, - ); - path = network; - sourceTree = ""; - }; - 471D5C800A98A3F900DA9C21 /* doc */ = { - isa = PBXGroup; - children = ( - 471D5C810A98A3F900DA9C21 /* Doxyfile */, - 471D5C820A98A3F900DA9C21 /* NetworkDoc.txt */, - ); - path = doc; - sourceTree = ""; - }; - 471D5C9E0A98A3F900DA9C21 /* server */ = { - isa = PBXGroup; - children = ( - 471D5C9F0A98A3F900DA9C21 /* dmain.cpp */, - 471D5CA00A98A3F900DA9C21 /* doc */, - 471030490CEF569900A7DF2B /* SaveLoad.cpp */, - 4710304A0CEF569900A7DF2B /* SaveLoad.h */, - 471D5CA20A98A3F900DA9C21 /* ServerApp.cpp */, - 471D5CA30A98A3F900DA9C21 /* ServerApp.h */, - 471030470CEF569200A7DF2B /* ServerFSM.cpp */, - 471030480CEF569200A7DF2B /* ServerFSM.h */, - ); - path = server; - sourceTree = ""; - }; - 471D5CA00A98A3F900DA9C21 /* doc */ = { - isa = PBXGroup; - children = ( - 471D5CA10A98A3F900DA9C21 /* Doxyfile */, - ); - path = doc; - sourceTree = ""; - }; - 471D5CA40A98A3F900DA9C21 /* UI */ = { - isa = PBXGroup; - children = ( - 471D5CA50A98A3F900DA9C21 /* About.cpp */, - 471D5CA60A98A3F900DA9C21 /* About.h */, - 8275F3DD1AD31381003568FF /* AccordionPanel.cpp */, - 8275F3DE1AD31381003568FF /* AccordionPanel.h */, - 471D5CA70A98A3F900DA9C21 /* BuildDesignatorWnd.cpp */, - 471D5CA80A98A3F900DA9C21 /* BuildDesignatorWnd.h */, - 8275F3DF1AD31381003568FF /* BuildingsPanel.cpp */, - 8275F3E01AD31381003568FF /* BuildingsPanel.h */, - 8275F3E11AD31381003568FF /* CensusBrowseWnd.cpp */, - 8275F3E21AD31381003568FF /* CensusBrowseWnd.h */, - 34972BE60F49CBE80015C864 /* ChatWnd.cpp */, - 34972BE70F49CBE80015C864 /* ChatWnd.h */, - 471D5CA90A98A3F900DA9C21 /* ClientUI.cpp */, - 471D5CAA0A98A3F900DA9C21 /* ClientUI.h */, - 82B9AE981AA71CCC00486B2A /* CombatReport */, - 471D5CAD0A98A3F900DA9C21 /* CUIControls.cpp */, - 471D5CAE0A98A3F900DA9C21 /* CUIControls.h */, - 471D5CAF0A98A3F900DA9C21 /* CUIDrawUtil.cpp */, - 471D5CB00A98A3F900DA9C21 /* CUIDrawUtil.h */, - CE6183AC1BB80D4900952BEA /* CUILinkTextBlock.cpp */, - CE6183AD1BB80D4900952BEA /* CUILinkTextBlock.h */, - 82592EEB147E365700B840A5 /* CUISlider.h */, - 471D5CB10A98A3F900DA9C21 /* CUISpin.h */, - 471D5CB20A98A3F900DA9C21 /* CUIStyle.cpp */, - 471D5CB30A98A3F900DA9C21 /* CUIStyle.h */, - 471D5CB60A98A3F900DA9C21 /* CUIWnd.cpp */, - 471D5CB70A98A3F900DA9C21 /* CUIWnd.h */, - 343EC6590F3F5B5300782AD3 /* DesignWnd.cpp */, - 343EC65A0F3F5B5300782AD3 /* DesignWnd.h */, - 471D5CB80A98A3F900DA9C21 /* doc */, - 343EC65D0F3F5C7C00782AD3 /* EncyclopediaDetailPanel.cpp */, - 343EC65E0F3F5C7C00782AD3 /* EncyclopediaDetailPanel.h */, - 828C793D160778AA0075F220 /* FieldIcon.cpp */, - 828C793E160778AA0075F220 /* FieldIcon.h */, - 471D5CBA0A98A3F900DA9C21 /* FleetButton.cpp */, - 471D5CBB0A98A3F900DA9C21 /* FleetButton.h */, - 471D5CBC0A98A3F900DA9C21 /* FleetWnd.cpp */, - 471D5CBD0A98A3F900DA9C21 /* FleetWnd.h */, - 471D5CC00A98A3F900DA9C21 /* GalaxySetupWnd.cpp */, - 471D5CC10A98A3F900DA9C21 /* GalaxySetupWnd.h */, - 82B9D445180C53FE0032F86D /* GraphControl.cpp */, - 82B9D446180C53FE0032F86D /* GraphControl.h */, - 820BDECB1848A93E009BC457 /* Hotkeys.cpp */, - 820BDECC1848A93E009BC457 /* Hotkeys.h */, - 8275F3E31AD31381003568FF /* IconTextBrowseWnd.cpp */, - 8275F3E41AD31381003568FF /* IconTextBrowseWnd.h */, - 471D5CC20A98A3F900DA9C21 /* InGameMenu.cpp */, - 471D5CC30A98A3F900DA9C21 /* InGameMenu.h */, - 471D5CC40A98A3F900DA9C21 /* IntroScreen.cpp */, - 471D5CC50A98A3F900DA9C21 /* IntroScreen.h */, - 471D5CC60A98A3F900DA9C21 /* LinkText.cpp */, - 471D5CC70A98A3F900DA9C21 /* LinkText.h */, - 471D5CC80A98A3F900DA9C21 /* MapWnd.cpp */, - 471D5CC90A98A3F900DA9C21 /* MapWnd.h */, - 8275F3E51AD31381003568FF /* MeterBrowseWnd.cpp */, - 8275F3E61AD31381003568FF /* MeterBrowseWnd.h */, - 8275F3E71AD31381003568FF /* MilitaryPanel.cpp */, - 8275F3E81AD31381003568FF /* MilitaryPanel.h */, - 827902361737C6EA008AF126 /* ModeratorActionsWnd.cpp */, - 827902371737C6EA008AF126 /* ModeratorActionsWnd.h */, - 8275F3E91AD31381003568FF /* MultiIconValueIndicator.cpp */, - 8275F3EA1AD31381003568FF /* MultiIconValueIndicator.h */, - 8275F3EB1AD31381003568FF /* MultiMeterStatusBar.cpp */, - 8275F3EC1AD31381003568FF /* MultiMeterStatusBar.h */, - 471D5CCA0A98A3F900DA9C21 /* MultiplayerLobbyWnd.cpp */, - 471D5CCB0A98A3F900DA9C21 /* MultiplayerLobbyWnd.h */, - 82132C2D15DA2BBC00F5B537 /* ObjectListWnd.cpp */, - 82132C2E15DA2BBC00F5B537 /* ObjectListWnd.h */, - 471D5CCC0A98A3F900DA9C21 /* OptionsWnd.cpp */, - 471D5CCD0A98A3F900DA9C21 /* OptionsWnd.h */, - 2F60966312EEAD2200F58913 /* PlayerListWnd.cpp */, - 2F60966412EEAD2200F58913 /* PlayerListWnd.h */, - 8275F3ED1AD31381003568FF /* PopulationPanel.cpp */, - 8275F3EE1AD31381003568FF /* PopulationPanel.h */, - 471D5CD00A98A3F900DA9C21 /* ProductionWnd.cpp */, - 471D5CD10A98A3F900DA9C21 /* ProductionWnd.h */, - 343EC6610F3F5C7C00782AD3 /* QueueListBox.cpp */, - 343EC6620F3F5C7C00782AD3 /* QueueListBox.h */, - 471D5CD20A98A3F900DA9C21 /* ResearchWnd.cpp */, - 471D5CD30A98A3F900DA9C21 /* ResearchWnd.h */, - CE2C454B1C65036F0085A4DC /* ResourceBrowseWnd.cpp */, - CE2C454C1C65036F0085A4DC /* ResourceBrowseWnd.h */, - 8275F3EF1AD31381003568FF /* ResourcePanel.cpp */, - 8275F3F01AD31381003568FF /* ResourcePanel.h */, - 8298A1AC18F9BB980001D3DA /* SaveFileDialog.cpp */, - 8298A1AD18F9BB980001D3DA /* SaveFileDialog.h */, - 471D5CD40A98A3F900DA9C21 /* ServerConnectWnd.cpp */, - 471D5CD50A98A3F900DA9C21 /* ServerConnectWnd.h */, - 346D87ED0FC5F6080095593E /* ShaderProgram.cpp */, - 346D87EE0FC5F6080095593E /* ShaderProgram.h */, - 471D5CD60A98A3F900DA9C21 /* SidePanel.cpp */, - 471D5CD70A98A3F900DA9C21 /* SidePanel.h */, - 471D5CD80A98A3F900DA9C21 /* SitRepPanel.cpp */, - 471D5CD90A98A3F900DA9C21 /* SitRepPanel.h */, - 34972BEC0F49CBF90015C864 /* Sound.cpp */, - 34972BED0F49CBF90015C864 /* Sound.h */, - 8275F3F31AD31381003568FF /* SpecialsPanel.cpp */, - 8275F3F41AD31381003568FF /* SpecialsPanel.h */, - 471D5CDE0A98A3F900DA9C21 /* SystemIcon.cpp */, - 471D5CDF0A98A3F900DA9C21 /* SystemIcon.h */, - 8275F3F51AD31381003568FF /* SystemResourceSummaryBrowseWnd.cpp */, - 8275F3F61AD31381003568FF /* SystemResourceSummaryBrowseWnd.h */, - 82970BED1A0CF53A008B37EF /* TechTreeArcs.cpp */, - 82970BEE1A0CF53A008B37EF /* TechTreeArcs.h */, - 2F22EC0412F7F4CF00456CDE /* TechTreeLayout.cpp */, - 2F22EC0512F7F4CF00456CDE /* TechTreeLayout.h */, - 471D5CE00A98A3F900DA9C21 /* TechTreeWnd.cpp */, - 471D5CE10A98A3F900DA9C21 /* TechTreeWnd.h */, - 8275F3F71AD31381003568FF /* TextBrowseWnd.cpp */, - 8275F3F81AD31381003568FF /* TextBrowseWnd.h */, - ); - path = UI; - sourceTree = ""; - }; - 471D5CB80A98A3F900DA9C21 /* doc */ = { - isa = PBXGroup; - children = ( - ); - path = doc; - sourceTree = ""; - }; - 471D5CE40A98A3F900DA9C21 /* universe */ = { - isa = PBXGroup; - children = ( - 471D5CE50A98A3F900DA9C21 /* Building.cpp */, - 471D5CE60A98A3F900DA9C21 /* Building.h */, - 471D5CE70A98A3F900DA9C21 /* Condition.cpp */, - 471D5CE80A98A3F900DA9C21 /* Condition.h */, - 471D5CEC0A98A3F900DA9C21 /* doc */, - 471D5CEF0A98A3F900DA9C21 /* Effect.cpp */, - 471D5CF00A98A3F900DA9C21 /* Effect.h */, - 82592EEF147E381B00B840A5 /* EffectAccounting.cpp */, - 82592EEE147E37FB00B840A5 /* EffectAccounting.h */, - 82EC86891B40504700D4584D /* Encyclopedia.cpp */, - 82EC868A1B40504700D4584D /* Encyclopedia.h */, - 471D5CF20A98A3F900DA9C21 /* Enums.cpp */, - 471D5CF30A98A3F900DA9C21 /* Enums.h */, - CE2227D41EA971A10085A4DC /* EnumsFwd.h */, - 8250FDE315FCEFDD00523C1C /* Field.cpp */, - 8250FDE415FCEFDE00523C1C /* Field.h */, - CED6E5501C6DDC0F0085A4DC /* Fighter.cpp */, - CED6E5511C6DDC0F0085A4DC /* Fighter.h */, - 471D5CF40A98A3F900DA9C21 /* Fleet.cpp */, - 471D5CF50A98A3F900DA9C21 /* Fleet.h */, - CE1939C41F31AD4C0085A4DC /* IDAllocator.cpp */, - CE1939C51F31AD4C0085A4DC /* IDAllocator.h */, - 471D5CF70A98A3F900DA9C21 /* Meter.cpp */, - 471D5CF80A98A3F900DA9C21 /* Meter.h */, - 82592EF3147E387100B840A5 /* ObjectMap.cpp */, - 82592EF4147E387100B840A5 /* ObjectMap.h */, - CE2BAD8D1E3E43E40085A4DC /* Pathfinder.cpp */, - CE2BAD8E1E3E43E40085A4DC /* Pathfinder.h */, - 471D5CFC0A98A3F900DA9C21 /* Planet.cpp */, - 471D5CFD0A98A3F900DA9C21 /* Planet.h */, - 471D5CFE0A98A3F900DA9C21 /* PopCenter.cpp */, - 471D5CFF0A98A3F900DA9C21 /* PopCenter.h */, - 471D5D000A98A3F900DA9C21 /* Predicates.cpp */, - 471D5D010A98A3F900DA9C21 /* Predicates.h */, - 471D5D020A98A3F900DA9C21 /* ResourceCenter.cpp */, - 471D5D030A98A3F900DA9C21 /* ResourceCenter.h */, - 471D5D040A98A3F900DA9C21 /* Ship.cpp */, - 471D5D050A98A3F900DA9C21 /* Ship.h */, - 471D5D060A98A3F900DA9C21 /* ShipDesign.cpp */, - 471D5D070A98A3F900DA9C21 /* ShipDesign.h */, - 471D5D080A98A3F900DA9C21 /* Special.cpp */, - 471D5D090A98A3F900DA9C21 /* Special.h */, - 34D3CE8A11D7DA03007C1E78 /* Species.cpp */, - 34D3CE8B11D7DA03007C1E78 /* Species.h */, - 471D5D0A0A98A3F900DA9C21 /* System.cpp */, - 471D5D0B0A98A3F900DA9C21 /* System.h */, - 471D5D0C0A98A3F900DA9C21 /* Tech.cpp */, - 471D5D0D0A98A3F900DA9C21 /* Tech.h */, - 471D5D0F0A98A3F900DA9C21 /* Universe.cpp */, - 471D5D100A98A3F900DA9C21 /* Universe.h */, - 82B4313314A74E07003EEE42 /* UniverseGenerator.cpp */, - 827AF376185B26AE001CD79C /* UniverseGenerator.h */, - 471D5D110A98A3F900DA9C21 /* UniverseObject.cpp */, - 471D5D120A98A3F900DA9C21 /* UniverseObject.h */, - 471D5D130A98A3F900DA9C21 /* ValueRef.cpp */, - 471D5D140A98A3F900DA9C21 /* ValueRef.h */, - 82592EF7147E38ED00B840A5 /* ValueRefFwd.h */, - ); - path = universe; - sourceTree = ""; - }; - 471D5CEC0A98A3F900DA9C21 /* doc */ = { - isa = PBXGroup; - children = ( - 471D5CEE0A98A3F900DA9C21 /* Doxyfile */, - ); - path = doc; - sourceTree = ""; - }; - 471D5D170A98A3F900DA9C21 /* util */ = { - isa = PBXGroup; - children = ( - 471D5D180A98A3F900DA9C21 /* AppInterface.cpp */, - 471D5D190A98A3F900DA9C21 /* AppInterface.h */, - 471D5D1A0A98A3F900DA9C21 /* binreloc.c */, - 471D5D1B0A98A3F900DA9C21 /* binreloc.h */, - 471D5CF60A98A3F900DA9C21 /* blocking_combiner.h */, - CE43BB291EC456A20085A4DC /* CheckSums.cpp */, - CE43BB2A1EC456A20085A4DC /* CheckSums.h */, - CE97F70A1BC7D74D0002988B /* DependencyVersions.cpp */, - 471D5D1E0A98A3F900DA9C21 /* Directories.cpp */, - 471D5D1F0A98A3F900DA9C21 /* Directories.h */, - 471D5D200A98A3F900DA9C21 /* doc */, - 82E68F5F190ECB8400BB1AD9 /* EnumText.cpp */, - 82E68F60190ECB8400BB1AD9 /* EnumText.h */, - 827AF39F185B2FB9001CD79C /* Export.h */, - 3A5105521748D68B00DC258B /* i18n.cpp */, - 3A5105531748D68B00DC258B /* i18n.h */, - 3A5105551748D6A600DC258B /* Logger.cpp */, - 3A5105561748D6A700DC258B /* Logger.h */, - 3A5105551748D6A600DC258C /* LoggerWithOptionsDB.cpp */, - 3A5105561748D6A700DC258C /* LoggerWithOptionsDB.h */, - 825EA75E16DB9DB10002A797 /* ModeratorAction.cpp */, - 825EA75F16DB9DB10002A797 /* ModeratorAction.h */, - 471D5D270A98A3F900DA9C21 /* MultiplayerCommon.cpp */, - 471D5D280A98A3F900DA9C21 /* MultiplayerCommon.h */, - 471D5D290A98A3F900DA9C21 /* OptionsDB.cpp */, - 471D5D2A0A98A3F900DA9C21 /* OptionsDB.h */, - 471D5D2B0A98A3F900DA9C21 /* OptionValidators.h */, - 471D5D2C0A98A3F900DA9C21 /* Order.cpp */, - 471D5D2D0A98A3F900DA9C21 /* Order.h */, - 471D5D2E0A98A3F900DA9C21 /* OrderSet.cpp */, - 471D5D2F0A98A3F900DA9C21 /* OrderSet.h */, - 471D5D300A98A3F900DA9C21 /* Process.cpp */, - 471D5D310A98A3F900DA9C21 /* Process.h */, - 471D5D320A98A3F900DA9C21 /* Random.cpp */, - 471D5D330A98A3F900DA9C21 /* Random.h */, - 82E68F61190ECB8400BB1AD9 /* SaveGamePreviewUtils.cpp */, - 82E68F62190ECB8400BB1AD9 /* SaveGamePreviewUtils.h */, - 8242C81A176B0C8E001E1CF2 /* ScopedTimer.cpp */, - 8242C81B176B0C8E001E1CF2 /* ScopedTimer.h */, - 471D5D350A98A3F900DA9C21 /* Serialize.h */, - 34C44956118271590071E09A /* Serialize.ipp */, - 34C44957118271590071E09A /* SerializeEmpire.cpp */, - 82C96D0A16E4C1F900579E56 /* SerializeModeratorAction.cpp */, - 34C44958118271590071E09A /* SerializeMultiplayerCommon.cpp */, - 34C44959118271590071E09A /* SerializeOrderSet.cpp */, - 34C4495B118271590071E09A /* SerializeUniverse.cpp */, - 471D5D360A98A3F900DA9C21 /* SitRepEntry.cpp */, - 471D5D370A98A3F900DA9C21 /* SitRepEntry.h */, - 471D5CDC0A98A3F900DA9C21 /* StringTable.cpp */, - 471D5CDD0A98A3F900DA9C21 /* StringTable.h */, - 471D5D380A98A3F900DA9C21 /* VarText.cpp */, - 471D5D390A98A3F900DA9C21 /* VarText.h */, - 3A1BF3EA1748875200812237 /* Version.cpp */, - 34C4495D118271590071E09A /* Version.cpp.in */, - 471D5D3B0A98A3F900DA9C21 /* Version.h */, - 471D5D3C0A98A3F900DA9C21 /* XMLDoc.cpp */, - 471D5D3D0A98A3F900DA9C21 /* XMLDoc.h */, - ); - path = util; - sourceTree = ""; - }; - 471D5D200A98A3F900DA9C21 /* doc */ = { - isa = PBXGroup; - children = ( - 471D5D210A98A3F900DA9C21 /* Doxyfile */, - ); - path = doc; - sourceTree = ""; - }; - 47960BAE0A9CBF1D00D0C556 /* Static Libraries */ = { - isa = PBXGroup; - children = ( - 47B60DED0CF0761E00318761 /* boost */, - 82483B7B15F4F26200D27614 /* libFreeImage.a */, - 471D654D0A98B7F800DA9C21 /* libfreetype.a */, - 82E250AD19C2317700F0E2F2 /* libGLEW.a */, - 828AB7D315F7BCDD0075BE24 /* libpng.a */, - 822E325B19BEED6C00A7B707 /* libz.a */, - ); - includeInIndex = 1; - name = "Static Libraries"; - path = dep/lib; - sourceTree = ""; - }; - 47960BAF0A9CBF2900D0C556 /* Dependencies */ = { - isa = PBXGroup; - children = ( - 8288CC3515F4AEF100F8318D /* Frameworks */, - 8288CC3D15F4AF6000F8318D /* System Frameworks */, - 47960BAE0A9CBF1D00D0C556 /* Static Libraries */, - CEDABCF11C688DE90085A4DC /* System Libraries */, - ); - name = Dependencies; - sourceTree = ""; - }; - 47B60DED0CF0761E00318761 /* boost */ = { - isa = PBXGroup; - children = ( - 82483B7A15F4F24100D27614 /* libboost_date_time.a */, - 9EEEEA7313ACB91A0085B1A0 /* libboost_filesystem.a */, - 8280AC5C19C0741B00ADDB65 /* libboost_iostreams.a */, - CEDABCEF1C688D050085A4DC /* libboost_locale.a */, - 82714D761ADABE680071A329 /* libboost_log.a */, - CED22D531EB8F2B50085A4DC /* libboost_log_setup.a */, - 9EEEEA7413ACB91A0085B1A0 /* libboost_python.a */, - 9EEEEA7513ACB91A0085B1A0 /* libboost_regex.a */, - 9EEEEA7613ACB91A0085B1A0 /* libboost_serialization.a */, - 9EEEEA7713ACB91A0085B1A0 /* libboost_signals.a */, - 9EEEEA7813ACB91A0085B1A0 /* libboost_system.a */, - 9EEEEA7913ACB91A0085B1A0 /* libboost_thread.a */, - ); - name = boost; - sourceTree = ""; - }; - 824CE19D1AA0C28A00111CD9 /* server */ = { - isa = PBXGroup; - children = ( - 82D7B5A11AA37C9000A85842 /* ServerFramework.cpp */, - 82D7B5A21AA37C9000A85842 /* ServerFramework.h */, - 82D7B59E1AA3666F00A85842 /* ServerWrapper.cpp */, - 82D7B59F1AA3666F00A85842 /* ServerWrapper.h */, - ); - path = server; - sourceTree = ""; - }; - 825936D5147E3B1B00B840A5 /* parse */ = { - isa = PBXGroup; - children = ( - 825936D7147E3B1B00B840A5 /* BuildingsParser.cpp */, - CE13B48D1C8CB0CF0085A4DC /* CommonParams.h */, - CE13B48E1C8CB0CF0085A4DC /* CommonParamsParser.cpp */, - 825936D9147E3B1B00B840A5 /* ConditionParser.cpp */, - 825936DA147E3B1B00B840A5 /* ConditionParser.h */, - 825936DB147E3B1B00B840A5 /* ConditionParser1.cpp */, - 825936DC147E3B1B00B840A5 /* ConditionParser2.cpp */, - 825936DD147E3B1B00B840A5 /* ConditionParser3.cpp */, - 8250FDE115FCEFC400523C1C /* ConditionParser4.cpp */, - 82D2D9A11838C8340011C212 /* ConditionParser5.cpp */, - 82D2D9A21838C8340011C212 /* ConditionParser6.cpp */, - 82B3265219B603C1005082AC /* ConditionParser7.cpp */, - 825936DE147E3B1B00B840A5 /* ConditionParserImpl.h */, - 82D563D51A6AEBD6007E1131 /* DoubleComplexValueRefParser.cpp */, - 825936E1147E3B1B00B840A5 /* DoubleValueRefParser.cpp */, - 825936E2147E3B1B00B840A5 /* EffectParser.cpp */, - 825936E3147E3B1B00B840A5 /* EffectParser.h */, - 8201E5F215E2C0770037D453 /* EffectParser1.cpp */, - 8201E5F315E2C0770037D453 /* EffectParser2.cpp */, - 822B16B51642A6F8000ADE58 /* EffectParser3.cpp */, - 822B16B61642A6F8000ADE58 /* EffectParser4.cpp */, - 82E944E81B04C726004F3343 /* EffectParser5.cpp */, - 8201E5F415E2C0770037D453 /* EffectParserImpl.h */, - 829E16B01811181F00F7F52D /* EmpireStatsParser.cpp */, - 82BB838215AAD20A006F635F /* EncyclopediaParser.cpp */, - 825936E4147E3B1B00B840A5 /* EnumParser.cpp */, - 825936E5147E3B1B00B840A5 /* EnumParser.h */, - 8250FDE215FCEFC400523C1C /* FieldsParser.cpp */, - 825936E6147E3B1B00B840A5 /* FleetPlansParser.cpp */, - CE3656A81F22244E0085A4DC /* GameRulesParser.cpp */, - 8246FECF194D8F83008C07B5 /* IntComplexValueRefParser.cpp */, - 825936EA147E3B1B00B840A5 /* IntValueRefParser.cpp */, - 825936EB147E3B1B00B840A5 /* ItemsParser.cpp */, - 82D68C6C15F4F720006B772D /* KeymapParser.cpp */, - 825936EE147E3B1B00B840A5 /* Lexer.cpp */, - 825936EF147E3B1B00B840A5 /* Lexer.h */, - 825936F0147E3B1B00B840A5 /* MonsterFleetPlansParser.cpp */, - 825936F1147E3B1B00B840A5 /* Parse.cpp */, - 825936F2147E3B1B00B840A5 /* Parse.h */, - 825936F3147E3B1B00B840A5 /* ParseImpl.h */, - 825936F5147E3B1B00B840A5 /* PlanetEnvironmentValueRefParser.cpp */, - 825936F6147E3B1B00B840A5 /* PlanetSizeValueRefParser.cpp */, - 825936F7147E3B1B00B840A5 /* PlanetTypeValueRefParser.cpp */, - 825936F8147E3B1B00B840A5 /* ReportParseError.cpp */, - 825936F9147E3B1B00B840A5 /* ReportParseError.h */, - 825936FA147E3B1B00B840A5 /* ShipDesignsParser.cpp */, - 825936FB147E3B1B00B840A5 /* ShipHullsParser.cpp */, - 825936FC147E3B1B00B840A5 /* ShipPartsParser.cpp */, - 825936FD147E3B1B00B840A5 /* SpecialsParser.cpp */, - 825936FE147E3B1B00B840A5 /* SpeciesParser.cpp */, - 825936FF147E3B1B00B840A5 /* StarTypeValueRefParser.cpp */, - 8295CEC61A88FFF8000EE624 /* StringComplexValueRefParser.cpp */, - 82593700147E3B1B00B840A5 /* StringValueRefParser.cpp */, - 82593701147E3B1B00B840A5 /* TechsParser.cpp */, - 82593702147E3B1B00B840A5 /* test */, - 82C560D117D0CC2F00FE3B35 /* Tokens.cpp */, - 82C560D217D0CC2F00FE3B35 /* Tokens.h */, - 8259376A147E3B1C00B840A5 /* UniverseObjectTypeValueRefParser.cpp */, - 8259376B147E3B1C00B840A5 /* ValueRefParser.h */, - 829BBB6117D09017009D3735 /* ValueRefParserImpl.cpp */, - 8259376C147E3B1C00B840A5 /* ValueRefParserImpl.h */, - ); - path = parse; - sourceTree = ""; - }; - 82593702147E3B1B00B840A5 /* test */ = { - isa = PBXGroup; - children = ( - 82593704147E3B1C00B840A5 /* buildings */, - 82593705147E3B1C00B840A5 /* buildings_errors */, - 82593706147E3B1C00B840A5 /* buildings_permutations */, - 8259370A147E3B1C00B840A5 /* condition_parser_1 */, - 8259370B147E3B1C00B840A5 /* condition_parser_1_errors */, - 8259370C147E3B1C00B840A5 /* condition_parser_2 */, - 8259370D147E3B1C00B840A5 /* condition_parser_2_errors */, - 8259370E147E3B1C00B840A5 /* condition_parser_3 */, - 8259370F147E3B1C00B840A5 /* condition_parser_3_errors */, - 82593713147E3B1C00B840A5 /* double_statistic */, - 82593714147E3B1C00B840A5 /* double_statistic_errors */, - 82593715147E3B1C00B840A5 /* double_variable */, - 82593716147E3B1C00B840A5 /* double_variable_arithmetic */, - 82593717147E3B1C00B840A5 /* double_variable_errors */, - 82593718147E3B1C00B840A5 /* effect_parser */, - 82593719147E3B1C00B840A5 /* effect_parser_errors */, - 8259371B147E3B1C00B840A5 /* fleet_plans */, - 8259371C147E3B1C00B840A5 /* fleet_plans_errors */, - 8259371D147E3B1C00B840A5 /* fleet_plans_permutations */, - 82593726147E3B1C00B840A5 /* items */, - 82593727147E3B1C00B840A5 /* items_errors */, - 82593728147E3B1C00B840A5 /* items_permutations */, - 82593729147E3B1C00B840A5 /* lexer_test_rules.cpp */, - 8259372A147E3B1C00B840A5 /* lexer_tokens */, - 8259372B147E3B1C00B840A5 /* monster_fleet_plans */, - 8259372C147E3B1C00B840A5 /* monster_fleet_plans_errors */, - 8259372D147E3B1C00B840A5 /* monster_fleet_plans_permutations */, - 8259372F147E3B1C00B840A5 /* planet_environment_constant */, - 82593731147E3B1C00B840A5 /* planet_environment_statistic */, - 82593732147E3B1C00B840A5 /* planet_environment_statistic_errors */, - 82593733147E3B1C00B840A5 /* planet_environment_variable */, - 82593734147E3B1C00B840A5 /* planet_environment_variable_errors */, - 82593735147E3B1C00B840A5 /* planet_size_constant */, - 82593737147E3B1C00B840A5 /* planet_size_statistic */, - 82593738147E3B1C00B840A5 /* planet_size_statistic_errors */, - 82593739147E3B1C00B840A5 /* planet_size_variable */, - 8259373A147E3B1C00B840A5 /* planet_size_variable_errors */, - 8259373B147E3B1C00B840A5 /* planet_type_constant */, - 8259373D147E3B1C00B840A5 /* planet_type_statistic */, - 8259373E147E3B1C00B840A5 /* planet_type_statistic_errors */, - 8259373F147E3B1C00B840A5 /* planet_type_variable */, - 82593740147E3B1C00B840A5 /* planet_type_variable_errors */, - 82593741147E3B1C00B840A5 /* ship_designs */, - 82593742147E3B1C00B840A5 /* ship_designs_errors */, - 82593743147E3B1C00B840A5 /* ship_designs_permutations */, - 82593744147E3B1C00B840A5 /* ship_hulls */, - 82593745147E3B1C00B840A5 /* ship_hulls_errors */, - 82593747147E3B1C00B840A5 /* ship_parts */, - 82593748147E3B1C00B840A5 /* ship_parts_errors */, - 8259374A147E3B1C00B840A5 /* specials */, - 8259374B147E3B1C00B840A5 /* specials_errors */, - 8259374C147E3B1C00B840A5 /* specials_permutations */, - 8259374D147E3B1C00B840A5 /* species */, - 8259374E147E3B1C00B840A5 /* species_errors */, - 8259374F147E3B1C00B840A5 /* species_permutations */, - 82593750147E3B1C00B840A5 /* star_type_constant */, - 82593752147E3B1C00B840A5 /* star_type_statistic */, - 82593753147E3B1C00B840A5 /* star_type_statistic_errors */, - 82593754147E3B1C00B840A5 /* star_type_variable */, - 82593755147E3B1C00B840A5 /* star_type_variable_errors */, - 82593759147E3B1C00B840A5 /* string_statistic */, - 8259375A147E3B1C00B840A5 /* string_statistic_errors */, - 8259375B147E3B1C00B840A5 /* string_variable */, - 8259375C147E3B1C00B840A5 /* string_variable_errors */, - 8259375E147E3B1C00B840A5 /* techs */, - 8259375F147E3B1C00B840A5 /* techs_errors */, - 82593760147E3B1C00B840A5 /* techs_permutations */, - 82593761147E3B1C00B840A5 /* test.cpp */, - 82593762147E3B1C00B840A5 /* test.h */, - ); - path = test; - sourceTree = ""; - }; - 8288CC3515F4AEF100F8318D /* Frameworks */ = { - isa = PBXGroup; - children = ( - 471032380CEF6A7600A7DF2B /* Ogg.framework */, - 34F0E427113F491C00A10EED /* Python.framework */, - 8289A72A19BDB3D8001256C1 /* SDL2.framework */, - 4710323F0CEF6A8B00A7DF2B /* vorbis.framework */, - ); - name = Frameworks; - path = dep/Frameworks; - sourceTree = ""; - }; - 8288CC3D15F4AF6000F8318D /* System Frameworks */ = { - isa = PBXGroup; - children = ( - 82E5238E192FE8CE001E2374 /* AppKit.framework */, - 82E52392192FE99C001E2374 /* Carbon.framework */, - 82E52393192FE99C001E2374 /* Cocoa.framework */, - 82E52394192FE99C001E2374 /* CoreData.framework */, - 82E5239E192FEAA6001E2374 /* CoreFoundation.framework */, - 82E52395192FE99C001E2374 /* Foundation.framework */, - 82E52390192FE90F001E2374 /* IOKit.framework */, - 82E52396192FE99C001E2374 /* OpenAL.framework */, - 82E52397192FE99D001E2374 /* OpenGL.framework */, - ); - name = "System Frameworks"; - sourceTree = ""; - }; - 82B9AE981AA71CCC00486B2A /* CombatReport */ = { - isa = PBXGroup; - children = ( - 82B9AE991AA71CCC00486B2A /* CombatLogWnd.cpp */, - 82B9AE9A1AA71CCC00486B2A /* CombatLogWnd.h */, - 82B9AE9B1AA71CCC00486B2A /* CombatReportData.cpp */, - 82B9AE9C1AA71CCC00486B2A /* CombatReportData.h */, - 82B9AE9D1AA71CCC00486B2A /* CombatReportWnd.cpp */, - 82B9AE9E1AA71CCC00486B2A /* CombatReportWnd.h */, - 82B9AE9F1AA71CCC00486B2A /* GraphicalSummary.cpp */, - 82B9AEA01AA71CCC00486B2A /* GraphicalSummary.h */, - ); - path = CombatReport; - sourceTree = ""; - }; - 82ED913F194F5C96002F0A4A /* Utils */ = { - isa = PBXGroup; - children = ( - 82ED9141194F5CDD002F0A4A /* make_versioncpp.py */, - 82ED913E194F5C85002F0A4A /* make_dmg.py */, - ); - name = Utils; - sourceTree = ""; - }; - CE594BA41CB2D1360085A4DC /* gilext */ = { - isa = PBXGroup; - children = ( - CE594BAD1CB2D1360085A4DC /* color_convert.hpp */, - CE594BBC1CB2D1360085A4DC /* io */, - CE594BCB1CB2D1360085A4DC /* gray_alpha.hpp */, - CE594BDC1CB2D1360085A4DC /* typedefs.hpp */, - ); - path = gilext; - sourceTree = ""; - }; - CE594BBC1CB2D1360085A4DC /* io */ = { - isa = PBXGroup; - children = ( - CE594BC21CB2D1360085A4DC /* png_dynamic_io.hpp */, - CE594BC31CB2D1360085A4DC /* png_io.hpp */, - CE594BC41CB2D1360085A4DC /* png_io_private.hpp */, - ); - path = io; - sourceTree = ""; - }; - CE6183971BB80D1300952BEA /* RichText */ = { - isa = PBXGroup; - children = ( - CE6183981BB80D1300952BEA /* BlockControl.h */, - CE6183991BB80D1300952BEA /* ImageBlock.h */, - CE61839A1BB80D1300952BEA /* RichText.h */, - ); - path = RichText; - sourceTree = ""; - }; - CE61839C1BB80D2E00952BEA /* RichText */ = { - isa = PBXGroup; - children = ( - CE61839D1BB80D2E00952BEA /* BlockControl.cpp */, - CE61839E1BB80D2E00952BEA /* ImageBlock.cpp */, - CE61839F1BB80D2E00952BEA /* RichText.cpp */, - CE6183A11BB80D2E00952BEA /* TagParser.cpp */, - CE6183A21BB80D2E00952BEA /* TagParser.h */, - CE6183A31BB80D2E00952BEA /* TextBlock.cpp */, - CE6183A41BB80D2E00952BEA /* TextBlock.h */, - ); - path = RichText; - sourceTree = ""; - }; - CEC4ADBE1BC13741000DFA9B /* AI */ = { - isa = PBXGroup; - children = ( - CEC4ADBF1BC13741000DFA9B /* AIFramework.cpp */, - CEC4ADC01BC13741000DFA9B /* AIFramework.h */, - CE0ED5F71BCFE16E00375001 /* AIWrapper.cpp */, - CE0ED5F81BCFE16E00375001 /* AIWrapper.h */, - ); - path = AI; - sourceTree = ""; - }; - CEDABCF11C688DE90085A4DC /* System Libraries */ = { - isa = PBXGroup; - children = ( - CEDABCF21C688E290085A4DC /* libiconv.dylib */, - ); - name = "System Libraries"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 3402E25C0F5A317400DF6FE7 /* FreeOrion */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3402E2610F5A319200DF6FE7 /* Build configuration list for PBXNativeTarget "FreeOrion" */; - buildPhases = ( - CE5C92C81B81FE8F002346C0 /* Sources */, - 3402E53C0F5A33E300DF6FE7 /* Copy Main Executable */, - 3402EB750F5A34FC00DF6FE7 /* Copy Server and AI client */, - 3402E2590F5A317400DF6FE7 /* Resources */, - 3402E3DA0F5A31FA00DF6FE7 /* Copy Frameworks */, - 3402EB620F5A345D00DF6FE7 /* Copy Shared Libraries */, - ); - buildRules = ( - ); - dependencies = ( - 829C763F1689F0EA005881B4 /* PBXTargetDependency */, - 3402E2650F5A319B00DF6FE7 /* PBXTargetDependency */, - 3402E2670F5A319B00DF6FE7 /* PBXTargetDependency */, - 3402E2630F5A319B00DF6FE7 /* PBXTargetDependency */, - ); - name = FreeOrion; - productName = FreeOrion; - productReference = 3402E25D0F5A317400DF6FE7 /* FreeOrion.app */; - productType = "com.apple.product-type.application"; - }; - 47103BE10CF04D8800A7DF2B /* Common */ = { - isa = PBXNativeTarget; - buildConfigurationList = 47103BE30CF04DC700A7DF2B /* Build configuration list for PBXNativeTarget "Common" */; - buildPhases = ( - 47103BDF0CF04D8800A7DF2B /* Sources */, - 3ABEAB391749BFDB00E34912 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 82A1B47119240B3100A3C137 /* PBXTargetDependency */, - ); - name = Common; - productName = Common; - productReference = 47103BE20CF04D8800A7DF2B /* freeorioncommon.dylib */; - productType = "com.apple.product-type.library.dynamic"; - }; - 471D5A2F0A988D5700DA9C21 /* GG */ = { - isa = PBXNativeTarget; - buildConfigurationList = 471D5A3A0A988D8600DA9C21 /* Build configuration list for PBXNativeTarget "GG" */; - buildPhases = ( - 471D5A2D0A988D5700DA9C21 /* Sources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = GG; - productName = GG; - productReference = 471D5A300A988D5700DA9C21 /* libGG.a */; - productType = "com.apple.product-type.library.static"; - }; - 471D5D430A98A46000DA9C21 /* FreeOrionServer */ = { - isa = PBXNativeTarget; - buildConfigurationList = 471D5D6B0A98A48500DA9C21 /* Build configuration list for PBXNativeTarget "FreeOrionServer" */; - buildPhases = ( - 471D5D410A98A46000DA9C21 /* Sources */, - 471D5D420A98A46000DA9C21 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 82C0747B149DE7EB00E76876 /* PBXTargetDependency */, - 478417670CF057BF00BE4710 /* PBXTargetDependency */, - ); - name = FreeOrionServer; - productName = FreeOrionServer; - productReference = 471D5D440A98A46000DA9C21 /* freeoriond */; - productType = "com.apple.product-type.tool"; - }; - 471FEF870A9A629800C36AA3 /* FreeOrionAI */ = { - isa = PBXNativeTarget; - buildConfigurationList = 471FEFD40A9A629800C36AA3 /* Build configuration list for PBXNativeTarget "FreeOrionAI" */; - buildPhases = ( - 471FEF8A0A9A629800C36AA3 /* Sources */, - 471FEFC10A9A629800C36AA3 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 8237404B19BDA4120046BFD1 /* PBXTargetDependency */, - 478418EA0CF05A2800BE4710 /* PBXTargetDependency */, - 478417690CF057C300BE4710 /* PBXTargetDependency */, - ); - name = FreeOrionAI; - productName = FreeOrionServer; - productReference = 471FEFD70A9A629800C36AA3 /* freeorionca */; - productType = "com.apple.product-type.tool"; - }; - 4784177C0CF0592E00BE4710 /* ClientCommon */ = { - isa = PBXNativeTarget; - buildConfigurationList = 478417AE0CF0592E00BE4710 /* Build configuration list for PBXNativeTarget "ClientCommon" */; - buildPhases = ( - 4784177D0CF0592E00BE4710 /* Sources */, - ); - buildRules = ( - ); - dependencies = ( - 82728DE81A49A0360034F4FE /* PBXTargetDependency */, - ); - name = ClientCommon; - productName = Common; - productReference = 478417B10CF0592E00BE4710 /* libClientCommon.a */; - productType = "com.apple.product-type.library.static"; - }; - 82C073D8149DE46200E76876 /* Parse */ = { - isa = PBXNativeTarget; - buildConfigurationList = 82C07434149DE46200E76876 /* Build configuration list for PBXNativeTarget "Parse" */; - buildPhases = ( - 82C073D9149DE46200E76876 /* Sources */, - 3ABEAB361749BF8A00E34912 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 3ABEAB1F1749BDF100E34912 /* PBXTargetDependency */, - ); - name = Parse; - productName = Common; - productReference = 82C07438149DE46200E76876 /* freeorionparse.dylib */; - productType = "com.apple.product-type.library.dynamic"; - }; - 8DD76F620486A84900D96B5E /* FreeOrionClient */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "FreeOrionClient" */; - buildPhases = ( - 8DD76F640486A84900D96B5E /* Sources */, - 8DD76F660486A84900D96B5E /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 82C07478149DE7AC00E76876 /* PBXTargetDependency */, - 478418EC0CF05A2E00BE4710 /* PBXTargetDependency */, - 478417650CF057B800BE4710 /* PBXTargetDependency */, - 471D65490A98B73C00DA9C21 /* PBXTargetDependency */, - ); - name = FreeOrionClient; - productInstallPath = "$(HOME)/bin"; - productName = FreeOrion; - productReference = 8DD76F6C0486A84900D96B5E /* FreeOrion */; - productType = "com.apple.product-type.tool"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 08FB7793FE84155DC02AAC07 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0510; - }; - buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "FreeOrion" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 1; - knownRegions = ( - English, - Japanese, - French, - German, - ); - mainGroup = 08FB7794FE84155DC02AAC07 /* FreeOrion */; - projectDirPath = ""; - projectRoots = ( - "", - .., - ); - targets = ( - 829C76351689EFD4005881B4 /* Configure */, - 471D5A2F0A988D5700DA9C21 /* GG */, - 82C073D8149DE46200E76876 /* Parse */, - 47103BE10CF04D8800A7DF2B /* Common */, - 4784177C0CF0592E00BE4710 /* ClientCommon */, - 471D5D430A98A46000DA9C21 /* FreeOrionServer */, - 8DD76F620486A84900D96B5E /* FreeOrionClient */, - 471FEF870A9A629800C36AA3 /* FreeOrionAI */, - 3402E25C0F5A317400DF6FE7 /* FreeOrion */, - 82EC621C18C6109D000237C2 /* MakeDMG */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 3402E2590F5A317400DF6FE7 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 825E7259196EC28A00F8114D /* default in Resources */, - 3402E26A0F5A31BB00DF6FE7 /* FreeOrion.icns in Resources */, - CE76B9CF1CBD7ACC0085A4DC /* ChangeLog.md in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 82728DEB1A49A58D0034F4FE /* Delete existing app bundle */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Delete existing app bundle"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"Deleting ${BUILT_PRODUCTS_DIR}/FreeOrion.app\"\nrm -rf ${BUILT_PRODUCTS_DIR}/FreeOrion.app\ntouch ${SRCROOT}/main.xib"; - }; - 829C76341689EFD4005881B4 /* Version.cpp */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/../util/Version.cpp.in", - ); - name = Version.cpp; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ \"${ACTION}\" == \"build\" ]; then\n \"python\" \"${SRCROOT}/../cmake/make_versioncpp.py\" \"${SRCROOT}/..\" \"Xcode ${XCODE_VERSION_ACTUAL}\"\nfi"; - }; - 82EC621B18C6109D000237C2 /* make_dmg */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = make_dmg; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ \"${ACTION}\" == \"build\" ]; then\n \"python\" \"${SRCROOT}/make_dmg.py\" \"${SRCROOT}/..\" $BUILT_PRODUCTS_DIR\nfi"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 47103BDF0CF04D8800A7DF2B /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A01E1E31749F93500EC9110 /* AppInterface.cpp in Sources */, - 34D3CE8C11D7DA11007C1E78 /* Species.cpp in Sources */, - 34C4495F1182716E0071E09A /* SerializeEmpire.cpp in Sources */, - 34C449601182716E0071E09A /* SerializeMultiplayerCommon.cpp in Sources */, - 34C449611182716E0071E09A /* SerializeOrderSet.cpp in Sources */, - 34C449631182716E0071E09A /* SerializeUniverse.cpp in Sources */, - 47103BF20CF04E5900A7DF2B /* MultiplayerCommon.cpp in Sources */, - 47103BF30CF04E5900A7DF2B /* OptionsDB.cpp in Sources */, - 47103BF40CF04E5900A7DF2B /* Order.cpp in Sources */, - 47103BF50CF04E5900A7DF2B /* OrderSet.cpp in Sources */, - 47103BF60CF04E5900A7DF2B /* Random.cpp in Sources */, - CE2BAD8F1E3E43E40085A4DC /* Pathfinder.cpp in Sources */, - 47103BF70CF04E5900A7DF2B /* SitRepEntry.cpp in Sources */, - 47103BF80CF04E5900A7DF2B /* VarText.cpp in Sources */, - 47103BF90CF04E5900A7DF2B /* XMLDoc.cpp in Sources */, - CEED195F1C79BA2F0085A4DC /* Supply.cpp in Sources */, - 47103BFA0CF04E5900A7DF2B /* Building.cpp in Sources */, - 82EC868B1B40504700D4584D /* Encyclopedia.cpp in Sources */, - CE1939C61F31AD4C0085A4DC /* IDAllocator.cpp in Sources */, - 47103BFB0CF04E5900A7DF2B /* Condition.cpp in Sources */, - 47103BFF0CF04E5900A7DF2B /* Effect.cpp in Sources */, - 47103C010CF04E5900A7DF2B /* Enums.cpp in Sources */, - 47103C020CF04E5900A7DF2B /* Fleet.cpp in Sources */, - 47103C030CF04E5900A7DF2B /* Meter.cpp in Sources */, - 47103C050CF04E5900A7DF2B /* Planet.cpp in Sources */, - 47103C060CF04E5900A7DF2B /* PopCenter.cpp in Sources */, - 47103C070CF04E5900A7DF2B /* Predicates.cpp in Sources */, - 47103C080CF04E5900A7DF2B /* ResourceCenter.cpp in Sources */, - 47103C090CF04E5900A7DF2B /* Ship.cpp in Sources */, - 47103C0A0CF04E5900A7DF2B /* ShipDesign.cpp in Sources */, - CED6E5521C6DDC0F0085A4DC /* Fighter.cpp in Sources */, - 47103C0B0CF04E5900A7DF2B /* Special.cpp in Sources */, - 47103C0C0CF04E5900A7DF2B /* System.cpp in Sources */, - 3ABEAB871749C72000E34912 /* Universe.cpp in Sources */, - 47103C0D0CF04E5900A7DF2B /* Tech.cpp in Sources */, - 47103C100CF04E5900A7DF2B /* UniverseObject.cpp in Sources */, - 47103C110CF04E5900A7DF2B /* ValueRef.cpp in Sources */, - 47103C150CF04E5900A7DF2B /* Message.cpp in Sources */, - 47103C160CF04E5900A7DF2B /* Empire.cpp in Sources */, - 47103C170CF04E5900A7DF2B /* EmpireManager.cpp in Sources */, - 47103C180CF04E5900A7DF2B /* ResourcePool.cpp in Sources */, - 47103C190CF04E5900A7DF2B /* StringTable.cpp in Sources */, - 47103C1B0CF04E5900A7DF2B /* Networking.cpp in Sources */, - 47103C3B0CF051B000A7DF2B /* Directories.cpp in Sources */, - 478417760CF058B100BE4710 /* Process.cpp in Sources */, - 82592EF0147E381B00B840A5 /* EffectAccounting.cpp in Sources */, - 82592EF6147E387100B840A5 /* ObjectMap.cpp in Sources */, - 82BEB659159853B300B024CB /* Diplomacy.cpp in Sources */, - CE43BB2B1EC456A20085A4DC /* CheckSums.cpp in Sources */, - 8250FDE515FCEFDE00523C1C /* Field.cpp in Sources */, - 825EA76016DB9DB10002A797 /* ModeratorAction.cpp in Sources */, - 82C96D0B16E4C1F900579E56 /* SerializeModeratorAction.cpp in Sources */, - 82E9DDF119530E5D007E681B /* CombatEvent.cpp in Sources */, - 822EB0FF170832240083EB38 /* CombatLogManager.cpp in Sources */, - 3A1BF3EB1748875300812237 /* Version.cpp in Sources */, - 3A5105541748D68B00DC258B /* i18n.cpp in Sources */, - 3A5105571748D6A700DC258B /* Logger.cpp in Sources */, - 3A5105571748D6A700DC258C /* LoggerWithOptionsDB.cpp in Sources */, - 8242C81C176B0C8E001E1CF2 /* ScopedTimer.cpp in Sources */, - 82E68F63190ECB8400BB1AD9 /* EnumText.cpp in Sources */, - 82E68F64190ECB8400BB1AD9 /* SaveGamePreviewUtils.cpp in Sources */, - 82E9DDF219530E5D007E681B /* CombatEvents.cpp in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 471D5A2D0A988D5700DA9C21 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 34C9B34511DA2EA0003695F4 /* ClrConstants.cpp in Sources */, - 4710240A0CEF3AAC00A7DF2B /* AlignmentFlags.cpp in Sources */, - 4710240B0CEF3AAC00A7DF2B /* Base.cpp in Sources */, - 4710240C0CEF3AAC00A7DF2B /* BrowseInfoWnd.cpp in Sources */, - 4710240D0CEF3AAC00A7DF2B /* Button.cpp in Sources */, - 4710240F0CEF3AAC00A7DF2B /* Control.cpp in Sources */, - 471024100CEF3AAC00A7DF2B /* Cursor.cpp in Sources */, - 471024110CEF3AAC00A7DF2B /* ColorDlg.cpp in Sources */, - 471024120CEF3AAC00A7DF2B /* FileDlg.cpp in Sources */, - 471024130CEF3AAC00A7DF2B /* ThreeButtonDlg.cpp in Sources */, - 471024140CEF3AAC00A7DF2B /* DrawUtil.cpp in Sources */, - 471024150CEF3AAC00A7DF2B /* DropDownList.cpp in Sources */, - 471024160CEF3AAC00A7DF2B /* DynamicGraphic.cpp in Sources */, - CE6183A71BB80D2E00952BEA /* ImageBlock.cpp in Sources */, - CE6183AA1BB80D2E00952BEA /* TextBlock.cpp in Sources */, - 471024170CEF3AAC00A7DF2B /* Edit.cpp in Sources */, - 471024180CEF3AAC00A7DF2B /* EventPump.cpp in Sources */, - 471024190CEF3AAC00A7DF2B /* Font.cpp in Sources */, - 4710241A0CEF3AAC00A7DF2B /* GUI.cpp in Sources */, - 4710241B0CEF3AAC00A7DF2B /* Layout.cpp in Sources */, - 4710241C0CEF3AAC00A7DF2B /* ListBox.cpp in Sources */, - 4710241D0CEF3AAC00A7DF2B /* Menu.cpp in Sources */, - 4710241E0CEF3AAC00A7DF2B /* MultiEdit.cpp in Sources */, - CE6183A81BB80D2E00952BEA /* RichText.cpp in Sources */, - 471024260CEF3AAC00A7DF2B /* PtRect.cpp in Sources */, - 471024270CEF3AAC00A7DF2B /* Scroll.cpp in Sources */, - 4710242A0CEF3AAC00A7DF2B /* StaticGraphic.cpp in Sources */, - 4710242B0CEF3AAC00A7DF2B /* StyleFactory.cpp in Sources */, - 4710242C0CEF3AAC00A7DF2B /* TabWnd.cpp in Sources */, - CE6183AB1BB80D2E00952BEA /* ScrollPanel.cpp in Sources */, - 8257A8D71A90BA610006777D /* GLClientAndServerBuffer.cpp in Sources */, - 4710242D0CEF3AAC00A7DF2B /* TextControl.cpp in Sources */, - 8289A72919BDB307001256C1 /* SDLGUI.cpp in Sources */, - CE6183A91BB80D2E00952BEA /* TagParser.cpp in Sources */, - 4710242E0CEF3AAC00A7DF2B /* Texture.cpp in Sources */, - 4710242F0CEF3AAC00A7DF2B /* Timer.cpp in Sources */, - CE3102B61DBFD0730085A4DC /* DeferredLayout.cpp in Sources */, - 471024300CEF3AAC00A7DF2B /* Wnd.cpp in Sources */, - 471024320CEF3AAC00A7DF2B /* WndEvent.cpp in Sources */, - CE6183A61BB80D2E00952BEA /* BlockControl.cpp in Sources */, - 471024330CEF3AAC00A7DF2B /* ZList.cpp in Sources */, - 343EC6340F3F513700782AD3 /* UnicodeCharsets.cpp in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 471D5D410A98A46000DA9C21 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 47FAB6770A98D89F00F0AF3F /* dmain.cpp in Sources */, - 47FAB6780A98D8A600F0AF3F /* ServerApp.cpp in Sources */, - 471032540CEF6C9700A7DF2B /* ServerNetworking.cpp in Sources */, - 82D7B5A01AA3666F00A85842 /* ServerWrapper.cpp in Sources */, - CEDC4AEF1BD3E361009BB38D /* EmpireWrapper.cpp in Sources */, - 82D7B5A31AA37C9000A85842 /* ServerFramework.cpp in Sources */, - 471032600CEF6D0D00A7DF2B /* ServerFSM.cpp in Sources */, - 2F60967E12EEB1AE00F58913 /* MessageQueue.cpp in Sources */, - 2F6096CE12EF046500F58913 /* ClientApp.cpp in Sources */, - CE71ED7A1DADAB120085A4DC /* DependencyVersions.cpp in Sources */, - 2F6096CF12EF046B00F58913 /* ClientNetworking.cpp in Sources */, - 82B4313414A74E07003EEE42 /* UniverseGenerator.cpp in Sources */, - 82655A401716D9240007EEDD /* CombatSystem.cpp in Sources */, - 828843341850A2550023C4D7 /* LoggingWrapper.cpp in Sources */, - 82884C4B1850B0E60023C4D7 /* EnumWrapper.cpp in Sources */, - CE6730B01BC29F23004179D1 /* CommonFramework.cpp in Sources */, - 82182CFA185B506200D70FF2 /* UniverseWrapper.cpp in Sources */, - 82E958D3188EC86A00479B77 /* SaveLoad.cpp in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 471FEF8A0A9A629800C36AA3 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CE71ED7C1DADAB140085A4DC /* DependencyVersions.cpp in Sources */, - 471FEFDE0A9A631C00C36AA3 /* AIClientApp.cpp in Sources */, - CE6730B11BC29F25004179D1 /* CommonFramework.cpp in Sources */, - CEC4ADC11BC13741000DFA9B /* AIFramework.cpp in Sources */, - 471FEFDF0A9A632000C36AA3 /* camain.cpp in Sources */, - 4710338A0CEF723900A7DF2B /* AIInterface.cpp in Sources */, - CE0ED5F91BCFE16E00375001 /* AIWrapper.cpp in Sources */, - 343EC67A0F3F61D000782AD3 /* EmpireWrapper.cpp in Sources */, - 343EC67B0F3F61D000782AD3 /* EnumWrapper.cpp in Sources */, - 343EC67C0F3F61D000782AD3 /* LoggingWrapper.cpp in Sources */, - 343EC67D0F3F61D000782AD3 /* UniverseWrapper.cpp in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4784177D0CF0592E00BE4710 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 478418E00CF059DC00BE4710 /* ClientApp.cpp in Sources */, - 478418DF0CF059D700BE4710 /* ClientNetworking.cpp in Sources */, - 478418F70CF05AAA00BE4710 /* MessageQueue.cpp in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 82C073D9149DE46200E76876 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7604B41749A16200363653 /* BuildingsParser.cpp in Sources */, - 3A7604AF1749A15500363653 /* ConditionParser.cpp in Sources */, - 3A7604B01749A15500363653 /* ConditionParser1.cpp in Sources */, - 3A7604B11749A15500363653 /* ConditionParser2.cpp in Sources */, - 3A7604B21749A15500363653 /* ConditionParser3.cpp in Sources */, - 3A7604B31749A15500363653 /* ConditionParser4.cpp in Sources */, - 82E944E91B04C726004F3343 /* EffectParser5.cpp in Sources */, - 3A7604AE1749A14600363653 /* DoubleValueRefParser.cpp in Sources */, - 3A7604A91749A13400363653 /* EffectParser.cpp in Sources */, - 3A7604AA1749A13400363653 /* EffectParser1.cpp in Sources */, - 3A7604AB1749A13400363653 /* EffectParser2.cpp in Sources */, - 3A7604AC1749A13400363653 /* EffectParser3.cpp in Sources */, - 3A7604AD1749A13400363653 /* EffectParser4.cpp in Sources */, - 3A7604A81749A12200363653 /* EncyclopediaParser.cpp in Sources */, - 3A7604A71749A11800363653 /* EnumParser.cpp in Sources */, - 3A7604A61749A10D00363653 /* FieldsParser.cpp in Sources */, - 3A7604A51749A10100363653 /* FleetPlansParser.cpp in Sources */, - 3A7604A41749A0F200363653 /* IntValueRefParser.cpp in Sources */, - 3A7604A31749A0E600363653 /* ItemsParser.cpp in Sources */, - 3A7604A21749A0D700363653 /* KeymapParser.cpp in Sources */, - 3A7604A11749A0CB00363653 /* Lexer.cpp in Sources */, - 3A7604A01749A0BF00363653 /* MonsterFleetPlansParser.cpp in Sources */, - 82C07470149DE72D00E76876 /* Parse.cpp in Sources */, - 3A76046217499F3000363653 /* PlanetEnvironmentValueRefParser.cpp in Sources */, - 3A76046317499F3D00363653 /* PlanetSizeValueRefParser.cpp in Sources */, - 3A76046417499F4C00363653 /* PlanetTypeValueRefParser.cpp in Sources */, - 3A76046517499F7D00363653 /* ReportParseError.cpp in Sources */, - 3A76046617499F8800363653 /* ShipDesignsParser.cpp in Sources */, - 8246FED0194D8F83008C07B5 /* IntComplexValueRefParser.cpp in Sources */, - 8295CEC71A88FFF8000EE624 /* StringComplexValueRefParser.cpp in Sources */, - 3A76046717499F9600363653 /* ShipHullsParser.cpp in Sources */, - 3A76046817499FA400363653 /* ShipPartsParser.cpp in Sources */, - 3A76046A17499FBD00363653 /* SpecialsParser.cpp in Sources */, - 3A76046B1749A01600363653 /* SpeciesParser.cpp in Sources */, - 3A76046E1749A03E00363653 /* StarTypeValueRefParser.cpp in Sources */, - 3A76049C1749A09400363653 /* StringValueRefParser.cpp in Sources */, - 3A76049E1749A0A300363653 /* TechsParser.cpp in Sources */, - CE3656A91F22244E0085A4DC /* GameRulesParser.cpp in Sources */, - 3A76049F1749A0AF00363653 /* UniverseObjectTypeValueRefParser.cpp in Sources */, - 82B3265319B603C1005082AC /* ConditionParser7.cpp in Sources */, - 82D563D71A6AEBD6007E1131 /* DoubleComplexValueRefParser.cpp in Sources */, - 829BBB6217D09017009D3735 /* ValueRefParserImpl.cpp in Sources */, - 82C560D317D0CC2F00FE3B35 /* Tokens.cpp in Sources */, - 829E16B11811181F00F7F52D /* EmpireStatsParser.cpp in Sources */, - 82D2D9A31838C8340011C212 /* ConditionParser5.cpp in Sources */, - 82D2D9A41838C8340011C212 /* ConditionParser6.cpp in Sources */, - CE13B48F1C8CB0CF0085A4DC /* CommonParamsParser.cpp in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 8DD76F640486A84900D96B5E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 8275F3FA1AD31381003568FF /* BuildingsPanel.cpp in Sources */, - 471D5E0E0A98A96700DA9C21 /* About.cpp in Sources */, - 8275F4061AD31381003568FF /* TextBrowseWnd.cpp in Sources */, - 471D5E0F0A98A96D00DA9C21 /* BuildDesignatorWnd.cpp in Sources */, - 34972BEA0F49CBE80015C864 /* ChatWnd.cpp in Sources */, - 34ACA4C70FFFDBC000500F40 /* chmain.cpp in Sources */, - 34ACA4C10FFFD2DB00500F40 /* chmain.mm in Sources */, - CE2C454D1C65036F0085A4DC /* ResourceBrowseWnd.cpp in Sources */, - 471031380CEF617C00A7DF2B /* ClientFSMEvents.cpp in Sources */, - 82B9AEA21AA71CCC00486B2A /* CombatReportData.cpp in Sources */, - 471D5E100A98A97300DA9C21 /* ClientUI.cpp in Sources */, - 8275F4051AD31381003568FF /* SystemResourceSummaryBrowseWnd.cpp in Sources */, - 471D5E150A98A97D00DA9C21 /* CUIControls.cpp in Sources */, - 471D5E160A98A98700DA9C21 /* CUIDrawUtil.cpp in Sources */, - 471D5E180A98A98B00DA9C21 /* CUIStyle.cpp in Sources */, - 82B9AEA31AA71CCC00486B2A /* CombatReportWnd.cpp in Sources */, - 471D5E1A0A98A99600DA9C21 /* CUIWnd.cpp in Sources */, - 343EC66F0F3F60F000782AD3 /* DesignWnd.cpp in Sources */, - 343EC6630F3F5C7C00782AD3 /* EncyclopediaDetailPanel.cpp in Sources */, - 828C793F160778AA0075F220 /* FieldIcon.cpp in Sources */, - 471D5E1B0A98A99D00DA9C21 /* FleetButton.cpp in Sources */, - 471D5E1C0A98A9A300DA9C21 /* FleetWnd.cpp in Sources */, - 471D5E1E0A98A9AE00DA9C21 /* GalaxySetupWnd.cpp in Sources */, - 8275F4041AD31381003568FF /* SpecialsPanel.cpp in Sources */, - 8275F3FD1AD31381003568FF /* MeterBrowseWnd.cpp in Sources */, - 82B9AEA41AA71CCC00486B2A /* GraphicalSummary.cpp in Sources */, - 82B9D447180C53FE0032F86D /* GraphControl.cpp in Sources */, - 82B9AEA11AA71CCC00486B2A /* CombatLogWnd.cpp in Sources */, - 2F60966B12EEAF0200F58913 /* GroupBox.cpp in Sources */, - 820BDECD1848A93E009BC457 /* Hotkeys.cpp in Sources */, - 8275F3FC1AD31381003568FF /* IconTextBrowseWnd.cpp in Sources */, - 470DF9A10A9CE53500A88AD6 /* HumanClientApp.cpp in Sources */, - 8275F3FF1AD31381003568FF /* MultiIconValueIndicator.cpp in Sources */, - CE71ED7B1DADAB130085A4DC /* DependencyVersions.cpp in Sources */, - 471031390CEF618C00A7DF2B /* HumanClientFSM.cpp in Sources */, - 8275F4021AD31381003568FF /* ResourcePanel.cpp in Sources */, - 8275F3FE1AD31381003568FF /* MilitaryPanel.cpp in Sources */, - 471D5E1F0A98A9B000DA9C21 /* InGameMenu.cpp in Sources */, - 471D5E200A98A9B200DA9C21 /* IntroScreen.cpp in Sources */, - 8275F4001AD31381003568FF /* MultiMeterStatusBar.cpp in Sources */, - 471D5E210A98A9C000DA9C21 /* LinkText.cpp in Sources */, - 471D5E220A98A9C500DA9C21 /* MapWnd.cpp in Sources */, - 827902381737C6EA008AF126 /* ModeratorActionsWnd.cpp in Sources */, - 8275F4011AD31381003568FF /* PopulationPanel.cpp in Sources */, - 471D5E230A98A9C700DA9C21 /* MultiplayerLobbyWnd.cpp in Sources */, - 82132C2F15DA2BBC00F5B537 /* ObjectListWnd.cpp in Sources */, - CE6183AE1BB80D4900952BEA /* CUILinkTextBlock.cpp in Sources */, - 471D5E240A98A9C900DA9C21 /* OptionsWnd.cpp in Sources */, - 2F60966512EEAD2200F58913 /* PlayerListWnd.cpp in Sources */, - 471D5E260A98A9CD00DA9C21 /* ProductionWnd.cpp in Sources */, - 343EC6650F3F5C7C00782AD3 /* QueueListBox.cpp in Sources */, - 82970BEF1A0CF53A008B37EF /* TechTreeArcs.cpp in Sources */, - 471D5E270A98A9CF00DA9C21 /* ResearchWnd.cpp in Sources */, - 8298A1AE18F9BB980001D3DA /* SaveFileDialog.cpp in Sources */, - 471D5E280A98A9D100DA9C21 /* ServerConnectWnd.cpp in Sources */, - 8275F3F91AD31381003568FF /* AccordionPanel.cpp in Sources */, - 346D87EF0FC5F6080095593E /* ShaderProgram.cpp in Sources */, - 82CE867F19BF27D0004DF6DF /* GUIController.mm in Sources */, - 471D5E290A98A9D700DA9C21 /* SidePanel.cpp in Sources */, - 471D5E2A0A98A9D900DA9C21 /* SitRepPanel.cpp in Sources */, - 34972BEE0F49CBF90015C864 /* Sound.cpp in Sources */, - 471D5E2D0A98AA1700DA9C21 /* SystemIcon.cpp in Sources */, - 2F22EC0612F7F4CF00456CDE /* TechTreeLayout.cpp in Sources */, - 471D5E2E0A98AA1A00DA9C21 /* TechTreeWnd.cpp in Sources */, - 8275F3FB1AD31381003568FF /* CensusBrowseWnd.cpp in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CE5C92C81B81FE8F002346C0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CE5C92C91B81FE9C002346C0 /* main.xib in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 3402E2630F5A319B00DF6FE7 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 471FEF870A9A629800C36AA3 /* FreeOrionAI */; - targetProxy = 3402E2620F5A319B00DF6FE7 /* PBXContainerItemProxy */; - }; - 3402E2650F5A319B00DF6FE7 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 471D5D430A98A46000DA9C21 /* FreeOrionServer */; - targetProxy = 3402E2640F5A319B00DF6FE7 /* PBXContainerItemProxy */; - }; - 3402E2670F5A319B00DF6FE7 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8DD76F620486A84900D96B5E /* FreeOrionClient */; - targetProxy = 3402E2660F5A319B00DF6FE7 /* PBXContainerItemProxy */; - }; - 3ABEAB1F1749BDF100E34912 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 47103BE10CF04D8800A7DF2B /* Common */; - targetProxy = 3ABEAB1E1749BDF100E34912 /* PBXContainerItemProxy */; - }; - 471D65490A98B73C00DA9C21 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 471D5A2F0A988D5700DA9C21 /* GG */; - targetProxy = 471D65480A98B73C00DA9C21 /* PBXContainerItemProxy */; - }; - 478417650CF057B800BE4710 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 47103BE10CF04D8800A7DF2B /* Common */; - targetProxy = 478417640CF057B800BE4710 /* PBXContainerItemProxy */; - }; - 478417670CF057BF00BE4710 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 47103BE10CF04D8800A7DF2B /* Common */; - targetProxy = 478417660CF057BF00BE4710 /* PBXContainerItemProxy */; - }; - 478417690CF057C300BE4710 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 47103BE10CF04D8800A7DF2B /* Common */; - targetProxy = 478417680CF057C300BE4710 /* PBXContainerItemProxy */; - }; - 478418EA0CF05A2800BE4710 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4784177C0CF0592E00BE4710 /* ClientCommon */; - targetProxy = 478418E90CF05A2800BE4710 /* PBXContainerItemProxy */; - }; - 478418EC0CF05A2E00BE4710 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4784177C0CF0592E00BE4710 /* ClientCommon */; - targetProxy = 478418EB0CF05A2E00BE4710 /* PBXContainerItemProxy */; - }; - 8237404B19BDA4120046BFD1 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 82C073D8149DE46200E76876 /* Parse */; - targetProxy = 8237404A19BDA4120046BFD1 /* PBXContainerItemProxy */; - }; - 82728DE81A49A0360034F4FE /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 829C76351689EFD4005881B4 /* Configure */; - targetProxy = 8257A8D51A90B7EF0006777D /* PBXContainerItemProxy */; - }; - 829C763F1689F0EA005881B4 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 829C76351689EFD4005881B4 /* Configure */; - targetProxy = 829C763E1689F0EA005881B4 /* PBXContainerItemProxy */; - }; - 82A1B47119240B3100A3C137 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 829C76351689EFD4005881B4 /* Configure */; - targetProxy = 82A1B47019240B3100A3C137 /* PBXContainerItemProxy */; - }; - 82C07478149DE7AC00E76876 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 82C073D8149DE46200E76876 /* Parse */; - targetProxy = 82C07477149DE7AC00E76876 /* PBXContainerItemProxy */; - }; - 82C0747B149DE7EB00E76876 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 82C073D8149DE46200E76876 /* Parse */; - targetProxy = 82C0747A149DE7EB00E76876 /* PBXContainerItemProxy */; - }; - 82EC622418C610AB000237C2 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 3402E25C0F5A317400DF6FE7 /* FreeOrion */; - targetProxy = 82EC622318C610AB000237C2 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 1DEB923208733DC60010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_HUMAN, - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - PRODUCT_NAME = FreeOrion; - ZERO_LINK = NO; - }; - name = Debug; - }; - 1DEB923608733DC60010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - FRAMEWORK_SEARCH_PATHS = "\"$(SRCROOT)/dep/Frameworks\""; - GCC_DEBUGGING_SYMBOLS = full; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_INLINES_ARE_PRIVATE_EXTERN = NO; - GCC_MODEL_TUNING = ""; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - FREEORION_MACOSX, - __MACOSX__, - BOOST_DATE_TIME_NO_LOCALE, - BOOST_SPIRIT_USE_PHOENIX_V3, - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - HEADER_SEARCH_PATHS = ( - "\"$(SOURCE_ROOT)/dep/include\"", - "\"$(SOURCE_ROOT)/dep/include/freetype\"", - "\"$(SOURCE_ROOT)/../GG\"", - "\"$(SOURCE_ROOT)/../network\"", - "\"$(SOURCE_ROOT)/dep/Frameworks/Python.framework/Headers\"", - "\"$(SOURCE_ROOT)/dep/Frameworks/SDL2.framework/Headers\"", - "\"$(SOURCE_ROOT)/..\"", - ); - LD_GENERATE_MAP_FILE = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; - LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/dep/lib\""; - MACOSX_DEPLOYMENT_TARGET = 10.9; - ONLY_ACTIVE_ARCH = YES; - OTHER_CPLUSPLUSFLAGS = ( - "$(OTHER_CFLAGS)", - "-std=c++11", - "-stdlib=libc++", - "-ftemplate-depth=1024", - "-fvisibility=hidden", - ); - OTHER_LDFLAGS = "-stdlib=libc++"; - SEPARATE_STRIP = NO; - STRIP_INSTALLED_PRODUCT = NO; - SYMROOT = $SRCROOT/build; - ZERO_LINK = NO; - }; - name = Debug; - }; - 3402E25F0F5A317400DF6FE7 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - INFOPLIST_FILE = Info.plist; - OTHER_LDFLAGS = ( - "-framework", - Carbon, - ); - PRODUCT_NAME = FreeOrion; - }; - name = Debug; - }; - 47103BE40CF04DC700A7DF2B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_COMMON, - ); - LD_DYLIB_INSTALL_NAME = "@executable_path/../SharedSupport/$(EXECUTABLE_PATH)"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-undefined", - dynamic_lookup, - ); - PRODUCT_NAME = freeorioncommon; - ZERO_LINK = NO; - }; - name = Debug; - }; - 471D5A3B0A988D8600DA9C21 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = GG; - ZERO_LINK = YES; - }; - name = Debug; - }; - 471D5D6C0A98A48500DA9C21 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_SERVER, - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - PRODUCT_NAME = freeoriond; - ZERO_LINK = NO; - }; - name = Debug; - }; - 471FEFD50A9A629800C36AA3 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_AI, - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - PRODUCT_NAME = freeorionca; - ZERO_LINK = NO; - }; - name = Debug; - }; - 478417AF0CF0592E00BE4710 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = ClientCommon; - ZERO_LINK = NO; - }; - name = Debug; - }; - 8218682814A9F82D009441B8 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - COPY_PHASE_STRIP = YES; - FRAMEWORK_SEARCH_PATHS = "\"$(SRCROOT)/dep/Frameworks\""; - GCC_DEBUGGING_SYMBOLS = full; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_INLINES_ARE_PRIVATE_EXTERN = NO; - GCC_MODEL_TUNING = ""; - GCC_OPTIMIZATION_LEVEL = s; - GCC_PREPROCESSOR_DEFINITIONS = ( - FREEORION_MACOSX, - __MACOSX__, - BOOST_DATE_TIME_NO_LOCALE, - BOOST_SPIRIT_USE_PHOENIX_V3, - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - HEADER_SEARCH_PATHS = ( - "\"$(SOURCE_ROOT)/dep/include\"", - "\"$(SOURCE_ROOT)/dep/include/freetype\"", - "\"$(SOURCE_ROOT)/../GG\"", - "\"$(SOURCE_ROOT)/../network\"", - "\"$(SOURCE_ROOT)/dep/Frameworks/Python.framework/Headers\"", - "\"$(SOURCE_ROOT)/dep/Frameworks/SDL2.framework/Headers\"", - "\"$(SOURCE_ROOT)/..\"", - ); - LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; - LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/dep/lib\""; - MACOSX_DEPLOYMENT_TARGET = 10.9; - ONLY_ACTIVE_ARCH = YES; - OTHER_CPLUSPLUSFLAGS = ( - "$(OTHER_CFLAGS)", - "-std=c++11", - "-stdlib=libc++", - "-ftemplate-depth=1024", - "-fvisibility=hidden", - ); - OTHER_LDFLAGS = "-stdlib=libc++"; - SEPARATE_STRIP = NO; - STRIP_INSTALLED_PRODUCT = YES; - SYMROOT = $SRCROOT/build; - ZERO_LINK = NO; - }; - name = Test; - }; - 8218682A14A9F82D009441B8 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = GG; - ZERO_LINK = YES; - }; - name = Test; - }; - 8218682D14A9F82D009441B8 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_PARSE, - ); - LD_DYLIB_INSTALL_NAME = "@executable_path/../SharedSupport/$(EXECUTABLE_PATH)"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-undefined", - dynamic_lookup, - ); - PRODUCT_NAME = freeorionparse; - ZERO_LINK = NO; - }; - name = Test; - }; - 8218682E14A9F82D009441B8 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_COMMON, - ); - LD_DYLIB_INSTALL_NAME = "@executable_path/../SharedSupport/$(EXECUTABLE_PATH)"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-undefined", - dynamic_lookup, - ); - PRODUCT_NAME = freeorioncommon; - ZERO_LINK = NO; - }; - name = Test; - }; - 8218682F14A9F82D009441B8 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = ClientCommon; - ZERO_LINK = NO; - }; - name = Test; - }; - 8218683014A9F82D009441B8 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_SERVER, - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - PRODUCT_NAME = freeoriond; - ZERO_LINK = NO; - }; - name = Test; - }; - 8218683114A9F82D009441B8 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_HUMAN, - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - PRODUCT_NAME = FreeOrion; - ZERO_LINK = NO; - }; - name = Test; - }; - 8218683214A9F82D009441B8 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_AI, - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - PRODUCT_NAME = freeorionca; - ZERO_LINK = NO; - }; - name = Test; - }; - 8218683314A9F82D009441B8 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - INFOPLIST_FILE = Info.plist; - OTHER_LDFLAGS = ( - "-framework", - Carbon, - ); - PRODUCT_NAME = FreeOrion; - }; - name = Test; - }; - 8221B22F160F2211007D86B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - COPY_PHASE_STRIP = YES; - FRAMEWORK_SEARCH_PATHS = "\"$(SRCROOT)/dep/Frameworks\""; - GCC_DEBUGGING_SYMBOLS = full; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_INLINES_ARE_PRIVATE_EXTERN = NO; - GCC_MODEL_TUNING = ""; - GCC_OPTIMIZATION_LEVEL = s; - GCC_PREPROCESSOR_DEFINITIONS = ( - FREEORION_MACOSX, - __MACOSX__, - BOOST_DATE_TIME_NO_LOCALE, - BOOST_SPIRIT_USE_PHOENIX_V3, - NDEBUG, - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - HEADER_SEARCH_PATHS = ( - "\"$(SOURCE_ROOT)/dep/include\"", - "\"$(SOURCE_ROOT)/dep/include/freetype\"", - "\"$(SOURCE_ROOT)/../GG\"", - "\"$(SOURCE_ROOT)/../network\"", - "\"$(SOURCE_ROOT)/dep/Frameworks/Python.framework/Headers\"", - "\"$(SOURCE_ROOT)/dep/Frameworks/SDL2.framework/Headers\"", - "\"$(SOURCE_ROOT)/..\"", - ); - LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; - LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/dep/lib\""; - MACOSX_DEPLOYMENT_TARGET = 10.9; - ONLY_ACTIVE_ARCH = YES; - OTHER_CPLUSPLUSFLAGS = ( - "$(OTHER_CFLAGS)", - "-std=c++11", - "-stdlib=libc++", - "-ftemplate-depth=1024", - "-fvisibility=hidden", - ); - OTHER_LDFLAGS = "-stdlib=libc++"; - SEPARATE_STRIP = NO; - STRIP_INSTALLED_PRODUCT = YES; - SYMROOT = $SRCROOT/build; - ZERO_LINK = NO; - }; - name = Release; - }; - 8221B231160F2211007D86B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = GG; - ZERO_LINK = YES; - }; - name = Release; - }; - 8221B242160F2211007D86B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_PARSE, - ); - LD_DYLIB_INSTALL_NAME = "@executable_path/../SharedSupport/$(EXECUTABLE_PATH)"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-undefined", - dynamic_lookup, - ); - PRODUCT_NAME = freeorionparse; - ZERO_LINK = NO; - }; - name = Release; - }; - 8221B251160F2211007D86B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_COMMON, - ); - LD_DYLIB_INSTALL_NAME = "@executable_path/../SharedSupport/$(EXECUTABLE_PATH)"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-undefined", - dynamic_lookup, - ); - PRODUCT_NAME = freeorioncommon; - ZERO_LINK = NO; - }; - name = Release; - }; - 8221B252160F2211007D86B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = ClientCommon; - ZERO_LINK = NO; - }; - name = Release; - }; - 8221B253160F2211007D86B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_SERVER, - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - PRODUCT_NAME = freeoriond; - ZERO_LINK = NO; - }; - name = Release; - }; - 8221B254160F2211007D86B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_HUMAN, - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - PRODUCT_NAME = FreeOrion; - ZERO_LINK = NO; - }; - name = Release; - }; - 8221B255160F2211007D86B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_AI, - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/dep/lib", - ); - PRODUCT_NAME = freeorionca; - ZERO_LINK = NO; - }; - name = Release; - }; - 8221B256160F2211007D86B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - INFOPLIST_FILE = Info.plist; - OTHER_LDFLAGS = ( - "-framework", - Carbon, - ); - PRODUCT_NAME = FreeOrion; - }; - name = Release; - }; - 829C76361689EFD4005881B4 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - PRODUCT_NAME = INIT; - }; - name = Debug; - }; - 829C76371689EFD4005881B4 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = INIT; - }; - name = Test; - }; - 829C76381689EFD4005881B4 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - PRODUCT_NAME = INIT; - }; - name = Release; - }; - 82C07435149DE46200E76876 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - FREEORION_BUILD_PARSE, - ); - LD_DYLIB_INSTALL_NAME = "@executable_path/../SharedSupport/$(EXECUTABLE_PATH)"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-undefined", - dynamic_lookup, - ); - PRODUCT_NAME = freeorionparse; - ZERO_LINK = NO; - }; - name = Debug; - }; - 82EC621D18C6109D000237C2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - PRODUCT_NAME = MakeDMG; - }; - name = Debug; - }; - 82EC621E18C6109D000237C2 /* Test */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = MakeDMG; - }; - name = Test; - }; - 82EC622018C6109D000237C2 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - PRODUCT_NAME = MakeDMG; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "FreeOrionClient" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB923208733DC60010E9CD /* Debug */, - 8218683114A9F82D009441B8 /* Test */, - 8221B254160F2211007D86B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; - 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "FreeOrion" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB923608733DC60010E9CD /* Debug */, - 8218682814A9F82D009441B8 /* Test */, - 8221B22F160F2211007D86B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; - 3402E2610F5A319200DF6FE7 /* Build configuration list for PBXNativeTarget "FreeOrion" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3402E25F0F5A317400DF6FE7 /* Debug */, - 8218683314A9F82D009441B8 /* Test */, - 8221B256160F2211007D86B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; - 47103BE30CF04DC700A7DF2B /* Build configuration list for PBXNativeTarget "Common" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 47103BE40CF04DC700A7DF2B /* Debug */, - 8218682E14A9F82D009441B8 /* Test */, - 8221B251160F2211007D86B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; - 471D5A3A0A988D8600DA9C21 /* Build configuration list for PBXNativeTarget "GG" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 471D5A3B0A988D8600DA9C21 /* Debug */, - 8218682A14A9F82D009441B8 /* Test */, - 8221B231160F2211007D86B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; - 471D5D6B0A98A48500DA9C21 /* Build configuration list for PBXNativeTarget "FreeOrionServer" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 471D5D6C0A98A48500DA9C21 /* Debug */, - 8218683014A9F82D009441B8 /* Test */, - 8221B253160F2211007D86B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; - 471FEFD40A9A629800C36AA3 /* Build configuration list for PBXNativeTarget "FreeOrionAI" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 471FEFD50A9A629800C36AA3 /* Debug */, - 8218683214A9F82D009441B8 /* Test */, - 8221B255160F2211007D86B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; - 478417AE0CF0592E00BE4710 /* Build configuration list for PBXNativeTarget "ClientCommon" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 478417AF0CF0592E00BE4710 /* Debug */, - 8218682F14A9F82D009441B8 /* Test */, - 8221B252160F2211007D86B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; - 829C763B1689EFF3005881B4 /* Build configuration list for PBXAggregateTarget "Configure" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 829C76361689EFD4005881B4 /* Debug */, - 829C76371689EFD4005881B4 /* Test */, - 829C76381689EFD4005881B4 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; - 82C07434149DE46200E76876 /* Build configuration list for PBXNativeTarget "Parse" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 82C07435149DE46200E76876 /* Debug */, - 8218682D14A9F82D009441B8 /* Test */, - 8221B242160F2211007D86B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; - 82EC622518C610C9000237C2 /* Build configuration list for PBXAggregateTarget "MakeDMG" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 82EC621D18C6109D000237C2 /* Debug */, - 82EC621E18C6109D000237C2 /* Test */, - 82EC622018C6109D000237C2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Test; - }; -/* End XCConfigurationList section */ - }; - rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; -} diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/ClientCommon.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/ClientCommon.xcscheme deleted file mode 100644 index d556ea2fd81..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/ClientCommon.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Common.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Common.xcscheme deleted file mode 100644 index 86282f8f4f6..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Common.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Configure.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Configure.xcscheme deleted file mode 100644 index e1a9a48e918..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Configure.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrion.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrion.xcscheme deleted file mode 100644 index 7ffce661a6b..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrion.xcscheme +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrionAI.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrionAI.xcscheme deleted file mode 100644 index 7edc40cee26..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrionAI.xcscheme +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrionClient.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrionClient.xcscheme deleted file mode 100644 index a509a06e262..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrionClient.xcscheme +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrionServer.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrionServer.xcscheme deleted file mode 100644 index 777063fbca2..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/FreeOrionServer.xcscheme +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/GG.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/GG.xcscheme deleted file mode 100644 index 162b0a0572a..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/GG.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Parse.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Parse.xcscheme deleted file mode 100644 index 73f2db79aaa..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Parse.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Release.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Release.xcscheme deleted file mode 100644 index f7687dc316a..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Release.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Test Release.xcscheme b/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Test Release.xcscheme deleted file mode 100644 index 6f153a94b8f..00000000000 --- a/Xcode/FreeOrion.xcodeproj/xcshareddata/xcschemes/Test Release.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcode/make_dmg.py b/Xcode/make_dmg.py deleted file mode 100755 index f8049b4a4fa..00000000000 --- a/Xcode/make_dmg.py +++ /dev/null @@ -1,29 +0,0 @@ -import sys -import os -from datetime import datetime -from subprocess import check_output, check_call - -if len(sys.argv) != 3: - print "ERROR: invalid parameters." - print "make_dmg.py " - quit() - -os.chdir(sys.argv[1]) -build_no = "XXXX" -try: - commit = check_output(["git", "show", "-s", "--format=%h", "--abbrev=7", "HEAD"]).strip() - timestamp = float(check_output(["git", "show", "-s", "--format=%ct", "HEAD"]).strip()) - build_no = ".".join([datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d"), commit]) -except: - print "WARNING: git not installed, can't determine build number" - -built_product_dir = sys.argv[2] -app = os.path.join(built_product_dir, "FreeOrion.app") -dmg_file = "FreeOrion_%s_MacOSX_10.9.dmg" % build_no -out_path = os.path.join(built_product_dir, dmg_file) -if os.path.exists(out_path): - os.remove(out_path) - -print "Creating %s in %s" % (dmg_file, built_product_dir) - -check_call('hdiutil create -volname FreeOrion -megabytes 1000 -srcfolder "%s" -ov -format UDZO "%s"' % (app, out_path), shell=True) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index e847ef14569..00000000000 --- a/appveyor.yml +++ /dev/null @@ -1,27 +0,0 @@ -os: Visual Studio 2015 - -version: ci-{build} - -init: - - git config --global core.eol native - - git config --global core.autocrlf true - -clone_depth: 50 - -install: - # Remove the VS Xamarin targets to reduce AppVeyor specific noise in build - # logs. See also http://help.appveyor.com/discussions/problems/4569 - - del "C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.targets\ImportAfter\Xamarin.Common.targets" - - cd .. - - ps: Start-FileDownload https://github.com/freeorion/freeorion-sdk/releases/download/v7/FreeOrionSDK_7_MSVC-v140-xp.zip -FileName FreeOrionSDK.zip - - unzip -q FreeOrionSDK.zip - - cp bin/* freeorion/ - - cd freeorion - -before_build: - - cd msvc2015 - -build_script: - - msbuild FreeOrion.sln /property:Configuration=Release-XP /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" /verbosity:minimal - -test: off diff --git a/check/st-tool.py b/check/st-tool.py new file mode 100755 index 00000000000..db0c9eab143 --- /dev/null +++ b/check/st-tool.py @@ -0,0 +1,641 @@ +#!/usr/bin/python3 +import argparse +import datetime +import errno +import os +import re +import subprocess +import sys +import textwrap +from collections import namedtuple, OrderedDict + +STRING_TABLE_KEY_PATTERN = r'^[A-Z0-9_]+$' +# Provides the named capture groups 'ref_type' and 'key' +INTERNAL_REFERENCE_PATTERN = r'\[\[(?P(?:(?:encyclopedia|(?:building|field|meter)type|predefinedshipdesign|ship(?:hull|part)|special|species|tech) )?)(?P{})\]\]' +# Provides the named capture groups 'sha', 'old_line', 'new_line' and 'range' +GIT_BLAME_INCREMENTAL_PATTERN = r'^(?P[0-9a-f]{40}) (?P[1-9][0-9]*) (?P[1-9][0-9]*)(?P [1-9][0-9]*)?$' + +class StringTableEntry(object): + def __init__(self, key, keyline, value, notes, value_times): + if not re.match(STRING_TABLE_KEY_PATTERN, key): + raise ValueError("Key doesn't match expected format [A-Z0-9_]+, was '{}'".format(key)) + self.key = key + self.keyline = keyline + self.value = value + self.value_times = value_times + self.notes = notes + + def __str__(self): + result = "" + for note in self.notes: + result += '# ' + note + "\n" + if self.value is not None: + result += self.key + "\n" + if not self.value or '\n' in self.value or re.search(r'^\s', self.value) or re.search(r'\s$', self.value): + result += "'''" + self.value + "'''" + "\n" + else: + result += self.value + "\n" + else: + result += "#*" + self.key + "\n" + for e in self.value_times: + result += "#* \n".format( + datetime.datetime.fromtimestamp(e).isoformat(sep=' ')) + + return result + + def __repr__(self): + return "StringTableEntry(key={}, keyline={}, value={})".format(self.key, self.keyline, self.value) + + +class StringTable(object): + def __init__(self, fpath, language, notes, includes, entries): + self.fpath = fpath + self.language = language + self.notes = notes + self.includes = includes + self._entries = entries + self._ientries = OrderedDict() + + for entry in self._entries: + if isinstance(entry, StringTableEntry): + if entry.key in self._ientries: + msg = "{fpath}:{fline}: Duplicate key '{key}', previously defined on line {pline}" + msg = msg.format(**{ + 'fpath': self.fpath, + 'fline': entry.keyline, + 'key': entry.key, + 'pline': self._ientries[entry.key].keyline, + }) + raise ValueError(msg) + self._ientries[entry.key] = entry + + def __getitem__(self, key): + return self._ientries[key] + + def __contains__(self, key): + return key in self._ientries + + def __iter__(self): + return self._ientries.iteritems() + + def __len__(self): + return len(self._ientries) + + def __str__(self): + result = "" + result += self.language + "\n" + + if self.notes: + result += "\n" + for note in self.notes: + if note: + result += "# " + note + "\n" + else: + result += "#\n" + + for entry in self._entries: + if isinstance(entry, StringTableEntry): + result += "\n" + result += str(entry) + elif isinstance(entry, list): + result += "\n" + result += "\n" + result += "##\n" + for line in entry: + if line: + result += "## " + line + "\n" + else: + result += "##\n" + result += "##\n" + + if self.includes: + result += "\n" + + for include in self.includes: + result += "#include \"" + include + "\"\n" + + return result + + def __repr__(self): + return "StringTable(fpath={}, language={}, entries={})".format( + self.fpath, self.language, self.entries) + + def items(self): + return self._entries + + def keys(self): + return self._ientries.keys() + + @staticmethod + def from_file(fhandle): + fpath = fhandle.name + + is_quoted = False + + language = None + fnotes = [] + section = [] + entries = [] + includes = [] + blames = OrderedDict(); + + key = None + keyline = None + value = None + vline_start = 0 + vline_end = 0 + notes = [] + untranslated_key = None + untranslated_keyline = None + untranslated_lines = [] + + for fline, line in enumerate(fhandle, 1): + if not is_quoted and not line.strip(): + # discard empty lines when not in quoted value + if section: + while not section[0]: + section.pop(0) + while not section[-1]: + section.pop(-1) + entries.append(section) + section = [] + if language and notes and not entries: + fnotes = notes + notes = [] + if untranslated_key: + try: + entries.append(StringTableEntry(untranslated_key, untranslated_keyline, None, notes, untranslated_lines)) + except ValueError as e: + raise ValueError("{}:{}: {}".format(fpath, keyline, str(e))) + untranslated_key = None + untranslated_keyline = None + notes = [] + untranslated_lines = [] + vline_start = vline_end = fline + continue + + if not is_quoted and line.startswith('#'): + # discard comments when not in quoted value + if line.startswith('#include'): + includes.append(line[8:].strip()[1:-1].strip()) + if line.startswith('##'): + section.append(line[3:].rstrip()) + elif line.startswith('#*') and not untranslated_key: + untranslated_key = line[2:].strip() + untranslated_keyline = fline + elif line.startswith('#*'): + untranslated_lines.append(0) + elif line.startswith('#'): + notes.append(line[2:].rstrip()) + vline_start = vline_end = fline + continue + + if not language: + # read first non comment and non empty line as language name + language = line.strip() + vline_start = vline_end = fline + continue + + if not key: + if section: + while not section[0]: + section.pop(0) + while not section[-1]: + section.pop(-1) + entries.append(section) + section = [] + if language and notes and not entries: + fnotes = notes + notes = [] + if untranslated_key: + try: + entries.append(StringTableEntry(untranslated_key, untranslated_keyline, None, notes, untranslated_lines)) + except ValueError as e: + raise ValueError("{}:{}: {}".format(fpath, keyline, str(e))) + untranslated_key = None + untranslated_keyline = None + notes = [] + untranslated_lines = [] + # read non comment and non empty line after language and after + # completing an stringtable language + key = line.strip().split()[0] + keyline = fline + vline_start = vline_end = fline + continue + + if not is_quoted: + if line.startswith("'''"): + # prepare begin of quoted value + line = line[3:] + is_quoted = True + else: + # prepare unquoted value + line = line.strip() + vline_end = fline + vline_start = fline + + if is_quoted and line.rstrip().endswith("'''"): + # prepare end of quoted value + line = line.rstrip()[:-3] + is_quoted = False + vline_end = fline + + # extract value or concatenate quoted value + value = (value if value else '') + line + + if not is_quoted and key is not None and value is not None: + # consume collected string table entry + try: + value_range = ({line: key for line in range(vline_start, vline_end + 1)}) + entries.append(StringTableEntry(key, keyline, value, notes, [0] * len(value_range))) + blames.update(value_range) + except ValueError as e: + raise ValueError("{}:{}: {}".format(fpath, keyline, str(e))) + + key = None + keyline = None + value = None + notes = [] + vline_start = vline_end = fline + continue + + if is_quoted: + # continue consuming quoted value lines + vline_end = fline + continue + + raise ValueError("{}:{}: Impossible parser state; last valid line: {}".format(fpath, fline_start, line)) + + if key: + raise ValueError("{}:{}: Key '{}' without value".format(fpath, vline_start, key)) + + if is_quoted: + raise ValueError("{}:{}: Quotes not closed for key '{}'".format(fpath, vline_start, key)) + + blame_cmd = ['git', 'blame', '--incremental', fpath] + git_blame = subprocess.check_output(blame_cmd) + git_blame = git_blame.decode('utf-8', 'strict') + git_blame = git_blame.splitlines(True) + + value_times = {} + author_time = None + line = None + line_range = None + + for line_blame in git_blame: + match = re.match(GIT_BLAME_INCREMENTAL_PATTERN, line_blame) + if match: + line = int(match['new_line']) + line_range = int(match['range']) + if line_blame.startswith('author-time '): + author_time = int(line_blame.strip().split(' ')[1]) + if line_blame.startswith('filename '): + for line in range(line, line + line_range): + if line not in blames: + continue + line_times = value_times.get(blames[line], []) + line_times.append(author_time) + value_times[blames[line]] = line_times + + for entry in entries: + if isinstance(entry, StringTableEntry): + if not entry.value: + # ignore untranslated entries + continue + if entry.key not in value_times or len(value_times[entry.key]) != len(entry.value_times): + raise RuntimeError("{}: git blame did not collect any matching author times for key {}".format(fpath, entry.key)) + entry.value_times = value_times[entry.key] + + return StringTable(fpath, language, fnotes, includes, entries) + + @staticmethod + def statistic(left, right): + STStatistic = namedtuple('STStatistic', [ + 'left', + 'left_only', + 'left_older', + 'left_pure_reference', + 'right', + 'right_only', + 'right_older', + 'right_pure_reference', + 'untranslated', + 'identical', + 'layout_mismatch']) + + all_keys = set(left.keys()).union(right.keys()) + + untranslated = set() + identical = set() + layout_mismatch = set() + left_only = set() + right_only = set() + left_older = set() + right_older = set() + left_pure_reference = set() + right_pure_reference = set() + + for key in all_keys: + if key in left and key not in right: + left_only.add(key) + if key not in left and key in right: + right_only.add(key) + if key in left and key in right: + if not left[key].value or not right[key].value: + untranslated.add(key) + continue + if left[key].value == right[key].value: + identical.add(key) + if (left[key].value.startswith('[[') and 2 == left[key].value.count('[') and + left[key].value.endswith(']]') and 2 == left[key].value.count(']')): + left_pure_reference.add(key) + if (right[key].value.startswith('[[') and 2 == right[key].value.count('[') and + right[key].value.endswith(']]') and 2 == right[key].value.count(']')): + right_pure_reference.add(key) + if len(left[key].value_times) != len(right[key].value_times): + layout_mismatch.add(key) + continue + value_times = list(zip(left[key].value_times, right[key].value_times)) + if any([e[0] > e[1] for e in value_times]): + right_older.add(key) + if any([e[0] < e[1] for e in value_times]): + left_older.add(key) + + return STStatistic( + left=left, left_only=left_only, left_older=left_older, + right=right, right_only=right_only, right_older=right_older, + left_pure_reference=left_pure_reference, + right_pure_reference=right_pure_reference, + untranslated=untranslated, identical=identical, + layout_mismatch=layout_mismatch) + + @staticmethod + def sync(reference, source): + entries = [] + + for item in reference.items(): + if isinstance(item, StringTableEntry): + if item.key in source: + source_item = source[item.key] + entries.append(StringTableEntry( + item.key, + item.keyline, + source_item.value if source_item.value != item.value else None, + item.notes, + source_item.value_times + )) + else: + entries.append(StringTableEntry( + item.key, + item.keyline, + None, + item.notes, + [0] * len(item.value_times) + )) + else: + entries.append(item) + + return StringTable(source.fpath, source.language, source.notes, source.includes, entries) + + +def format_action(args): + source_st = StringTable.from_file(args.source) + + print(source_st, end='') + + return 0 + + +def sync_action(args): + reference_st = StringTable.from_file(args.reference) + source_st = StringTable.from_file(args.source) + + entries = [] + + for item in reference_st.items(): + if isinstance(item, StringTableEntry): + if item.key in source_st: + source_item = source_st[item.key] + entries.append(StringTableEntry( + item.key, + item.keyline, + source_item.value, + item.notes, + source_item.value_times + )) + else: + entries.append(StringTableEntry( + item.key, + item.keyline, + None, + item.notes, + item.value_times + )) + else: + entries.append(item) + + out_st = StringTable(source_st.fpath, source_st.language, source_st.notes, source_st.includes, entries) + + print(out_st, end='') + + return 0 + + +def rename_key_action(args): + if not re.match(STRING_TABLE_KEY_PATTERN, args.old_key): + raise ValueError("The given old key '{}' is not a valid name".format(args.old_key)) + + if not re.match(STRING_TABLE_KEY_PATTERN, args.new_key): + raise ValueError("The given new key '{}' is not a valid name".format(args.new_key)) + + source_st = StringTable.from_file(args.source) + + for key in source_st.keys(): + entry = source_st[key] + entry.value = re.sub( + INTERNAL_REFERENCE_PATTERN.format(re.escape(args.old_key)), + r'[[\1' + args.new_key + ']]', + entry.value) + if entry.key == args.old_key: + entry.key = args.new_key + + print(source_st, end='') + + return 0 + + +def check_action(args): + exit_code = 0 + reference_st = None + if args.reference: + reference_st = StringTable.from_file(args.reference) + for source in args.sources: + source_st = StringTable.from_file(source) + + for key in source_st.keys(): + entry = source_st[key] + + if entry.value: + for match in re.finditer(INTERNAL_REFERENCE_PATTERN.format('.*?'), entry.value): + match = match['key'] + if not (match in source_st.keys() or (reference_st and match in reference_st.keys())): + print("{}:{}: Referenced key '{}' in value of '{}' was not found.".format(source_st.fpath, entry.keyline, match, entry.key)) + exit_code = 1 + + return exit_code + + +def compare_action(args): + exit_code = 0 + reference_st = StringTable.from_file(args.reference) + source_st = StringTable.from_file(args.source) + + comp = StringTable.statistic(reference_st, source_st) + + if not args.summary_only: + for key in source_st.keys(): + if key in comp.right_only: + print("{}:{}: Key '{}' is not in reference file {}".format(comp.right.fpath, comp.right[key].keyline, key, comp.left.fpath)) + exit_code = 1 + + if key in comp.left_pure_reference: + print("{}:{}: Key '{}' contains translation for pure reference in reference file value {}:{}".format(comp.right.fpath, comp.right[key].keyline, key, comp.left.fpath, comp.left[key].keyline)) + exit_code = 1 + + if key in comp.layout_mismatch: + print("{}:{}: Value of key '{}' layout does not match that of reference file value {}:{}".format(comp.right.fpath, comp.right[key].keyline, key, comp.left.fpath, comp.left[key].keyline)) + exit_code = 1 + + if key in comp.right_older: + print("{}:{}: Value of key '{}' is older than reference file value {}:{}".format(comp.right.fpath, comp.right[key].keyline, key, comp.left.fpath, comp.left[key].keyline)) + exit_code = 1 + + if key in comp.untranslated: + print("{}:{}: Value of key '{}' has no translation compared to reference file {}:{}".format(comp.right.fpath, comp.right[key].keyline, key, comp.left.fpath, comp.left[key].keyline)) + exit_code = 1 + + if key in comp.identical: + print("{}:{}: Value of key '{}' is identical to reference file {}:{}".format(comp.right.fpath, comp.right[key].keyline, key, comp.left.fpath, comp.left[key].keyline)) + exit_code = 1 + + print(""" +Summary comparing '{}' against '{}': + Keys matching - {}/{} ({:3.1f}%) + Keys not in reference - {} + Value is reference - {} + Values layout mismatch - {} + Values older than reference - {} + Values untranslated - {} + Values same as reference - {} +""".strip().format( + comp.right.fpath, + comp.left.fpath, + len(comp.right) - len(comp.right_only), + len(comp.left), + 100.0 * (len(comp.right) - len(comp.right_only)) / len(comp.left), + len(comp.right_only), + len(comp.left_pure_reference), + len(comp.layout_mismatch), + len(comp.right_older), + len(comp.untranslated), + len(comp.identical))) + + return exit_code + + +if __name__ == "__main__": + root_parser = argparse.ArgumentParser(description="Verify and modify string tables") + verb_parsers = root_parser.add_subparsers( + title="verbs", description="For more details run `{} --help`.".format(root_parser.prog), + ) + + format_parser = verb_parsers.add_parser('format', + help="format a string table and exit", + description=textwrap.dedent("""\ + Pretty prints a given string table onto the standard output. + + Formatting a string tables makes sure that: + * Translation entries (translated or not) are separated by one empty + line. + * Translation notes (prefix: '# ') and untranslated entries + (prefix: '#*') are properly prefixed and formatted + * Translation notes, key and value don't have empty lines between + each other. + * String tables contain the language name as first line, followed by + the file notes. + * Section titles are prefixed with a block comment (prefix '##'). + * Section titles have two leading whitespaces and one trailing + whitespace. + """), + formatter_class=argparse.RawTextHelpFormatter) + format_parser.set_defaults(action=format_action) + format_parser.add_argument('source', metavar='SOURCE', help="string table to format", + type=argparse.FileType(encoding='utf-8', errors='strict')) + + sync_parser = verb_parsers.add_parser('sync', + help="synchronize two string tables and exit", + description=textwrap.dedent("""\ + Synchronizes two string tables by copying over translation entry key, + notes, section titles and includes from the reference into the source, + while preserving existing translation values, language name and file notes + from source. + + Source translation entries without matching reference counterpart are + discarded. + + The resulting string table is printed out to standard output. + """), + formatter_class=argparse.RawTextHelpFormatter) + sync_parser.set_defaults(action=sync_action) + sync_parser.add_argument('reference', metavar='REFERENCE', help="reference string table", + type=argparse.FileType(encoding='utf-8', errors='strict')) + sync_parser.add_argument('source', metavar='SOURCE', help="string table to sync", + type=argparse.FileType(encoding='utf-8', errors='strict')) + + rename_key_parser = verb_parsers.add_parser('rename-key', + help="rename all occurences of a key within a stringtable and exit", + description=textwrap.dedent("""\ + Replace all occurances of a translation entry key within the given key, + including internal references. + + The resulting string table is printed out to standard output. + """), + formatter_class=argparse.RawTextHelpFormatter) + rename_key_parser.set_defaults(action=rename_key_action) + rename_key_parser.add_argument('source', metavar='SOURCE', help="string table to rename old key within", + type=argparse.FileType(encoding='utf-8', errors='strict')) + rename_key_parser.add_argument('old_key', metavar='OLD_KEY', help="key to rename") + rename_key_parser.add_argument('new_key', metavar='NEW_KEY', help="new key name") + + check_parser = verb_parsers.add_parser('check', + help="check a stringtable for consistency and exit", + description=textwrap.dedent("""\ + Check if all references within translation entries are provided by the source or + or the reference string table. + """), + formatter_class=argparse.RawTextHelpFormatter) + check_parser.set_defaults(action=check_action) + check_parser.add_argument('-r', '--reference', metavar='REFERENCE', help="reference string table", + type=argparse.FileType(encoding='utf-8', errors='strict')) + check_parser.add_argument('sources', metavar='SOURCES', help="string tables to check", + nargs='+', + type=argparse.FileType(encoding='utf-8', errors='strict')) + + compare_parser = verb_parsers.add_parser('compare', + help="compare two string tables and exit", + description=textwrap.dedent("""\ + Compare two string tables and point out differences between them. + """), + formatter_class=argparse.RawTextHelpFormatter) + compare_parser.set_defaults(action=compare_action) + compare_parser.add_argument('-s', '--summary-only', help="print only a summary of differences", action='store_true', dest='summary_only') + compare_parser.add_argument('reference', metavar='REFERENCE', help="reference string table", + type=argparse.FileType(encoding='utf-8', errors='strict')) + compare_parser.add_argument('source', metavar='SOURCE', help="string table to compare", + type=argparse.FileType(encoding='utf-8', errors='strict')) + + args = root_parser.parse_args() + if 'action' not in args: + root_parser.print_usage() + root_parser.exit() + sys.exit(args.action(args)) diff --git a/client/AI/AIClientApp.cpp b/client/AI/AIClientApp.cpp index ff927d8f511..0cbd45a1b05 100644 --- a/client/AI/AIClientApp.cpp +++ b/client/AI/AIClientApp.cpp @@ -1,16 +1,15 @@ #include "AIClientApp.h" -#include "../../python/AI/AIFramework.h" +#include "AIFramework.h" +#include "../ClientNetworking.h" #include "../../util/Logger.h" #include "../../util/LoggerWithOptionsDB.h" -#include "../../util/MultiplayerCommon.h" #include "../../util/OptionsDB.h" #include "../../util/Directories.h" #include "../../util/Serialize.h" #include "../../util/i18n.h" #include "../util/AppInterface.h" #include "../../network/Message.h" -#include "../../network/ClientNetworking.h" #include "../util/Random.h" #include "../util/Version.h" @@ -26,7 +25,7 @@ #include #include #include - +#include #include #include @@ -39,23 +38,24 @@ namespace { /** AddTraitBypassOption creates a set of options for debugging of the form: - AI.config.trait.\.force -- If true use the following options to bypass the trait - AI.config.trait.\.all -- If present use this value for all of the AIs not individually set - AI.config.trait.\.AI_1 -- Use for AI_1 + ai.trait.\.force.enabled -- If true use the following options to bypass the trait + ai.trait.\.default -- If present use this value for all of the AIs not individually set + ai.trait.\.ai_1 -- Use for AI_1 ... - AI.config.trait.\.AI_40 -- Use for AI_40 + ai.trait.\.ai_40 -- Use for AI_40 */ template void AddTraitBypassOption(OptionsDB& db, std::string const & root, std::string ROOT, - T def, ValidatorBase const & validator) { - std::string option_root = "AI.config.trait." + root +"."; + T def, ValidatorBase const & validator) + { + std::string option_root = "ai.trait." + root + "."; std::string user_string_root = "OPTIONS_DB_AI_CONFIG_TRAIT_"+ROOT; - db.Add(option_root + "force", UserStringNop(user_string_root + "_FORCE"), false, Validator()); - db.Add(option_root + "all", UserStringNop(user_string_root + "_FORCE_VALUE"), def, validator); + db.Add(option_root + "force.enabled", UserStringNop(user_string_root + "_FORCE"), false); + db.Add(option_root + "default", UserStringNop(user_string_root + "_FORCE_VALUE"), def, validator); for (int ii = 1; ii <= IApp::MAX_AI_PLAYERS(); ++ii) { std::stringstream ss; - ss << option_root << "AI_" << std::to_string(ii); + ss << option_root << "ai_" << std::to_string(ii); db.Add(ss.str(), UserStringNop(user_string_root + "_FORCE_VALUE"), def, validator); } } @@ -81,23 +81,27 @@ AIClientApp::AIClientApp(const std::vector& args) : { if (args.size() < 2) { std::cerr << "The AI client should not be executed directly! Run freeorion to start the game."; - Exit(1); + ExitApp(1); } // read command line args m_player_name = args.at(1); - const std::string AICLIENT_LOG_FILENAME((GetUserDataDir() / (m_player_name + ".log")).string()); if (args.size() >=3) { m_max_aggression = boost::lexical_cast(args.at(2)); } + // Force the log file if requested. + if (GetOptionsDB().Get("log-file").empty()) { + const std::string AICLIENT_LOG_FILENAME((GetUserDataDir() / (m_player_name + ".log")).string()); + GetOptionsDB().Set("log-file", AICLIENT_LOG_FILENAME); + } // Force the log threshold if requested. auto force_log_level = GetOptionsDB().Get("log-level"); if (!force_log_level.empty()) OverrideAllLoggersThresholds(to_LogLevel(force_log_level)); - InitLoggingSystem(AICLIENT_LOG_FILENAME, "AI"); + InitLoggingSystem(GetOptionsDB().Get("log-file"), "AI"); InitLoggingOptionsDBSystem(); InfoLogger() << FreeOrionVersionString(); @@ -110,10 +114,7 @@ AIClientApp::~AIClientApp() { DebugLogger() << "AIClientApp exited cleanly for ai client " << PlayerName(); } -void AIClientApp::operator()() -{ Run(); } - -void AIClientApp::Exit(int code) { +void AIClientApp::ExitApp(int code) { DebugLogger() << "Initiating Exit (code " << code << " - " << (code ? "error" : "normal") << " termination)"; if (code) exit(code); @@ -121,12 +122,12 @@ void AIClientApp::Exit(int code) { } int AIClientApp::EffectsProcessingThreads() const -{ return GetOptionsDB().Get("effects-threads-ai"); } +{ return GetOptionsDB().Get("effects.ai.threads"); } AIClientApp* AIClientApp::GetApp() { return static_cast(s_app); } -const AIBase* AIClientApp::GetAI() +const PythonAI* AIClientApp::GetAI() { return m_AI.get(); } void AIClientApp::Run() { @@ -136,7 +137,12 @@ void AIClientApp::Run() { StartPythonAI(); // join game - Networking().SendMessage(JoinGameMessage(PlayerName(), Networking::CLIENT_TYPE_AI_PLAYER)); + Networking().SendMessage(JoinGameMessage(PlayerName(), + Networking::CLIENT_TYPE_AI_PLAYER, + boost::uuids::nil_uuid())); + + // Start parsing content + StartBackgroundParsing(); // respond to messages until disconnected while (1) { @@ -149,7 +155,7 @@ void AIClientApp::Run() { else std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } catch (boost::python::error_already_set) { + } catch (const boost::python::error_already_set&) { /* If the python interpreter is still running then keep going, otherwise exit.*/ m_AI->HandleErrorAlreadySet(); @@ -159,7 +165,8 @@ void AIClientApp::Run() { } } catch (const NormalExitException&) { // intentionally empty. - } catch (boost::python::error_already_set) { + } catch (const boost::python::error_already_set&) { + m_AI->HandleErrorAlreadySet(); HandlePythonAICrash(); } @@ -168,7 +175,7 @@ void AIClientApp::Run() { void AIClientApp::ConnectToServer() { if (!Networking().ConnectToLocalHostServer()) - Exit(1); + ExitApp(1); } void AIClientApp::StartPythonAI() { @@ -218,12 +225,13 @@ void AIClientApp::HandleMessage(const Message& msg) { case Message::JOIN_GAME: { if (PlayerID() == Networking::INVALID_PLAYER_ID) { DebugLogger() << "AIClientApp::HandleMessage : Received JOIN_GAME acknowledgement"; - const std::string& text = msg.Text(); try { - int player_id = boost::lexical_cast(text); + int player_id; + boost::uuids::uuid cookie; // ignore + ExtractJoinAckMessageData(msg, player_id, cookie); m_networking->SetPlayerID(player_id); } catch(const boost::bad_lexical_cast& ex) { - ErrorLogger() << "AIClientApp::HandleMessage for JOIN_GAME : Couldn't parse message text \"" << text << "\": " << ex.what(); + ErrorLogger() << "AIClientApp::HandleMessage for JOIN_GAME : Couldn't parse message text \"" << msg.Text() << "\": " << ex.what(); } } else { ErrorLogger() << "AIClientApp::HandleMessage : Received erroneous JOIN_GAME acknowledgement when already in a game"; @@ -239,7 +247,6 @@ void AIClientApp::HandleMessage(const Message& msg) { SaveGameUIData ui_data; // ignored bool state_string_available; // ignored, as save_state_string is sent even if not set by ExtractMessageData std::string save_state_string; - m_player_status.clear(); ExtractGameStartMessageData(msg, single_player_game, m_empire_id, m_current_turn, m_empires, m_universe, @@ -315,17 +322,15 @@ void AIClientApp::HandleMessage(const Message& msg) { break; } - case Message::SAVE_GAME_DATA_REQUEST: { - //DebugLogger() << "AIClientApp::HandleMessage Message::SAVE_GAME_DATA_REQUEST"; - Networking().SendMessage(ClientSaveDataMessage(Orders(), m_AI->GetSaveStateString())); - //DebugLogger() << "AIClientApp::HandleMessage sent save data message"; + case Message::PLAYER_INFO: + ExtractPlayerInfoMessageData(msg, m_player_info); break; - } case Message::SAVE_GAME_COMPLETE: break; case Message::TURN_UPDATE: { + m_orders.Reset(); //DebugLogger() << "AIClientApp::HandleMessage : extracting turn update message data"; ExtractTurnUpdateMessageData(msg, m_empire_id, m_current_turn, m_empires, m_universe, GetSpeciesManager(), @@ -341,7 +346,13 @@ void AIClientApp::HandleMessage(const Message& msg) { ExtractTurnPartialUpdateMessageData(msg, m_empire_id, m_universe); break; - case Message::TURN_PROGRESS: + case Message::TURN_PROGRESS: { + Message::TurnProgressPhase phase_id; + ExtractTurnProgressMessageData(msg, phase_id); + ClientApp::HandleTurnPhaseUpdate(phase_id); + break; + } + case Message::PLAYER_STATUS: break; @@ -349,14 +360,16 @@ void AIClientApp::HandleMessage(const Message& msg) { DebugLogger() << "Message::END_GAME : Exiting"; DebugLogger() << "Acknowledge server shutdown message."; Networking().SendMessage(AIEndGameAcknowledgeMessage()); - Exit(0); + ExitApp(0); break; } case Message::PLAYER_CHAT: { std::string data; int player_id; - ExtractServerPlayerChatMessageData(msg, player_id, data); + boost::posix_time::ptime timestamp; + bool pm; + ExtractServerPlayerChatMessageData(msg, player_id, timestamp, data, pm); m_AI->HandleChatMessage(player_id, data); break; } @@ -383,6 +396,19 @@ void AIClientApp::HandleMessage(const Message& msg) { break; } + case Message::CHECKSUM: { + TraceLogger() << "(AIClientApp) CheckSum."; + bool result = VerifyCheckSum(msg); + if (!result) { + ErrorLogger() << "Wrong checksum"; + throw std::runtime_error("AI got incorrect checksum."); + } + break; + } + + case Message::TURN_TIMEOUT: + break; + default: { ErrorLogger() << "AIClientApp::HandleMessage : Received unknown Message type code " << msg.Type(); break; diff --git a/client/AI/AIClientApp.h b/client/AI/AIClientApp.h index 5ae8bc6233a..a4e41fb5f30 100644 --- a/client/AI/AIClientApp.h +++ b/client/AI/AIClientApp.h @@ -7,35 +7,48 @@ #include -class AIBase; class PythonAI; /** the application framework for an AI player FreeOrion client.*/ class AIClientApp : public ClientApp { public: - /** \name Structors */ //@{ + AIClientApp() = delete; + AIClientApp(const std::vector& args); - ~AIClientApp(); - //@} + + AIClientApp(const AIClientApp&) = delete; + + AIClientApp(AIClientApp&&) = delete; + + ~AIClientApp() override; + + const AIClientApp& operator=(const AIClientApp&) = delete; + + AIClientApp& operator=(const AIClientApp&&) = delete; /** \name Mutators */ //@{ - void operator()(); ///< external interface to Run() - void Exit(int code); ///< does basic clean-up, then calls exit(); callable from anywhere in user code via GetApp() + //! Executes main event handler + void Run(); + void ExitApp(int code = 0); ///< does basic clean-up, then calls exit(); callable from anywhere in user code via GetApp() void SetPlayerName(const std::string& player_name) { m_player_name = player_name; } //@} /** \name Accessors */ //@{ int EffectsProcessingThreads() const override; + /** @brief Return the player name of this client + * + * @return An UTF-8 encoded and NUL terminated string containing the player + * name of this client. + */ const std::string& PlayerName() const { return m_player_name; } //@} static AIClientApp* GetApp(); ///< returns a AIClientApp pointer to the singleton instance of the app - const AIBase* GetAI(); ///< returns pointer to AIBase implementation of AI for this client + const PythonAI* GetAI(); ///< returns pointer to AIBase implementation of AI for this client private: - void Run(); ///< initializes app state, then executes main event handler/render loop (PollAndRender()) void ConnectToServer(); void StartPythonAI(); void HandlePythonAICrash(); diff --git a/python/AI/AIFramework.cpp b/client/AI/AIFramework.cpp similarity index 85% rename from python/AI/AIFramework.cpp rename to client/AI/AIFramework.cpp index 0bc1ff82bbc..243b1003f87 100644 --- a/python/AI/AIFramework.cpp +++ b/client/AI/AIFramework.cpp @@ -1,26 +1,25 @@ #include "AIFramework.h" -#include "../../universe/Building.h" +#include "AIClientApp.h" +#include "AIWrapper.h" +#include "../../universe/BuildingType.h" #include "../../universe/Universe.h" #include "../../util/Directories.h" #include "../../util/Logger.h" #include "../../util/i18n.h" -#include "../../util/MultiplayerCommon.h" #include "../../util/OptionsDB.h" +#include "../../util/ScopedTimer.h" #include "../../Empire/Empire.h" -#include "../../Empire/EmpireManager.h" #include "../../Empire/Diplomacy.h" -#include "../CommonFramework.h" -#include "../SetWrapper.h" -#include "../CommonWrappers.h" -#include "AIWrapper.h" +#include "../../python/CommonFramework.h" +#include "../../python/SetWrapper.h" +#include "../../python/CommonWrappers.h" #include #include #include #include #include -#include #include #include #include @@ -53,7 +52,7 @@ BOOST_PYTHON_MODULE(freeOrionAIInterface) boost::python::docstring_options doc_options(true, true, false); /////////////////// - // AIInterface // + // Game client // /////////////////// FreeOrionPython::WrapAI(); @@ -73,6 +72,11 @@ BOOST_PYTHON_MODULE(freeOrionAIInterface) /////////////////// FreeOrionPython::WrapGameStateEnums(); + /////////////////// + // Config // + /////////////////// + FreeOrionPython::WrapConfig(); + //////////////////// // STL Containers // //////////////////// @@ -90,7 +94,7 @@ BOOST_PYTHON_MODULE(freeOrionAIInterface) FreeOrionPython::SetWrapper::Wrap("IntSet"); FreeOrionPython::SetWrapper::Wrap("StringSet"); } - + ////////////////////// // PythonAI // ////////////////////// @@ -104,17 +108,15 @@ bool PythonAI::Initialize() { return false; } +bool PythonAI::InitImports() { + DebugLogger() << "Initializing AI Python imports"; + // allows the "freeOrionAIInterface" C++ module to be imported within Python code + return PyImport_AppendInittab("freeOrionAIInterface", &PyInit_freeOrionAIInterface) != -1; +} + bool PythonAI::InitModules() { DebugLogger() << "Initializing AI Python modules"; - // allows the "freeOrionAIInterface" C++ module to be imported within Python code - try { - initfreeOrionAIInterface(); - } catch (...) { - ErrorLogger() << "Unable to initialize 'freeOrionAIInterface' AI Python module"; - return false; - } - // Confirm existence of the directory containing the AI Python scripts // and add it to Pythons sys.path to make sure Python will find our scripts std::string ai_path = boost::filesystem::canonical(GetResourceDir() / GetOptionsDB().Get("ai-path")).string(); @@ -134,24 +136,27 @@ bool PythonAI::InitModules() { void PythonAI::GenerateOrders() { DebugLogger() << "PythonAI::GenerateOrders : initializing turn"; - //AIInterface::InitTurn(); - boost::timer order_timer; + ScopedTimer order_timer; try { // call Python function that generates orders for current turn //DebugLogger() << "PythonAI::GenerateOrders : getting generate orders object"; object generateOrdersPythonFunction = m_python_module_ai.attr("generateOrders"); //DebugLogger() << "PythonAI::GenerateOrders : generating orders"; generateOrdersPythonFunction(); - } catch (error_already_set err) { + } catch (const error_already_set& err) { HandleErrorAlreadySet(); - if (!IsPythonRunning()) + if (!IsPythonRunning() || GetOptionsDB().Get("testing")) throw; ErrorLogger() << "PythonAI::GenerateOrders : Python error caught. Partial orders sent to server"; - AIInterface::DoneTurn(); } - DebugLogger() << "PythonAI::GenerateOrders order generating time: " << (order_timer.elapsed() * 1000.0); + + AIClientApp* app = AIClientApp::GetApp(); + // encodes order sets and sends turn orders message. "done" the turn for the client, but "starts" the turn for the server + app->StartTurn(app->GetAI()->GetSaveStateString()); + + DebugLogger() << "PythonAI::GenerateOrders order generating time: " << order_timer.DurationString(); } void PythonAI::HandleChatMessage(int sender_id, const std::string& msg) { @@ -187,7 +192,7 @@ void PythonAI::ResumeLoadedGame(const std::string& save_state_string) { resumeLoadedGamePythonFunction(FreeOrionPython::GetStaticSaveStateString()); } -const std::string& PythonAI::GetSaveStateString() { +const std::string& PythonAI::GetSaveStateString() const { // call Python function that serializes AI state for storage in save file and sets s_save_state_string // to contain that string object prepareForSavePythonFunction = m_python_module_ai.attr("prepareForSave"); @@ -195,3 +200,6 @@ const std::string& PythonAI::GetSaveStateString() { //DebugLogger() << "PythonAI::GetSaveStateString() returning: " << s_save_state_string; return FreeOrionPython::GetStaticSaveStateString(); } + +void PythonAI::SetAggression(int aggr) +{ m_aggression = aggr; } diff --git a/client/AI/AIFramework.h b/client/AI/AIFramework.h new file mode 100644 index 00000000000..da57dbb7b09 --- /dev/null +++ b/client/AI/AIFramework.h @@ -0,0 +1,122 @@ +#ifndef _AIFramework_h_ +#define _AIFramework_h_ + +#include "../../python/CommonFramework.h" + +#include +#include + +#ifdef FREEORION_MACOSX +// Bugfix for https://github.com/freeorion/freeorion/issues/1228 + +// The problem on OSX is that the boost/python/str.hpp redefines toupper() and +// similar functions if they are not already defined. + +// This includes iostream before the boost/python/str.hpp to fix this issue. +// If the subsequent #include is removed then so can this workaround. +#include +#endif + +#include + +class DiplomaticMessage; +struct DiplomaticStatusUpdateInfo; + + +/** @brief Class allowing AI to recieve basic game events. + */ +class PythonAI : public PythonBase { +public: + bool Initialize(); + + /** Initializes AI Python imports. */ + bool InitImports() override; + /** Initializes AI Python modules. */ + bool InitModules() override; + + /** @brief Call when the server has sent a new turn update. + * + * The AI subclass should review the new gamestate and send orders for + * this turn. + */ + void GenerateOrders(); + + /** @brief Called when another player sends a chat message to this AI. + * + * The AI subclass should respond or react to the message in a meaningful + * way. + * + * @param sender_id The player identifier representing the player, who sent + * the message. + * @param msg The text body of the sent message. + */ + void HandleChatMessage(int sender_id, const std::string& msg); + + /** @brief Called when another player sends a diplomatic message that + * affects this player + * + * The AI subclass should respond or react to the message in a meaningful + * way. + * + * @param msg The diplomatic message sent. + */ + void HandleDiplomaticMessage(const DiplomaticMessage& msg); + + /** @brief Called when two empires diplomatic status changes + * + * The AI subclass should respond or react to the change in a meaningful + * way. + * + * @param u The diplomatic status changed. + */ + void HandleDiplomaticStatusUpdate(const DiplomaticStatusUpdateInfo& u); + + /** @brief Called when a new game (not loaded) is started + * + * The AI subclass should clear its state and prepare to start for a new + * game. + */ + void StartNewGame(); + + /** @brief Called when a game is loaded from a save + * + * The AI subclass should extract any state information stored in + * @a save_state so it is able to continue generating orders when + * asked to do so. + * + * @param save_state The serialized state information from a previous game + * run. + */ + void ResumeLoadedGame(const std::string& save_state_string); + + /** @brief Called when the server is saving the game + * + * The AI should store any state information it will need to resume at any + * later time, and return this information. + * + * @return The serialized state information of the game running. + */ + const std::string& GetSaveStateString() const; + + /** @brief Set the aggressiveness of this AI + * + * The AI should change their behaviour when setting another aggression + * level. + * + * @param aggr The new aggression level. The value should be one of + * Aggression. + */ + void SetAggression(int aggr); + +private: + // reference to imported Python AI module + boost::python::object m_python_module_ai; + + /** @brief The current aggressiveness of this AI + * + * The value should be one of Aggression. + */ + int m_aggression; +}; + +#endif // _AIFramework_h_ diff --git a/client/AI/AIWrapper.cpp b/client/AI/AIWrapper.cpp new file mode 100644 index 00000000000..9d83ba3a555 --- /dev/null +++ b/client/AI/AIWrapper.cpp @@ -0,0 +1,711 @@ +#include "AIWrapper.h" + +#include "AIClientApp.h" +#include "../ClientNetworking.h" +#include "../../universe/Planet.h" +#include "../../universe/ShipDesign.h" +#include "../../universe/Tech.h" +#include "../../universe/Universe.h" +#include "../../util/AppInterface.h" +#include "../../util/Directories.h" +#include "../../util/Logger.h" +#include "../../util/i18n.h" +#include "../../util/MultiplayerCommon.h" +#include "../../util/OptionsDB.h" +#include "../../util/OrderSet.h" +#include "../../util/Order.h" +#include "../../Empire/Empire.h" +#include "../../Empire/EmpireManager.h" +#include "../../Empire/Diplomacy.h" +#include "../../python/SetWrapper.h" +#include "../../python/CommonWrappers.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using boost::python::class_; +using boost::python::def; +using boost::python::no_init; +using boost::noncopyable; +using boost::python::return_value_policy; +using boost::python::copy_const_reference; +using boost::python::reference_existing_object; +using boost::python::return_by_value; +using boost::python::return_internal_reference; + +using boost::python::vector_indexing_suite; +using boost::python::map_indexing_suite; + +using boost::python::object; +using boost::python::import; +using boost::python::exec; +using boost::python::dict; +using boost::python::list; +using boost::python::extract; + + +//////////////////////// +// Python AIWrapper // +//////////////////////// +namespace { + // static string to save AI state + static std::string s_save_state_string(""); + + const std::string& PlayerName() + { return AIClientApp::GetApp()->PlayerName(); } + + /** @brief Return the player name of the client identified by @a player_id + * + * @param player_id An client identifier. + * + * @return An UTF-8 encoded and NUL terminated string containing the player + * name of this client or an empty string the player is not known or + * does not exist. + */ + const std::string& PlayerNameByID(int player_id) { + auto& players = AIClientApp::GetApp()->Players(); + auto it = players.find(player_id); + if (it != players.end()) + return it->second.name; + else { + DebugLogger() << "AIWrapper::PlayerName(" << std::to_string(player_id) << ") - passed an invalid player_id"; + throw std::invalid_argument("AIWrapper::PlayerName : given invalid player_id"); + } + } + + int PlayerID() + { return AIClientApp::GetApp()->PlayerID(); } + + int EmpirePlayerID(int empire_id) { + int player_id = AIClientApp::GetApp()->EmpirePlayerID(empire_id); + if (Networking::INVALID_PLAYER_ID == player_id) + DebugLogger() << "AIWrapper::EmpirePlayerID(" << empire_id << ") - passed an invalid empire_id"; + return player_id; + } + + /** @brief Return all player identifiers that are in game + * + * @return A vector containing the identifiers of all players. + */ + std::vector AllPlayerIDs() { + std::vector player_ids; + for (auto& entry : AIClientApp::GetApp()->Players()) + player_ids.push_back(entry.first); + return player_ids; + } + + /** @brief Return if the player identified by @a player_id is an AI + * + * @param player_id An client identifier. + * + * @return True if the player is an AI, false if not. + */ + bool PlayerIsAI(int player_id) + { return AIClientApp::GetApp()->GetPlayerClientType(player_id) == Networking::CLIENT_TYPE_AI_PLAYER; } + + /** @brief Return if the player identified by @a player_id is the game + * host + * + * @param player_id An client identifier. + * + * @return True if the player is the game host, false if not. + */ + bool PlayerIsHost(int player_id) { + auto& players = AIClientApp::GetApp()->Players(); + auto it = players.find(player_id); + if (it == players.end()) + return false; + return it->second.host; + } + + int EmpireID() + { return AIClientApp::GetApp()->EmpireID(); } + + /** @brief Return the empire identifier of the empire @a player_id controls + * + * @param player_id An client identifier. + * + * @return An empire identifier. + */ + int PlayerEmpireID(int player_id) { + for (auto& entry : AIClientApp::GetApp()->Players()) { + if (entry.first == player_id) + return entry.second.empire_id; + } + return ALL_EMPIRES; // default invalid value + } + + /** @brief Return all empire identifiers that are in game + * + * @return A vector containing the identifiers of all empires. + */ + std::vector AllEmpireIDs() { + std::vector empire_ids; + for (auto& entry : AIClientApp::GetApp()->Players()) { + auto empire_id = entry.second.empire_id; + if (empire_id != ALL_EMPIRES) + empire_ids.push_back(empire_id); + } + return empire_ids; + } + + /** @brief Return the ::Empire this client controls + * + * @return A pointer to the Empire instance this client has the control + * over. + */ + const Empire* PlayerEmpire() + { return AIClientApp::GetApp()->GetEmpire(AIClientApp::GetApp()->EmpireID()); } + + const Empire* GetEmpireByID(int empire_id) + { return AIClientApp::GetApp()->GetEmpire(empire_id); } + + const GalaxySetupData& PythonGalaxySetupData() + { return AIClientApp::GetApp()->GetGalaxySetupData(); } + + void InitMeterEstimatesAndDiscrepancies() { + Universe& universe = AIClientApp::GetApp()->GetUniverse(); + universe.InitMeterEstimatesAndDiscrepancies(); + } + + /** @brief Set ::Universe ::Meter instances to their estimated values as + * if the next turn processing phase were done + * + * @param pretend_to_own_unowned_planets When set to true pretend during + * calculation that this clients Empire owns all known uncolonized + * planets. The unowned planets MAX ::Meter values will contain the + * estimated value for those planets. + */ + void UpdateMeterEstimates(bool pretend_to_own_unowned_planets) { + std::vector> unowned_planets; + int player_id = -1; + Universe& universe = AIClientApp::GetApp()->GetUniverse(); + if (pretend_to_own_unowned_planets) { + // Add this player ownership to all planets that the player can see + // but which aren't currently colonized. This way, any effects the + // player knows about that would act on those planets if the player + // colonized them include those planets in their scope. This lets + // effects from techs the player knows alter the max population of + // planet that is displayed to the player, even if those effects + // have a condition that causes them to only act on planets the + // player owns (so as to not improve enemy planets if a player + // reseraches a tech that should only benefit him/herself). + player_id = AIClientApp::GetApp()->PlayerID(); + + // get all planets the player knows about that aren't yet colonized + // (aren't owned by anyone). Add this the current player's + // ownership to all, while remembering which planets this is done + // to. + universe.InhibitUniverseObjectSignals(true); + for (auto& planet : universe.Objects().all()) { + if (planet->Unowned()) { + unowned_planets.push_back(planet); + planet->SetOwner(player_id); + } + } + } + + // update meter estimates with temporary ownership + universe.UpdateMeterEstimates(); + + if (pretend_to_own_unowned_planets) { + // remove temporary ownership added above + for (auto& planet : unowned_planets) + planet->SetOwner(ALL_EMPIRES); + universe.InhibitUniverseObjectSignals(false); + } + } + + void UpdateResourcePools() { + int empire_id = AIClientApp::GetApp()->EmpireID(); + Empire* empire = ::GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "UpdateResourcePools : couldn't get empire with id " << empire_id; + return; + } + empire->UpdateResourcePools(); + } + + void UpdateResearchQueue() { + int empire_id = AIClientApp::GetApp()->EmpireID(); + Empire* empire = ::GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "UpdateResearchQueue : couldn't get empire with id " << empire_id; + return; + } + empire->UpdateResearchQueue(); + } + + void UpdateProductionQueue() { + int empire_id = AIClientApp::GetApp()->EmpireID(); + Empire* empire = ::GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "UpdateProductionQueue : couldn't get empire with id " << empire_id; + return; + } + empire->UpdateProductionQueue(); + } + + boost::python::list GetUserStringList(const std::string& list_key) { + boost::python::list ret_list; + for (const std::string& string : UserStringList(list_key)) + ret_list.append(string); + return ret_list; + } + + //! Return the canonical AI directory path + //! + //! The value depends on the ::OptionsDB `resource.path` and `ai-path` keys. + //! + //! @return + //! The canonical path pointing to the directory containing all python AI + //! scripts. + std::string GetAIDir() + { return (GetResourceDir() / GetOptionsDB().Get("ai-path")).string(); } + + OrderSet& IssuedOrders() + { return AIClientApp::GetApp()->Orders(); } + + template + int Issue(Args &&... args) { + auto app = ClientApp::GetApp(); + + if (!OrderType::Check(app->EmpireID(), std::forward(args)...)) + return 0; + + app->Orders().IssueOrder(std::make_shared(app->EmpireID(), std::forward(args)...)); + + return 1; + } + + int IssueNewFleetOrder(const std::string& fleet_name, int ship_id) { + std::vector ship_ids{ship_id}; + auto app = ClientApp::GetApp(); + if (!NewFleetOrder::Check(app->EmpireID(), fleet_name, ship_ids, false)) + return 0; + + auto order = std::make_shared(app->EmpireID(), fleet_name, ship_ids, false); + app->Orders().IssueOrder(order); + + return order->FleetID(); + } + + int IssueFleetTransferOrder(int ship_id, int new_fleet_id) { + std::vector ship_ids{ship_id}; + return Issue(new_fleet_id, ship_ids); + } + + int IssueRenameOrder(int object_id, const std::string& new_name) + { return Issue(object_id, new_name); } + + int IssueScrapOrder(int object_id) + { return Issue(object_id); } + + int IssueChangeFocusOrder(int planet_id, const std::string& focus) + { return Issue(planet_id, focus); } + + int IssueEnqueueTechOrder(const std::string& tech_name, int position) { + const Tech* tech = GetTech(tech_name); + if (!tech) { + ErrorLogger() << "IssueEnqueueTechOrder : passed tech_name that is not the name of a tech."; + return 0; + } + + int empire_id = AIClientApp::GetApp()->EmpireID(); + + AIClientApp::GetApp()->Orders().IssueOrder( + std::make_shared(empire_id, tech_name, position)); + + return 1; + } + + int IssueDequeueTechOrder(const std::string& tech_name) { + const Tech* tech = GetTech(tech_name); + if (!tech) { + ErrorLogger() << "IssueDequeueTechOrder : passed tech_name that is not the name of a tech."; + return 0; + } + + int empire_id = AIClientApp::GetApp()->EmpireID(); + + AIClientApp::GetApp()->Orders().IssueOrder(std::make_shared(empire_id, tech_name)); + + return 1; + } + + int IssueEnqueueBuildingProductionOrder(const std::string& item_name, int location_id) { + int empire_id = AIClientApp::GetApp()->EmpireID(); + Empire* empire = AIClientApp::GetApp()->GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "IssueEnqueueBuildingProductionOrder : couldn't get empire with id " << empire_id; + return 0; + } + + if (!empire->ProducibleItem(BT_BUILDING, item_name, location_id)) { + ErrorLogger() << "IssueEnqueueBuildingProductionOrder : specified item_name and location_id that don't indicate an item that can be built at that location"; + return 0; + } + + if (!empire->EnqueuableItem(BT_BUILDING, item_name, location_id)) { + ErrorLogger() << "IssueEnqueueBuildingProductionOrder : specified item_name and location_id that don't indicate an item that can be enqueued at that location"; + return 0; + } + + auto item = ProductionQueue::ProductionItem(BT_BUILDING, item_name); + + AIClientApp::GetApp()->Orders().IssueOrder( + std::make_shared(ProductionQueueOrder::PLACE_IN_QUEUE, + empire_id, item, 1, location_id)); + + return 1; + } + + int IssueEnqueueShipProductionOrder(int design_id, int location_id) { + int empire_id = AIClientApp::GetApp()->EmpireID(); + Empire* empire = AIClientApp::GetApp()->GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "IssueEnqueueShipProductionOrder : couldn't get empire with id " << empire_id; + return 0; + } + + if (!empire->ProducibleItem(BT_SHIP, design_id, location_id)) { + ErrorLogger() << "IssueEnqueueShipProductionOrder : specified design_id and location_id that don't indicate a design that can be built at that location"; + return 0; + } + + auto item = ProductionQueue::ProductionItem(BT_SHIP, design_id); + + AIClientApp::GetApp()->Orders().IssueOrder( + std::make_shared(ProductionQueueOrder::PLACE_IN_QUEUE, + empire_id, item, 1, location_id)); + + return 1; + } + + int IssueChangeProductionQuantityOrder(int queue_index, int new_quantity, int new_blocksize) { + int empire_id = AIClientApp::GetApp()->EmpireID(); + Empire* empire = AIClientApp::GetApp()->GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "IssueChangeProductionQuantityOrder : couldn't get empire with id " << empire_id; + return 0; + } + + const ProductionQueue& queue = empire->GetProductionQueue(); + if (queue_index < 0 || static_cast(queue.size()) <= queue_index) { + ErrorLogger() << "IssueChangeProductionQuantityOrder : passed queue_index outside range of items on queue."; + return 0; + } + if (queue[queue_index].item.build_type != BT_SHIP) { + ErrorLogger() << "IssueChangeProductionQuantityOrder : passed queue_index for a non-ship item."; + return 0; + } + + auto queue_it = empire->GetProductionQueue().find(queue_index); + + if (queue_it != empire->GetProductionQueue().end()) + AIClientApp::GetApp()->Orders().IssueOrder( + std::make_shared(ProductionQueueOrder::SET_QUANTITY_AND_BLOCK_SIZE, + empire_id, queue_it->uuid, + new_quantity, new_blocksize)); + + return 1; + } + + int IssueRequeueProductionOrder(int old_queue_index, int new_queue_index) { + if (old_queue_index == new_queue_index) { + ErrorLogger() << "IssueRequeueProductionOrder : passed same old and new indexes... nothing to do."; + return 0; + } + + int empire_id = AIClientApp::GetApp()->EmpireID(); + Empire* empire = AIClientApp::GetApp()->GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "IssueRequeueProductionOrder : couldn't get empire with id " << empire_id; + return 0; + } + + const ProductionQueue& queue = empire->GetProductionQueue(); + if (old_queue_index < 0 || static_cast(queue.size()) <= old_queue_index) { + ErrorLogger() << "IssueRequeueProductionOrder : passed old_queue_index outside range of items on queue."; + return 0; + } + + // After removing an earlier entry in queue, all later entries are shifted down one queue index, so + // inserting before the specified item index should now insert before the previous item index. This + // also allows moving to the end of the queue, rather than only before the last item on the queue. + int actual_new_index = new_queue_index; + if (old_queue_index < new_queue_index) + actual_new_index = new_queue_index - 1; + + if (new_queue_index < 0 || static_cast(queue.size()) <= actual_new_index) { + ErrorLogger() << "IssueRequeueProductionOrder : passed new_queue_index outside range of items on queue."; + return 0; + } + + auto queue_it = empire->GetProductionQueue().find(old_queue_index); + + if (queue_it != empire->GetProductionQueue().end()) + AIClientApp::GetApp()->Orders().IssueOrder( + std::make_shared(ProductionQueueOrder::MOVE_ITEM_TO_INDEX, + empire_id, queue_it->uuid, new_queue_index)); + + return 1; + } + + int IssueDequeueProductionOrder(int queue_index) { + int empire_id = AIClientApp::GetApp()->EmpireID(); + Empire* empire = AIClientApp::GetApp()->GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "IssueDequeueProductionOrder : couldn't get empire with id " << empire_id; + return 0; + } + + const ProductionQueue& queue = empire->GetProductionQueue(); + if (queue_index < 0 || static_cast(queue.size()) <= queue_index) { + ErrorLogger() << "IssueDequeueProductionOrder : passed queue_index outside range of items on queue."; + return 0; + } + + auto queue_it = empire->GetProductionQueue().find(queue_index); + + if (queue_it != empire->GetProductionQueue().end()) + AIClientApp::GetApp()->Orders().IssueOrder( + std::make_shared(ProductionQueueOrder::REMOVE_FROM_QUEUE, + empire_id, queue_it->uuid)); + + return 1; + } + + int IssuePauseProductionOrder(int queue_index, bool pause) { + int empire_id = AIClientApp::GetApp()->EmpireID(); + Empire* empire = AIClientApp::GetApp()->GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "IssuePauseProductionOrder : couldn't get empire with id " << empire_id; + return 0; + } + + const ProductionQueue& queue = empire->GetProductionQueue(); + if (queue_index < 0 || static_cast(queue.size()) <= queue_index) { + ErrorLogger() << "IssueChangeProductionPauseOrder : passed queue_index outside range of items on queue."; + return 0; + } + + auto queue_it = empire->GetProductionQueue().find(queue_index); + + if (queue_it != empire->GetProductionQueue().end()) + AIClientApp::GetApp()->Orders().IssueOrder( + std::make_shared(ProductionQueueOrder::PAUSE_PRODUCTION, + empire_id, queue_it->uuid)); + + return 1; + } + + int IssueAllowStockpileProductionOrder(int queue_index, bool use_stockpile) { + int empire_id = AIClientApp::GetApp()->EmpireID(); + Empire* empire = AIClientApp::GetApp()->GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "IssueAllowStockpileProductionOrder : couldn't get empire with id " << empire_id; + return 0; + } + + const ProductionQueue& queue = empire->GetProductionQueue(); + if (queue_index < 0 || static_cast(queue.size()) <= queue_index) { + ErrorLogger() << "IssueChangeProductionStockpileOrder : passed queue_index outside range of items on queue."; + return 0; + } + + auto queue_it = empire->GetProductionQueue().find(queue_index); + + if (queue_it != empire->GetProductionQueue().end()) + AIClientApp::GetApp()->Orders().IssueOrder( + std::make_shared(ProductionQueueOrder::ALLOW_STOCKPILE_USE, + empire_id, queue_it->uuid)); + + return 1; + } + + int IssueCreateShipDesignOrder(const std::string& name, const std::string& description, + const std::string& hull, const boost::python::list parts_list, + const std::string& icon, const std::string& model, + bool name_desc_in_stringtable) + { + if (name.empty() || description.empty() || hull.empty()) { + ErrorLogger() << "IssueCreateShipDesignOrderOrder : passed an empty name, description, or hull."; + return 0; + } + + std::vector parts; + int const num_parts = boost::python::len(parts_list); + for (int i = 0; i < num_parts; i++) + parts.push_back(boost::python::extract(parts_list[i])); + + int empire_id = AIClientApp::GetApp()->EmpireID(); + int current_turn = CurrentTurn(); + + const auto uuid = boost::uuids::random_generator()(); + + // create design from stuff chosen in UI + ShipDesign* design; + try { + design = new ShipDesign(std::invalid_argument(""), name, description, current_turn, ClientApp::GetApp()->EmpireID(), + hull, parts, icon, model, name_desc_in_stringtable, false, uuid); + + } catch (const std::invalid_argument&) { + ErrorLogger() << "IssueCreateShipDesignOrderOrder failed to create a new ShipDesign object"; + return 0; + } + + auto order = std::make_shared(empire_id, *design); + AIClientApp::GetApp()->Orders().IssueOrder(order); + delete design; + + return 1; + } + + int IssueFleetMoveOrder(int fleet_id, int destination_id) + { return Issue(fleet_id, destination_id); } + + int AppendFleetMoveOrder(int fleet_id, int destination_id) + { return Issue(fleet_id, destination_id, true); } + + int IssueColonizeOrder(int ship_id, int planet_id) + { return Issue(ship_id, planet_id); } + + int IssueInvadeOrder(int ship_id, int planet_id) + { return Issue(ship_id, planet_id); } + + int IssueBombardOrder(int ship_id, int planet_id) + { return Issue(ship_id, planet_id); } + + int IssueAggressionOrder(int object_id, bool aggressive) + { return Issue(object_id, aggressive); } + + int IssueGiveObjectToEmpireOrder(int object_id, int recipient_id) + { return Issue(object_id, recipient_id); } + + void SendPlayerChatMessage(int recipient_player_id, const std::string& message_text) + { AIClientApp::GetApp()->Networking().SendMessage(PlayerChatMessage(message_text, {recipient_player_id}, false)); } + + void SendDiplomaticMessage(const DiplomaticMessage& diplo_message) { + AIClientApp* app = AIClientApp::GetApp(); + if (!app) return; + + int sender_player_id = app->PlayerID(); + if (sender_player_id == Networking::INVALID_PLAYER_ID) return; + + int recipient_player_id = app->EmpirePlayerID(diplo_message.RecipientEmpireID()); + if (recipient_player_id == Networking::INVALID_PLAYER_ID) return; + + app->Networking().SendMessage(DiplomacyMessage(diplo_message)); + } +} + +namespace FreeOrionPython { + const std::string& GetStaticSaveStateString() + { return s_save_state_string; } + + void SetStaticSaveStateString(const std::string& new_state_string) + { s_save_state_string = new_state_string; } + + void ClearStaticSaveStateString() + { s_save_state_string.clear(); } + + /** Expose game client to Python. + * + * CallPolicies: + * + * return_value_policy when returning a relatively small object, such as a string, + * that is returned by const reference or pointer + * + * return_value_policy when returning either a simple data type or a temporary object + * in a function that will go out of scope after being returned + * + * return_value_policy when returning an object from a non-member function + */ + void WrapAI() + { + def("playerName", PlayerName, return_value_policy(), "Returns the name (string) of this AI player."); + def("playerName", PlayerNameByID, return_value_policy(), "Returns the name (string) of the player with the indicated playerID (int)."); + + def("playerID", PlayerID, "Returns the integer id of this AI player."); + def("empirePlayerID", EmpirePlayerID, "Returns the player ID (int) of the player who is controlling the empire with the indicated empireID (int)."); + def("allPlayerIDs", AllPlayerIDs, return_value_policy(), "Returns an object (intVec) that contains the player IDs of all players in the game."); + + def("playerIsAI", PlayerIsAI, "Returns True (boolean) if the player with the indicated playerID (int) is controlled by an AI and false (boolean) otherwise."); + def("playerIsHost", PlayerIsHost, "Returns True (boolean) if the player with the indicated playerID (int) is the host player for the game and false (boolean) otherwise."); + + def("empireID", EmpireID, "Returns the empire ID (int) of this AI player's empire."); + def("playerEmpireID", PlayerEmpireID, "Returns the empire ID (int) of the player with the specified player ID (int)."); + def("allEmpireIDs", AllEmpireIDs, return_value_policy(), "Returns an object (intVec) that contains the empire IDs of all empires in the game."); + + def("getEmpire", PlayerEmpire, return_value_policy(), "Returns the empire object (Empire) of this AI player"); + def("getEmpire", GetEmpireByID, return_value_policy(), "Returns the empire object (Empire) with the specified empire ID (int)"); + + def("getUniverse", GetUniverse, return_value_policy(), "Returns the universe object (Universe)"); + + def("currentTurn", CurrentTurn, "Returns the current game turn (int)."); + + def("getAIDir", GetAIDir, return_value_policy()); + + def("initMeterEstimatesDiscrepancies", InitMeterEstimatesAndDiscrepancies); + def("updateMeterEstimates", UpdateMeterEstimates); + def("updateResourcePools", UpdateResourcePools); + def("updateResearchQueue", UpdateResearchQueue); + def("updateProductionQueue", UpdateProductionQueue); + + def("issueFleetMoveOrder", IssueFleetMoveOrder, "Orders the fleet with indicated fleetID (int) to move to the system with the indicated destinationID (int). Returns 1 (int) on success or 0 (int) on failure due to not finding the indicated fleet or system."); + def("appendFleetMoveOrder", AppendFleetMoveOrder, "Orders the fleet with indicated fleetID (int) to append the system with the indicated destinationID (int) to its possibly already enqueued route. Returns 1 (int) on success or 0 (int) on failure due to not finding the indicated fleet or system."); + def("issueRenameOrder", IssueRenameOrder, "Orders the renaming of the object with indicated objectID (int) to the new indicated name (string). Returns 1 (int) on success or 0 (int) on failure due to this AI player not being able to rename the indicated object (which this player must fully own, and which must be a fleet, ship or planet)."); + def("issueScrapOrder", IssueScrapOrder, "Orders the ship or building with the indicated objectID (int) to be scrapped. Returns 1 (int) on success or 0 (int) on failure due to not finding a ship or building with the indicated ID, or if the indicated ship or building is not owned by this AI client's empire."); + def("issueNewFleetOrder", IssueNewFleetOrder, "Orders a new fleet to be created with the indicated name (string) and containing the indicated shipIDs (IntVec). The ships must be located in the same system and must all be owned by this player. Returns the new fleets id (int) on success or 0 (int) on failure due to one of the noted conditions not being met."); + def("issueFleetTransferOrder", IssueFleetTransferOrder, "Orders the ship with ID shipID (int) to be transferred to the fleet with ID newFleetID. Returns 1 (int) on success, or 0 (int) on failure due to not finding the fleet or ship, or the client's empire not owning either, or the two not being in the same system (or either not being in a system) or the ship already being in the fleet."); + def("issueColonizeOrder", IssueColonizeOrder, "Orders the ship with ID shipID (int) to colonize the planet with ID planetID (int). Returns 1 (int) on success or 0 (int) on failure due to not finding the indicated ship or planet, this client's player not owning the indicated ship, the planet already being colonized, or the planet and ship not being in the same system."); + def("issueInvadeOrder", IssueInvadeOrder); + def("issueBombardOrder", IssueBombardOrder); + def("issueAggressionOrder", IssueAggressionOrder); + def("issueGiveObjectToEmpireOrder", IssueGiveObjectToEmpireOrder); + def("issueChangeFocusOrder", IssueChangeFocusOrder, "Orders the planet with ID planetID (int) to use focus setting focus (string). Returns 1 (int) on success or 0 (int) on failure if the planet can't be found or isn't owned by this player, or if the specified focus is not valid on the planet."); + def("issueEnqueueTechOrder", IssueEnqueueTechOrder, "Orders the tech with name techName (string) to be added to the tech queue at position (int) on the queue. Returns 1 (int) on success or 0 (int) on failure if the indicated tech can't be found. Will return 1 (int) but do nothing if the indicated tech can't be enqueued by this player's empire."); + def("issueDequeueTechOrder", IssueDequeueTechOrder, "Orders the tech with name techName (string) to be removed from the queue. Returns 1 (int) on success or 0 (int) on failure if the indicated tech can't be found. Will return 1 (int) but do nothing if the indicated tech isn't on this player's empire's tech queue."); + def("issueEnqueueBuildingProductionOrder", IssueEnqueueBuildingProductionOrder, "Orders the building with name (string) to be added to the production queue at the location of the planet with id locationID. Returns 1 (int) on success or 0 (int) on failure if there is no such building or it is not available to this player's empire, or if the building can't be produced at the specified location."); + def("issueEnqueueShipProductionOrder", IssueEnqueueShipProductionOrder, "Orders the ship design with ID designID (int) to be added to the production queue at the location of the planet with id locationID (int). Returns 1 (int) on success or 0 (int) on failure there is no such ship design or it not available to this player's empire, or if the design can't be produced at the specified location."); + def("issueChangeProductionQuantityOrder", IssueChangeProductionQuantityOrder); + def("issueRequeueProductionOrder", IssueRequeueProductionOrder, "Orders the item on the production queue at index oldQueueIndex (int) to be moved to index newQueueIndex (int). Returns 1 (int) on success or 0 (int) on failure if the old and new queue indices are equal, if either queue index is less than 0 or greater than the largest indexed item on the queue."); + def("issueDequeueProductionOrder", IssueDequeueProductionOrder, "Orders the item on the production queue at index queueIndex (int) to be removed form the production queue. Returns 1 (int) on success or 0 (int) on failure if the queue index is less than 0 or greater than the largest indexed item on the queue."); + def("issuePauseProductionOrder", IssuePauseProductionOrder, "Orders the item on the production queue at index queueIndex (int) to be paused (or unpaused). Returns 1 (int) on success or 0 (int) on failure if the queue index is less than 0 or greater than the largest indexed item on the queue."); + def("issueAllowStockpileProductionOrder", IssueAllowStockpileProductionOrder, "Orders the item on the production queue at index queueIndex (int) to be enabled (or disabled) to use the imperial stockpile. Returns 1 (int) on success or 0 (int) on failure if the queue index is less than 0 or greater than the largest indexed item on the queue."); + def("issueCreateShipDesignOrder", IssueCreateShipDesignOrder, "Orders the creation of a new ship design with the name (string), description (string), hull (string), parts vector partsVec (StringVec), graphic (string) and model (string). model should be left as an empty string as of this writing. There is currently no easy way to find the id of the new design, though the client's empire should have the new design after this order is issued successfully. Returns 1 (int) on success or 0 (int) on failure if any of the name, description, hull or graphic are empty strings, if the design is invalid (due to not following number and type of slot requirements for the hull) or if creating the design fails for some reason."); + + class_("OrderSet", no_init) + .def(map_indexing_suite()) + .add_property("size", &OrderSet::size) + ; + + class_("Order", no_init) + .add_property("empireID", &Order::EmpireID) + .add_property("executed", &Order::Executed) + ; + + def("getOrders", IssuedOrders, return_value_policy(), "Returns the orders the client empire has issued (OrderSet)."); + + def("sendChatMessage", SendPlayerChatMessage, "Sends the indicated message (string) to the player with the indicated recipientID (int) or to all players if recipientID is -1."); + def("sendDiplomaticMessage", SendDiplomaticMessage); + + def("setSaveStateString", SetStaticSaveStateString, "Sets the save state string (string). This is a persistant storage space for the AI script to retain state information when the game is saved and reloaded. Any AI state information to be saved should be stored in a single string (likely using Python's pickle module) and stored using this function when the prepareForSave() Python function is called."); + def("getSaveStateString", GetStaticSaveStateString, return_value_policy(), "Returns the previously-saved state string (string). Can be used to retrieve the last-set save state string at any time, although this string is also passed to the resumeLoadedGame(savedStateString) Python function when a game is loaded, so this function isn't necessary to use if resumeLoadedGame stores the passed string. "); + + def("userString", make_function(&UserString, return_value_policy())); + def("userStringExists", make_function(&UserStringExists,return_value_policy())); + def("userStringList", &GetUserStringList); + + def("getGalaxySetupData", PythonGalaxySetupData, return_value_policy()); + + boost::python::scope().attr("INVALID_GAME_TURN") = INVALID_GAME_TURN; + } +} diff --git a/python/AI/AIWrapper.h b/client/AI/AIWrapper.h similarity index 71% rename from python/AI/AIWrapper.h rename to client/AI/AIWrapper.h index 8e7674d14e2..f435e1928f3 100644 --- a/python/AI/AIWrapper.h +++ b/client/AI/AIWrapper.h @@ -1,5 +1,5 @@ -#ifndef __FreeOrion__Python__AIWrapper__ -#define __FreeOrion__Python__AIWrapper__ +#ifndef _AIWrapper_h_ +#define _AIWrapper_h_ #include @@ -13,4 +13,4 @@ namespace FreeOrionPython { void WrapAI(); } -#endif /* defined(__FreeOrion__Python__AIWrapper__) */ +#endif // _AIWrapper_h_ diff --git a/client/AI/CMakeLists.txt b/client/AI/CMakeLists.txt index 2bde57d59f5..ac0ab3e99bb 100644 --- a/client/AI/CMakeLists.txt +++ b/client/AI/CMakeLists.txt @@ -1,7 +1,11 @@ target_sources(freeorionca PUBLIC ${CMAKE_CURRENT_LIST_DIR}/AIClientApp.h + ${CMAKE_CURRENT_LIST_DIR}/AIFramework.h + ${CMAKE_CURRENT_LIST_DIR}/AIWrapper.h PRIVATE ${CMAKE_CURRENT_LIST_DIR}/AIClientApp.cpp + ${CMAKE_CURRENT_LIST_DIR}/AIFramework.cpp + ${CMAKE_CURRENT_LIST_DIR}/AIWrapper.cpp ${CMAKE_CURRENT_LIST_DIR}/camain.cpp ) diff --git a/client/AI/camain.cpp b/client/AI/camain.cpp index 4d8e51bd582..aade1671251 100644 --- a/client/AI/camain.cpp +++ b/client/AI/camain.cpp @@ -1,10 +1,10 @@ #include "AIClientApp.h" -#include "../../parse/Parse.h" #include "../../util/OptionsDB.h" #include "../../util/Directories.h" #include "../../util/Logger.h" #include "../../util/Version.h" +#include "../../util/i18n.h" #include @@ -44,18 +44,28 @@ int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) { InitDirs((args.empty() ? "" : *args.begin())); #endif + GetOptionsDB().AddFlag("testing", UserStringNop("OPTIONS_DB_TESTING"), false); + GetOptionsDB().Add('h', "help", UserStringNop("OPTIONS_DB_HELP"), "NOOP", + Validator(), false); + // if config.xml and persistent_config.xml are present, read and set options entries GetOptionsDB().SetFromFile(GetConfigPath(), FreeOrionVersionString()); GetOptionsDB().SetFromFile(GetPersistentConfigPath()); + GetOptionsDB().SetFromCommandLine(args); + + auto help_arg = GetOptionsDB().Get("help"); + if (help_arg != "NOOP") { + GetOptionsDB().GetUsage(std::cerr, help_arg); + ShutdownLoggingSystemFileSink(); + return 0; + } #ifndef FREEORION_CAMAIN_KEEP_STACKTRACE try { #endif - parse::init(); - AIClientApp g_app(args); - g_app(); + g_app.Run(); #ifndef FREEORION_CAMAIN_KEEP_STACKTRACE } catch (const std::invalid_argument& e) { diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 9eaa8d5d40e..0853c9ce717 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -7,9 +7,11 @@ target_sources(freeorionca PUBLIC ${CMAKE_CURRENT_LIST_DIR}/ClientApp.h ${CMAKE_CURRENT_LIST_DIR}/ClientFSMEvents.h + ${CMAKE_CURRENT_LIST_DIR}/ClientNetworking.h PRIVATE ${CMAKE_CURRENT_LIST_DIR}/ClientApp.cpp ${CMAKE_CURRENT_LIST_DIR}/ClientFSMEvents.cpp + ${CMAKE_CURRENT_LIST_DIR}/ClientNetworking.cpp ) if(NOT BUILD_HEADLESS) @@ -17,8 +19,10 @@ if(NOT BUILD_HEADLESS) PUBLIC ${CMAKE_CURRENT_LIST_DIR}/ClientApp.h ${CMAKE_CURRENT_LIST_DIR}/ClientFSMEvents.h + ${CMAKE_CURRENT_LIST_DIR}/ClientNetworking.h PRIVATE ${CMAKE_CURRENT_LIST_DIR}/ClientApp.cpp ${CMAKE_CURRENT_LIST_DIR}/ClientFSMEvents.cpp + ${CMAKE_CURRENT_LIST_DIR}/ClientNetworking.cpp ) endif() diff --git a/client/ClientApp.cpp b/client/ClientApp.cpp index 33db75fe537..16c92dd7a95 100644 --- a/client/ClientApp.cpp +++ b/client/ClientApp.cpp @@ -7,7 +7,7 @@ #include "../Empire/Empire.h" #include "../Empire/EmpireManager.h" #include "../network/Networking.h" -#include "../network/ClientNetworking.h" +#include "ClientNetworking.h" #include #include @@ -15,15 +15,11 @@ ClientApp::ClientApp() : IApp(), - m_universe(), m_networking(std::make_shared()), m_empire_id(ALL_EMPIRES), m_current_turn(INVALID_GAME_TURN) {} -ClientApp::~ClientApp() -{} - int ClientApp::PlayerID() const { return m_networking->PlayerID(); } @@ -48,6 +44,9 @@ const GalaxySetupData& ClientApp::GetGalaxySetupData() const EmpireManager& ClientApp::Empires() { return m_empires; } +const EmpireManager& ClientApp::Empires() const +{ return m_empires; } + Empire* ClientApp::GetEmpire(int empire_id) { return m_empires.GetEmpire(empire_id); } @@ -55,7 +54,7 @@ SupplyManager& ClientApp::GetSupplyManager() { return m_supply_manager; } std::shared_ptr ClientApp::GetUniverseObject(int object_id) -{ return GetUniverse().Objects().Object(object_id); } +{ return GetUniverse().Objects().get(object_id); } ObjectMap& ClientApp::EmpireKnownObjects(int empire_id) { // observers and moderators should have accurate info about what each empire knows @@ -68,7 +67,7 @@ ObjectMap& ClientApp::EmpireKnownObjects(int empire_id) { } std::shared_ptr ClientApp::EmpireKnownObject(int object_id, int empire_id) -{ return EmpireKnownObjects(empire_id).Object(object_id); } +{ return EmpireKnownObjects(empire_id).get(object_id); } const OrderSet& ClientApp::Orders() const { return m_orders; } @@ -77,7 +76,7 @@ const ClientNetworking& ClientApp::Networking() const { return *m_networking; } int ClientApp::EmpirePlayerID(int empire_id) const { - for (const std::map::value_type& entry : m_player_info) + for (const auto& entry : m_player_info) if (entry.second.empire_id == empire_id) return entry.first; return Networking::INVALID_PLAYER_ID; @@ -89,7 +88,7 @@ Networking::ClientType ClientApp::GetEmpireClientType(int empire_id) const Networking::ClientType ClientApp::GetPlayerClientType(int player_id) const { if (player_id == Networking::INVALID_PLAYER_ID) return Networking::INVALID_CLIENT_TYPE; - std::map::const_iterator it = m_player_info.find(player_id); + auto it = m_player_info.find(player_id); if (it != m_player_info.end()) return it->second.client_type; return Networking::INVALID_CLIENT_TYPE; @@ -104,21 +103,27 @@ const std::map& ClientApp::Players() const std::map& ClientApp::Players() { return m_player_info; } -const std::map& ClientApp::PlayerStatus() const -{ return m_player_status; } +void ClientApp::SetEmpireStatus(int empire_id, Message::PlayerStatus status) { + if (auto* empire = m_empires.GetEmpire(empire_id)) + empire->SetReady(status == Message::WAITING); +} -std::map& ClientApp::PlayerStatus() -{ return m_player_status; } +void ClientApp::StartTurn(const SaveGameUIData& ui_data) +{ m_networking->SendMessage(TurnOrdersMessage(m_orders, ui_data)); } -void ClientApp::SetPlayerStatus(int player_id, Message::PlayerStatus status) { - if (player_id == Networking::INVALID_PLAYER_ID) +void ClientApp::StartTurn(const std::string& save_state_string) +{ m_networking->SendMessage(TurnOrdersMessage(m_orders, save_state_string)); } + +void ClientApp::SendPartialOrders() { + if (!m_networking || !m_networking->IsTxConnected()) + return; + auto changes = m_orders.ExtractChanges(); + if (changes.first.empty() && changes.second.empty()) return; - m_player_status[player_id] = status; + m_networking->SendMessage(TurnPartialOrdersMessage(changes)); } -void ClientApp::StartTurn() { - m_networking->SendMessage(TurnOrdersMessage(m_orders)); - m_orders.Reset(); +void ClientApp::HandleTurnPhaseUpdate(Message::TurnProgressPhase phase_id) { } OrderSet& ClientApp::Orders() @@ -134,7 +139,7 @@ std::string ClientApp::GetVisibleObjectName(std::shared_ptrPublicName(m_empire_id); - if (std::shared_ptr system = std::dynamic_pointer_cast(object)) + if (auto system = std::dynamic_pointer_cast(object)) name_text = system->ApparentName(m_empire_id); return name_text; @@ -148,3 +153,26 @@ void ClientApp::SetEmpireID(int empire_id) void ClientApp::SetCurrentTurn(int turn) { m_current_turn = turn; } + +bool ClientApp::VerifyCheckSum(const Message& msg) { + std::map server_checksums; + ExtractContentCheckSumMessageData(msg, server_checksums); + + auto client_checksums = CheckSumContent(); + + if (server_checksums == client_checksums) { + InfoLogger() << "Checksum received from server matches client checksum."; + return true; + } else { + WarnLogger() << "Checksum received from server does not match client checksum."; + for (const auto& name_and_checksum : server_checksums) { + const auto& name = name_and_checksum.first; + const auto client_checksum = client_checksums[name]; + if (client_checksum != name_and_checksum.second) + WarnLogger() << "Checksum for " << name << " on server " + << name_and_checksum.second << " != client " + << client_checksum; + } + return false; + } +} diff --git a/client/ClientApp.h b/client/ClientApp.h index ec6829ecc56..e8542c295a2 100644 --- a/client/ClientApp.h +++ b/client/ClientApp.h @@ -20,10 +20,16 @@ class ClientNetworking; * being run in. */ class ClientApp : public IApp { -public: +protected: ClientApp(); - virtual ~ClientApp(); +public: + ClientApp(const ClientApp&) = delete; + ClientApp(ClientApp&&) = delete; + ~ClientApp() override = default; + + const ClientApp& operator=(const ClientApp&) = delete; + ClientApp& operator=(ClientApp&&) = delete; /** @brief Return the player identifier of this client * @@ -62,16 +68,6 @@ class ClientApp : public IApp { const std::map& Players() const; /** @} */ - /** @brief Return the player statuses in game - * - * @return Return a map containing PlayerStatus instances as value and - * their player identifier as key. - * - * @{ */ - std::map& PlayerStatus(); - const std::map& PlayerStatus() const; - /** @} */ - /** @brief Return the ::Universe known to this client * * @return A reference to the single ::Universe instance representing @@ -152,15 +148,27 @@ class ClientApp : public IApp { */ std::string GetVisibleObjectName(std::shared_ptr object) override; - /** @brief Send the OrderSet to the server and start a new turn */ - virtual void StartTurn(); + /** @brief Send the OrderSet and UI data to the server and start a new turn */ + virtual void StartTurn(const SaveGameUIData& ui_data); + + /** @brief Send the OrderSet and AI state to the server and start a new turn */ + virtual void StartTurn(const std::string& save_state_string); + + /** @brief Send turn orders updates to server without starting new turn */ + void SendPartialOrders(); + + /** \brief Handle server acknowledgement of receipt of orders and clear + the orders. */ + virtual void HandleTurnPhaseUpdate(Message::TurnProgressPhase phase_id); /** @brief Return the set of known Empire s for this client * * @return The EmpireManager instance in charge of maintaining the Empire * object instances. - */ + * @{ */ EmpireManager& Empires() override; + const EmpireManager& Empires() const; + /** @} */ /** @brief Return the Empire identified by @a empire_id * @@ -212,20 +220,13 @@ class ClientApp : public IApp { */ virtual void HandleTurnUpdate() {} - /** @brief Set whether the current game is a single player game or not - * - * @param single_player Set true if the game is a single player game, - * false if not. - */ - void SetSinglePlayerGame(bool single_player = true); - - /** @brief Set the Message::PlayerStatus @a status for @a player_id + /** @brief Set the Message::PlayerStatus @a status for @a empire_id * - * @param player_id A player identifier. + * @param player_id A empire identifier. * @param status The new Message::PlayerStatus of the player identified by - * @a player_id. + * @a empire_id. */ - void SetPlayerStatus(int player_id, Message::PlayerStatus status); + void SetEmpireStatus(int empire_id, Message::PlayerStatus status); /** @brief Return the singleton instance of this Application * @@ -233,6 +234,12 @@ class ClientApp : public IApp { */ static ClientApp* GetApp(); + /** @brief Compare content checksum from server with client content checksum. + * + * @return true if verify successed. + */ + bool VerifyCheckSum(const Message& msg); + protected: Universe m_universe; GalaxySetupData m_galaxy_setup_data; @@ -245,15 +252,6 @@ class ClientApp : public IApp { /** Indexed by player id, contains info about all players in the game */ std::map m_player_info; - /** Indexed by player id, contains the last known PlayerStatus for each - * player. - */ - std::map - m_player_status; - -private: - const ClientApp& operator=(const ClientApp&); // disabled - ClientApp(const ClientApp&); // disabled }; #endif // _ClientApp_h_ diff --git a/client/ClientFSMEvents.h b/client/ClientFSMEvents.h index bc2e0253e9a..1f5541756e9 100644 --- a/client/ClientFSMEvents.h +++ b/client/ClientFSMEvents.h @@ -28,18 +28,22 @@ struct MessageEventBase { (HostID) \ (LobbyUpdate) \ (LobbyChat) \ - (SaveGameDataRequest) \ (SaveGameComplete) \ (GameStart) \ (TurnUpdate) \ (TurnPartialUpdate) \ (TurnProgress) \ + (TurnRevoked) \ (PlayerStatus) \ (PlayerChat) \ (Diplomacy) \ (DiplomaticStatusUpdate) \ (EndGame) \ - (CheckSum) + (CheckSum) \ + (AuthRequest) \ + (ChatHistory) \ + (TurnTimeout) \ + (PlayerInfoMsg) #define DECLARE_MESSAGE_EVENT(r, data, name) \ diff --git a/network/ClientNetworking.cpp b/client/ClientNetworking.cpp similarity index 85% rename from network/ClientNetworking.cpp rename to client/ClientNetworking.cpp index 3d5dfcd24c9..792ac1ac0ac 100644 --- a/network/ClientNetworking.cpp +++ b/client/ClientNetworking.cpp @@ -2,8 +2,8 @@ #include "ClientNetworking.h" -#include "Message.h" -#include "MessageQueue.h" +#include "../network/Message.h" +#include "../network/MessageQueue.h" // boost::asio pulls in windows.h which in turn defines the macros GetMessage, // SendMessage, min and max. Disabling the generation of the min and max macros @@ -17,9 +17,8 @@ # undef SendMessage #endif -#include "Networking.h" +#include "../network/Networking.h" #include "../util/Logger.h" -#include "../util/MultiplayerCommon.h" #include "../util/OptionsDB.h" #include @@ -34,6 +33,16 @@ using boost::asio::ip::tcp; using namespace Networking; +/** In Boost 1.66, io_service was replaced with a typedef of io_context. + * That typedef was removed in Boost 1.70 along with other interface changes. + * This code uses io_context for future compatibility and adds the typedef + * here for old versions of Boost. */ +#if BOOST_VERSION < 106600 +namespace boost { namespace asio { + typedef io_service io_context; +}} +#endif + namespace { DeclareThreadSafeLogger(network); @@ -43,10 +52,10 @@ namespace { public: using ServerList = std::vector>; - ServerDiscoverer(boost::asio::io_service& io_service) : - m_io_service(&io_service), - m_timer(io_service), - m_socket(io_service), + ServerDiscoverer(boost::asio::io_context& io_context) : + m_io_context(&io_context), + m_timer(io_context), + m_socket(io_context), m_recv_buf(), m_receive_successful(false), m_server_name() @@ -57,7 +66,7 @@ namespace { void DiscoverServers() { using namespace boost::asio::ip; - udp::resolver resolver(*m_io_service); + udp::resolver resolver(*m_io_context); udp::resolver::query query(udp::v4(), "255.255.255.255", std::to_string(Networking::DiscoveryPort()), resolver_query_base::address_configured | @@ -80,10 +89,14 @@ namespace { this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); +#if BOOST_VERSION >= 106600 + m_timer.expires_after(std::chrono::seconds(2)); +#else m_timer.expires_from_now(std::chrono::seconds(2)); +#endif m_timer.async_wait(boost::bind(&ServerDiscoverer::CloseSocket, this)); - m_io_service->run(); - m_io_service->reset(); + m_io_context->run(); + m_io_context->reset(); if (m_receive_successful) { boost::asio::ip::address address = m_server_name == "localhost" ? boost::asio::ip::address::from_string("127.0.0.1") : @@ -91,7 +104,7 @@ namespace { m_servers.push_back({address, m_server_name}); } m_receive_successful = false; - m_server_name = ""; + m_server_name.clear(); } } @@ -122,16 +135,16 @@ namespace { void CloseSocket() { m_socket.close(); } - boost::asio::io_service* m_io_service; - boost::asio::high_resolution_timer m_timer; - boost::asio::ip::udp::socket m_socket; + boost::asio::io_context* m_io_context; + boost::asio::high_resolution_timer m_timer; + boost::asio::ip::udp::socket m_socket; - std::array m_recv_buf; + std::array m_recv_buf; - boost::asio::ip::udp::endpoint m_sender_endpoint; - bool m_receive_successful; - std::string m_server_name; - ServerList m_servers; + boost::asio::ip::udp::endpoint m_sender_endpoint; + bool m_receive_successful; + std::string m_server_name; + ServerList m_servers; }; } @@ -164,6 +177,12 @@ class ClientNetworking::Impl { /** Returns whether the indicated player ID is the host. */ bool PlayerIsHost(int player_id) const; + + /** Checks if the client has some authorization \a role. */ + bool HasAuthRole(Networking::RoleType role) const; + + /** Returns destination address of server. */ + const std::string& Destination() const; //@} /** \name Mutators */ //@{ @@ -172,16 +191,20 @@ class ClientNetworking::Impl { ClientNetworking::ServerNames DiscoverLANServerNames(); /** Connects to the server at \a ip_address. On failure, repeated - attempts will be made until \a timeout seconds has elapsed. */ + attempts will be made until \a timeout seconds has elapsed. If \p + expect_timeout is true, timeout is not reported as an error. */ bool ConnectToServer(const ClientNetworking* const self, const std::string& ip_address, - const std::chrono::milliseconds& timeout = std::chrono::seconds(10)); + const std::chrono::milliseconds& timeout = std::chrono::seconds(10), + bool expect_timeout = false); /** Connects to the server on the client's host. On failure, repeated - attempts will be made until \a timeout seconds has elapsed. */ - bool ConnectToLocalHostServer(const ClientNetworking* const self, - const std::chrono::milliseconds& timeout = - std::chrono::seconds(10)); + attempts will be made until \a timeout seconds has elapsed. If \p + expect_timeout is true, timeout is not reported as an error.*/ + bool ConnectToLocalHostServer( + const ClientNetworking* const self, + const std::chrono::milliseconds& timeout = std::chrono::seconds(10), + bool expect_timeout = false); /** Sends \a message to the server. This function actually just enqueues the message for sending and returns immediately. */ @@ -191,10 +214,6 @@ class ClientNetworking::Impl { Remove the message from the incoming message queue. */ boost::optional GetMessage(); - /** Sends \a message to the server, then blocks until it sees the first - synchronous response from the server. */ - boost::optional SendSynchronousMessage(const Message& message); - /** Disconnects the client from the server. */ void DisconnectFromServer(); @@ -203,13 +222,16 @@ class ClientNetworking::Impl { /** Sets Host player ID. */ void SetHostPlayerID(int host_player_id); + + /** Get authorization roles access. */ + Networking::AuthRoles& AuthorizationRoles(); //@} private: void HandleException(const boost::system::system_error& error); void HandleConnection(boost::asio::ip::tcp::resolver::iterator* it, const boost::system::error_code& error); - void CancelRetries(); + void NetworkingThread(const std::shared_ptr self); void HandleMessageBodyRead(const std::shared_ptr& keep_alive, boost::system::error_code error, std::size_t bytes_transferred); @@ -223,8 +245,9 @@ class ClientNetworking::Impl { int m_player_id; int m_host_player_id; + Networking::AuthRoles m_roles; - boost::asio::io_service m_io_service; + boost::asio::io_context m_io_context; boost::asio::ip::tcp::socket m_socket; // m_mutex guards m_incoming_message, m_rx_connected and m_tx_connected which are written by @@ -242,6 +265,8 @@ class ClientNetworking::Impl { Message::HeaderBuffer m_incoming_header; Message m_incoming_message; Message::HeaderBuffer m_outgoing_header; + + std::string m_destination; }; @@ -251,11 +276,11 @@ class ClientNetworking::Impl { ClientNetworking::Impl::Impl() : m_player_id(Networking::INVALID_PLAYER_ID), m_host_player_id(Networking::INVALID_PLAYER_ID), - m_io_service(), - m_socket(m_io_service), + m_io_context(), + m_socket(m_io_context), m_rx_connected(false), m_tx_connected(false), - m_incoming_messages(m_mutex, m_rx_connected) + m_incoming_messages(m_mutex) {} bool ClientNetworking::Impl::IsConnected() const { @@ -285,10 +310,13 @@ bool ClientNetworking::Impl::PlayerIsHost(int player_id) const { return player_id == m_host_player_id; } +bool ClientNetworking::Impl::HasAuthRole(Networking::RoleType role) const +{ return m_roles.HasRole(role); } + ClientNetworking::ServerNames ClientNetworking::Impl::DiscoverLANServerNames() { if (!IsConnected()) return ServerNames(); - ServerDiscoverer discoverer(m_io_service); + ServerDiscoverer discoverer(m_io_context); discoverer.DiscoverServers(); ServerNames names; for (const auto& server : discoverer.Servers()) { @@ -297,17 +325,21 @@ ClientNetworking::ServerNames ClientNetworking::Impl::DiscoverLANServerNames() { return names; } +const std::string& ClientNetworking::Impl::Destination() const +{ return m_destination; } + bool ClientNetworking::Impl::ConnectToServer( const ClientNetworking* const self, const std::string& ip_address, - const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/) + const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/, + bool expect_timeout /*=false*/) { using Clock = std::chrono::high_resolution_clock; Clock::time_point start_time = Clock::now(); auto deadline = start_time + timeout; using namespace boost::asio::ip; - tcp::resolver resolver(m_io_service); + tcp::resolver resolver(m_io_context); tcp::resolver::query query(ip_address, std::to_string(Networking::MessagePort()), boost::asio::ip::resolver_query_base::numeric_service); @@ -324,13 +356,18 @@ bool ClientNetworking::Impl::ConnectToServer( try { while(!IsConnected() && Clock::now() < deadline) { for (tcp::resolver::iterator it = resolver.resolve(query); it != end_it; ++it) { - m_socket.close(); + try { + m_socket.close(); + } catch (const std::exception& e) { + ErrorLogger(network) << "ConnectToServer() : unable to close socket due to exception: " << e.what(); + m_socket = boost::asio::ip::tcp::socket(m_io_context); + } m_socket.async_connect(*it, boost::bind(&ClientNetworking::Impl::HandleConnection, this, &it, boost::asio::placeholders::error)); - m_io_service.run(); - m_io_service.reset(); + m_io_context.run(); + m_io_context.reset(); auto connection_time = Clock::now() - start_time; @@ -340,7 +377,7 @@ bool ClientNetworking::Impl::ConnectToServer( << " port: " << it->endpoint().port(); //DebugLogger(network) << "ConnectToServer() : Client using " - // << ((GetOptionsDB().Get("binary-serialization")) ? "binary": "xml") + // << ((GetOptionsDB().Get("save.format.binary.enabled")) ? "binary": "xml") // << " serialization."; // Prepare the socket @@ -369,7 +406,7 @@ bool ClientNetworking::Impl::ConnectToServer( TraceLogger(network) << "Failed to connect to host_name: " << it->host_name() << " address: " << it->endpoint().address() << " port: " << it->endpoint().port(); - if (timeout < connection_time) { + if (timeout < connection_time && !expect_timeout) { ErrorLogger(network) << "Timed out (" << std::chrono::duration_cast(connection_time).count() << " ms." << ") attempting to connect to server."; @@ -384,18 +421,21 @@ bool ClientNetworking::Impl::ConnectToServer( ErrorLogger(network) << "ConnectToServer() : unable to connect to server at " << ip_address << " due to exception: " << e.what(); } + if (IsConnected()) + m_destination = ip_address; return IsConnected(); } bool ClientNetworking::Impl::ConnectToLocalHostServer( const ClientNetworking* const self, - const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/) + const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/, + bool expect_timeout /*=false*/) { bool retval = false; #if FREEORION_WIN32 try { #endif - retval = ConnectToServer(self, "127.0.0.1", timeout); + retval = ConnectToServer(self, "127.0.0.1", timeout, expect_timeout); #if FREEORION_WIN32 } catch (const boost::system::system_error& e) { if (e.code().value() != WSAEADDRNOTAVAIL) @@ -414,7 +454,7 @@ void ClientNetworking::Impl::DisconnectFromServer() { } if (is_open) - m_io_service.post(boost::bind(&ClientNetworking::Impl::DisconnectFromServerImpl, this)); + m_io_context.post(boost::bind(&ClientNetworking::Impl::DisconnectFromServerImpl, this)); } void ClientNetworking::Impl::SetPlayerID(int player_id) { @@ -425,13 +465,16 @@ void ClientNetworking::Impl::SetPlayerID(int player_id) { void ClientNetworking::Impl::SetHostPlayerID(int host_player_id) { m_host_player_id = host_player_id; } +Networking::AuthRoles& ClientNetworking::Impl::AuthorizationRoles() +{ return m_roles; } + void ClientNetworking::Impl::SendMessage(const Message& message) { if (!IsTxConnected()) { ErrorLogger(network) << "ClientNetworking::SendMessage can't send message when not transmit connected"; return; } TraceLogger(network) << "ClientNetworking::SendMessage() : sending message " << message; - m_io_service.post(boost::bind(&ClientNetworking::Impl::SendMessageImpl, this, message)); + m_io_context.post(boost::bind(&ClientNetworking::Impl::SendMessageImpl, this, message)); } boost::optional ClientNetworking::Impl::GetMessage() { @@ -442,20 +485,6 @@ boost::optional ClientNetworking::Impl::GetMessage() { return message; } -boost::optional ClientNetworking::Impl::SendSynchronousMessage(const Message& message) { - TraceLogger(network) << "ClientNetworking::SendSynchronousMessage : sending message " - << message; - SendMessage(message); - // note that this is a blocking operation - auto response_message = m_incoming_messages.GetFirstSynchronousMessage(); - - if (response_message) - TraceLogger(network) << "ClientNetworking::SendSynchronousMessage : received " - << "response message " << *response_message; - - return response_message; -} - void ClientNetworking::Impl::HandleConnection(tcp::resolver::iterator* it, const boost::system::error_code& error) { @@ -494,13 +523,12 @@ void ClientNetworking::Impl::NetworkingThread(const std::shared_ptrPlayerIsHost(player_id); } +bool ClientNetworking::HasAuthRole(Networking::RoleType role) const +{ return m_impl->HasAuthRole(role); } + +const std::string& ClientNetworking::Destination() const +{ return m_impl->Destination(); } + ClientNetworking::ServerNames ClientNetworking::DiscoverLANServerNames() { return m_impl->DiscoverLANServerNames(); } @@ -679,6 +713,15 @@ bool ClientNetworking::ConnectToLocalHostServer( const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/) { return m_impl->ConnectToLocalHostServer(this, timeout); } +bool ClientNetworking::PingServer( + const std::string& ip_address, + const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/) +{ return m_impl->ConnectToServer(this, ip_address, timeout, true /*expect_timeout*/); } + +bool ClientNetworking::PingLocalHostServer( + const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/) +{ return m_impl->ConnectToLocalHostServer(this, timeout, true /*expect_timeout*/); } + void ClientNetworking::DisconnectFromServer() { return m_impl->DisconnectFromServer(); } @@ -688,11 +731,11 @@ void ClientNetworking::SetPlayerID(int player_id) void ClientNetworking::SetHostPlayerID(int host_player_id) { return m_impl->SetHostPlayerID(host_player_id); } +Networking::AuthRoles& ClientNetworking::AuthorizationRoles() +{ return m_impl->AuthorizationRoles(); } + void ClientNetworking::SendMessage(const Message& message) { return m_impl->SendMessage(message); } boost::optional ClientNetworking::GetMessage() { return m_impl->GetMessage(); } - -boost::optional ClientNetworking::SendSynchronousMessage(const Message& message) -{ return m_impl->SendSynchronousMessage(message); } diff --git a/network/ClientNetworking.h b/client/ClientNetworking.h similarity index 71% rename from network/ClientNetworking.h rename to client/ClientNetworking.h index cd95be2fdce..8c6316c00d5 100644 --- a/network/ClientNetworking.h +++ b/client/ClientNetworking.h @@ -12,10 +12,15 @@ class Message; +namespace Networking { + class AuthRoles; + + enum RoleType : size_t; +} + /** Encapsulates the networking facilities of the client. The client must execute its networking code in a separate thread from its main processing - thread, for UI and networking responsiveness, and to process synchronous - communication with the server. + thread, for UI and networking responsiveness. Because of this, ClientNetworking operates in two threads: the main thread, in which the UI processing operates; the networking thread, which @@ -27,22 +32,10 @@ class Message; applies to unintentional disconnects from the server. The client must periodically check IsConnected(). - The ClientNetworking has three modes of operation. First, it can discover + The ClientNetworking has two modes of operation. First, it can discover FreeOrion servers on the local network; this is a blocking operation with a timeout. Second, it can send an asynchronous message to the server - (when connected); this is a non-blocking operation. Third, it can send a - synchronous message to the server (when connected) and return the server's - response; this is a blocking operation. - - Note that the SendSynchronousMessage() does not interrupt the sending or - receiving of asynchronous messages. When SendSynchronousMessage() is - called and the main thread blocks to wait for the response message, - regular messages are still being sent and received. Some of these regular - messages may arrive before the response message, but - SendSynchronousMessage() will still return the response message first, - even if it is not at the front of the queue at the time. This implies - that some response messages may be handled out of order with respect to - regular messages, but these are in fact the desired semantics. */ + (when connected); this is a non-blocking operation.*/ class ClientNetworking : public std::enable_shared_from_this { public: /** The type of list returned by a call to DiscoverLANServers(). */ @@ -71,6 +64,12 @@ class ClientNetworking : public std::enable_shared_from_this { /** Returns whether the indicated player ID is the host. */ bool PlayerIsHost(int player_id) const; + + /** Checks if the client has some authorization \a role. */ + bool HasAuthRole(Networking::RoleType role) const; + + /** Returns address of multiplayer server entered by player. */ + const std::string& Destination() const; //@} /** \name Mutators */ //@{ @@ -85,8 +84,17 @@ class ClientNetworking : public std::enable_shared_from_this { /** Connects to the server on the client's host. On failure, repeated attempts will be made until \a timeout seconds has elapsed. */ - bool ConnectToLocalHostServer(const std::chrono::milliseconds& timeout = - std::chrono::seconds(10)); + bool ConnectToLocalHostServer( + const std::chrono::milliseconds& timeout = std::chrono::seconds(10)); + + /** Return true if the server can be connected to within \p timeout seconds. */ + bool PingServer( + const std::string& ip_address, + const std::chrono::milliseconds& timeout = std::chrono::seconds(10)); + + /** Return true if the local server can be connected to within \p timeout seconds. */ + bool PingLocalHostServer( + const std::chrono::milliseconds& timeout = std::chrono::seconds(10)); /** Sends \a message to the server. This function actually just enqueues the message for sending and returns immediately. */ @@ -96,10 +104,6 @@ class ClientNetworking : public std::enable_shared_from_this { Remove the message from the incoming message queue. */ boost::optional GetMessage(); - /** Sends \a message to the server, then blocks until it sees the first - synchronous response from the server. */ - boost::optional SendSynchronousMessage(const Message& message); - /** Disconnects the client from the server. First tries to send any pending transmit messages. */ void DisconnectFromServer(); @@ -108,6 +112,9 @@ class ClientNetworking : public std::enable_shared_from_this { /** Sets Host player ID. */ void SetHostPlayerID(int host_player_id); + + /** Access to client's authorization roles */ + Networking::AuthRoles& AuthorizationRoles(); //@} private: @@ -115,4 +122,4 @@ class ClientNetworking : public std::enable_shared_from_this { std::unique_ptr const m_impl; }; -#endif +#endif // _ClientNetworking_h_ diff --git a/client/human/CMakeLists.txt b/client/human/CMakeLists.txt index 3a1c2ac5acb..cee7db6b11f 100644 --- a/client/human/CMakeLists.txt +++ b/client/human/CMakeLists.txt @@ -9,6 +9,19 @@ target_sources(freeorion ${CMAKE_CURRENT_LIST_DIR}/HumanClientFSM.cpp # Add icon resource file to freeorion.exe $<$:${CMAKE_CURRENT_LIST_DIR}/FreeOrion.rc> + $<$:${CMAKE_CURRENT_LIST_DIR}/FreeOrion.ico> + $<$:${CMAKE_CURRENT_LIST_DIR}/chmain.mm> $<$:${CMAKE_CURRENT_LIST_DIR}/GUIController.mm> + # Add icon and menu resource files to FreeOrion.app + $<$:${CMAKE_CURRENT_LIST_DIR}/main.xib> + $<$:${CMAKE_CURRENT_LIST_DIR}/FreeOrion.icns> +) + +set_property( + SOURCE + ${CMAKE_CURRENT_LIST_DIR}/main.xib + ${CMAKE_CURRENT_LIST_DIR}/FreeOrion.icns + PROPERTY + MACOSX_PACKAGE_LOCATION "Resources/" ) diff --git a/Xcode/FreeOrion.icns b/client/human/FreeOrion.icns similarity index 100% rename from Xcode/FreeOrion.icns rename to client/human/FreeOrion.icns diff --git a/FreeOrion.ico b/client/human/FreeOrion.ico similarity index 100% rename from FreeOrion.ico rename to client/human/FreeOrion.ico diff --git a/client/human/FreeOrion.rc b/client/human/FreeOrion.rc index 2eea848b19b..d82a84ce6fc 100644 --- a/client/human/FreeOrion.rc +++ b/client/human/FreeOrion.rc @@ -1 +1 @@ -IDI_ICON1 ICON "..\\..\\FreeOrion.ico" +IDI_ICON1 ICON "FreeOrion.ico" diff --git a/client/human/HumanClientApp.cpp b/client/human/HumanClientApp.cpp index 6c886e6388a..e33e40eb199 100644 --- a/client/human/HumanClientApp.cpp +++ b/client/human/HumanClientApp.cpp @@ -4,6 +4,7 @@ #include "HumanClientApp.h" #include "HumanClientFSM.h" +#include "../../UI/ChatWnd.h" #include "../../UI/CUIControls.h" #include "../../UI/CUIStyle.h" #include "../../UI/MapWnd.h" @@ -16,11 +17,10 @@ #include "../../UI/ServerConnectWnd.h" #include "../../UI/Sound.h" #include "../../network/Message.h" -#include "../../network/Networking.h" -#include "../../network/ClientNetworking.h" +#include "../ClientNetworking.h" #include "../../util/i18n.h" #include "../../util/LoggerWithOptionsDB.h" -#include "../../util/MultiplayerCommon.h" +#include "../../util/GameRules.h" #include "../../util/OptionsDB.h" #include "../../util/Process.h" #include "../../util/SaveGamePreviewUtils.h" @@ -28,6 +28,7 @@ #include "../../util/SitRepEntry.h" #include "../../util/Directories.h" #include "../../util/Version.h" +#include "../../util/ScopedTimer.h" #include "../../universe/Planet.h" #include "../../universe/Species.h" #include "../../universe/Enums.h" @@ -40,13 +41,18 @@ #include #include +#include #include #include #include #include #include +#include #include #include +#include +#include +#include #include #include @@ -94,19 +100,22 @@ namespace { // command-line options void AddOptions(OptionsDB& db) { - db.Add("autosave.single-player", UserStringNop("OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER"), true, Validator()); - db.Add("autosave.multiplayer", UserStringNop("OPTIONS_DB_AUTOSAVE_MULTIPLAYER"), true, Validator()); - db.Add("autosave.turns", UserStringNop("OPTIONS_DB_AUTOSAVE_TURNS"), 1, RangedValidator(1, 50)); - db.Add("autosave.limit", UserStringNop("OPTIONS_DB_AUTOSAVE_LIMIT"), 10, RangedValidator(1, 100)); - db.Add("UI.swap-mouse-lr", UserStringNop("OPTIONS_DB_UI_MOUSE_LR_SWAP"), false); - db.Add("UI.keypress-repeat-delay", UserStringNop("OPTIONS_DB_KEYPRESS_REPEAT_DELAY"), 360, RangedValidator(0, 1000)); - db.Add("UI.keypress-repeat-interval", UserStringNop("OPTIONS_DB_KEYPRESS_REPEAT_INTERVAL"), 20, RangedValidator(0, 1000)); - db.Add("UI.mouse-click-repeat-delay", UserStringNop("OPTIONS_DB_MOUSE_REPEAT_DELAY"), 360, RangedValidator(0, 1000)); - db.Add("UI.mouse-click-repeat-interval",UserStringNop("OPTIONS_DB_MOUSE_REPEAT_INTERVAL"), 15, RangedValidator(0, 1000)); - - Hotkey::AddHotkey("exit", UserStringNop("HOTKEY_EXIT"), GG::GGK_NONE, GG::MOD_KEY_NONE); - Hotkey::AddHotkey("quit", UserStringNop("HOTKEY_QUIT"), GG::GGK_NONE, GG::MOD_KEY_NONE); - Hotkey::AddHotkey("fullscreen", UserStringNop("HOTKEY_FULLSCREEN"), GG::GGK_RETURN, GG::MOD_KEY_ALT); + db.Add("save.auto.turn.start.enabled", UserStringNop("OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER_TURN_START"), true, Validator()); + db.Add("save.auto.turn.end.enabled", UserStringNop("OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER_TURN_END"), false, Validator()); + db.Add("save.auto.turn.multiplayer.start.enabled", UserStringNop("OPTIONS_DB_AUTOSAVE_MULTIPLAYER_TURN_START"), true, Validator()); + db.Add("save.auto.turn.interval", UserStringNop("OPTIONS_DB_AUTOSAVE_TURNS"), 1, RangedValidator(1, 50)); + db.Add("save.auto.file.limit", UserStringNop("OPTIONS_DB_AUTOSAVE_LIMIT"), 10, RangedValidator(1, 10000)); + db.Add("save.auto.initial.enabled", UserStringNop("OPTIONS_DB_AUTOSAVE_GALAXY_CREATION"), true, Validator()); + db.Add("ui.input.mouse.button.swap.enabled", UserStringNop("OPTIONS_DB_UI_MOUSE_LR_SWAP"), false); + db.Add("ui.input.keyboard.repeat.delay", UserStringNop("OPTIONS_DB_KEYPRESS_REPEAT_DELAY"), 360, RangedValidator(0, 1000)); + db.Add("ui.input.keyboard.repeat.interval", UserStringNop("OPTIONS_DB_KEYPRESS_REPEAT_INTERVAL"), 20, RangedValidator(0, 1000)); + db.Add("ui.input.mouse.button.repeat.delay", UserStringNop("OPTIONS_DB_MOUSE_REPEAT_DELAY"), 360, RangedValidator(0, 1000)); + db.Add("ui.input.mouse.button.repeat.interval", UserStringNop("OPTIONS_DB_MOUSE_REPEAT_INTERVAL"), 15, RangedValidator(0, 1000)); + db.Add("ui.map.messages.timestamp.shown", UserStringNop("OPTIONS_DB_DISPLAY_TIMESTAMP"), true, Validator()); + + Hotkey::AddHotkey("exit", UserStringNop("HOTKEY_EXIT"), GG::GGK_NONE, GG::MOD_KEY_NONE); + Hotkey::AddHotkey("quit", UserStringNop("HOTKEY_QUIT"), GG::GGK_NONE, GG::MOD_KEY_NONE); + Hotkey::AddHotkey("video.fullscreen", UserStringNop("HOTKEY_FULLSCREEN"), GG::GGK_RETURN, GG::MOD_KEY_ALT); } bool temp_bool = RegisterOptions(&AddOptions); @@ -120,16 +129,21 @@ namespace { const int MIN_WIDTH = 800; const int MIN_HEIGHT = 600; + /** Sets the default and current values for the string option @p option_name to @p option_value if initially empty */ + void SetEmptyStringDefaultOption(const std::string& option_name, const std::string& option_value) { + OptionsDB& db = GetOptionsDB(); + if (db.Get(option_name).empty()) { + db.SetDefault(option_name, option_value); + db.Set(option_name, option_value); + } + } + /* Sets the value of options that need language-dependent default values.*/ void SetStringtableDependentOptionDefaults() { - if (GetOptionsDB().Get("GameSetup.empire-name").empty()) - GetOptionsDB().Set("GameSetup.empire-name", UserString("DEFAULT_EMPIRE_NAME")); - - if (GetOptionsDB().Get("GameSetup.player-name").empty()) - GetOptionsDB().Set("GameSetup.player-name", UserString("DEFAULT_PLAYER_NAME")); - - if (GetOptionsDB().Get("multiplayersetup.player-name").empty()) - GetOptionsDB().Set("multiplayersetup.player-name", UserString("DEFAULT_PLAYER_NAME")); + SetEmptyStringDefaultOption("setup.empire.name", UserString("DEFAULT_EMPIRE_NAME")); + std::string player_name = UserString("DEFAULT_PLAYER_NAME"); + SetEmptyStringDefaultOption("setup.player.name", player_name); + SetEmptyStringDefaultOption("setup.multiplayer.player.name", player_name); } std::string GetGLVersionString() @@ -157,22 +171,22 @@ namespace { void SetGLVersionDependentOptionDefaults() { // get OpenGL version string and parse to get version number float version_number = GetGLVersion(); - DebugLogger() << "OpenGL Version Number: " << DoubleToString(version_number, 2, false); // combination of floating point precision and DoubleToString preferring to round down means the +0.05 is needed to round properly + DebugLogger() << "OpenGL Version Number: " << DoubleToString(version_number, 2, false); if (version_number < 2.0) { ErrorLogger() << "OpenGL Version is less than 2.0. FreeOrion may crash when trying to start a game."; } // only execute default option setting once - if (GetOptionsDB().Get("checked-gl-version")) + if (GetOptionsDB().Get("version.gl.check.done")) return; - GetOptionsDB().Set("checked-gl-version", true); + GetOptionsDB().Set("version.gl.check.done", true); // if GL version is too low, set various map rendering options to // disabled, to hopefully improve frame rate. if (version_number < 2.0) { - GetOptionsDB().Set("UI.galaxy-gas-background", false); - GetOptionsDB().Set("UI.galaxy-starfields", false); - GetOptionsDB().Set("UI.system-fog-of-war", false); + GetOptionsDB().Set("ui.map.background.gas.shown", false); + GetOptionsDB().Set("ui.map.background.starfields.shown", false); + GetOptionsDB().Set("ui.map.scanlines.shown", false); } } } @@ -181,24 +195,24 @@ void HumanClientApp::AddWindowSizeOptionsAfterMainStart(OptionsDB& db) { const int max_width_plus_one = HumanClientApp::MaximumPossibleWidth() + 1; const int max_height_plus_one = HumanClientApp::MaximumPossibleHeight() + 1; - db.Add("app-width", UserStringNop("OPTIONS_DB_APP_WIDTH"), DEFAULT_WIDTH, RangedValidator(MIN_WIDTH, max_width_plus_one)); - db.Add("app-height", UserStringNop("OPTIONS_DB_APP_HEIGHT"), DEFAULT_HEIGHT, RangedValidator(MIN_HEIGHT, max_height_plus_one)); - db.Add("app-width-windowed", UserStringNop("OPTIONS_DB_APP_WIDTH_WINDOWED"), DEFAULT_WIDTH, RangedValidator(MIN_WIDTH, max_width_plus_one)); - db.Add("app-height-windowed", UserStringNop("OPTIONS_DB_APP_HEIGHT_WINDOWED"), DEFAULT_HEIGHT, RangedValidator(MIN_HEIGHT, max_height_plus_one)); - db.Add("app-left-windowed", UserStringNop("OPTIONS_DB_APP_LEFT_WINDOWED"), DEFAULT_LEFT, OrValidator( RangedValidator(-max_width_plus_one, max_width_plus_one), DiscreteValidator(DEFAULT_LEFT) )); - db.Add("app-top-windowed", UserStringNop("OPTIONS_DB_APP_TOP_WINDOWED"), DEFAULT_TOP, RangedValidator(-max_height_plus_one, max_height_plus_one)); + db.Add("video.fullscreen.width", UserStringNop("OPTIONS_DB_APP_WIDTH"), DEFAULT_WIDTH, RangedValidator(MIN_WIDTH, max_width_plus_one)); + db.Add("video.fullscreen.height", UserStringNop("OPTIONS_DB_APP_HEIGHT"), DEFAULT_HEIGHT, RangedValidator(MIN_HEIGHT, max_height_plus_one)); + db.Add("video.windowed.width", UserStringNop("OPTIONS_DB_APP_WIDTH_WINDOWED"), DEFAULT_WIDTH, RangedValidator(MIN_WIDTH, max_width_plus_one)); + db.Add("video.windowed.height", UserStringNop("OPTIONS_DB_APP_HEIGHT_WINDOWED"), DEFAULT_HEIGHT, RangedValidator(MIN_HEIGHT, max_height_plus_one)); + db.Add("video.windowed.left", UserStringNop("OPTIONS_DB_APP_LEFT_WINDOWED"), DEFAULT_LEFT, OrValidator( RangedValidator(-max_width_plus_one, max_width_plus_one), DiscreteValidator(DEFAULT_LEFT) )); + db.Add("video.windowed.top", UserStringNop("OPTIONS_DB_APP_TOP_WINDOWED"), DEFAULT_TOP, RangedValidator(-max_height_plus_one, max_height_plus_one)); +} + +std::string HumanClientApp::EncodeServerAddressOption(const std::string& server) { + std::string server_encoded = boost::replace_all_copy(server, ".", "_"); + boost::replace_all(server_encoded, ":", "_"); + return "network.known-servers._" + server_encoded; } HumanClientApp::HumanClientApp(int width, int height, bool calculate_fps, const std::string& name, int x, int y, bool fullscreen, bool fake_mode_change) : ClientApp(), - SDLGUI(width, height, calculate_fps, name, x, y, fullscreen, fake_mode_change), - m_single_player_game(true), - m_game_started(false), - m_connected(false), - m_auto_turns(0), - m_have_window_focus(true), - m_save_game_in_progress(false) + SDLGUI(width, height, calculate_fps, name, x, y, fullscreen, fake_mode_change) { #ifdef ENABLE_CRASH_BACKTRACE signal(SIGSEGV, SigHandler); @@ -208,14 +222,17 @@ HumanClientApp::HumanClientApp(int width, int height, bool calculate_fps, const #endif m_fsm.reset(new HumanClientFSM(*this)); - const std::string HUMAN_CLIENT_LOG_FILENAME((GetUserDataDir() / "freeorion.log").string()); - + // Force the log file if requested. + if (GetOptionsDB().Get("log-file").empty()) { + const std::string HUMAN_CLIENT_LOG_FILENAME((GetUserDataDir() / "freeorion.log").string()); + GetOptionsDB().Set("log-file", HUMAN_CLIENT_LOG_FILENAME); + } // Force the log threshold if requested. auto force_log_level = GetOptionsDB().Get("log-level"); if (!force_log_level.empty()) OverrideAllLoggersThresholds(to_LogLevel(force_log_level)); - InitLoggingSystem(HUMAN_CLIENT_LOG_FILENAME, "Client"); + InitLoggingSystem(GetOptionsDB().Get("log-file"), "Client"); InitLoggingOptionsDBSystem(); // Force loggers to always appear in the config.xml and OptionsWnd even before their @@ -229,10 +246,14 @@ HumanClientApp::HumanClientApp(int width, int height, bool calculate_fps, const RegisterLoggerWithOptionsDB("server", true); RegisterLoggerWithOptionsDB("combat_log"); RegisterLoggerWithOptionsDB("combat"); + RegisterLoggerWithOptionsDB("supply"); RegisterLoggerWithOptionsDB("effects"); + RegisterLoggerWithOptionsDB("conditions"); RegisterLoggerWithOptionsDB("FSM"); RegisterLoggerWithOptionsDB("network"); RegisterLoggerWithOptionsDB("python"); + RegisterLoggerWithOptionsDB("timer"); + RegisterLoggerWithOptionsDB("IDallocator"); InfoLogger() << FreeOrionVersionString(); @@ -244,21 +265,34 @@ HumanClientApp::HumanClientApp(int width, int height, bool calculate_fps, const LogDependencyVersions(); + float version_number = GetGLVersion(); + if (version_number < 2.0f) { + ErrorLogger() << "OpenGL version is less than 2; FreeOrion will likely crash while starting up..."; + if (!GetOptionsDB().Get("version.gl.check.done")) { + auto mb_result = SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, UserString("OPENGL_VERSION_LOW_TITLE").c_str(), + UserString("OPENGL_VERSION_LOW_TEXT").c_str(), nullptr); + if (mb_result) + std::cerr << "OpenGL version is less than 2; FreeOrion will likely crash during initialization"; + } + } else if (version_number < 2.1f) { + ErrorLogger() << "OpenGL version is less than 2.1; FreeOrion may crash during initialization"; + } + SetStyleFactory(std::make_shared()); SetMinDragTime(0); bool inform_user_sound_failed(false); try { - if (GetOptionsDB().Get("UI.sound.enabled") || GetOptionsDB().Get("UI.sound.music-enabled")) + if (GetOptionsDB().Get("audio.effects.enabled") || GetOptionsDB().Get("audio.music.enabled")) Sound::GetSound().Enable(); - if ((GetOptionsDB().Get("UI.sound.music-enabled"))) - Sound::GetSound().PlayMusic(GetOptionsDB().Get("UI.sound.bg-music"), -1); + if ((GetOptionsDB().Get("audio.music.enabled"))) + Sound::GetSound().PlayMusic(GetOptionsDB().Get("audio.music.path"), -1); - Sound::GetSound().SetMusicVolume(GetOptionsDB().Get("UI.sound.music-volume")); - Sound::GetSound().SetUISoundsVolume(GetOptionsDB().Get("UI.sound.volume")); - } catch (Sound::InitializationFailureException const &) { + Sound::GetSound().SetMusicVolume(GetOptionsDB().Get("audio.music.volume")); + Sound::GetSound().SetUISoundsVolume(GetOptionsDB().Get("audio.effects.volume")); + } catch (const Sound::InitializationFailureException&) { inform_user_sound_failed = true; } @@ -266,9 +300,9 @@ HumanClientApp::HumanClientApp(int width, int height, bool calculate_fps, const EnableFPS(); UpdateFPSLimit(); - GetOptionsDB().OptionChangedSignal("show-fps").connect( + GetOptionsDB().OptionChangedSignal("video.fps.shown").connect( boost::bind(&HumanClientApp::UpdateFPSLimit, this)); - GetOptionsDB().OptionChangedSignal("max-fps").connect( + GetOptionsDB().OptionChangedSignal("video.fps.max").connect( boost::bind(&HumanClientApp::UpdateFPSLimit, this)); std::shared_ptr default_browse_info_wnd( @@ -278,52 +312,46 @@ HumanClientApp::HumanClientApp(int width, int height, bool calculate_fps, const GG::FORMAT_LEFT | GG::FORMAT_WORDBREAK, 1)); GG::Wnd::SetDefaultBrowseInfoWnd(default_browse_info_wnd); - std::shared_ptr cursor_texture = m_ui->GetTexture(ClientUI::ArtDir() / "cursors" / "default_cursor.png"); + auto cursor_texture = m_ui->GetTexture(ClientUI::ArtDir() / "cursors" / "default_cursor.png"); SetCursor(std::make_shared(cursor_texture, GG::Pt(GG::X(6), GG::Y(3)))); RenderCursor(true); - EnableKeyPressRepeat(GetOptionsDB().Get("UI.keypress-repeat-delay"), - GetOptionsDB().Get("UI.keypress-repeat-interval")); - EnableMouseButtonDownRepeat(GetOptionsDB().Get("UI.mouse-click-repeat-delay"), - GetOptionsDB().Get("UI.mouse-click-repeat-interval")); + EnableKeyPressRepeat(GetOptionsDB().Get("ui.input.keyboard.repeat.delay"), + GetOptionsDB().Get("ui.input.keyboard.repeat.interval")); + EnableMouseButtonDownRepeat(GetOptionsDB().Get("ui.input.mouse.button.repeat.delay"), + GetOptionsDB().Get("ui.input.mouse.button.repeat.interval")); EnableModalAcceleratorSignals(true); - WindowResizedSignal.connect( - boost::bind(&HumanClientApp::HandleWindowResize, this, _1, _2)); - FocusChangedSignal.connect( - boost::bind(&HumanClientApp::HandleFocusChange, this, _1)); - WindowMovedSignal.connect( - boost::bind(&HumanClientApp::HandleWindowMove, this, _1, _2)); - WindowClosingSignal.connect( - boost::bind(&HumanClientApp::HandleAppQuitting, this)); - AppQuittingSignal.connect( - boost::bind(&HumanClientApp::HandleAppQuitting, this)); + WindowResizedSignal.connect(boost::bind(&HumanClientApp::HandleWindowResize,this, _1, _2)); + FocusChangedSignal.connect( boost::bind(&HumanClientApp::HandleFocusChange, this, _1)); + WindowMovedSignal.connect( boost::bind(&HumanClientApp::HandleWindowMove, this, _1, _2)); + WindowClosingSignal.connect(boost::bind(&HumanClientApp::HandleAppQuitting, this)); + AppQuittingSignal.connect( boost::bind(&HumanClientApp::HandleAppQuitting, this)); SetStringtableDependentOptionDefaults(); SetGLVersionDependentOptionDefaults(); - this->SetMouseLRSwapped(GetOptionsDB().Get("UI.swap-mouse-lr")); + this->SetMouseLRSwapped(GetOptionsDB().Get("ui.input.mouse.button.swap.enabled")); - std::map> named_key_maps; - parse::keymaps(named_key_maps); + auto named_key_maps = parse::keymaps(GetResourceDir() / "scripting/keymaps.inf"); TraceLogger() << "Keymaps:"; - for (std::map>::value_type& km : named_key_maps) { + for (auto& km : named_key_maps) { TraceLogger() << "Keymap name = \"" << km.first << "\""; - for (std::map::value_type& keys : km.second) + for (auto& keys : km.second) TraceLogger() << " " << char(keys.first) << " : " << char(keys.second); } - std::map>::const_iterator km_it = named_key_maps.find("TEST"); + auto km_it = named_key_maps.find("TEST"); if (km_it != named_key_maps.end()) { - const std::map int_key_map = km_it->second; + const auto int_key_map = km_it->second; std::map key_map; - for (const std::map::value_type& int_key : int_key_map) + for (const auto& int_key : int_key_map) { key_map[GG::Key(int_key.first)] = GG::Key(int_key.second); } this->SetKeyMap(key_map); } ConnectKeyboardAcceleratorSignals(); - InitAutoTurns(GetOptionsDB().Get("auto-advance-n-turns")); + m_auto_turns = GetOptionsDB().Get("auto-advance-n-turns"); if (fake_mode_change && !FramebuffersAvailable()) { ErrorLogger() << "Requested fake mode changes, but the framebuffer opengl extension is not available. Ignoring."; @@ -337,6 +365,11 @@ HumanClientApp::HumanClientApp(int width, int height, bool calculate_fps, const RegisterLinkTags(); m_fsm->initiate(); + + // Start parsing content + StartBackgroundParsing(); + GetOptionsDB().OptionChangedSignal("resource.path").connect( + boost::bind(&HumanClientApp::HandleResoureDirChange, this)); } void HumanClientApp::ConnectKeyboardAcceleratorSignals() { @@ -347,7 +380,7 @@ void HumanClientApp::ConnectKeyboardAcceleratorSignals() { NoModalWndsOpenCondition); hkm->Connect(boost::bind(&HumanClientApp::HandleHotkeyResetGame, this), "quit", NoModalWndsOpenCondition); - hkm->Connect(boost::bind(&HumanClientApp::ToggleFullscreen, this), "fullscreen", + hkm->Connect(boost::bind(&HumanClientApp::ToggleFullscreen, this), "video.fullscreen", NoModalWndsOpenCondition); hkm->RebuildShortcuts(); @@ -368,18 +401,11 @@ bool HumanClientApp::CanSaveNow() const { return false; // can't save while AIs are playing their turns... - for (const std::map::value_type& entry : m_player_info) { - const PlayerInfo& info = entry.second; - if (info.client_type != Networking::CLIENT_TYPE_AI_PLAYER) + for (const auto& entry : Empires()) { + if (GetEmpireClientType(entry.first) != Networking::CLIENT_TYPE_AI_PLAYER) continue; // only care about AIs - std::map::const_iterator - status_it = m_player_status.find(entry.first); - - if (status_it == this->m_player_status.end()) { - return false; // missing status for AI; can't assume it's ready - } - if (status_it->second != Message::WAITING) { + if (!entry.second->Ready()) { return false; } } @@ -393,7 +419,7 @@ void HumanClientApp::SetSinglePlayerGame(bool sp/* = true*/) namespace { std::string ServerClientExe() { #ifdef FREEORION_WIN32 - return PathString(GetBinDir() / "freeoriond.exe"); + return PathToString(GetBinDir() / "freeoriond.exe"); #else return (GetBinDir() / "freeoriond").string(); #endif @@ -404,7 +430,28 @@ namespace { #include #endif +namespace { + class LocalServerAlreadyRunningException : public std::runtime_error { + public: + LocalServerAlreadyRunningException() : + std::runtime_error("LOCAL_SERVER_ALREADY_RUNNING_ERROR") + {} + }; + + void ClearPreviousPendingSaves(std::queue& pending_saves) { + if (pending_saves.empty()) + return; + WarnLogger() << "Clearing " << std::to_string(pending_saves.size()) << " pending save game request(s)"; + std::queue().swap(pending_saves); + } +} + void HumanClientApp::StartServer() { + if (m_networking->PingLocalHostServer(std::chrono::milliseconds(100))) { + ErrorLogger() << "Can't start local server because a server is already connecting at 127.0.0.0."; + throw LocalServerAlreadyRunningException(); + } + std::string SERVER_CLIENT_EXE = ServerClientExe(); DebugLogger() << "HumanClientApp::StartServer: " << SERVER_CLIENT_EXE; @@ -421,8 +468,8 @@ void HumanClientApp::StartServer() { std::string ai_config = GetOptionsDB().Get("ai-config"); std::string ai_path = GetOptionsDB().Get("ai-path"); args.push_back("\"" + SERVER_CLIENT_EXE + "\""); - args.push_back("--resource-dir"); - args.push_back("\"" + GetOptionsDB().Get("resource-dir") + "\""); + args.push_back("--resource.path"); + args.push_back("\"" + GetOptionsDB().Get("resource.path") + "\""); auto force_log_level = GetOptionsDB().Get("log-level"); if (!force_log_level.empty()) { @@ -444,8 +491,13 @@ void HumanClientApp::StartServer() { } if (m_single_player_game) { args.push_back("--singleplayer"); + args.push_back("--skip-checksum"); } + DebugLogger() << "Launching server process with args: "; + for (auto arg : args) + DebugLogger() << arg; m_server_process = Process(SERVER_CLIENT_EXE, args); + DebugLogger() << "... finished launching server process."; } void HumanClientApp::FreeServer() { @@ -456,10 +508,16 @@ void HumanClientApp::FreeServer() { } void HumanClientApp::NewSinglePlayerGame(bool quickstart) { - if (!GetOptionsDB().Get("force-external-server")) { + TraceLogger() << "HumanClientApp::NewSinglePlayerGame start"; + ClearPreviousPendingSaves(m_game_saves_in_progress); + + if (!GetOptionsDB().Get("network.server.external.force")) { m_single_player_game = true; try { StartServer(); + } catch (const LocalServerAlreadyRunningException& err) { + ClientUI::MessageBox(UserString("LOCAL_SERVER_ALREADY_RUNNING_ERROR"), true); + return; } catch (const std::runtime_error& err) { ErrorLogger() << "HumanClientApp::NewSinglePlayerGame : Couldn't start server. Got error message: " << err.what(); ClientUI::MessageBox(UserString("SERVER_WONT_START"), true); @@ -468,109 +526,118 @@ void HumanClientApp::NewSinglePlayerGame(bool quickstart) { } bool ended_with_ok = false; - std::vector> game_rules = GetGameRules().GetRulesAsStrings(); + auto game_rules = GetGameRules().GetRulesAsStrings(); if (!quickstart) { + DebugLogger() << "Initializing galaxy setup window"; auto galaxy_wnd = GG::Wnd::Create(); + TraceLogger() << "Running galaxy setup window"; galaxy_wnd->Run(); ended_with_ok = galaxy_wnd->EndedWithOk(); + TraceLogger() << "Setup ran, " << (ended_with_ok ? "ended with OK" : "ended without OK"); if (ended_with_ok) game_rules = galaxy_wnd->GetRulesAsStrings(); + TraceLogger() << "Got rules as strings"; } + m_connected = m_networking->ConnectToLocalHostServer(); if (!m_connected) { + DebugLogger() << "Not connected; returning to intro screen and showing timed out error"; ResetToIntro(true); ClientUI::MessageBox(UserString("ERR_CONNECT_TIMED_OUT"), true); return; } - if (quickstart || ended_with_ok) { - - SinglePlayerSetupData setup_data; - setup_data.m_new_game = true; - setup_data.m_filename.clear(); // not used for new game - - // get values stored in options from previous time game was run or - // from just having run GalaxySetupWnd - - // GalaxySetupData - setup_data.m_seed = GetOptionsDB().Get("GameSetup.seed"); - setup_data.m_size = GetOptionsDB().Get("GameSetup.stars"); - setup_data.m_shape = GetOptionsDB().Get("GameSetup.galaxy-shape"); - setup_data.m_age = GetOptionsDB().Get("GameSetup.galaxy-age"); - setup_data.m_starlane_freq = GetOptionsDB().Get("GameSetup.starlane-frequency"); - setup_data.m_planet_density = GetOptionsDB().Get("GameSetup.planet-density"); - setup_data.m_specials_freq = GetOptionsDB().Get("GameSetup.specials-frequency"); - setup_data.m_monster_freq = GetOptionsDB().Get("GameSetup.monster-frequency"); - setup_data.m_native_freq = GetOptionsDB().Get("GameSetup.native-frequency"); - setup_data.m_ai_aggr = GetOptionsDB().Get("GameSetup.ai-aggression"); - setup_data.m_game_rules = game_rules; - - - // SinglePlayerSetupData contains a map of PlayerSetupData, for - // the human and AI players. Need to compile this information - // from the specified human options and number of requested AIs - - // Human player setup data - PlayerSetupData human_player_setup_data; - human_player_setup_data.m_player_name = GetOptionsDB().Get("GameSetup.player-name"); - human_player_setup_data.m_empire_name = GetOptionsDB().Get("GameSetup.empire-name"); - - // DB stores index into array of available colours, so need to get that array to look up value of index. - // if stored value is invalid, use a default colour - const std::vector& empire_colours = EmpireColors(); - int colour_index = GetOptionsDB().Get("GameSetup.empire-color"); - if (colour_index >= 0 && colour_index < static_cast(empire_colours.size())) - human_player_setup_data.m_empire_color = empire_colours[colour_index]; - else - human_player_setup_data.m_empire_color = GG::CLR_GREEN; - - human_player_setup_data.m_starting_species_name = GetOptionsDB().Get("GameSetup.starting-species"); - if (human_player_setup_data.m_starting_species_name == "1") - human_player_setup_data.m_starting_species_name = "SP_HUMAN"; // kludge / bug workaround for bug with options storage and retreival. Empty-string options are stored, but read in as "true" boolean, and converted to string equal to "1" + if (!(quickstart || ended_with_ok)) { + ErrorLogger() << "HumanClientApp::NewSinglePlayerGame failed to start new game, killing server."; + ResetToIntro(true); + } - if (human_player_setup_data.m_starting_species_name != "RANDOM" && - !GetSpecies(human_player_setup_data.m_starting_species_name)) - { - const SpeciesManager& sm = GetSpeciesManager(); - if (sm.NumPlayableSpecies() < 1) - human_player_setup_data.m_starting_species_name.clear(); - else - human_player_setup_data.m_starting_species_name = sm.playable_begin()->first; - } + SinglePlayerSetupData setup_data; + setup_data.m_new_game = true; + setup_data.m_filename.clear(); // not used for new game + + // get values stored in options from previous time game was run or + // from just having run GalaxySetupWnd + + // GalaxySetupData + setup_data.SetSeed(GetOptionsDB().Get("setup.seed")); + setup_data.m_size = GetOptionsDB().Get("setup.star.count"); + setup_data.m_shape = GetOptionsDB().Get("setup.galaxy.shape"); + setup_data.m_age = GetOptionsDB().Get("setup.galaxy.age"); + setup_data.m_starlane_freq = GetOptionsDB().Get("setup.starlane.frequency"); + setup_data.m_planet_density = GetOptionsDB().Get("setup.planet.density"); + setup_data.m_specials_freq = GetOptionsDB().Get("setup.specials.frequency"); + setup_data.m_monster_freq = GetOptionsDB().Get("setup.monster.frequency"); + setup_data.m_native_freq = GetOptionsDB().Get("setup.native.frequency"); + setup_data.m_ai_aggr = GetOptionsDB().Get("setup.ai.aggression"); + setup_data.m_game_rules = game_rules; + + + // SinglePlayerSetupData contains a map of PlayerSetupData, for + // the human and AI players. Need to compile this information + // from the specified human options and number of requested AIs + + // Human player setup data + PlayerSetupData human_player_setup_data; + human_player_setup_data.m_player_name = GetOptionsDB().Get("setup.player.name"); + human_player_setup_data.m_empire_name = GetOptionsDB().Get("setup.empire.name"); + + // DB stores index into array of available colours, so need to get that array to look up value of index. + // if stored value is invalid, use a default colour + const std::vector& empire_colours = EmpireColors(); + int colour_index = GetOptionsDB().Get("setup.empire.color.index"); + if (colour_index >= 0 && colour_index < static_cast(empire_colours.size())) + human_player_setup_data.m_empire_color = empire_colours[colour_index]; + else + human_player_setup_data.m_empire_color = GG::CLR_GREEN; - human_player_setup_data.m_save_game_empire_id = ALL_EMPIRES; // not used for new games - human_player_setup_data.m_client_type = Networking::CLIENT_TYPE_HUMAN_PLAYER; + human_player_setup_data.m_starting_species_name = GetOptionsDB().Get("setup.initial.species"); + if (human_player_setup_data.m_starting_species_name == "1") + human_player_setup_data.m_starting_species_name = "SP_HUMAN"; // kludge / bug workaround for bug with options storage and retreival. Empty-string options are stored, but read in as "true" boolean, and converted to string equal to "1" - // add to setup data players - setup_data.m_players.push_back(human_player_setup_data); + if (human_player_setup_data.m_starting_species_name != "RANDOM" && + !GetSpecies(human_player_setup_data.m_starting_species_name)) + { + const SpeciesManager& sm = GetSpeciesManager(); + if (sm.NumPlayableSpecies() < 1) + human_player_setup_data.m_starting_species_name.clear(); + else + human_player_setup_data.m_starting_species_name = sm.playable_begin()->first; + } + human_player_setup_data.m_save_game_empire_id = ALL_EMPIRES; // not used for new games + human_player_setup_data.m_client_type = Networking::CLIENT_TYPE_HUMAN_PLAYER; - // AI player setup data. One entry for each requested AI - int num_AIs = GetOptionsDB().Get("GameSetup.ai-players"); - for (int ai_i = 1; ai_i <= num_AIs; ++ai_i) { - PlayerSetupData ai_setup_data; + // add to setup data players + setup_data.m_players.push_back(human_player_setup_data); - ai_setup_data.m_player_name = "AI_" + std::to_string(ai_i); - ai_setup_data.m_empire_name.clear(); // leave blank, to be set by server in Universe::GenerateEmpires - ai_setup_data.m_empire_color = GG::CLR_ZERO; // to be set by server - ai_setup_data.m_starting_species_name.clear(); // leave blank, to be set by server - ai_setup_data.m_save_game_empire_id = ALL_EMPIRES; // not used for new games - ai_setup_data.m_client_type = Networking::CLIENT_TYPE_AI_PLAYER; - setup_data.m_players.push_back(ai_setup_data); - } + // AI player setup data. One entry for each requested AI + int num_AIs = GetOptionsDB().Get("setup.ai.player.count"); + for (int ai_i = 1; ai_i <= num_AIs; ++ai_i) { + PlayerSetupData ai_setup_data; - m_networking->SendMessage(HostSPGameMessage(setup_data)); - m_fsm->process_event(HostSPGameRequested()); + ai_setup_data.m_player_name = "AI_" + std::to_string(ai_i); + ai_setup_data.m_empire_name.clear(); // leave blank, to be set by server in Universe::GenerateEmpires + ai_setup_data.m_empire_color = GG::CLR_ZERO; // to be set by server + ai_setup_data.m_starting_species_name.clear(); // leave blank, to be set by server + ai_setup_data.m_save_game_empire_id = ALL_EMPIRES; // not used for new games + ai_setup_data.m_client_type = Networking::CLIENT_TYPE_AI_PLAYER; - } else { - ErrorLogger() << "HumanClientApp::NewSinglePlayerGame failed to start new game, killing server."; - ResetToIntro(true); + setup_data.m_players.push_back(ai_setup_data); } + + + TraceLogger() << "Sending host SP setup message"; + m_networking->SendMessage(HostSPGameMessage(setup_data)); + m_fsm->process_event(HostSPGameRequested()); + TraceLogger() << "HumanClientApp::NewSinglePlayerGame done"; } void HumanClientApp::MultiPlayerGame() { + ClearPreviousPendingSaves(m_game_saves_in_progress); + if (m_networking->IsConnected()) { ErrorLogger() << "HumanClientApp::MultiPlayerGame aborting because already connected to a server"; return; @@ -579,41 +646,62 @@ void HumanClientApp::MultiPlayerGame() { auto server_connect_wnd = GG::Wnd::Create(); server_connect_wnd->Run(); - std::string server_name = server_connect_wnd->Result().second; + std::string server_dest = server_connect_wnd->GetResult().server_dest; - if (server_name.empty()) + if (server_dest.empty()) return; - if (server_name == "HOST GAME SELECTED") { - if (!GetOptionsDB().Get("force-external-server")) { + if (server_dest == "HOST GAME SELECTED") { + if (!GetOptionsDB().Get("network.server.external.force")) { m_single_player_game = false; try { StartServer(); FreeServer(); + } catch (const LocalServerAlreadyRunningException& err) { + ClientUI::MessageBox(UserString("LOCAL_SERVER_ALREADY_RUNNING_ERROR"), true); + return; } catch (const std::runtime_error& err) { ErrorLogger() << "Couldn't start server. Got error message: " << err.what(); ClientUI::MessageBox(UserString("SERVER_WONT_START"), true); return; } - server_name = "localhost"; + server_dest = "localhost"; } - server_name = GetOptionsDB().Get("external-server-address"); + server_dest = GetOptionsDB().Get("network.server.uri"); } - - m_connected = m_networking->ConnectToServer(server_name); + m_connected = m_networking->ConnectToServer(server_dest); if (!m_connected) { ClientUI::MessageBox(UserString("ERR_CONNECT_TIMED_OUT"), true); - if (server_connect_wnd->Result().second == "HOST GAME SELECTED") + if (server_connect_wnd->GetResult().server_dest == "HOST GAME SELECTED") ResetToIntro(true); return; } - if (server_connect_wnd->Result().second == "HOST GAME SELECTED") { - m_networking->SendMessage(HostMPGameMessage(server_connect_wnd->Result().first)); + if (server_connect_wnd->GetResult().server_dest == "HOST GAME SELECTED") { + m_networking->SendMessage(HostMPGameMessage(server_connect_wnd->GetResult().player_name)); m_fsm->process_event(HostMPGameRequested()); } else { - m_networking->SendMessage(JoinGameMessage(server_connect_wnd->Result().first, Networking::CLIENT_TYPE_HUMAN_PLAYER)); + boost::uuids::uuid cookie = boost::uuids::nil_uuid(); + try { + std::string cookie_option = EncodeServerAddressOption(server_dest); + if (!GetOptionsDB().OptionExists(cookie_option + ".cookie")) + GetOptionsDB().Add(cookie_option + ".cookie", "OPTIONS_DB_SERVER_COOKIE", boost::uuids::to_string(cookie)); + if (!GetOptionsDB().OptionExists(cookie_option + ".address")) + GetOptionsDB().Add(cookie_option + ".address", "OPTIONS_DB_SERVER_COOKIE", ""); + GetOptionsDB().Set(cookie_option + ".address", server_dest); + std::string cookie_str = GetOptionsDB().Get(cookie_option + ".cookie"); + boost::uuids::string_generator gen; + cookie = gen(cookie_str); + } catch(const std::exception& err) { + WarnLogger() << "Cann't get cookie for server " << server_dest << ". Get error message" + << err.what(); + // ignore + } + + m_networking->SendMessage(JoinGameMessage(server_connect_wnd->GetResult().player_name, + server_connect_wnd->GetResult().type, + cookie)); m_fsm->process_event(JoinMPGameRequested()); } } @@ -625,16 +713,31 @@ void HumanClientApp::CancelMultiplayerGameFromLobby() { m_fsm->process_event(CancelMPGameClicked()); } void HumanClientApp::SaveGame(const std::string& filename) { - m_save_game_in_progress = true; - Message response_msg; + m_game_saves_in_progress.push(filename); + + // Start a save if there is not one in progress + if (m_game_saves_in_progress.size() > 1) { + DebugLogger() << "Add pending save to queue."; + return; + } + m_networking->SendMessage(HostSaveGameInitiateMessage(filename)); - DebugLogger() << "HumanClientApp::SaveGame sent save initiate message to server..."; + DebugLogger() << "Sent save initiate message to server."; } void HumanClientApp::SaveGameCompleted() { - DebugLogger() << "HumanClientApp::SaveGameCompleted by server."; - m_save_game_in_progress = false; - SaveGameCompletedSignal(); + if (!m_game_saves_in_progress.empty()) + m_game_saves_in_progress.pop(); + + // Either indicate that all saves are completed or start the next save. + // Autosaves and player saves can be concurrent. + if (m_game_saves_in_progress.empty()) { + DebugLogger() << "Save games completed."; + SaveGamesCompletedSignal(); + } else { + m_networking->SendMessage(HostSaveGameInitiateMessage(m_game_saves_in_progress.front())); + DebugLogger() << "Sent next save initiate message to server."; + } } void HumanClientApp::LoadSinglePlayerGame(std::string filename/* = ""*/) { @@ -650,10 +753,13 @@ void HumanClientApp::LoadSinglePlayerGame(std::string filename/* = ""*/) { } } else { try { - auto sfd = GG::Wnd::Create(SP_SAVE_FILE_EXTENSION, true); - sfd->Run(); - if (!sfd->Result().empty()) - filename = sfd->Result(); + filename = ClientUI::GetClientUI()->GetFilenameWithSaveFileDialog( + SaveFileDialog::Purpose::Load, + SaveFileDialog::SaveType::SinglePlayer); + + // Update intro screen Load & Continue buttons if all savegames are deleted. + m_ui->GetIntroScreen()->RequirePreRender(); + } catch (const std::exception& e) { ClientUI::MessageBox(e.what(), true); } @@ -666,18 +772,25 @@ void HumanClientApp::LoadSinglePlayerGame(std::string filename/* = ""*/) { // end any currently-playing game before loading new one if (m_game_started) { - ResetToIntro(false); + ResetToIntro(true); // delay to make sure old game is fully cleaned up before attempting to start a new one std::this_thread::sleep_for(std::chrono::seconds(3)); } else { DebugLogger() << "HumanClientApp::LoadSinglePlayerGame() not already in a game, so don't need to end it"; } - if (!GetOptionsDB().Get("force-external-server")) { + if (!GetOptionsDB().Get("network.server.external.force")) { m_single_player_game = true; - DebugLogger() << "HumanClientApp::LoadSinglePlayerGame() Starting server"; - StartServer(); - DebugLogger() << "HumanClientApp::LoadSinglePlayerGame() Server started"; + try { + StartServer(); + } catch (const LocalServerAlreadyRunningException& err) { + ClientUI::MessageBox(UserString("LOCAL_SERVER_ALREADY_RUNNING_ERROR"), true); + return; + } catch (const std::runtime_error& err) { + ErrorLogger() << "HumanClientApp::NewSinglePlayerGame : Couldn't start server. Got error message: " << err.what(); + ClientUI::MessageBox(UserString("SERVER_WONT_START"), true); + return; + } } else { DebugLogger() << "HumanClientApp::LoadSinglePlayerGame() assuming external server will be available"; } @@ -705,16 +818,25 @@ void HumanClientApp::LoadSinglePlayerGame(std::string filename/* = ""*/) { m_fsm->process_event(HostSPGameRequested()); } -void HumanClientApp::RequestSavePreviews(const std::string& directory, PreviewInformation& previews) { - //TraceLogger() << "HumanClientApp::RequestSavePreviews directory: " << directory << " valid UTF-8: " << utf8::is_valid(directory.begin(), directory.end()) << std::endl; - DebugLogger() << "HumanClientApp::RequestSavePreviews directory: " << directory << " valid UTF-8: " << utf8::is_valid(directory.begin(), directory.end()); +void HumanClientApp::RequestSavePreviews(const std::string& relative_directory) { + TraceLogger() << "HumanClientApp::RequestSavePreviews directory: " << relative_directory + << " valid UTF-8: " << utf8::is_valid(relative_directory.begin(), relative_directory.end()); - std::string generic_directory = directory;//PathString(fs::path(directory)); + std::string generic_directory = relative_directory; if (!m_networking->IsConnected()) { DebugLogger() << "HumanClientApp::RequestSavePreviews: No game running. Start a server for savegame queries."; m_single_player_game = true; - StartServer(); + try { + StartServer(); + } catch (const LocalServerAlreadyRunningException& err) { + ClientUI::MessageBox(UserString("LOCAL_SERVER_ALREADY_RUNNING_ERROR"), true); + return; + } catch (const std::runtime_error& err) { + ErrorLogger() << "HumanClientApp::NewSinglePlayerGame : Couldn't start server. Got error message: " << err.what(); + ClientUI::MessageBox(UserString("SERVER_WONT_START"), true); + return; + } DebugLogger() << "HumanClientApp::RequestSavePreviews Connecting to server"; m_connected = m_networking->ConnectToLocalHostServer(); @@ -729,20 +851,14 @@ void HumanClientApp::RequestSavePreviews(const std::string& directory, PreviewIn SendLoggingConfigToServer(); } DebugLogger() << "HumanClientApp::RequestSavePreviews Requesting previews for " << generic_directory; - const auto response = m_networking->SendSynchronousMessage(RequestSavePreviewsMessage(generic_directory)); - if (response && response->Type() == Message::DISPATCH_SAVE_PREVIEWS) { - ExtractDispatchSavePreviewsMessageData(*response, previews); - DebugLogger() << "HumanClientApp::RequestSavePreviews Got " << previews.previews.size() << " previews."; - } else { - ErrorLogger() << "HumanClientApp::RequestSavePreviews: Wrong response type from server: " << response->Type(); - } + m_networking->SendMessage(RequestSavePreviewsMessage(generic_directory)); } std::pair HumanClientApp::GetWindowLeftTop() { int left(0), top(0); - left = GetOptionsDB().Get("app-left-windowed"); - top = GetOptionsDB().Get("app-top-windowed"); + left = GetOptionsDB().Get("video.windowed.left"); + top = GetOptionsDB().Get("video.windowed.top"); // clamp to edges to avoid weird bug with maximizing windows setting their // left and top to -9 which lead to weird issues when attmepting to recreate @@ -758,31 +874,31 @@ std::pair HumanClientApp::GetWindowLeftTop() { std::pair HumanClientApp::GetWindowWidthHeight() { int width(800), height(600); - bool fullscreen = GetOptionsDB().Get("fullscreen"); + bool fullscreen = GetOptionsDB().Get("video.fullscreen.enabled"); if (!fullscreen) { - width = GetOptionsDB().Get("app-width-windowed"); - height = GetOptionsDB().Get("app-height-windowed"); + width = GetOptionsDB().Get("video.windowed.width"); + height = GetOptionsDB().Get("video.windowed.height"); return {width, height}; } - bool reset_fullscreen = GetOptionsDB().Get("reset-fullscreen-size"); + bool reset_fullscreen = GetOptionsDB().Get("video.fullscreen.reset"); if (!reset_fullscreen) { - width = GetOptionsDB().Get("app-width"); - height = GetOptionsDB().Get("app-height"); + width = GetOptionsDB().Get("video.fullscreen.width"); + height = GetOptionsDB().Get("video.fullscreen.height"); return {width, height}; } - GetOptionsDB().Set("reset-fullscreen-size", false); - GG::Pt default_resolution = GetDefaultResolutionStatic(GetOptionsDB().Get("fullscreen-monitor-id")); - GetOptionsDB().Set("app-width", Value(default_resolution.x)); - GetOptionsDB().Set("app-height", Value(default_resolution.y)); + GetOptionsDB().Set("video.fullscreen.reset", false); + GG::Pt default_resolution = GetDefaultResolutionStatic(GetOptionsDB().Get("video.monitor.id")); + GetOptionsDB().Set("video.fullscreen.width", Value(default_resolution.x)); + GetOptionsDB().Set("video.fullscreen.height", Value(default_resolution.y)); GetOptionsDB().Commit(); return {Value(default_resolution.x), Value(default_resolution.y)}; } void HumanClientApp::Reinitialize() { - bool fullscreen = GetOptionsDB().Get("fullscreen"); - bool fake_mode_change = GetOptionsDB().Get("fake-mode-change"); + bool fullscreen = GetOptionsDB().Get("video.fullscreen.enabled"); + bool fake_mode_change = GetOptionsDB().Get("video.fullscreen.fake.enabled"); std::pair size = GetWindowWidthHeight(); bool fullscreen_transition = Fullscreen() != fullscreen; @@ -794,7 +910,7 @@ void HumanClientApp::Reinitialize() { FullscreenSwitchSignal(fullscreen); // after video mode is changed but before DoLayout() calls } else if (fullscreen && (old_width != size.first || old_height != size.second) && - GetOptionsDB().Get("UI.auto-reposition-windows")) + GetOptionsDB().Get("ui.reposition.auto.enabled")) { // Reposition windows if in fullscreen mode... handled here instead of // HandleWindowResize() because the prev. fullscreen resolution is only @@ -814,7 +930,7 @@ void HumanClientApp::Reinitialize() { float HumanClientApp::GLVersion() const { return GetGLVersion(); } -void HumanClientApp::StartTurn() { +void HumanClientApp::StartTurn(const SaveGameUIData& ui_data) { DebugLogger() << "HumanClientApp::StartTurn"; if (const Empire* empire = GetEmpire(EmpireID())) { @@ -830,10 +946,27 @@ void HumanClientApp::StartTurn() { << " " << static_cast(color.a); } - ClientApp::StartTurn(); + // Do the turn end autosave. + if (m_single_player_game && GetOptionsDB().Get("save.auto.turn.end.enabled")) { + DebugLogger() << "Starting end of turn autosave."; + Autosave(); + } + + ClientApp::StartTurn(ui_data); m_fsm->process_event(TurnEnded()); } +void HumanClientApp::UnreadyTurn() { + m_networking->SendMessage(UnreadyMessage()); +} + +void HumanClientApp::HandleTurnPhaseUpdate(Message::TurnProgressPhase phase_id) { + ClientApp::HandleTurnPhaseUpdate(phase_id); + + // Pass updates to message window. + GetClientUI().GetMessageWnd()->HandleTurnPhaseUpdate(phase_id); +} + void HumanClientApp::HandleSystemEvents() { try { SDLGUI::HandleSystemEvents(); @@ -864,46 +997,58 @@ void HumanClientApp::HandleMessage(Message& msg) { case Message::JOIN_GAME: m_fsm->process_event(JoinGame(msg)); break; case Message::HOST_ID: m_fsm->process_event(HostID(msg)); break; case Message::LOBBY_UPDATE: m_fsm->process_event(LobbyUpdate(msg)); break; - case Message::SAVE_GAME_DATA_REQUEST: m_fsm->process_event(SaveGameDataRequest(msg)); break; case Message::SAVE_GAME_COMPLETE: m_fsm->process_event(SaveGameComplete(msg)); break; case Message::CHECKSUM: m_fsm->process_event(CheckSum(msg)); break; case Message::GAME_START: m_fsm->process_event(GameStart(msg)); break; case Message::TURN_UPDATE: m_fsm->process_event(TurnUpdate(msg)); break; case Message::TURN_PARTIAL_UPDATE: m_fsm->process_event(TurnPartialUpdate(msg)); break; case Message::TURN_PROGRESS: m_fsm->process_event(TurnProgress(msg)); break; + case Message::UNREADY: m_fsm->process_event(TurnRevoked(msg)); break; case Message::PLAYER_STATUS: m_fsm->process_event(::PlayerStatus(msg)); break; case Message::PLAYER_CHAT: m_fsm->process_event(PlayerChat(msg)); break; case Message::DIPLOMACY: m_fsm->process_event(Diplomacy(msg)); break; case Message::DIPLOMATIC_STATUS: m_fsm->process_event(DiplomaticStatusUpdate(msg)); break; case Message::END_GAME: m_fsm->process_event(::EndGame(msg)); break; - case Message::DISPATCH_COMBAT_LOGS: m_fsm->process_event(DispatchCombatLogs(msg)); break; + case Message::DISPATCH_COMBAT_LOGS: m_fsm->process_event(DispatchCombatLogs(msg)); break; + case Message::DISPATCH_SAVE_PREVIEWS: HandleSaveGamePreviews(msg); break; + case Message::AUTH_REQUEST: m_fsm->process_event(AuthRequest(msg)); break; + case Message::CHAT_HISTORY: m_fsm->process_event(ChatHistory(msg)); break; + case Message::SET_AUTH_ROLES: HandleSetAuthRoles(msg); break; + case Message::TURN_TIMEOUT: m_fsm->process_event(TurnTimeout(msg)); break; + case Message::PLAYER_INFO: m_fsm->process_event(PlayerInfoMsg(msg)); break; default: ErrorLogger() << "HumanClientApp::HandleMessage : Received an unknown message type \"" << msg.Type() << "\"."; } } -void HumanClientApp::HandleSaveGameDataRequest() { - if (INSTRUMENT_MESSAGE_HANDLING) - std::cerr << "HumanClientApp::HandleSaveGameDataRequest(" << Message::SAVE_GAME_DATA_REQUEST << ")\n"; - SaveGameUIData ui_data; - m_ui->GetSaveGameUIData(ui_data); - m_networking->SendMessage(ClientSaveDataMessage(Orders(), ui_data)); -} - void HumanClientApp::UpdateCombatLogs(const Message& msg){ - DebugLogger() << "HCL Update Combat Logs"; + ScopedTimer timer("HumanClientApp::UpdateCombatLogs"); // Unpack the combat logs from the message std::vector> logs; ExtractDispatchCombatLogsMessageData(msg, logs); // Update the combat log manager with the completed logs. - for (std::vector>::const_iterator it = logs.begin(); - it != logs.end(); ++it) - { + for (auto it = logs.begin(); it != logs.end(); ++it) GetCombatLogManager().CompleteLog(it->first, it->second); - } +} + +void HumanClientApp::HandleSaveGamePreviews(const Message& msg) { + auto sfd = GetClientUI().GetSaveFileDialog(); + if (!sfd) + return; + + PreviewInformation previews; + ExtractDispatchSavePreviewsMessageData(msg, previews); + DebugLogger() << "HumanClientApp::RequestSavePreviews Got " << previews.previews.size() << " previews."; + + sfd->SetPreviewList(previews); +} + +void HumanClientApp::HandleSetAuthRoles(const Message& msg) { + ExtractSetAuthorizationRolesMessage(msg, m_networking->AuthorizationRoles()); + DebugLogger() << "New roles: " << m_networking->AuthorizationRoles().Text(); } void HumanClientApp::ChangeLoggerThreshold(const std::string& option_name, LogLevel option_value) { @@ -926,8 +1071,8 @@ void HumanClientApp::SendLoggingConfigToServer() { void HumanClientApp::HandleWindowMove(GG::X w, GG::Y h) { if (!Fullscreen()) { - GetOptionsDB().Set("app-left-windowed", Value(w)); - GetOptionsDB().Set("app-top-windowed", Value(h)); + GetOptionsDB().Set("video.windowed.left", Value(w)); + GetOptionsDB().Set("video.windowed.top", Value(h)); GetOptionsDB().Commit(); } } @@ -936,24 +1081,22 @@ void HumanClientApp::HandleWindowResize(GG::X w, GG::Y h) { if (ClientUI* ui = ClientUI::GetClientUI()) { if (auto&& map_wnd = ui->GetMapWnd()) map_wnd->DoLayout(); - if (auto&& intro_screen = ui->GetIntroScreen()) { + if (auto&& intro_screen = ui->GetIntroScreen()) intro_screen->Resize(GG::Pt(w, h)); - intro_screen->DoLayout(); - } } - if (!GetOptionsDB().Get("fullscreen") && - (GetOptionsDB().Get("app-width-windowed") != w || - GetOptionsDB().Get("app-height-windowed") != h)) + if (!GetOptionsDB().Get("video.fullscreen.enabled") && + (GetOptionsDB().Get("video.windowed.width") != w || + GetOptionsDB().Get("video.windowed.height") != h)) { - if (GetOptionsDB().Get("UI.auto-reposition-windows")) { + if (GetOptionsDB().Get("ui.reposition.auto.enabled")) { // Reposition windows if in windowed mode. RepositionWindowsSignal(); } // store resize if window is not full-screen (so that fullscreen // resolution doesn't overwrite windowed resolution) - GetOptionsDB().Set("app-width-windowed", Value(w)); - GetOptionsDB().Set("app-height-windowed", Value(h)); + GetOptionsDB().Set("video.windowed.width", Value(w)); + GetOptionsDB().Set("video.windowed.height", Value(h)); } glViewport(0, 0, Value(w), Value(h)); @@ -970,21 +1113,21 @@ void HumanClientApp::HandleFocusChange(bool gained_focus) { // limit rendering frequency when defocused to limit CPU use, and disable sound if (!m_have_window_focus) { - if (GetOptionsDB().Get("limit-fps-no-focus")) - this->SetMaxFPS(GetOptionsDB().Get("max-fps-no_focus")); + if (GetOptionsDB().Get("video.fps.unfocused.enabled")) + this->SetMaxFPS(GetOptionsDB().Get("video.fps.unfocused")); else this->SetMaxFPS(0.0); - if (GetOptionsDB().Get("UI.sound.music-enabled")) + if (GetOptionsDB().Get("audio.music.enabled")) Sound::GetSound().PauseMusic(); } else { - if (GetOptionsDB().Get("limit-fps")) - this->SetMaxFPS(GetOptionsDB().Get("max-fps")); + if (GetOptionsDB().Get("video.fps.max.enabled")) + this->SetMaxFPS(GetOptionsDB().Get("video.fps.max")); else this->SetMaxFPS(0.0); - if (GetOptionsDB().Get("UI.sound.music-enabled")) + if (GetOptionsDB().Get("audio.music.enabled")) Sound::GetSound().ResumeMusic(); } @@ -994,7 +1137,7 @@ void HumanClientApp::HandleFocusChange(bool gained_focus) { void HumanClientApp::HandleAppQuitting() { DebugLogger() << "HumanClientApp::HandleAppQuitting()"; - ExitApp(); + ExitApp(0); } bool HumanClientApp::HandleHotkeyResetGame() { @@ -1011,8 +1154,8 @@ bool HumanClientApp::HandleHotkeyExitApp() { } bool HumanClientApp::ToggleFullscreen() { - bool fs = GetOptionsDB().Get("fullscreen"); - GetOptionsDB().Set("fullscreen", !fs); + bool fs = GetOptionsDB().Get("video.fullscreen.enabled"); + GetOptionsDB().Set("video.fullscreen.enabled", !fs); Reinitialize(); return true; } @@ -1033,11 +1176,66 @@ void HumanClientApp::HandleTurnUpdate() void HumanClientApp::UpdateCombatLogManager() { boost::optional> incomplete_ids = GetCombatLogManager().IncompleteLogIDs(); - if (incomplete_ids) - m_networking->SendMessage(RequestCombatLogsMessage(*incomplete_ids)); + if (incomplete_ids) { + for (auto it = incomplete_ids->begin(); it != incomplete_ids->end();) { + // request at most 50 logs per message to avoid trying to allocate too much space to send all at once + std::vector a_few_log_ids; + for (unsigned int count = 0; count < 50 && it != incomplete_ids->end(); ++it, ++count) + a_few_log_ids.push_back(*it); + m_networking->SendMessage(RequestCombatLogsMessage(a_few_log_ids)); + } + } } namespace { + boost::optional NewestSinglePlayerSavegame() { + using namespace boost::filesystem; + try { + std::map files_by_write_time; + + auto add_all_savegames_in = [&files_by_write_time](const path& path) { + if (!is_directory(path)) + return; + + for (directory_iterator dir_it(path); + dir_it != directory_iterator(); ++dir_it) + { + const auto& file_path = dir_it->path(); + if (!is_regular_file(file_path)) + continue; + if (file_path.extension() != SP_SAVE_FILE_EXTENSION) + continue; + + std::time_t t = last_write_time(file_path); + files_by_write_time.insert({t, file_path}); + } + }; + + // Find all save games in either player or autosaves + add_all_savegames_in(GetSaveDir()); + add_all_savegames_in(GetSaveDir() / "auto"); + + if (files_by_write_time.empty()) + return boost::none; + + // Return the newest file that has a valid header + for (auto file_it = files_by_write_time.rbegin(); + file_it != files_by_write_time.rend(); ++file_it) + { + auto file = file_it->second; + // attempt to load header + if (SaveFileWithValidHeader(file)) + return PathToString(file); // load succeeded, return path to OK file + } + + return boost::none; + + } catch (const boost::filesystem::filesystem_error& e) { + ErrorLogger() << "File system error " << e.what() << " while finding newest autosave"; + return boost::none; + } + } + void RemoveOldestFiles(int files_limit, boost::filesystem::path& p) { using namespace boost::filesystem; try { @@ -1061,7 +1259,7 @@ namespace { } //DebugLogger() << "files by write time:"; - //for (std::multimap::value_type& entry : files_by_write_time) + //for (auto& entry : files_by_write_time) //{ DebugLogger() << entry.first << " : " << entry.second.filename(); } int num_to_delete = files_by_write_time.size() - files_limit + 1; // +1 because will add a new file after deleting, bringing number back up to limit @@ -1069,7 +1267,7 @@ namespace { return; // don't need to delete anything. int num_deleted = 0; - for (std::multimap::value_type& entry : files_by_write_time) { + for (auto& entry : files_by_write_time) { if (num_deleted >= num_to_delete) break; remove(entry.second); @@ -1116,7 +1314,7 @@ namespace { // Add timestamp to autosave generated files std::string datetime_str = FilenameTimestamp(); - boost::filesystem::path autosave_dir_path(GetSaveDir() / "auto"); + boost::filesystem::path autosave_dir_path((is_single_player ? GetSaveDir() : GetServerSaveDir()) / "auto"); std::string save_filename = boost::io::str(boost::format("FreeOrion_%s_%s_%04d_%s%s") % player_name % empire_name % CurrentTurn() % datetime_str % extension); boost::filesystem::path save_path(autosave_dir_path / save_filename); @@ -1134,35 +1332,64 @@ namespace { } void HumanClientApp::Autosave() { + // only host can save in multiplayer + if (!m_single_player_game && !Networking().PlayerIsHost(PlayerID())) + return; + // Create an auto save for 1) new games on turn 1, 2) if auto save is - // requested on turn number modulo autosave.turns or 3) on the last turn of - // an auto run. + // requested on turn number modulo save.auto.turn.interval or 3) on the last turn of + // play. // autosave only on appropriate turn numbers, and when enabled for current // game type (single vs. multiplayer) - int autosave_turns = GetOptionsDB().Get("autosave.turns"); + int autosave_turns = GetOptionsDB().Get("save.auto.turn.interval"); + bool is_single_player_enabled = + (m_single_player_game + && (GetOptionsDB().Get("save.auto.turn.start.enabled") + || GetOptionsDB().Get("save.auto.turn.end.enabled"))); + bool is_multi_player_enabled = + (!m_single_player_game + && GetOptionsDB().Get("save.auto.turn.multiplayer.start.enabled")); bool is_valid_autosave = (autosave_turns > 0 && CurrentTurn() % autosave_turns == 0 - && ((m_single_player_game && GetOptionsDB().Get("autosave.single-player")) - || (!m_single_player_game && GetOptionsDB().Get("autosave.multiplayer")))); + && (is_single_player_enabled || is_multi_player_enabled)); // is_initial_save is gated in HumanClientFSM for new game vs loaded game - bool is_initial_save = CurrentTurn() == 1; - bool is_final_save = (AutoTurnsLeft() <= 0 && GetOptionsDB().Get("auto-quit")); + bool is_initial_save = (GetOptionsDB().Get("save.auto.initial.enabled") && CurrentTurn() == 1); + bool is_final_save = (GetOptionsDB().Get("save.auto.exit.enabled") && !m_game_started); if (!(is_initial_save || is_valid_autosave || is_final_save)) return; - auto autosave_dir_path = CreateNewAutosaveFilePath(EmpireID(), m_single_player_game); - - // check for and remove excess oldest autosaves - int max_autosaves = GetOptionsDB().Get("autosave.limit"); + auto autosave_file_path = CreateNewAutosaveFilePath(EmpireID(), m_single_player_game); + + // check for and remove excess oldest autosaves. + boost::filesystem::path autosave_dir_path((m_single_player_game ? GetSaveDir() : GetServerSaveDir()) / "auto"); + int max_turns = std::max(1, GetOptionsDB().Get("save.auto.file.limit")); + bool is_two_saves_per_turn = + (m_single_player_game + && GetOptionsDB().Get("save.auto.turn.start.enabled") + && GetOptionsDB().Get("save.auto.turn.end.enabled")) + || + (!m_single_player_game + && GetOptionsDB().Get("save.auto.turn.multiplayer.start.enabled")); + int max_autosaves = + (max_turns * (is_two_saves_per_turn ? 2 : 1) + + (GetOptionsDB().Get("save.auto.initial.enabled") ? 1 : 0) + + (GetOptionsDB().Get("save.auto.exit.enabled") ? 1 : 0)); RemoveOldestFiles(max_autosaves, autosave_dir_path); // create new save - auto path_string = PathString(autosave_dir_path); - DebugLogger() << "Autosaving to: " << path_string; + auto path_string = PathToString(autosave_file_path); + + if (is_initial_save) + DebugLogger() << "Turn 0 autosave to: " << path_string; + if (is_valid_autosave) + DebugLogger() << "Autosave to: " << path_string; + if (is_final_save) + DebugLogger() << "End of play autosave to: " << path_string; + try { SaveGame(path_string); } catch (const std::exception& e) { @@ -1170,15 +1397,25 @@ void HumanClientApp::Autosave() { } } +void HumanClientApp::ContinueSinglePlayerGame() { + if (const auto file = NewestSinglePlayerSavegame()) + LoadSinglePlayerGame(*file); +} + +bool HumanClientApp::IsLoadGameAvailable() const +{ return bool(NewestSinglePlayerSavegame()); } + std::string HumanClientApp::SelectLoadFile() { - auto sfd = GG::Wnd::Create(true); - sfd->Run(); - return sfd->Result(); + return ClientUI::GetClientUI()->GetFilenameWithSaveFileDialog( + SaveFileDialog::Purpose::Load, + SaveFileDialog::SaveType::MultiPlayer); } -void HumanClientApp::ResetClientData() { - m_networking->SetPlayerID(Networking::INVALID_PLAYER_ID); - m_networking->SetHostPlayerID(Networking::INVALID_PLAYER_ID); +void HumanClientApp::ResetClientData(bool save_connection) { + if (!save_connection) { + m_networking->SetPlayerID(Networking::INVALID_PLAYER_ID); + m_networking->SetHostPlayerID(Networking::INVALID_PLAYER_ID); + } SetEmpireID(ALL_EMPIRES); m_ui->GetMapWnd()->Sanitize(); @@ -1186,64 +1423,100 @@ void HumanClientApp::ResetClientData() { m_empires.Clear(); m_orders.Reset(); GetCombatLogManager().Clear(); -} - -namespace { - // Ask the player if they want to wait for the save game to complete - // The dialog automatically closes if the save completes while the user is waiting - class SaveGamePendingDialog : public GG::ThreeButtonDlg { - public: - SaveGamePendingDialog(bool reset, boost::signals2::signal& save_completed_signal) : - GG::ThreeButtonDlg( - GG::X(320), GG::Y(200), UserString("SAVE_GAME_IN_PROGRESS"), - ClientUI::GetFont(ClientUI::Pts()+2), - ClientUI::WndColor(), ClientUI::WndOuterBorderColor(), - ClientUI::CtrlColor(), ClientUI::TextColor(), 1, - (reset ? UserString("ABORT_SAVE_AND_RESET") : UserString("ABORT_SAVE_AND_EXIT"))) - { - save_completed_signal.connect( - boost::bind(&SaveGamePendingDialog::SaveCompletedHandler, this)); - } - - void SaveCompletedHandler() { - DebugLogger() << "SaveGamePendingDialog::SaveCompletedHandler save game completed handled."; - m_done = true; - } - }; + ClearPreviousPendingSaves(m_game_saves_in_progress); } void HumanClientApp::ResetToIntro(bool skip_savegame) { ResetOrExitApp(true, skip_savegame); } -void HumanClientApp::ExitApp() -{ ResetOrExitApp(false, false); } +void HumanClientApp::ExitApp(int exit_code) +{ ResetOrExitApp(false, false, exit_code); } + +void HumanClientApp::ExitSDL(int exit_code) +{ SDLGUI::ExitApp(exit_code); } -void HumanClientApp::ResetOrExitApp(bool reset, bool skip_savegame) { +void HumanClientApp::ResetOrExitApp(bool reset, bool skip_savegame, int exit_code /* = 0*/) { + if (m_exit_handled) return; + m_exit_handled = true; DebugLogger() << (reset ? "HumanClientApp::ResetToIntro" : "HumanClientApp::ExitApp"); + auto was_playing = m_game_started; m_game_started = false; - if (m_save_game_in_progress && !skip_savegame) { - DebugLogger() << "save game in progress. Checking with player."; - auto dlg = GG::Wnd::Create(reset, this->SaveGameCompletedSignal); - dlg->Run(); + // Only save or allow user to cancel if not exiting due to an error. + if (!skip_savegame) { + // Check if this is a multiplayer game and the player has not set status to ready + if (was_playing && !m_single_player_game && + m_empires.GetEmpire(m_empire_id) != nullptr && + !m_empires.GetEmpire(m_empire_id)->Ready() && + GetClientType() == Networking::CLIENT_TYPE_HUMAN_PLAYER) + { + std::shared_ptr font = ClientUI::GetFont(); + auto prompt = GG::GUI::GetGUI()->GetStyleFactory()->NewThreeButtonDlg( + GG::X(275), GG::Y(75), UserString("GAME_MENU_CONFIRM_NOT_READY"), font, + ClientUI::CtrlColor(), ClientUI::CtrlBorderColor(), ClientUI::CtrlColor(), ClientUI::TextColor(), + 2, UserString("YES"), UserString("CANCEL")); + prompt->Run(); + if (prompt->Result() != 0) { + // User aborted exit/resign, reset variables + m_game_started = was_playing; + m_exit_handled = false; + return; + } + } + + if (was_playing && GetOptionsDB().Get("save.auto.exit.enabled")) + Autosave(); + + if (!m_game_saves_in_progress.empty()) { + DebugLogger() << "save game in progress. Checking with player."; + // Ask the player if they want to wait for the save game to complete + auto dlg = GG::GUI::GetGUI()->GetStyleFactory()->NewThreeButtonDlg( + GG::X(320), GG::Y(200), UserString("SAVE_GAME_IN_PROGRESS"), + ClientUI::GetFont(ClientUI::Pts()+2), + ClientUI::WndColor(), ClientUI::WndOuterBorderColor(), + ClientUI::CtrlColor(), ClientUI::TextColor(), 1, + (reset ? + UserString("ABORT_SAVE_AND_RESET") : + UserString("ABORT_SAVE_AND_EXIT"))); + // The dialog automatically closes if the save completes while the + // user is waiting + this->SaveGamesCompletedSignal.connect( + [dlg](){ + DebugLogger() << "SaveGamePendingDialog::SaveCompletedHandler save game completed handled."; + + dlg->EndRun(); + } + ); + + dlg->Run(); + } } - m_fsm->process_event(StartQuittingGame(reset, m_server_process)); + // Create an action to reset to intro or quit the app as appropriate. + std::function after_server_shutdown_action; + if (reset) + after_server_shutdown_action = std::bind(&HumanClientApp::ResetClientData, this, false); + else + // This throws to exit the GUI + after_server_shutdown_action = std::bind(&HumanClientApp::ExitSDL, this, exit_code); + + m_fsm->process_event(StartQuittingGame(m_server_process, std::move(after_server_shutdown_action))); + + m_exit_handled = false; } void HumanClientApp::InitAutoTurns(int auto_turns) { - if (auto_turns > 0) - m_auto_turns = auto_turns; - else + m_auto_turns = auto_turns; + if (!m_game_started || m_auto_turns < 0) m_auto_turns = 0; } -void HumanClientApp::DecAutoTurns(int n) { - m_auto_turns -= n; - if (m_auto_turns < 0) - m_auto_turns = 0; -} +void HumanClientApp::DecAutoTurns(int n) +{ InitAutoTurns(m_auto_turns - n); } + +void HumanClientApp::EliminateSelf() +{ m_networking->SendMessage(EliminateSelfMessage()); } int HumanClientApp::AutoTurnsLeft() const { return m_auto_turns; } @@ -1252,11 +1525,11 @@ bool HumanClientApp::HaveWindowFocus() const { return m_have_window_focus; } int HumanClientApp::EffectsProcessingThreads() const -{ return GetOptionsDB().Get("effects-threads-ui"); } +{ return GetOptionsDB().Get("effects.ui.threads"); } void HumanClientApp::UpdateFPSLimit() { - if (GetOptionsDB().Get("limit-fps")) { - double fps = GetOptionsDB().Get("max-fps"); + if (GetOptionsDB().Get("video.fps.max.enabled")) { + double fps = GetOptionsDB().Get("video.fps.max"); SetMaxFPS(fps); DebugLogger() << "Limited FPS to " << fps; } else { @@ -1265,6 +1538,15 @@ void HumanClientApp::UpdateFPSLimit() { } } +void HumanClientApp::HandleResoureDirChange() { + if (!m_game_started) { + DebugLogger() << "Resource directory changed. Reparsing universe ..."; + StartBackgroundParsing(); + } else { + WarnLogger() << "Resource directory changes will take effect on application restart."; + } +} + void HumanClientApp::DisconnectedFromServer() { DebugLogger() << "HumanClientApp::DisconnectedFromServer"; m_fsm->process_event(Disconnection()); @@ -1314,5 +1596,94 @@ void HumanClientApp::OpenURL(const std::string& url) { command += url; // execute open command - system(command.c_str()); + int rv = system(command.c_str()); + if (rv != 0) + ErrorLogger() << "HumanClientApp::OpenURL `" << command << "` returned a non-zero exit code: " << rv; +} + +void HumanClientApp::BrowsePath(const boost::filesystem::path& browse_path) { + if (browse_path.empty() || browse_path == "/") { + ErrorLogger() << "Invalid path: " << PathToString(browse_path); + return; + } + + boost::filesystem::path full_path(browse_path); + + try { + boost::filesystem::file_status status = boost::filesystem::status(full_path); + if (!boost::filesystem::exists(status)) { + std::string exists_debug_msg("Non-existant path: " + PathToString(full_path)); + if (full_path.has_parent_path()) { + DebugLogger() << exists_debug_msg << ", trying parent directory"; + BrowsePath(full_path.parent_path()); + } else { + DebugLogger() << exists_debug_msg << ", aborting"; + } + return; + } + + // Validate as a canonical path + if (boost::filesystem::is_directory(status)) { + full_path = boost::filesystem::canonical(full_path); + } else { + // If given a file, use the files containing directory + DebugLogger() << "Non-directory target: " << PathToString(full_path) << ", using parent directory"; + full_path = boost::filesystem::canonical(full_path.parent_path()); + } + + // Verify not a regular file + if (boost::filesystem::is_regular_file(full_path)) { + ErrorLogger() << "Target directory " << PathToString(full_path) << " is a regular file, given path argument: " + << PathToString(browse_path); + return; + } + + } catch (const boost::filesystem::filesystem_error& ec) { + ErrorLogger() << "Filesystem error when attempting to browse directory " << PathToString(full_path) + << ": " << ec.what(); + return; + } + + if (full_path.empty()) { + ErrorLogger() << "Unable to determine directory for path " << PathToString(full_path); + return; + } + + full_path.make_preferred(); + // Trailing slash post-fixed to prevent executing a file with same name(minus extension) as folder + full_path += boost::filesystem::path::preferred_separator; + auto target(full_path.native()); + decltype(target) command; + + // Double quotes around target to support paths containing spaces + // Non-Windows platforms: Post-fix ampersand to prevent blocking until process exits + // On Windows: the trailing path separator may be interpreted as escaping a double quote. + // The trailing separator should not be removed, as that poses the risk of executing a file. + // The trailing separator is escaped by 2 additional back-slashes (total 3). + // see http://www.windowsinspired.com/how-a-windows-programs-splits-its-command-line-into-individual-arguments/ + // + // Contrary to official documentation for start, the first argument (title) is not always optional. + // The argument for window title is left as an empty string. + // see https://ss64.com/nt/start.html +#ifdef _WIN32 + command = L"start \"\" \"" + target + L"\\\\\""; +#elif __APPLE__ + command = "open \"" + target + "\" &"; +#else + command = "xdg-open \"" + target + "\" &"; +#endif + +#ifdef _WIN32 + std::string u8_command; + utf8::utf16to8(command.begin(), command.end(), std::back_inserter(u8_command)); + InfoLogger() << "Sending OS request to browse directory: " << u8_command; + // Flush all streams prior to _wsystem call per https://msdn.microsoft.com/en-us/library/277bwbdz.aspx + std::fflush(NULL); + if (auto sys_retval = _wsystem(command.c_str())) + WarnLogger() << "System call " << u8_command << " returned non-zero value " << sys_retval; +#else + InfoLogger() << "Sending OS request to browse directory: " << command; + if (auto sys_retval = std::system(command.c_str())) + WarnLogger() << "System call " << command << " returned non-zero value " << sys_retval; +#endif } diff --git a/client/human/HumanClientApp.h b/client/human/HumanClientApp.h index b39ea9ab8d2..6e4c226d7a7 100644 --- a/client/human/HumanClientApp.h +++ b/client/human/HumanClientApp.h @@ -3,12 +3,13 @@ #include "../ClientApp.h" #include "../../util/Process.h" +#include "../../UI/SDLGUI.h" #include "../../UI/ClientUI.h" #include "../../util/OptionsDB.h" -#include #include +#include #include struct HumanClientFSM; @@ -18,78 +19,94 @@ struct PreviewInformation; /** the application framework class for the human player FreeOrion client. */ class HumanClientApp : public ClientApp, - public GG::SDLGUI + public SDLGUI { public: - class CleanQuit : public std::exception {}; - typedef boost::signals2::signal FullscreenSwitchSignalType; typedef boost::signals2::signal RepositionWindowsSignalType; - /** \name Structors */ //@{ - HumanClientApp(int width, int height, bool calculate_FPS, const std::string& name, int x, int y, bool fullscreen, bool fake_mode_change); - virtual ~HumanClientApp(); - //@} + HumanClientApp() = delete; + + HumanClientApp(int width, int height, bool calculate_FPS, + const std::string& name, int x, int y, + bool fullscreen, bool fake_mode_change); + + HumanClientApp(const HumanClientApp&) = delete; + HumanClientApp(HumanClientApp&&) = delete; + ~HumanClientApp() override; + + const HumanClientApp& operator=(const HumanClientApp&) = delete; + HumanClientApp& operator=(const HumanClientApp&&) = delete; /** \name Accessors */ //@{ int EffectsProcessingThreads() const override; - - bool SinglePlayerGame() const; ///< returns true iff this game is a single-player game - bool CanSaveNow() const; ///< returns true / false to indicate whether this client can currently safely initiate a game save - int AutoTurnsLeft() const; ///< returns number of turns left to execute automatically - bool HaveWindowFocus() const; ///< as far as the HCA knows, does the game window have focus? + bool SinglePlayerGame() const; ///< returns true iff this game is a single-player game + bool CanSaveNow() const; ///< returns true / false to indicate whether this client can currently safely initiate a game save + int AutoTurnsLeft() const; ///< returns number of turns left to execute automatically + bool HaveWindowFocus() const; ///< as far as the HCA knows, does the game window have focus? //@} /** \name Mutators */ //@{ - /** Handle background events that need starting when the turn updates. - */ - void HandleTurnUpdate() override; - - void StartTurn() override; - - void SetSinglePlayerGame(bool sp = true); - - void StartServer(); ///< starts a server process on localhost - void FreeServer(); ///< frees (relinquishes ownership and control of) any running server process already started by this client; performs no cleanup of other processes, such as AIs - void NewSinglePlayerGame(bool quickstart = false); - void MultiPlayerGame(); ///< shows multiplayer connection window, and then transitions to multiplayer lobby if connected - void StartMultiPlayerGameFromLobby(); ///< begins - void CancelMultiplayerGameFromLobby(); ///< cancels out of multiplayer game - void SaveGame(const std::string& filename); ///< saves the current game; blocks until all save-related network traffic is resolved. - /** Accepts acknowledgement that server has completed the save.*/ - void SaveGameCompleted(); + void HandleTurnUpdate() override; ///< Handle background events that need starting when the turn updates + void StartTurn(const SaveGameUIData& ui_data) override; + void UnreadyTurn(); ///< Revoke ready state of turn orders. + + /** \brief Handle UI and state updates with changes in turn phase. */ + void HandleTurnPhaseUpdate(Message::TurnProgressPhase phase_id) override; + void SetSinglePlayerGame(bool sp = true); + void NewSinglePlayerGame(bool quickstart = false); + void MultiPlayerGame(); ///< shows multiplayer connection window, and then transitions to multiplayer lobby if connected + void StartMultiPlayerGameFromLobby(); ///< begins + void CancelMultiplayerGameFromLobby(); ///< cancels out of multiplayer game + void SaveGame(const std::string& filename); ///< saves the current game; blocks until all save-related network traffic is resolved + + void SaveGameCompleted(); ///< Accepts acknowledgement that server has completed the save + /** \p is_new_game should be true for a new game and false for a loaded game. */ - void StartGame(bool is_new_game); + void StartGame(bool is_new_game); - /** Check if the CombatLogManager has incomplete logs that need fetching and start fetching - them from the server. - */ - void UpdateCombatLogManager(); + /** Check if the CombatLogManager has incomplete logs that need fetching and + * start fetching them from the server. */ + void UpdateCombatLogManager(); /** Update the logger in OptionsDB and the other processes if hosting. */ - void ChangeLoggerThreshold(const std::string& option_name, LogLevel option_value); - - void ResetToIntro(bool skip_savegame); - void ExitApp(); - void ResetClientData(); - void LoadSinglePlayerGame(std::string filename = "");///< loads a single player game chosen by the user; returns true if a game was loaded, and false if the operation was cancelled - void RequestSavePreviews(const std::string& directory, PreviewInformation& previews); ///< Requests the savegame previews for choosing one. - void Autosave(); ///< autosaves the current game, iff autosaves are enabled and any turn number requirements are met - std::string SelectLoadFile(); //< Lets the user select a multiplayer save to load. - void InitAutoTurns(int auto_turns); ///< Initialize auto turn counter - void DecAutoTurns(int n = 1); ///< Decrease auto turn counter + void ChangeLoggerThreshold(const std::string& option_name, LogLevel option_value); + + void ResetToIntro(bool skip_savegame); + + /** Exit the App with \p exit_code. */ + void ExitApp(int exit_code = 0) override; + + void ResetClientData(bool save_connection = false); + void LoadSinglePlayerGame(std::string filename = ""); + + /** Requests the savegame previews from the server in \p relative_directory + * to the server save directory. */ + void RequestSavePreviews(const std::string& relative_directory); + void Autosave(); ///< Autosaves the current game, iff autosaves are enabled and any turn number requirements are met + void ContinueSinglePlayerGame(); ///< Load the newest single player autosave and continue playing game + bool IsLoadGameAvailable() const; + std::string SelectLoadFile(); ///< Lets the user select a multiplayer save to load + void InitAutoTurns(int auto_turns); ///< Initialize auto turn counter + void DecAutoTurns(int n = 1); ///< Decrease auto turn counter + void EliminateSelf(); ///< Resign from the game ClientUI& GetClientUI() { return *m_ui.get(); } - void Reinitialize(); + void Reinitialize(); + float GLVersion() const; - float GLVersion() const; + void UpdateCombatLogs(const Message& msg); + /** Update any open SaveGameDialog with previews from the server. */ + void HandleSaveGamePreviews(const Message& msg); - void HandleSaveGameDataRequest(); - void UpdateCombatLogs(const Message& msg); + /** Update this client's authorization roles */ + void HandleSetAuthRoles(const Message& msg); - void OpenURL(const std::string& url); + void OpenURL(const std::string& url); + /** Opens the users preferred application for file manager at the specified path @p browse_path */ + void BrowsePath(const boost::filesystem::path& browse_path); //@} mutable FullscreenSwitchSignalType FullscreenSwitchSignal; @@ -104,6 +121,9 @@ class HumanClientApp : OSX will not tolerate static initialization of SDL, to check screen size. */ static void AddWindowSizeOptionsAfterMainStart(OptionsDB& db); + /** Converts server address to correct option name */ + static std::string EncodeServerAddressOption(const std::string& server); + /** If hosting then send the logger state to the server. */ void SendLoggingConfigToServer(); @@ -111,45 +131,64 @@ class HumanClientApp : void Initialize() override; private: - void HandleSystemEvents() override; - - void RenderBegin() override; - - void HandleMessage(Message& msg); + /** Starts a server process on localhost. - void HandleWindowMove(GG::X w, GG::Y h); - void HandleWindowResize(GG::X w, GG::Y h); - void HandleAppQuitting(); - void HandleFocusChange(bool gained_focus); + Throws a runtime_error if the server process can't be started. - void ConnectKeyboardAcceleratorSignals();///< installs the following 3 global hotkeys: quit, exit, togglefullscreen - bool HandleHotkeyResetGame(); ///< quit current game to IntroScreen - bool HandleHotkeyExitApp(); ///< quit current game & freeorion to Desktop - bool ToggleFullscreen(); ///< toggle to/from fullscreen display + Throws LocalServerAlreadyRunningException (derived from runtime_error + in HumanClientApp.cpp) if another server is already running. */ + void StartServer(); - void UpdateFPSLimit(); ///< polls options database to find if FPS should be limited, and if so, to what rate + /** Frees (relinquishes ownership and control of) any running server + * process already started by this client; performs no cleanup of other + * processes, such as AIs. */ + void FreeServer(); - void DisconnectedFromServer(); ///< called by ClientNetworking when the TCP connection to the server is lost + void HandleSystemEvents() override; + void RenderBegin() override; + void HandleMessage(Message& msg); + void HandleWindowMove(GG::X w, GG::Y h); + void HandleWindowResize(GG::X w, GG::Y h); + void HandleAppQuitting(); + void HandleFocusChange(bool gained_focus); + void ConnectKeyboardAcceleratorSignals(); ///< installs the following 3 global hotkeys: quit, exit, togglefullscreen + bool HandleHotkeyResetGame(); ///< quit current game to IntroScreen + bool HandleHotkeyExitApp(); ///< quit current game & freeorion to Desktop + bool ToggleFullscreen(); ///< toggle to/from fullscreen display + void UpdateFPSLimit(); ///< polls options database to find if FPS should be limited, and if so, to what rate + + /** If a game is not running re-parse the universe otherwise inform the + player changes will effect new games. */ + void HandleResoureDirChange(); + + void DisconnectedFromServer(); ///< called by ClientNetworking when the TCP connection to the server is lost + + /** ExitSDL is bound by ResetOrExitApp() to exit the application. */ + void ExitSDL(int exit_code); /** Either reset to IntroMenu (\p reset is true), or exit the - application. If \p skip_savegame is true abort in progress save games.*/ - void ResetOrExitApp(bool reset, bool skip_savegame); + application. If \p skip_savegame is true abort in progress save + games. \p exit_code is the exit code. */ + void ResetOrExitApp(bool reset, bool skip_savegame, int exit_code = 0); std::unique_ptr m_fsm; - Process m_server_process; ///< the server process (when hosting a game or playing single player); will be empty when playing multiplayer as a non-host player + Process m_server_process; ///< the server process (when hosting a game or playing single player); will be empty when playing multiplayer as a non-host player /** The only instance of the ClientUI. */ std::unique_ptr m_ui; - bool m_single_player_game; ///< true when this game is a single-player game - bool m_game_started; ///< true when a game is currently in progress - bool m_connected; ///< true if we are in a state in which we are supposed to be connected to the server - int m_auto_turns; ///< auto turn counter - bool m_have_window_focus; - - bool m_save_game_in_progress; - boost::signals2::signal SaveGameCompletedSignal; + bool m_single_player_game = true; ///< true when this game is a single-player game + bool m_game_started = false; ///< true when a game is currently in progress + bool m_exit_handled = false; ///< true when the exit logic is already being handled + bool m_connected = false; ///< true if we are in a state in which we are supposed to be connected to the server + int m_auto_turns = 0; ///< auto turn counter + bool m_have_window_focus = true; + + /** Filenames of all in progress saves. There maybe multiple saves in + progress if a player and an autosave are initiated at the same time. */ + std::queue m_game_saves_in_progress; + boost::signals2::signal SaveGamesCompletedSignal; }; #endif // _HumanClientApp_h_ diff --git a/client/human/HumanClientFSM.cpp b/client/human/HumanClientFSM.cpp index 615e1e439a6..757a2407c37 100644 --- a/client/human/HumanClientFSM.cpp +++ b/client/human/HumanClientFSM.cpp @@ -4,18 +4,22 @@ #include "../../Empire/Empire.h" #include "../../universe/System.h" #include "../../universe/Species.h" +#include "../../universe/Universe.h" #include "../../network/Networking.h" -#include "../../network/ClientNetworking.h" +#include "../ClientNetworking.h" #include "../../util/i18n.h" -#include "../../util/MultiplayerCommon.h" +#include "../util/GameRules.h" #include "../../util/OptionsDB.h" #include "../../UI/ChatWnd.h" #include "../../UI/PlayerListWnd.h" #include "../../UI/IntroScreen.h" #include "../../UI/MultiplayerLobbyWnd.h" +#include "../../UI/PasswordEnterWnd.h" #include "../../UI/MapWnd.h" #include +#include +#include #include /** \page statechart_notes Notes on Boost Statechart transitions @@ -55,7 +59,7 @@ Two constructions that may cause problems are: - - MessageBox blocks execution locally and starts an EventPump that handles events that may + - MessageBox blocks execution locally and starts an modal loop that handles events that may transit<> to a new state which makes a local transition lexically after the MessageBox potentially fatal. @@ -66,7 +70,7 @@ class CombatLogManager; -CombatLogManager& GetCombatLogManager(); +CombatLogManager& GetCombatLogManager(); namespace { DeclareThreadSafeLogger(FSM); @@ -102,7 +106,8 @@ void HumanClientFSM::unconsumed_event(const boost::statechart::event_base &event for (auto leaf_state_it = state_begin(); leaf_state_it != state_end();) { // The following use of typeid assumes that // BOOST_STATECHART_USE_NATIVE_RTTI is defined - ss << typeid( *leaf_state_it ).name(); + const auto& leaf_state = *leaf_state_it; + ss << typeid(leaf_state).name(); ++leaf_state_it; if (leaf_state_it != state_end()) ss << ", "; @@ -123,6 +128,7 @@ IntroMenu::IntroMenu(my_context ctx) : { TraceLogger(FSM) << "(HumanClientFSM) IntroMenu"; Client().GetClientUI().ShowIntroScreen(); + GetGameRules().ResetToDefaults(); } IntroMenu::~IntroMenu() { @@ -153,6 +159,16 @@ boost::statechart::result IntroMenu::react(const StartQuittingGame& e) { return transit(); } +boost::statechart::result IntroMenu::react(const EndGame&) { + TraceLogger(FSM) << "(HumanClientFSM) IntroMenu ignoring EndGame."; + return discard_event(); +} + +boost::statechart::result IntroMenu::react(const Disconnection&) { + TraceLogger(FSM) << "(HumanClientFSM) IntroMenu ignoring disconnection."; + return discard_event(); +} + //////////////////////////////////////////////////////////// // WaitingForSPHostAck @@ -228,12 +244,9 @@ boost::statechart::result WaitingForSPHostAck::react(const StartQuittingGame& e) boost::statechart::result WaitingForSPHostAck::react(const CheckSum& e) { TraceLogger(FSM) << "(HumanClientFSM) CheckSum."; - - std::map checksums; - ExtractContentCheckSumMessageData(e.m_message, checksums); - InfoLogger() << "Got checksum message from server:"; - for (const auto& c : checksums) - InfoLogger() << c.first << " : " << c.second; + bool result = Client().VerifyCheckSum(e.m_message); + if (!result) + ClientUI::MessageBox(UserString("ERROR_CHECKSUM_MISMATCH"), true); return discard_event(); } @@ -310,12 +323,9 @@ boost::statechart::result WaitingForMPHostAck::react(const StartQuittingGame& e) boost::statechart::result WaitingForMPHostAck::react(const CheckSum& e) { TraceLogger(FSM) << "(HumanClientFSM) CheckSum."; - - std::map checksums; - ExtractContentCheckSumMessageData(e.m_message, checksums); - InfoLogger() << "Got checksum message from server:"; - for (const auto& c : checksums) - InfoLogger() << c.first << " : " << c.second; + bool result = Client().VerifyCheckSum(e.m_message); + if (!result) + ClientUI::MessageBox(UserString("ERROR_CHECKSUM_MISMATCH"), true); return discard_event(); } @@ -334,7 +344,24 @@ boost::statechart::result WaitingForMPJoinAck::react(const JoinGame& msg) { TraceLogger(FSM) << "(HumanClientFSM) WaitingForMPJoinAck.JoinGame"; try { - int player_id = boost::lexical_cast(msg.m_message.Text()); + int player_id; + boost::uuids::uuid cookie; + ExtractJoinAckMessageData(msg.m_message, player_id, cookie); + + if (!cookie.is_nil()) { + try { + std::string cookie_option = HumanClientApp::EncodeServerAddressOption(Client().Networking().Destination()); + if (!GetOptionsDB().OptionExists(cookie_option + ".cookie")) { + GetOptionsDB().Add(cookie_option + ".cookie", "OPTIONS_DB_SERVER_COOKIE", boost::uuids::to_string(boost::uuids::nil_uuid())); + } + GetOptionsDB().Set(cookie_option + ".cookie", boost::uuids::to_string(cookie)); + GetOptionsDB().Commit(); + } catch(const std::exception& err) { + WarnLogger() << "Cann't save cookie for server " << Client().Networking().Destination() << ": " + << err.what(); + // ignore + } + } Client().Networking().SetPlayerID(player_id); @@ -345,6 +372,20 @@ boost::statechart::result WaitingForMPJoinAck::react(const JoinGame& msg) { } } +boost::statechart::result WaitingForMPJoinAck::react(const AuthRequest& msg) { + TraceLogger(FSM) << "(HumanClientFSM) WaitingForMPJoinAck.AuthRequest"; + + std::string player_name; + std::string auth; + ExtractAuthRequestMessageData(msg.m_message, player_name, auth); + + auto password_dialog = Client().GetClientUI().GetPasswordEnterWnd(); + password_dialog->SetPlayerName(player_name); + Client().Register(password_dialog); + + return discard_event(); +} + boost::statechart::result WaitingForMPJoinAck::react(const Disconnection& d) { TraceLogger(FSM) << "(HumanClientFSM) PlayingGame.Disconnection"; @@ -387,6 +428,14 @@ boost::statechart::result WaitingForMPJoinAck::react(const StartQuittingGame& e) return transit(); } +boost::statechart::result WaitingForMPJoinAck::react(const CancelMPGameClicked& a) { + TraceLogger(FSM) << "(HumanClientFSM) WaitingForMPJoinAck.CancelMPGameClicked"; + + // See reaction_transition_note. + auto retval = discard_event(); + Client().ResetToIntro(true); + return retval; +} //////////////////////////////////////////////////////////// // MPLobby @@ -396,7 +445,9 @@ MPLobby::MPLobby(my_context ctx) : { TraceLogger(FSM) << "(HumanClientFSM) MPLobby"; - Client().Register(Client().GetClientUI().GetMultiPlayerLobbyWnd()); + const auto& wnd = Client().GetClientUI().GetMultiPlayerLobbyWnd(); + Client().Register(wnd); + wnd->CleanupChat(); } MPLobby::~MPLobby() { @@ -444,10 +495,12 @@ boost::statechart::result MPLobby::react(const PlayerChat& msg) { TraceLogger(FSM) << "(HumanClientFSM) MPLobby.PlayerChat"; int player_id; + boost::posix_time::ptime timestamp; std::string data; - ExtractServerPlayerChatMessageData(msg.m_message, player_id, data); + bool pm; + ExtractServerPlayerChatMessageData(msg.m_message, player_id, timestamp, data, pm); - Client().GetClientUI().GetMultiPlayerLobbyWnd()->ChatMessage(player_id, data); + Client().GetClientUI().GetMultiPlayerLobbyWnd()->ChatMessage(player_id, timestamp, data); return discard_event(); } @@ -479,8 +532,14 @@ boost::statechart::result MPLobby::react(const GameStart& msg) { // transitioning into WaitingForGameStart post_event(msg); + std::string chat_text = Client().GetClientUI().GetMultiPlayerLobbyWnd()->GetChatText(); + Client().GetClientUI().GetMapWnd()->Sanitize(); Client().Remove(Client().GetClientUI().GetMultiPlayerLobbyWnd()); + Client().Register(Client().GetClientUI().GetMessageWnd()); + + Client().GetClientUI().GetMessageWnd()->SetChatText(chat_text); + return transit(); } @@ -514,12 +573,50 @@ boost::statechart::result MPLobby::react(const StartQuittingGame& e) { boost::statechart::result MPLobby::react(const CheckSum& e) { TraceLogger(FSM) << "(HumanClientFSM) CheckSum."; + bool result = Client().VerifyCheckSum(e.m_message); + if (!result) + ClientUI::MessageBox(UserString("ERROR_CHECKSUM_MISMATCH"), true); + return discard_event(); +} + +boost::statechart::result MPLobby::react(const ChatHistory& msg) { + TraceLogger(FSM) << "(HumanClientFSM) ChatHistory."; + + std::vector chat_history; + ExtractChatHistoryMessage(msg.m_message, chat_history); + + const auto& wnd = Client().GetClientUI().GetMultiPlayerLobbyWnd(); + for (const auto& elem : chat_history) + wnd->ChatMessage(elem.m_text, + elem.m_player_name, + elem.m_player_name.empty() ? Client().GetClientUI().TextColor() : elem.m_text_color, + elem.m_timestamp); + + return discard_event(); +} + +boost::statechart::result MPLobby::react(const PlayerStatus& msg) { + TraceLogger(FSM) << "(HumanClientFSM) PlayerStatus."; + // ToDo: show it in player ready status + + return discard_event(); +} + +boost::statechart::result MPLobby::react(const SaveGameComplete& msg) { + TraceLogger(FSM) << "(HumanClientFSM) SaveGameComplete."; + // ignore it + + return discard_event(); +} + +boost::statechart::result MPLobby::react(const TurnProgress& msg) { + TraceLogger(FSM) << "(HumanClientFSM) TurnProgress."; + + Message::TurnProgressPhase phase_id; + ExtractTurnProgressMessageData(msg.m_message, phase_id); + const auto& wnd = Client().GetClientUI().GetMultiPlayerLobbyWnd(); + wnd->TurnPhaseUpdate(phase_id); - std::map checksums; - ExtractContentCheckSumMessageData(e.m_message, checksums); - InfoLogger() << "Got checksum message from server:"; - for (const auto& c : checksums) - InfoLogger() << c.first << " : " << c.second; return discard_event(); } @@ -534,7 +631,6 @@ PlayingGame::PlayingGame(my_context ctx) : Client().Register(Client().GetClientUI().GetMapWnd()); Client().GetClientUI().GetMapWnd()->Show(); - Client().GetClientUI().GetMessageWnd()->Clear(); } PlayingGame::~PlayingGame() { @@ -566,9 +662,26 @@ boost::statechart::result PlayingGame::react(const PlayerChat& msg) { TraceLogger(FSM) << "(HumanClientFSM) PlayingGame.PlayerChat: " << msg.m_message.Text(); std::string text; int sending_player_id; - ExtractPlayerChatMessageData(msg.m_message, sending_player_id, text); + boost::posix_time::ptime timestamp; + bool pm; + ExtractServerPlayerChatMessageData(msg.m_message, sending_player_id, timestamp, text, pm); + + std::string player_name{UserString("PLAYER") + " " + std::to_string(sending_player_id)}; + GG::Clr text_color{Client().GetClientUI().TextColor()}; + if (sending_player_id != Networking::INVALID_PLAYER_ID) { + auto players = Client().Players(); + auto player_it = players.find(sending_player_id); + if (player_it != players.end()) { + player_name = player_it->second.name; + if (auto empire = GetEmpire(player_it->second.empire_id)) + text_color = empire->Color(); + } + } else { + // It's a server message. Don't set player name. + player_name = ""; + } - Client().GetClientUI().GetMessageWnd()->HandlePlayerChatMessage(text, sending_player_id, Client().PlayerID()); + Client().GetClientUI().GetMessageWnd()->HandlePlayerChatMessage(text, player_name, text_color, timestamp, Client().PlayerID(), pm); return discard_event(); } @@ -585,13 +698,11 @@ boost::statechart::result PlayingGame::react(const Disconnection& d) { boost::statechart::result PlayingGame::react(const PlayerStatus& msg) { TraceLogger(FSM) << "(HumanClientFSM) PlayingGame.PlayerStatus"; - int about_player_id; Message::PlayerStatus status; - ExtractPlayerStatusMessageData(msg.m_message, about_player_id, status); + int about_empire_id; + ExtractPlayerStatusMessageData(msg.m_message, status, about_empire_id); - Client().SetPlayerStatus(about_player_id, status); - Client().GetClientUI().GetMessageWnd()->HandlePlayerStatusUpdate(status, about_player_id); - Client().GetClientUI().GetPlayerListWnd()->HandlePlayerStatusUpdate(status, about_player_id); + Client().SetEmpireStatus(about_empire_id, status); // TODO: tell the map wnd or something else as well? return discard_event(); @@ -675,7 +786,7 @@ boost::statechart::result PlayingGame::react(const TurnProgress& msg) { Message::TurnProgressPhase phase_id; ExtractTurnProgressMessageData(msg.m_message, phase_id); - Client().GetClientUI().GetMessageWnd()->HandleTurnPhaseUpdate(phase_id); + Client().HandleTurnPhaseUpdate(phase_id); return discard_event(); } @@ -690,6 +801,38 @@ boost::statechart::result PlayingGame::react(const TurnPartialUpdate& msg) { return discard_event(); } +boost::statechart::result PlayingGame::react(const LobbyUpdate& msg) { + TraceLogger(FSM) << "(HumanClientFSM) PlayingGame.LobbyUpdate"; + + // need to re-post the lobby update message to be re-handled after + // transitioning into MPLobby + post_event(msg); + + Client().ResetClientData(true); + Client().GetClientUI().ShowMultiPlayerLobbyWnd(); + + return transit(); +} + +boost::statechart::result PlayingGame::react(const TurnTimeout& msg) { + DebugLogger(FSM) << "(PlayerFSM) PlayingGame::TurnTimeout message received: " << msg.m_message.Text(); + const std::string& text = msg.m_message.Text(); + int timeout_remain = 0; + try { + timeout_remain = boost::lexical_cast(text); + } catch (const boost::bad_lexical_cast& ex) { + ErrorLogger(FSM) << "PlayingGame::react(const TurnTimeout& msg) could not convert \"" << text << "\" to timeout"; + } + Client().GetClientUI().GetMapWnd()->ResetTimeoutClock(timeout_remain); + return discard_event(); +} + +boost::statechart::result PlayingGame::react(const PlayerInfoMsg& msg) { + DebugLogger(FSM) << "(PlayerFSM) PlayingGame::PlayerInfoMsg message received: " << msg.m_message.Text(); + ExtractPlayerInfoMessageData(msg.m_message, Client().Players()); + Client().GetClientUI().GetPlayerListWnd()->Refresh(); + return discard_event(); +} //////////////////////////////////////////////////////////// // WaitingForGameStart @@ -699,7 +842,6 @@ WaitingForGameStart::WaitingForGameStart(my_context ctx) : { TraceLogger(FSM) << "(HumanClientFSM) WaitingForGameStart"; - Client().Register(Client().GetClientUI().GetMessageWnd()); Client().Register(Client().GetClientUI().GetPlayerListWnd()); Client().GetClientUI().GetMapWnd()->EnableOrderIssuing(false); @@ -720,7 +862,6 @@ boost::statechart::result WaitingForGameStart::react(const GameStart& msg) { bool single_player_game = false; int empire_id = ALL_EMPIRES; int current_turn = INVALID_GAME_TURN; - Client().PlayerStatus().clear(); Client().Orders().Reset(); ExtractGameStartMessageData(msg.m_message, single_player_game, empire_id, @@ -741,13 +882,20 @@ boost::statechart::result WaitingForGameStart::react(const GameStart& msg) { bool is_new_game = !(loaded_game_data && ui_data_available); Client().StartGame(is_new_game); + TraceLogger(FSM) << "Restoring UI data from save data..."; + if (!is_new_game) Client().GetClientUI().RestoreFromSaveData(ui_data); + TraceLogger(FSM) << "UI data from save data restored"; + // if I am the host on the first turn, do an autosave. if (is_new_game && Client().Networking().PlayerIsHost(Client().PlayerID())) Client().Autosave(); + Client().GetClientUI().GetPlayerListWnd()->Refresh(); + Client().GetClientUI().GetMapWnd()->ResetTimeoutClock(0); + return transit(); } @@ -765,14 +913,6 @@ WaitingForTurnData::WaitingForTurnData(my_context ctx) : WaitingForTurnData::~WaitingForTurnData() { TraceLogger(FSM) << "(HumanClientFSM) ~WaitingForTurnData"; } -boost::statechart::result WaitingForTurnData::react(const SaveGameDataRequest& msg) { - TraceLogger(FSM) << "(HumanClientFSM) WaitingForTurnData.SaveGameDataRequest"; - DebugLogger(FSM) << "Sending Save Game Data to Server"; - Client().GetClientUI().GetMessageWnd()->HandleGameStatusUpdate(UserString("SERVER_SAVE_INITIATE_ACK") + "\n"); - Client().HandleSaveGameDataRequest(); - return discard_event(); -} - boost::statechart::result WaitingForTurnData::react(const SaveGameComplete& msg) { TraceLogger(FSM) << "(HumanClientFSM) WaitingForTurnData.SaveGameComplete"; @@ -792,6 +932,7 @@ boost::statechart::result WaitingForTurnData::react(const TurnUpdate& msg) { TraceLogger(FSM) << "(HumanClientFSM) PlayingGame.TurnUpdate"; int current_turn = INVALID_GAME_TURN; + Client().Orders().Reset(); try { ExtractTurnUpdateMessageData(msg.m_message, Client().EmpireID(), current_turn, @@ -812,6 +953,16 @@ boost::statechart::result WaitingForTurnData::react(const TurnUpdate& msg) { Client().HandleTurnUpdate(); + Client().GetClientUI().GetPlayerListWnd()->Refresh(); + Client().GetClientUI().GetMapWnd()->ResetTimeoutClock(0); + + return transit(); +} + +boost::statechart::result WaitingForTurnData::react(const TurnRevoked& msg) { + TraceLogger(FSM) << "(HumanClientFSM) PlayingGame.TurnRevoked"; + + // Allow player to change orders return transit(); } @@ -835,9 +986,6 @@ PlayingTurn::PlayingTurn(my_context ctx) : // TODO: reselect last fleet if stored in save game ui data? Client().GetClientUI().GetMessageWnd()->HandleGameStatusUpdate( boost::io::str(FlexibleFormat(UserString("TURN_BEGIN")) % CurrentTurn()) + "\n"); - Client().GetClientUI().GetMessageWnd()->HandlePlayerStatusUpdate(Message::PLAYING_TURN, Client().PlayerID()); - Client().GetClientUI().GetPlayerListWnd()->Refresh(); - Client().GetClientUI().GetPlayerListWnd()->HandlePlayerStatusUpdate(Message::PLAYING_TURN, Client().PlayerID()); if (Client().GetApp()->GetClientType() != Networking::CLIENT_TYPE_HUMAN_OBSERVER) Client().GetClientUI().GetMapWnd()->EnableOrderIssuing(true); @@ -872,14 +1020,6 @@ PlayingTurn::PlayingTurn(my_context ctx) : PlayingTurn::~PlayingTurn() { TraceLogger(FSM) << "(HumanClientFSM) ~PlayingTurn"; } -boost::statechart::result PlayingTurn::react(const SaveGameDataRequest& msg) { - TraceLogger(FSM) << "(HumanClientFSM) PlayingTurn.SaveGameDataRequest"; - DebugLogger(FSM) << "Sending Save Game Data to Server"; - Client().GetClientUI().GetMessageWnd()->HandleGameStatusUpdate(UserString("SERVER_SAVE_INITIATE_ACK") + "\n"); - Client().HandleSaveGameDataRequest(); - return discard_event(); -} - boost::statechart::result PlayingTurn::react(const SaveGameComplete& msg) { TraceLogger(FSM) << "(HumanClientFSM) PlayingTurn.SaveGameComplete"; @@ -898,14 +1038,16 @@ boost::statechart::result PlayingTurn::react(const SaveGameComplete& msg) { && GetOptionsDB().Get("auto-quit")) { DebugLogger(FSM) << "auto-quit save completed, ending game."; - Client().ExitApp(); + Client().ExitApp(0); } return discard_event(); } boost::statechart::result PlayingTurn::react(const AdvanceTurn& d) { - Client().StartTurn(); + SaveGameUIData ui_data; + Client().GetClientUI().GetSaveGameUIData(ui_data); + Client().StartTurn(ui_data); return discard_event(); } @@ -928,33 +1070,19 @@ boost::statechart::result PlayingTurn::react(const TurnEnded& msg) { boost::statechart::result PlayingTurn::react(const PlayerStatus& msg) { TraceLogger(FSM) << "(HumanClientFSM) PlayingTurn.PlayerStatus"; - int about_player_id; Message::PlayerStatus status; - ExtractPlayerStatusMessageData(msg.m_message, about_player_id, status); + int about_empire_id; + ExtractPlayerStatusMessageData(msg.m_message, status, about_empire_id); - Client().SetPlayerStatus(about_player_id, status); - Client().GetClientUI().GetMessageWnd()->HandlePlayerStatusUpdate(status, about_player_id); - Client().GetClientUI().GetPlayerListWnd()->HandlePlayerStatusUpdate(status, about_player_id); + Client().SetEmpireStatus(about_empire_id, status); if (Client().GetApp()->GetClientType() == Networking::CLIENT_TYPE_HUMAN_MODERATOR && Client().GetClientUI().GetMapWnd()->AutoEndTurnEnabled()) { - // check status of all non-mod non-obs players: are they all done their turns? + // check status of all empires: are they all done their turns? bool all_participants_waiting = true; - const std::map& player_status = Client().PlayerStatus(); - for (std::map::value_type& entry : Client().Players()) { - int player_id = entry.first; - - if (entry.second.client_type != Networking::CLIENT_TYPE_AI_PLAYER && - entry.second.client_type != Networking::CLIENT_TYPE_HUMAN_PLAYER) - { continue; } // only active participants matter... - - std::map::const_iterator status_it = player_status.find(player_id); - if (status_it == player_status.end()) { - all_participants_waiting = false; - break; - } - if (status_it->second != Message::WAITING) { + for (auto& entry : Client().Empires()) { + if (!entry.second->Ready()) { all_participants_waiting = false; break; } @@ -981,9 +1109,7 @@ boost::statechart::result PlayingTurn::react(const DispatchCombatLogs& msg) { /** The QuittingGame state expects to start with a StartQuittingGame message posted. */ QuittingGame::QuittingGame(my_context c) : my_base(c), - m_start_time(Clock::now()), - m_reset_to_intro(true), - m_server_process(nullptr) + m_start_time(Clock::now()) { // Quit the game by sending a shutdown message to the server and waiting for // the disconnection event. Free the server if it starts an orderly @@ -996,10 +1122,10 @@ QuittingGame::~QuittingGame() { TraceLogger(FSM) << "(HumanClientFSM) ~QuittingGame"; } boost::statechart::result QuittingGame::react(const StartQuittingGame& u) { - TraceLogger(FSM) << "(HumanClientFSM) QuittingGame reset to intro is " << u.m_reset_to_intro; + TraceLogger(FSM) << "(HumanClientFSM) QuittingGame"; - m_reset_to_intro = u.m_reset_to_intro; m_server_process = &u.m_server; + m_after_server_shutdown_action = u.m_after_server_shutdown_action; post_event(ShutdownServer()); return discard_event(); @@ -1082,14 +1208,8 @@ boost::statechart::result QuittingGame::react(const TerminateServer& u) { m_server_process->RequestTermination(); } - // Reset the game or quit the app as appropriate - if (m_reset_to_intro) { - TraceLogger(FSM) << "QuittingGame resetting to intro."; - Client().ResetClientData(); - return transit(); - } else { - TraceLogger(FSM) << "QuittingGame throwing CleanQuit."; - throw HumanClientApp::CleanQuit(); - } -} + m_after_server_shutdown_action(); + // If m_after_server_shutdown_action() exits the app, this line will never be reached + return transit(); +} diff --git a/client/human/HumanClientFSM.h b/client/human/HumanClientFSM.h index 8f04674d0e3..843c2c71c2a 100644 --- a/client/human/HumanClientFSM.h +++ b/client/human/HumanClientFSM.h @@ -39,10 +39,13 @@ struct AdvanceTurn : boost::statechart::event {}; /** The set of events used by the QuittingGame state. */ class Process; struct StartQuittingGame : boost::statechart::event { - StartQuittingGame(bool _r, Process& _s) : m_reset_to_intro(_r), m_server(_s) {} + StartQuittingGame(Process& server_, std::function&& after_server_shutdown_action_) : + m_server(server_), m_after_server_shutdown_action(std::move(after_server_shutdown_action_)) + {} - bool m_reset_to_intro; ///< Determines if on completion it resets to intro (true) or exits app (false). Process& m_server; + /// An action to be completed after disconnecting from the server + std::function m_after_server_shutdown_action; }; struct ShutdownServer : boost::statechart::event {}; @@ -106,7 +109,9 @@ struct IntroMenu : boost::statechart::state { boost::statechart::custom_reaction, boost::statechart::custom_reaction, boost::statechart::custom_reaction, - boost::statechart::custom_reaction + boost::statechart::custom_reaction, + boost::statechart::custom_reaction, + boost::statechart::custom_reaction > reactions; IntroMenu(my_context ctx); @@ -116,6 +121,8 @@ struct IntroMenu : boost::statechart::state { boost::statechart::result react(const HostMPGameRequested& a); boost::statechart::result react(const JoinMPGameRequested& a); boost::statechart::result react(const StartQuittingGame& msg); + boost::statechart::result react(const EndGame&); + boost::statechart::result react(const Disconnection&); CLIENT_ACCESSOR }; @@ -181,8 +188,10 @@ struct WaitingForMPJoinAck : boost::statechart::simple_state, + boost::statechart::custom_reaction, boost::statechart::custom_reaction, boost::statechart::custom_reaction, + boost::statechart::custom_reaction, boost::statechart::custom_reaction > reactions; @@ -190,8 +199,10 @@ struct WaitingForMPJoinAck : boost::statechart::simple_state { boost::statechart::custom_reaction, boost::statechart::custom_reaction, boost::statechart::custom_reaction, - boost::statechart::custom_reaction + boost::statechart::custom_reaction, + boost::statechart::custom_reaction, + boost::statechart::custom_reaction, + boost::statechart::custom_reaction, + boost::statechart::custom_reaction > reactions; MPLobby(my_context ctx); @@ -228,6 +243,10 @@ struct MPLobby : boost::statechart::state { boost::statechart::result react(const StartQuittingGame& msg); boost::statechart::result react(const Error& msg); boost::statechart::result react(const CheckSum& msg); + boost::statechart::result react(const ChatHistory& msg); + boost::statechart::result react(const PlayerStatus& msg); + boost::statechart::result react(const SaveGameComplete& msg); + boost::statechart::result react(const TurnProgress& msg); CLIENT_ACCESSOR }; @@ -248,7 +267,10 @@ struct PlayingGame : boost::statechart::state, boost::statechart::custom_reaction, boost::statechart::custom_reaction, - boost::statechart::custom_reaction + boost::statechart::custom_reaction, + boost::statechart::custom_reaction, + boost::statechart::custom_reaction, + boost::statechart::custom_reaction > reactions; PlayingGame(my_context ctx); @@ -265,6 +287,9 @@ struct PlayingGame : boost::statechart::state Base; typedef boost::mpl::list< - boost::statechart::custom_reaction, boost::statechart::custom_reaction, boost::statechart::custom_reaction, + boost::statechart::custom_reaction, boost::statechart::custom_reaction > reactions; WaitingForTurnData(my_context ctx); ~WaitingForTurnData(); - boost::statechart::result react(const SaveGameDataRequest& d); boost::statechart::result react(const SaveGameComplete& d); boost::statechart::result react(const TurnUpdate& msg); + boost::statechart::result react(const TurnRevoked& msg); boost::statechart::result react(const DispatchCombatLogs& msg); CLIENT_ACCESSOR @@ -317,7 +342,6 @@ struct PlayingTurn : boost::statechart::state { typedef boost::statechart::state Base; typedef boost::mpl::list< - boost::statechart::custom_reaction, boost::statechart::custom_reaction, boost::statechart::custom_reaction, boost::statechart::custom_reaction, @@ -329,7 +353,6 @@ struct PlayingTurn : boost::statechart::state { PlayingTurn(my_context ctx); ~PlayingTurn(); - boost::statechart::result react(const SaveGameDataRequest& d); boost::statechart::result react(const SaveGameComplete& d); boost::statechart::result react(const AdvanceTurn& d); boost::statechart::result react(const TurnUpdate& msg); @@ -364,10 +387,9 @@ struct QuittingGame : boost::statechart::state { std::chrono::steady_clock::time_point m_start_time; - // if m_reset_to_intro is true QuittingGame transits to the Intro menu, otherwise it - // exits the app. - bool m_reset_to_intro; - Process* m_server_process; + Process* m_server_process = nullptr; + /// An action to be completed after disconnecting from the server + std::function m_after_server_shutdown_action = std::function(); CLIENT_ACCESSOR }; diff --git a/client/human/chmain.cpp b/client/human/chmain.cpp index 7968fb54b55..dd3d4864845 100644 --- a/client/human/chmain.cpp +++ b/client/human/chmain.cpp @@ -1,7 +1,6 @@ #include "HumanClientApp.h" -#include "../../parse/Parse.h" #include "../../util/OptionsDB.h" #include "../../util/Directories.h" #include "../../util/Logger.h" @@ -38,7 +37,7 @@ unroll and hide the stack trace, print a message and still crash anyways. */ // installed version of FO is run with the command-line flag added in as // appropriate. #ifdef FREEORION_WIN32 -const bool STORE_FULLSCREEN_FLAG = false; +const bool STORE_FULLSCREEN_FLAG = false; // Windows keeps good care of the resolution state itself, // so there is no reason to default to not touching it. const bool FAKE_MODE_CHANGE_FLAG = false; @@ -54,7 +53,7 @@ int mainSetupAndRun(); int mainConfigOptionsSetup(const std::vector& args); -#if defined(FREEORION_LINUX) || defined(FREEORION_FREEBSD) +#if defined(FREEORION_LINUX) || defined(FREEORION_FREEBSD) || defined(FREEORION_OPENBSD) int main(int argc, char* argv[]) { // copy command line arguments to vector std::vector args; @@ -87,9 +86,10 @@ int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) { #endif #ifndef FREEORION_MACOSX // did the player request help output? - if (GetOptionsDB().Get("help")) { + auto help_arg = GetOptionsDB().Get("help"); + if (help_arg != "NOOP") { ShutdownLoggingSystemFileSink(); - GetOptionsDB().GetUsage(std::cout); + GetOptionsDB().GetUsage(std::cout, help_arg, true); return 0; // quit without actually starting game } @@ -120,22 +120,59 @@ int mainConfigOptionsSetup(const std::vector& args) { try { #endif // add entries in options DB that have no other obvious place - GetOptionsDB().AddFlag('h', "help", UserStringNop("OPTIONS_DB_HELP"), false); - GetOptionsDB().AddFlag('v', "version", UserStringNop("OPTIONS_DB_VERSION"), false); - GetOptionsDB().AddFlag('g', "generate-config-xml", UserStringNop("OPTIONS_DB_GENERATE_CONFIG_XML"), false); - GetOptionsDB().AddFlag('f', "fullscreen", UserStringNop("OPTIONS_DB_FULLSCREEN"), STORE_FULLSCREEN_FLAG); - GetOptionsDB().Add("reset-fullscreen-size", UserStringNop("OPTIONS_DB_RESET_FSSIZE"), true); - GetOptionsDB().Add("fake-mode-change", UserStringNop("OPTIONS_DB_FAKE_MODE_CHANGE"), FAKE_MODE_CHANGE_FLAG); - GetOptionsDB().Add("fullscreen-monitor-id", UserStringNop("OPTIONS_DB_FULLSCREEN_MONITOR_ID"), 0, RangedValidator(0, 5)); - GetOptionsDB().AddFlag('q', "quickstart", UserStringNop("OPTIONS_DB_QUICKSTART"), false); - GetOptionsDB().AddFlag("auto-quit", UserStringNop("OPTIONS_DB_AUTO_QUIT"), false); - GetOptionsDB().Add("auto-advance-n-turns", UserStringNop("OPTIONS_DB_AUTO_N_TURNS"), 0, RangedValidator(0, 400), false); - GetOptionsDB().Add("load", UserStringNop("OPTIONS_DB_LOAD"), "", Validator(), false); - GetOptionsDB().Add("UI.sound.music-enabled", UserStringNop("OPTIONS_DB_MUSIC_ON"), true); - GetOptionsDB().Add("UI.sound.enabled", UserStringNop("OPTIONS_DB_SOUND_ON"), true); - GetOptionsDB().Add("version-string", UserStringNop("OPTIONS_DB_VERSION_STRING"), - FreeOrionVersionString(), Validator(), true); - GetOptionsDB().AddFlag('r', "render-simple", UserStringNop("OPTIONS_DB_RENDER_SIMPLE"), false); + GetOptionsDB().Add('h', "help", UserStringNop("OPTIONS_DB_HELP"), "NOOP", + Validator(), false); + GetOptionsDB().AddFlag('v', "version", UserStringNop("OPTIONS_DB_VERSION"), false, + "version"); + GetOptionsDB().AddFlag('g', "generate-config-xml", UserStringNop("OPTIONS_DB_GENERATE_CONFIG_XML"), false); + GetOptionsDB().AddFlag('f', "video.fullscreen.enabled", UserStringNop("OPTIONS_DB_FULLSCREEN"), STORE_FULLSCREEN_FLAG); + GetOptionsDB().Add("video.fullscreen.reset", UserStringNop("OPTIONS_DB_RESET_FSSIZE"), true); + GetOptionsDB().Add("video.fullscreen.fake.enabled", UserStringNop("OPTIONS_DB_FAKE_MODE_CHANGE"), FAKE_MODE_CHANGE_FLAG); + GetOptionsDB().Add("video.monitor.id", UserStringNop("OPTIONS_DB_FULLSCREEN_MONITOR_ID"), 0, + RangedValidator(0, 5)); + GetOptionsDB().AddFlag("continue", UserStringNop("OPTIONS_DB_CONTINUE"), false); + GetOptionsDB().AddFlag("auto-quit", UserStringNop("OPTIONS_DB_AUTO_QUIT"), false); + GetOptionsDB().Add("auto-advance-n-turns", UserStringNop("OPTIONS_DB_AUTO_N_TURNS"), 0, + RangedValidator(0, 400), false); + GetOptionsDB().Add("audio.music.enabled", UserStringNop("OPTIONS_DB_MUSIC_ON"), true); + GetOptionsDB().Add("audio.effects.enabled", UserStringNop("OPTIONS_DB_SOUND_ON"), true); + GetOptionsDB().Add("version.string", UserStringNop("OPTIONS_DB_VERSION_STRING"), FreeOrionVersionString(), + Validator(), true); + GetOptionsDB().AddFlag('r', "render-simple", UserStringNop("OPTIONS_DB_RENDER_SIMPLE"), false); + + // add sections for option sorting + GetOptionsDB().AddSection("audio", UserStringNop("OPTIONS_DB_SECTION_AUDIO")); + GetOptionsDB().AddSection("audio.music", UserStringNop("OPTIONS_DB_SECTION_AUDIO_MUSIC")); + GetOptionsDB().AddSection("audio.effects", UserStringNop("OPTIONS_DB_SECTION_AUDIO_EFFECTS")); + GetOptionsDB().AddSection("audio.effects.paths", UserStringNop("OPTIONS_DB_SECTION_AUDIO_EFFECTS_PATHS"), + [](const std::string& name)->bool { + std::string suffix { "sound.path" }; + return name.size() > suffix.size() && + name.substr(name.size() - suffix.size()) == suffix; + }); + GetOptionsDB().AddSection("effects", UserStringNop("OPTIONS_DB_SECTION_EFFECTS")); + GetOptionsDB().AddSection("logging", UserStringNop("OPTIONS_DB_SECTION_LOGGING")); + GetOptionsDB().AddSection("network", UserStringNop("OPTIONS_DB_SECTION_NETWORK")); + GetOptionsDB().AddSection("resource", UserStringNop("OPTIONS_DB_SECTION_RESOURCE")); + GetOptionsDB().AddSection("save", UserStringNop("OPTIONS_DB_SECTION_SAVE")); + GetOptionsDB().AddSection("setup", UserStringNop("OPTIONS_DB_SECTION_SETUP")); + GetOptionsDB().AddSection("ui", UserStringNop("OPTIONS_DB_SECTION_UI")); + GetOptionsDB().AddSection("ui.colors", UserStringNop("OPTIONS_DB_SECTION_UI_COLORS"), + [](const std::string& name)->bool { + std::string suffix { ".color" }; + return name.size() > suffix.size() && + name.substr(name.size() - suffix.size()) == suffix; + }); + GetOptionsDB().AddSection("ui.hotkeys", UserStringNop("OPTIONS_DB_SECTION_UI_HOTKEYS"), + [](const std::string& name)->bool { + std::string suffix { ".hotkey" }; + return name.size() > suffix.size() && + name.substr(name.size() - suffix.size()) == suffix; + }); + GetOptionsDB().AddSection("version", UserStringNop("OPTIONS_DB_SECTION_VERSION")); + GetOptionsDB().AddSection("video", UserStringNop("OPTIONS_DB_SECTION_VIDEO")); + GetOptionsDB().AddSection("video.fullscreen", UserStringNop("OPTIONS_DB_SECTION_VIDEO_FULLSCREEN")); + GetOptionsDB().AddSection("video.windowed", UserStringNop("OPTIONS_DB_SECTION_VIDEO_WINDOWED")); // Add the keyboard shortcuts Hotkey::AddOptions(GetOptionsDB()); @@ -149,7 +186,7 @@ int mainConfigOptionsSetup(const std::vector& args) { CompleteXDGMigration(); - // Handle the case where the resource-dir does not exist anymore + // Handle the case where the resource.path does not exist anymore // gracefully by resetting it to the standard path into the // application bundle. This may happen if a previous installed // version of FreeOrion was residing in a different directory. @@ -159,7 +196,7 @@ int mainConfigOptionsSetup(const std::vector& args) { { DebugLogger() << "Resources directory from config.xml missing or does not contain expected files. Resetting to default."; - GetOptionsDB().Set("resource-dir", ""); + GetOptionsDB().Set("resource.path", ""); // double-check that resetting actually fixed things... if (!boost::filesystem::exists(GetResourceDir()) || @@ -168,7 +205,7 @@ int mainConfigOptionsSetup(const std::vector& args) { { DebugLogger() << "Default Resources directory missing or does not contain expected files. Cannot start game."; throw std::runtime_error("Unable to load game resources at default location: " + - PathString(GetResourceDir()) + " : Install may be broken."); + PathToString(GetResourceDir()) + " : Install may be broken."); } } @@ -176,16 +213,16 @@ int mainConfigOptionsSetup(const std::vector& args) { // did the player request generation of config.xml, saving the default (or current) options to disk? if (GetOptionsDB().Get("generate-config-xml")) { try { - GetOptionsDB().Commit(); - } catch (const std::exception&) { + GetOptionsDB().Commit(false); + } catch (...) { std::cerr << UserString("UNABLE_TO_WRITE_CONFIG_XML") << std::endl; } } if (GetOptionsDB().Get("render-simple")) { - GetOptionsDB().Set("UI.galaxy-gas-background",false); - GetOptionsDB().Set("UI.galaxy-starfields", false); - GetOptionsDB().Set("show-fps", true); + GetOptionsDB().Set("ui.map.background.gas.shown", false); + GetOptionsDB().Set("ui.map.background.starfields.shown", false); + GetOptionsDB().Set("video.fps.shown", true); } #ifndef FREEORION_CHMAIN_KEEP_STACKTRACE @@ -209,15 +246,17 @@ int mainConfigOptionsSetup(const std::vector& args) { int mainSetupAndRun() { +#ifndef FREEORION_CHMAIN_KEEP_STACKTRACE try { +#endif RegisterOptions(&HumanClientApp::AddWindowSizeOptionsAfterMainStart); - bool fullscreen = GetOptionsDB().Get("fullscreen"); - bool fake_mode_change = GetOptionsDB().Get("fake-mode-change"); + bool fullscreen = GetOptionsDB().Get("video.fullscreen.enabled"); + bool fake_mode_change = GetOptionsDB().Get("video.fullscreen.fake.enabled"); - std::pair width_height = HumanClientApp::GetWindowWidthHeight(); + auto width_height = HumanClientApp::GetWindowWidthHeight(); int width(width_height.first), height(width_height.second); - std::pair left_top = HumanClientApp::GetWindowLeftTop(); + auto left_top = HumanClientApp::GetWindowLeftTop(); int left(left_top.first), top(left_top.second); #ifdef FREEORION_WIN32 @@ -231,8 +270,6 @@ int mainSetupAndRun() { # endif #endif - parse::init(); - HumanClientApp app(width, height, true, "FreeOrion " + FreeOrionVersionString(), left, top, fullscreen, fake_mode_change); @@ -244,6 +281,13 @@ int mainSetupAndRun() { app.NewSinglePlayerGame(true); // acceptable to call before app() } + if (GetOptionsDB().Get("continue")) { + // immediately start the server, establish network connections, and + // go into a single player game, continuing from the newest + // save game. + app.ContinueSinglePlayerGame(); // acceptable to call before app() + } + std::string load_filename = GetOptionsDB().Get("load"); if (!load_filename.empty()) { // immediately start the server, establish network connections, and @@ -253,13 +297,9 @@ int mainSetupAndRun() { } // run rendering loop - app(); // calls GUI::operator() which calls SDLGUI::Run() which starts rendering loop - - } catch (const HumanClientApp::CleanQuit&) { - // do nothing - } + app.Run(); #ifndef FREEORION_CHMAIN_KEEP_STACKTRACE - catch (const std::invalid_argument& e) { + } catch (const std::invalid_argument& e) { ErrorLogger() << "main() caught exception(std::invalid_argument): " << e.what(); std::cerr << "main() caught exception(std::invalid_arg): " << e.what() << std::endl; return 1; @@ -267,7 +307,7 @@ int mainSetupAndRun() { ErrorLogger() << "main() caught exception(std::runtime_error): " << e.what(); std::cerr << "main() caught exception(std::runtime_error): " << e.what() << std::endl; return 1; - } catch (const boost::io::format_error& e) { + } catch (const boost::io::format_error& e) { ErrorLogger() << "main() caught exception(boost::io::format_error): " << e.what(); std::cerr << "main() caught exception(boost::io::format_error): " << e.what() << std::endl; return 1; diff --git a/client/human/chmain.mm b/client/human/chmain.mm index fd91f6eaae9..1d0a62333e7 100644 --- a/client/human/chmain.mm +++ b/client/human/chmain.mm @@ -25,8 +25,9 @@ int main(int argc, char *argv[]) } // did the player request help output? - if (GetOptionsDB().Get("help")) { - GetOptionsDB().GetUsage(std::cerr); + auto help_arg = GetOptionsDB().Get("help"); + if (help_arg != "NOOP") { + GetOptionsDB().GetUsage(std::cerr, help_arg, true); return 0; // quit without actually starting game } diff --git a/Xcode/main.xib b/client/human/main.xib similarity index 96% rename from Xcode/main.xib rename to client/human/main.xib index 37bdb6c6e87..a0b8994426d 100644 --- a/Xcode/main.xib +++ b/client/human/main.xib @@ -1,7 +1,7 @@ - + @@ -11,7 +11,7 @@ - + diff --git a/cmake/FindBoost.cmake b/cmake/FindBoost.cmake new file mode 100644 index 00000000000..023cc8d9366 --- /dev/null +++ b/cmake/FindBoost.cmake @@ -0,0 +1,2215 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindBoost +--------- + +Find Boost include dirs and libraries + +Use this module by invoking find_package with the form:: + + find_package(Boost + [version] [EXACT] # Minimum or EXACT version e.g. 1.67.0 + [REQUIRED] # Fail with error if Boost is not found + [COMPONENTS ...] # Boost libraries by their canonical name + # e.g. "date_time" for "libboost_date_time" + [OPTIONAL_COMPONENTS ...] + # Optional Boost libraries by their canonical name) + ) # e.g. "date_time" for "libboost_date_time" + +This module finds headers and requested component libraries OR a CMake +package configuration file provided by a "Boost CMake" build. For the +latter case skip to the "Boost CMake" section below. For the former +case results are reported in variables:: + + Boost_FOUND - True if headers and requested libraries were found + Boost_INCLUDE_DIRS - Boost include directories + Boost_LIBRARY_DIRS - Link directories for Boost libraries + Boost_LIBRARIES - Boost component libraries to be linked + Boost__FOUND - True if component was found ( is upper-case) + Boost__LIBRARY - Libraries to link for component (may include + target_link_libraries debug/optimized keywords) + Boost_VERSION - BOOST_VERSION value from boost/version.hpp + Boost_LIB_VERSION - Version string appended to library filenames + Boost_MAJOR_VERSION - Boost major version number (X in X.y.z) + Boost_MINOR_VERSION - Boost minor version number (Y in x.Y.z) + Boost_SUBMINOR_VERSION - Boost subminor version number (Z in x.y.Z) + Boost_VERSION_STRING - Boost version number in x.y.z format + Boost_LIB_DIAGNOSTIC_DEFINITIONS (Windows) + - Pass to add_definitions() to have diagnostic + information about Boost's automatic linking + displayed during compilation + +Note that Boost Python components require a Python version suffix +(Boost 1.67 and later), e.g. ``python36`` or ``python27`` for the +versions built against Python 3.6 and 2.7, respectively. This also +applies to additional components using Python including +``mpi_python`` and ``numpy``. Earlier Boost releases may use +distribution-specific suffixes such as ``2``, ``3`` or ``2.7``. +These may also be used as suffixes, but note that they are not +portable. + +This module reads hints about search locations from variables:: + + BOOST_ROOT - Preferred installation prefix + (or BOOSTROOT) + BOOST_INCLUDEDIR - Preferred include directory e.g. /include + BOOST_LIBRARYDIR - Preferred library directory e.g. /lib + Boost_NO_SYSTEM_PATHS - Set to ON to disable searching in locations not + specified by these hint variables. Default is OFF. + Boost_ADDITIONAL_VERSIONS + - List of Boost versions not known to this module + (Boost install locations may contain the version) + +and saves search results persistently in CMake cache entries:: + + Boost_INCLUDE_DIR - Directory containing Boost headers + Boost_LIBRARY_DIR_RELEASE - Directory containing release Boost libraries + Boost_LIBRARY_DIR_DEBUG - Directory containing debug Boost libraries + Boost__LIBRARY_DEBUG - Component library debug variant + Boost__LIBRARY_RELEASE - Component library release variant + +The following :prop_tgt:`IMPORTED` targets are also defined:: + + Boost::boost - Target for header-only dependencies + (Boost include directory) + Boost:: - Target for specific component dependency + (shared or static library); is lower- + case + Boost::diagnostic_definitions - interface target to enable diagnostic + information about Boost's automatic linking + during compilation (adds BOOST_LIB_DIAGNOSTIC) + Boost::disable_autolinking - interface target to disable automatic + linking with MSVC (adds BOOST_ALL_NO_LIB) + Boost::dynamic_linking - interface target to enable dynamic linking + linking with MSVC (adds BOOST_ALL_DYN_LINK) + +Implicit dependencies such as ``Boost::filesystem`` requiring +``Boost::system`` will be automatically detected and satisfied, even +if system is not specified when using :command:`find_package` and if +``Boost::system`` is not added to :command:`target_link_libraries`. If using +``Boost::thread``, then ``Threads::Threads`` will also be added automatically. + +It is important to note that the imported targets behave differently +than variables created by this module: multiple calls to +:command:`find_package(Boost)` in the same directory or sub-directories with +different options (e.g. static or shared) will not override the +values of the targets created by the first call. + +Users may set these hints or results as ``CACHE`` entries. Projects +should not read these entries directly but instead use the above +result variables. Note that some hint names start in upper-case +"BOOST". One may specify these as environment variables if they are +not specified as CMake variables or cache entries. + +This module first searches for the ``Boost`` header files using the above +hint variables (excluding ``BOOST_LIBRARYDIR``) and saves the result in +``Boost_INCLUDE_DIR``. Then it searches for requested component libraries +using the above hints (excluding ``BOOST_INCLUDEDIR`` and +``Boost_ADDITIONAL_VERSIONS``), "lib" directories near ``Boost_INCLUDE_DIR``, +and the library name configuration settings below. It saves the +library directories in ``Boost_LIBRARY_DIR_DEBUG`` and +``Boost_LIBRARY_DIR_RELEASE`` and individual library +locations in ``Boost__LIBRARY_DEBUG`` and ``Boost__LIBRARY_RELEASE``. +When one changes settings used by previous searches in the same build +tree (excluding environment variables) this module discards previous +search results affected by the changes and searches again. + +Boost libraries come in many variants encoded in their file name. +Users or projects may tell this module which variant to find by +setting variables:: + + Boost_USE_DEBUG_LIBS - Set to ON or OFF to specify whether to search + and use the debug libraries. Default is ON. + Boost_USE_RELEASE_LIBS - Set to ON or OFF to specify whether to search + and use the release libraries. Default is ON. + Boost_USE_MULTITHREADED - Set to OFF to use the non-multithreaded + libraries ('mt' tag). Default is ON. + Boost_USE_STATIC_LIBS - Set to ON to force the use of the static + libraries. Default is OFF. + Boost_USE_STATIC_RUNTIME - Set to ON or OFF to specify whether to use + libraries linked statically to the C++ runtime + ('s' tag). Default is platform dependent. + Boost_USE_DEBUG_RUNTIME - Set to ON or OFF to specify whether to use + libraries linked to the MS debug C++ runtime + ('g' tag). Default is ON. + Boost_USE_DEBUG_PYTHON - Set to ON to use libraries compiled with a + debug Python build ('y' tag). Default is OFF. + Boost_USE_STLPORT - Set to ON to use libraries compiled with + STLPort ('p' tag). Default is OFF. + Boost_USE_STLPORT_DEPRECATED_NATIVE_IOSTREAMS + - Set to ON to use libraries compiled with + STLPort deprecated "native iostreams" + ('n' tag). Default is OFF. + Boost_COMPILER - Set to the compiler-specific library suffix + (e.g. "-gcc43"). Default is auto-computed + for the C++ compiler in use. A list may be + used if multiple compatible suffixes should + be tested for, in decreasing order of + preference. + Boost_ARCHITECTURE - Set to the architecture-specific library suffix + (e.g. "-x64"). Default is auto-computed for the + C++ compiler in use. + Boost_THREADAPI - Suffix for "thread" component library name, + such as "pthread" or "win32". Names with + and without this suffix will both be tried. + Boost_NAMESPACE - Alternate namespace used to build boost with + e.g. if set to "myboost", will search for + myboost_thread instead of boost_thread. + +Other variables one may set to control this module are:: + + Boost_DEBUG - Set to ON to enable debug output from FindBoost. + Please enable this before filing any bug report. + Boost_DETAILED_FAILURE_MSG + - Set to ON to add detailed information to the + failure message even when the REQUIRED option + is not given to the find_package call. + Boost_REALPATH - Set to ON to resolve symlinks for discovered + libraries to assist with packaging. For example, + the "system" component library may be resolved to + "/usr/lib/libboost_system.so.1.67.0" instead of + "/usr/lib/libboost_system.so". This does not + affect linking and should not be enabled unless + the user needs this information. + Boost_LIBRARY_DIR - Default value for Boost_LIBRARY_DIR_RELEASE and + Boost_LIBRARY_DIR_DEBUG. + +On Visual Studio and Borland compilers Boost headers request automatic +linking to corresponding libraries. This requires matching libraries +to be linked explicitly or available in the link library search path. +In this case setting ``Boost_USE_STATIC_LIBS`` to ``OFF`` may not achieve +dynamic linking. Boost automatic linking typically requests static +libraries with a few exceptions (such as ``Boost.Python``). Use:: + + add_definitions(${Boost_LIB_DIAGNOSTIC_DEFINITIONS}) + +to ask Boost to report information about automatic linking requests. + +Example to find Boost headers only:: + + find_package(Boost 1.36.0) + if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) + add_executable(foo foo.cc) + endif() + +Example to find Boost libraries and use imported targets:: + + find_package(Boost 1.56 REQUIRED COMPONENTS + date_time filesystem iostreams) + add_executable(foo foo.cc) + target_link_libraries(foo Boost::date_time Boost::filesystem + Boost::iostreams) + +Example to find Boost Python 3.6 libraries and use imported targets:: + + find_package(Boost 1.67 REQUIRED COMPONENTS + python36 numpy36) + add_executable(foo foo.cc) + target_link_libraries(foo Boost::python36 Boost::numpy36) + +Example to find Boost headers and some *static* (release only) libraries:: + + set(Boost_USE_STATIC_LIBS ON) # only find static libs + set(Boost_USE_DEBUG_LIBS OFF) # ignore debug libs and + set(Boost_USE_RELEASE_LIBS ON) # only find release libs + set(Boost_USE_MULTITHREADED ON) + set(Boost_USE_STATIC_RUNTIME OFF) + find_package(Boost 1.66.0 COMPONENTS date_time filesystem system ...) + if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) + add_executable(foo foo.cc) + target_link_libraries(foo ${Boost_LIBRARIES}) + endif() + +Boost CMake +^^^^^^^^^^^ + +If Boost was built using the boost-cmake project it provides a package +configuration file for use with find_package's Config mode. This +module looks for the package configuration file called +``BoostConfig.cmake`` or ``boost-config.cmake`` and stores the result in +``CACHE`` entry "Boost_DIR". If found, the package configuration file is loaded +and this module returns with no further action. See documentation of +the Boost CMake package configuration for details on what it provides. + +Set ``Boost_NO_BOOST_CMAKE`` to ``ON``, to disable the search for boost-cmake. +#]=======================================================================] + +# Save project's policies +cmake_policy(PUSH) +cmake_policy(SET CMP0057 NEW) # if IN_LIST + +#------------------------------------------------------------------------------- +# Before we go searching, check whether boost-cmake is available, unless the +# user specifically asked NOT to search for boost-cmake. +# +# If Boost_DIR is set, this behaves as any find_package call would. If not, +# it looks at BOOST_ROOT and BOOSTROOT to find Boost. +# +if (NOT Boost_NO_BOOST_CMAKE) + # If Boost_DIR is not set, look for BOOSTROOT and BOOST_ROOT as alternatives, + # since these are more conventional for Boost. + if ("$ENV{Boost_DIR}" STREQUAL "") + if (NOT "$ENV{BOOST_ROOT}" STREQUAL "") + set(ENV{Boost_DIR} $ENV{BOOST_ROOT}) + elseif (NOT "$ENV{BOOSTROOT}" STREQUAL "") + set(ENV{Boost_DIR} $ENV{BOOSTROOT}) + endif() + endif() + + # Do the same find_package call but look specifically for the CMake version. + # Note that args are passed in the Boost_FIND_xxxxx variables, so there is no + # need to delegate them to this find_package call. + find_package(Boost QUIET NO_MODULE) + mark_as_advanced(Boost_DIR) + + # If we found boost-cmake, then we're done. Print out what we found. + # Otherwise let the rest of the module try to find it. + if (Boost_FOUND) + message(STATUS "Boost ${Boost_FIND_VERSION} found.") + if (Boost_FIND_COMPONENTS) + message(STATUS "Found Boost components:\n ${Boost_FIND_COMPONENTS}") + endif() + # Restore project's policies + cmake_policy(POP) + return() + endif() +endif() + + +#------------------------------------------------------------------------------- +# FindBoost functions & macros +# + +############################################ +# +# Check the existence of the libraries. +# +############################################ +# This macro was taken directly from the FindQt4.cmake file that is included +# with the CMake distribution. This is NOT my work. All work was done by the +# original authors of the FindQt4.cmake file. Only minor modifications were +# made to remove references to Qt and make this file more generally applicable +# And ELSE/ENDIF pairs were removed for readability. +######################################################################### + +macro(_Boost_ADJUST_LIB_VARS basename) + if(Boost_INCLUDE_DIR ) + if(Boost_${basename}_LIBRARY_DEBUG AND Boost_${basename}_LIBRARY_RELEASE) + # if the generator is multi-config or if CMAKE_BUILD_TYPE is set for + # single-config generators, set optimized and debug libraries + get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(_isMultiConfig OR CMAKE_BUILD_TYPE) + set(Boost_${basename}_LIBRARY optimized ${Boost_${basename}_LIBRARY_RELEASE} debug ${Boost_${basename}_LIBRARY_DEBUG}) + else() + # For single-config generators where CMAKE_BUILD_TYPE has no value, + # just use the release libraries + set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_RELEASE} ) + endif() + # FIXME: This probably should be set for both cases + set(Boost_${basename}_LIBRARIES optimized ${Boost_${basename}_LIBRARY_RELEASE} debug ${Boost_${basename}_LIBRARY_DEBUG}) + endif() + + # if only the release version was found, set the debug variable also to the release version + if(Boost_${basename}_LIBRARY_RELEASE AND NOT Boost_${basename}_LIBRARY_DEBUG) + set(Boost_${basename}_LIBRARY_DEBUG ${Boost_${basename}_LIBRARY_RELEASE}) + set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_RELEASE}) + set(Boost_${basename}_LIBRARIES ${Boost_${basename}_LIBRARY_RELEASE}) + endif() + + # if only the debug version was found, set the release variable also to the debug version + if(Boost_${basename}_LIBRARY_DEBUG AND NOT Boost_${basename}_LIBRARY_RELEASE) + set(Boost_${basename}_LIBRARY_RELEASE ${Boost_${basename}_LIBRARY_DEBUG}) + set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_DEBUG}) + set(Boost_${basename}_LIBRARIES ${Boost_${basename}_LIBRARY_DEBUG}) + endif() + + # If the debug & release library ends up being the same, omit the keywords + if("${Boost_${basename}_LIBRARY_RELEASE}" STREQUAL "${Boost_${basename}_LIBRARY_DEBUG}") + set(Boost_${basename}_LIBRARY ${Boost_${basename}_LIBRARY_RELEASE} ) + set(Boost_${basename}_LIBRARIES ${Boost_${basename}_LIBRARY_RELEASE} ) + endif() + + if(Boost_${basename}_LIBRARY AND Boost_${basename}_HEADER) + set(Boost_${basename}_FOUND ON) + if("x${basename}" STREQUAL "xTHREAD" AND NOT TARGET Threads::Threads) + string(APPEND Boost_ERROR_REASON_THREAD " (missing dependency: Threads)") + set(Boost_THREAD_FOUND OFF) + endif() + endif() + + endif() + # Make variables changeable to the advanced user + mark_as_advanced( + Boost_${basename}_LIBRARY_RELEASE + Boost_${basename}_LIBRARY_DEBUG + ) +endmacro() + +# Detect changes in used variables. +# Compares the current variable value with the last one. +# In short form: +# v != v_LAST -> CHANGED = 1 +# v is defined, v_LAST not -> CHANGED = 1 +# v is not defined, but v_LAST is -> CHANGED = 1 +# otherwise -> CHANGED = 0 +# CHANGED is returned in variable named ${changed_var} +macro(_Boost_CHANGE_DETECT changed_var) + set(${changed_var} 0) + foreach(v ${ARGN}) + if(DEFINED _Boost_COMPONENTS_SEARCHED) + if(${v}) + if(_${v}_LAST) + string(COMPARE NOTEQUAL "${${v}}" "${_${v}_LAST}" _${v}_CHANGED) + else() + set(_${v}_CHANGED 1) + endif() + elseif(_${v}_LAST) + set(_${v}_CHANGED 1) + endif() + if(_${v}_CHANGED) + set(${changed_var} 1) + endif() + else() + set(_${v}_CHANGED 0) + endif() + endforeach() +endmacro() + +# +# Find the given library (var). +# Use 'build_type' to support different lib paths for RELEASE or DEBUG builds +# +macro(_Boost_FIND_LIBRARY var build_type) + + find_library(${var} ${ARGN}) + + if(${var}) + # If this is the first library found then save Boost_LIBRARY_DIR_[RELEASE,DEBUG]. + if(NOT Boost_LIBRARY_DIR_${build_type}) + get_filename_component(_dir "${${var}}" PATH) + set(Boost_LIBRARY_DIR_${build_type} "${_dir}" CACHE PATH "Boost library directory ${build_type}" FORCE) + endif() + elseif(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT) + # Try component-specific hints but do not save Boost_LIBRARY_DIR_[RELEASE,DEBUG]. + find_library(${var} HINTS ${_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT} ${ARGN}) + endif() + + # If Boost_LIBRARY_DIR_[RELEASE,DEBUG] is known then search only there. + if(Boost_LIBRARY_DIR_${build_type}) + set(_boost_LIBRARY_SEARCH_DIRS_${build_type} ${Boost_LIBRARY_DIR_${build_type}} NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " Boost_LIBRARY_DIR_${build_type} = ${Boost_LIBRARY_DIR_${build_type}}" + " _boost_LIBRARY_SEARCH_DIRS_${build_type} = ${_boost_LIBRARY_SEARCH_DIRS_${build_type}}") + endif() + endif() +endmacro() + +#------------------------------------------------------------------------------- + +# Convert CMAKE_CXX_COMPILER_VERSION to boost compiler suffix version. +function(_Boost_COMPILER_DUMPVERSION _OUTPUT_VERSION _OUTPUT_VERSION_MAJOR _OUTPUT_VERSION_MINOR) + string(REGEX REPLACE "([0-9]+)\\.([0-9]+)(\\.[0-9]+)?" "\\1" + _boost_COMPILER_VERSION_MAJOR "${CMAKE_CXX_COMPILER_VERSION}") + string(REGEX REPLACE "([0-9]+)\\.([0-9]+)(\\.[0-9]+)?" "\\2" + _boost_COMPILER_VERSION_MINOR "${CMAKE_CXX_COMPILER_VERSION}") + + set(_boost_COMPILER_VERSION "${_boost_COMPILER_VERSION_MAJOR}${_boost_COMPILER_VERSION_MINOR}") + + set(${_OUTPUT_VERSION} ${_boost_COMPILER_VERSION} PARENT_SCOPE) + set(${_OUTPUT_VERSION_MAJOR} ${_boost_COMPILER_VERSION_MAJOR} PARENT_SCOPE) + set(${_OUTPUT_VERSION_MINOR} ${_boost_COMPILER_VERSION_MINOR} PARENT_SCOPE) +endfunction() + +# +# Take a list of libraries with "thread" in it +# and prepend duplicates with "thread_${Boost_THREADAPI}" +# at the front of the list +# +function(_Boost_PREPEND_LIST_WITH_THREADAPI _output) + set(_orig_libnames ${ARGN}) + string(REPLACE "thread" "thread_${Boost_THREADAPI}" _threadapi_libnames "${_orig_libnames}") + set(${_output} ${_threadapi_libnames} ${_orig_libnames} PARENT_SCOPE) +endfunction() + +# +# If a library is found, replace its cache entry with its REALPATH +# +function(_Boost_SWAP_WITH_REALPATH _library _docstring) + if(${_library}) + get_filename_component(_boost_filepathreal ${${_library}} REALPATH) + unset(${_library} CACHE) + set(${_library} ${_boost_filepathreal} CACHE FILEPATH "${_docstring}") + endif() +endfunction() + +function(_Boost_CHECK_SPELLING _var) + if(${_var}) + string(TOUPPER ${_var} _var_UC) + message(FATAL_ERROR "ERROR: ${_var} is not the correct spelling. The proper spelling is ${_var_UC}.") + endif() +endfunction() + +# Guesses Boost's compiler prefix used in built library names +# Returns the guess by setting the variable pointed to by _ret +function(_Boost_GUESS_COMPILER_PREFIX _ret) + if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xIntel") + if(WIN32) + set (_boost_COMPILER "-iw") + else() + set (_boost_COMPILER "-il") + endif() + elseif (GHSMULTI) + set(_boost_COMPILER "-ghs") + elseif("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC" OR "x${CMAKE_CXX_SIMULATE_ID}" STREQUAL "xMSVC") + if(MSVC_TOOLSET_VERSION GREATER_EQUAL 150) + # Not yet known. + set(_boost_COMPILER "") + elseif(MSVC_TOOLSET_VERSION GREATER_EQUAL 140) + # MSVC toolset 14.x versions are forward compatible. + set(_boost_COMPILER "") + foreach(v 9 8 7 6 5 4 3 2 1 0) + if(MSVC_TOOLSET_VERSION GREATER_EQUAL 14${v}) + list(APPEND _boost_COMPILER "-vc14${v}") + endif() + endforeach() + elseif(MSVC_TOOLSET_VERSION GREATER_EQUAL 80) + set(_boost_COMPILER "-vc${MSVC_TOOLSET_VERSION}") + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13.10) + set(_boost_COMPILER "-vc71") + elseif(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13) # Good luck! + set(_boost_COMPILER "-vc7") # yes, this is correct + else() # VS 6.0 Good luck! + set(_boost_COMPILER "-vc6") # yes, this is correct + endif() + + if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang") + string(REPLACE "." ";" VERSION_LIST "${CMAKE_CXX_COMPILER_VERSION}") + list(GET VERSION_LIST 0 CLANG_VERSION_MAJOR) + set(_boost_COMPILER "-clangw${CLANG_VERSION_MAJOR};${_boost_COMPILER}") + endif() + elseif (BORLAND) + set(_boost_COMPILER "-bcb") + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "SunPro") + set(_boost_COMPILER "-sw") + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "XL") + set(_boost_COMPILER "-xlc") + elseif (MINGW) + if(${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION} VERSION_LESS 1.34) + set(_boost_COMPILER "-mgw") # no GCC version encoding prior to 1.34 + else() + _Boost_COMPILER_DUMPVERSION(_boost_COMPILER_VERSION _boost_COMPILER_VERSION_MAJOR _boost_COMPILER_VERSION_MINOR) + set(_boost_COMPILER "-mgw${_boost_COMPILER_VERSION}") + endif() + elseif (UNIX) + _Boost_COMPILER_DUMPVERSION(_boost_COMPILER_VERSION _boost_COMPILER_VERSION_MAJOR _boost_COMPILER_VERSION_MINOR) + if(NOT Boost_VERSION VERSION_LESS 106900) + # From GCC 5 and clang 4, versioning changes and minor becomes patch. + # For those compilers, patch is exclude from compiler tag in Boost 1.69+ library naming. + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND _boost_COMPILER_VERSION_MAJOR VERSION_GREATER 4) + set(_boost_COMPILER_VERSION "${_boost_COMPILER_VERSION_MAJOR}") + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND _boost_COMPILER_VERSION_MAJOR VERSION_GREATER 3) + set(_boost_COMPILER_VERSION "${_boost_COMPILER_VERSION_MAJOR}") + endif() + endif() + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION} VERSION_LESS 1.34) + set(_boost_COMPILER "-gcc") # no GCC version encoding prior to 1.34 + else() + # Determine which version of GCC we have. + if(APPLE) + if(Boost_MINOR_VERSION) + if(${Boost_MINOR_VERSION} GREATER 35) + # In Boost 1.36.0 and newer, the mangled compiler name used + # on macOS/Darwin is "xgcc". + set(_boost_COMPILER "-xgcc${_boost_COMPILER_VERSION}") + else() + # In Boost <= 1.35.0, there is no mangled compiler name for + # the macOS/Darwin version of GCC. + set(_boost_COMPILER "") + endif() + else() + # We don't know the Boost version, so assume it's + # pre-1.36.0. + set(_boost_COMPILER "") + endif() + else() + set(_boost_COMPILER "-gcc${_boost_COMPILER_VERSION}") + endif() + endif() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # TODO: Find out any Boost version constraints vs clang support. + set(_boost_COMPILER "-clang${_boost_COMPILER_VERSION}") + endif() + else() + # TODO at least Boost_DEBUG here? + set(_boost_COMPILER "") + endif() + set(${_ret} ${_boost_COMPILER} PARENT_SCOPE) +endfunction() + +# +# Get component dependencies. Requires the dependencies to have been +# defined for the Boost release version. +# +# component - the component to check +# _ret - list of library dependencies +# +function(_Boost_COMPONENT_DEPENDENCIES component _ret) + # Note: to add a new Boost release, run + # + # % cmake -DBOOST_DIR=/path/to/boost/source -P Utilities/Scripts/BoostScanDeps.cmake + # + # The output may be added in a new block below. If it's the same as + # the previous release, simply update the version range of the block + # for the previous release. Also check if any new components have + # been added, and add any new components to + # _Boost_COMPONENT_HEADERS. + # + # This information was originally generated by running + # BoostScanDeps.cmake against every boost release to date supported + # by FindBoost: + # + # % for version in /path/to/boost/sources/* + # do + # cmake -DBOOST_DIR=$version -P Utilities/Scripts/BoostScanDeps.cmake + # done + # + # The output was then updated by search and replace with these regexes: + # + # - Strip message(STATUS) prefix dashes + # s;^-- ;; + # - Indent + # s;^set(; set(;; + # - Add conditionals + # s;Scanning /path/to/boost/sources/boost_\(.*\)_\(.*\)_\(.*); elseif(NOT Boost_VERSION VERSION_LESS \10\20\3 AND Boost_VERSION VERSION_LESS xxxx); + # + # This results in the logic seen below, but will require the xxxx + # replacing with the following Boost release version (or the next + # minor version to be released, e.g. 1.59 was the latest at the time + # of writing, making 1.60 the next, so 106000 is the needed version + # number). Identical consecutive releases were then merged together + # by updating the end range of the first block and removing the + # following redundant blocks. + # + # Running the script against all historical releases should be + # required only if the BoostScanDeps.cmake script logic is changed. + # The addition of a new release should only require it to be run + # against the new release. + + # Handle Python version suffixes + if(component MATCHES "^(python|mpi_python|numpy)([0-9][0-9]?|[0-9]\\.[0-9])\$") + set(component "${CMAKE_MATCH_1}") + set(component_python_version "${CMAKE_MATCH_2}") + endif() + + set(_Boost_IMPORTED_TARGETS TRUE) + if(Boost_VERSION AND Boost_VERSION VERSION_LESS 103300) + message(WARNING "Imported targets and dependency information not available for Boost version ${Boost_VERSION} (all versions older than 1.33)") + set(_Boost_IMPORTED_TARGETS FALSE) + elseif(NOT Boost_VERSION VERSION_LESS 103300 AND Boost_VERSION VERSION_LESS 103500) + set(_Boost_IOSTREAMS_DEPENDENCIES regex thread) + set(_Boost_REGEX_DEPENDENCIES thread) + set(_Boost_WAVE_DEPENDENCIES filesystem thread) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 103500 AND Boost_VERSION VERSION_LESS 103600) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_WAVE_DEPENDENCIES filesystem system thread) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 103600 AND Boost_VERSION VERSION_LESS 103800) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_WAVE_DEPENDENCIES filesystem system thread) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 103800 AND Boost_VERSION VERSION_LESS 104300) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_WAVE_DEPENDENCIES filesystem system thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 104300 AND Boost_VERSION VERSION_LESS 104400) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_WAVE_DEPENDENCIES filesystem system thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 104400 AND Boost_VERSION VERSION_LESS 104500) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random serialization) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_WAVE_DEPENDENCIES serialization filesystem system thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 104500 AND Boost_VERSION VERSION_LESS 104700) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 104700 AND Boost_VERSION VERSION_LESS 104800) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 104800 AND Boost_VERSION VERSION_LESS 105000) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_THREAD_DEPENDENCIES date_time) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 105000 AND Boost_VERSION VERSION_LESS 105300) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 105300 AND Boost_VERSION VERSION_LESS 105400) + set(_Boost_ATOMIC_DEPENDENCIES thread chrono system date_time) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 105400 AND Boost_VERSION VERSION_LESS 105500) + set(_Boost_ATOMIC_DEPENDENCIES thread chrono system date_time) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 105500 AND Boost_VERSION VERSION_LESS 105600) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l regex random) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 105600 AND Boost_VERSION VERSION_LESS 105900) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 105900 AND Boost_VERSION VERSION_LESS 106000) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES log_setup date_time system filesystem thread regex chrono atomic) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106000 AND Boost_VERSION VERSION_LESS 106100) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106100 AND Boost_VERSION VERSION_LESS 106200) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106200 AND Boost_VERSION VERSION_LESS 106300) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106300 AND Boost_VERSION VERSION_LESS 106500) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_COROUTINE2_DEPENDENCIES context fiber thread chrono system date_time) + set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106500 AND Boost_VERSION VERSION_LESS 106700) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106700 AND Boost_VERSION VERSION_LESS 106800) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106800 AND Boost_VERSION VERSION_LESS 106900) + set(_Boost_CHRONO_DEPENDENCIES system) + set(_Boost_CONTEXT_DEPENDENCIES thread chrono system date_time) + set(_Boost_CONTRACT_DEPENDENCIES thread chrono system date_time) + set(_Boost_COROUTINE_DEPENDENCIES context system) + set(_Boost_FIBER_DEPENDENCIES context thread chrono system date_time) + set(_Boost_FILESYSTEM_DEPENDENCIES system) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup system filesystem thread regex chrono atomic) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) + set(_Boost_RANDOM_DEPENDENCIES system) + set(_Boost_THREAD_DEPENDENCIES chrono system date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem system serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + elseif(NOT Boost_VERSION VERSION_LESS 106900 AND Boost_VERSION VERSION_LESS 107000) + set(_Boost_CONTRACT_DEPENDENCIES thread chrono date_time) + set(_Boost_COROUTINE_DEPENDENCIES context) + set(_Boost_FIBER_DEPENDENCIES context) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup filesystem thread regex chrono atomic) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) + set(_Boost_THREAD_DEPENDENCIES chrono date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + else() + if(NOT Boost_VERSION VERSION_LESS 107000) + set(_Boost_CONTRACT_DEPENDENCIES thread chrono date_time) + set(_Boost_COROUTINE_DEPENDENCIES context) + set(_Boost_FIBER_DEPENDENCIES context) + set(_Boost_IOSTREAMS_DEPENDENCIES regex) + set(_Boost_LOG_DEPENDENCIES date_time log_setup filesystem thread regex chrono atomic) + set(_Boost_MATH_DEPENDENCIES math_c99 math_c99f math_c99l math_tr1 math_tr1f math_tr1l atomic) + set(_Boost_MPI_DEPENDENCIES serialization) + set(_Boost_MPI_PYTHON_DEPENDENCIES python${component_python_version} mpi serialization) + set(_Boost_NUMPY_DEPENDENCIES python${component_python_version}) + set(_Boost_THREAD_DEPENDENCIES chrono date_time atomic) + set(_Boost_TIMER_DEPENDENCIES chrono system) + set(_Boost_WAVE_DEPENDENCIES filesystem serialization thread chrono date_time atomic) + set(_Boost_WSERIALIZATION_DEPENDENCIES serialization) + endif() + if(NOT Boost_VERSION VERSION_LESS 107100) + message(WARNING "New Boost version may have incorrect or missing dependencies and imported targets") + endif() + endif() + + string(TOUPPER ${component} uppercomponent) + set(${_ret} ${_Boost_${uppercomponent}_DEPENDENCIES} PARENT_SCOPE) + set(_Boost_IMPORTED_TARGETS ${_Boost_IMPORTED_TARGETS} PARENT_SCOPE) + + string(REGEX REPLACE ";" " " _boost_DEPS_STRING "${_Boost_${uppercomponent}_DEPENDENCIES}") + if (NOT _boost_DEPS_STRING) + set(_boost_DEPS_STRING "(none)") + endif() + # message(STATUS "Dependencies for Boost::${component}: ${_boost_DEPS_STRING}") +endfunction() + +# +# Get component headers. This is the primary header (or headers) for +# a given component, and is used to check that the headers are present +# as well as the library itself as an extra sanity check of the build +# environment. +# +# component - the component to check +# _hdrs +# +function(_Boost_COMPONENT_HEADERS component _hdrs) + # Handle Python version suffixes + if(component MATCHES "^(python|mpi_python|numpy)([0-9][0-9]?|[0-9]\\.[0-9])\$") + set(component "${CMAKE_MATCH_1}") + set(component_python_version "${CMAKE_MATCH_2}") + endif() + + # Note: new boost components will require adding here. The header + # must be present in all versions of Boost providing a library. + set(_Boost_ATOMIC_HEADERS "boost/atomic.hpp") + set(_Boost_CHRONO_HEADERS "boost/chrono.hpp") + set(_Boost_CONTAINER_HEADERS "boost/container/container_fwd.hpp") + set(_Boost_CONTRACT_HEADERS "boost/contract.hpp") + if(Boost_VERSION VERSION_LESS 106100) + set(_Boost_CONTEXT_HEADERS "boost/context/all.hpp") + else() + set(_Boost_CONTEXT_HEADERS "boost/context/detail/fcontext.hpp") + endif() + set(_Boost_COROUTINE_HEADERS "boost/coroutine/all.hpp") + set(_Boost_DATE_TIME_HEADERS "boost/date_time/date.hpp") + set(_Boost_EXCEPTION_HEADERS "boost/exception/exception.hpp") + set(_Boost_FIBER_HEADERS "boost/fiber/all.hpp") + set(_Boost_FILESYSTEM_HEADERS "boost/filesystem/path.hpp") + set(_Boost_GRAPH_HEADERS "boost/graph/adjacency_list.hpp") + set(_Boost_GRAPH_PARALLEL_HEADERS "boost/graph/adjacency_list.hpp") + set(_Boost_IOSTREAMS_HEADERS "boost/iostreams/stream.hpp") + set(_Boost_LOCALE_HEADERS "boost/locale.hpp") + set(_Boost_LOG_HEADERS "boost/log/core.hpp") + set(_Boost_LOG_SETUP_HEADERS "boost/log/detail/setup_config.hpp") + set(_Boost_MATH_HEADERS "boost/math_fwd.hpp") + set(_Boost_MATH_C99_HEADERS "boost/math/tr1.hpp") + set(_Boost_MATH_C99F_HEADERS "boost/math/tr1.hpp") + set(_Boost_MATH_C99L_HEADERS "boost/math/tr1.hpp") + set(_Boost_MATH_TR1_HEADERS "boost/math/tr1.hpp") + set(_Boost_MATH_TR1F_HEADERS "boost/math/tr1.hpp") + set(_Boost_MATH_TR1L_HEADERS "boost/math/tr1.hpp") + set(_Boost_MPI_HEADERS "boost/mpi.hpp") + set(_Boost_MPI_PYTHON_HEADERS "boost/mpi/python/config.hpp") + set(_Boost_NUMPY_HEADERS "boost/python/numpy.hpp") + set(_Boost_PRG_EXEC_MONITOR_HEADERS "boost/test/prg_exec_monitor.hpp") + set(_Boost_PROGRAM_OPTIONS_HEADERS "boost/program_options.hpp") + set(_Boost_PYTHON_HEADERS "boost/python.hpp") + set(_Boost_RANDOM_HEADERS "boost/random.hpp") + set(_Boost_REGEX_HEADERS "boost/regex.hpp") + set(_Boost_SERIALIZATION_HEADERS "boost/serialization/serialization.hpp") + set(_Boost_SIGNALS_HEADERS "boost/signals.hpp") + set(_Boost_STACKTRACE_ADDR2LINE_HEADERS "boost/stacktrace.hpp") + set(_Boost_STACKTRACE_BACKTRACE_HEADERS "boost/stacktrace.hpp") + set(_Boost_STACKTRACE_BASIC_HEADERS "boost/stacktrace.hpp") + set(_Boost_STACKTRACE_NOOP_HEADERS "boost/stacktrace.hpp") + set(_Boost_STACKTRACE_WINDBG_CACHED_HEADERS "boost/stacktrace.hpp") + set(_Boost_STACKTRACE_WINDBG_HEADERS "boost/stacktrace.hpp") + set(_Boost_SYSTEM_HEADERS "boost/system/config.hpp") + set(_Boost_TEST_EXEC_MONITOR_HEADERS "boost/test/test_exec_monitor.hpp") + set(_Boost_THREAD_HEADERS "boost/thread.hpp") + set(_Boost_TIMER_HEADERS "boost/timer.hpp") + set(_Boost_TYPE_ERASURE_HEADERS "boost/type_erasure/config.hpp") + set(_Boost_UNIT_TEST_FRAMEWORK_HEADERS "boost/test/framework.hpp") + set(_Boost_WAVE_HEADERS "boost/wave.hpp") + set(_Boost_WSERIALIZATION_HEADERS "boost/archive/text_wiarchive.hpp") + if(WIN32) + set(_Boost_BZIP2_HEADERS "boost/iostreams/filter/bzip2.hpp") + set(_Boost_ZLIB_HEADERS "boost/iostreams/filter/zlib.hpp") + endif() + + string(TOUPPER ${component} uppercomponent) + set(${_hdrs} ${_Boost_${uppercomponent}_HEADERS} PARENT_SCOPE) + + string(REGEX REPLACE ";" " " _boost_HDRS_STRING "${_Boost_${uppercomponent}_HEADERS}") + if (NOT _boost_HDRS_STRING) + set(_boost_HDRS_STRING "(none)") + endif() + # message(STATUS "Headers for Boost::${component}: ${_boost_HDRS_STRING}") +endfunction() + +# +# Determine if any missing dependencies require adding to the component list. +# +# Sets _Boost_${COMPONENT}_DEPENDENCIES for each required component, +# plus _Boost_IMPORTED_TARGETS (TRUE if imported targets should be +# defined; FALSE if dependency information is unavailable). +# +# componentvar - the component list variable name +# extravar - the indirect dependency list variable name +# +# +function(_Boost_MISSING_DEPENDENCIES componentvar extravar) + # _boost_unprocessed_components - list of components requiring processing + # _boost_processed_components - components already processed (or currently being processed) + # _boost_new_components - new components discovered for future processing + # + list(APPEND _boost_unprocessed_components ${${componentvar}}) + + while(_boost_unprocessed_components) + list(APPEND _boost_processed_components ${_boost_unprocessed_components}) + foreach(component ${_boost_unprocessed_components}) + string(TOUPPER ${component} uppercomponent) + set(${_ret} ${_Boost_${uppercomponent}_DEPENDENCIES} PARENT_SCOPE) + _Boost_COMPONENT_DEPENDENCIES("${component}" _Boost_${uppercomponent}_DEPENDENCIES) + set(_Boost_${uppercomponent}_DEPENDENCIES ${_Boost_${uppercomponent}_DEPENDENCIES} PARENT_SCOPE) + set(_Boost_IMPORTED_TARGETS ${_Boost_IMPORTED_TARGETS} PARENT_SCOPE) + foreach(componentdep ${_Boost_${uppercomponent}_DEPENDENCIES}) + if (NOT ("${componentdep}" IN_LIST _boost_processed_components OR "${componentdep}" IN_LIST _boost_new_components)) + list(APPEND _boost_new_components ${componentdep}) + endif() + endforeach() + endforeach() + set(_boost_unprocessed_components ${_boost_new_components}) + unset(_boost_new_components) + endwhile() + set(_boost_extra_components ${_boost_processed_components}) + if(_boost_extra_components AND ${componentvar}) + list(REMOVE_ITEM _boost_extra_components ${${componentvar}}) + endif() + set(${componentvar} ${_boost_processed_components} PARENT_SCOPE) + set(${extravar} ${_boost_extra_components} PARENT_SCOPE) +endfunction() + +# +# Some boost libraries may require particular set of compler features. +# The very first one was `boost::fiber` introduced in Boost 1.62. +# One can check required compiler features of it in +# - `${Boost_ROOT}/libs/fiber/build/Jamfile.v2`; +# - `${Boost_ROOT}/libs/context/build/Jamfile.v2`. +# +# TODO (Re)Check compiler features on (every?) release ??? +# One may use the following command to get the files to check: +# +# $ find . -name Jamfile.v2 | grep build | xargs grep -l cxx1 +# +function(_Boost_COMPILER_FEATURES component _ret) + # Boost >= 1.62 + if(NOT Boost_VERSION VERSION_LESS 106200) + set(_Boost_FIBER_COMPILER_FEATURES + cxx_alias_templates + cxx_auto_type + cxx_constexpr + cxx_defaulted_functions + cxx_final + cxx_lambdas + cxx_noexcept + cxx_nullptr + cxx_rvalue_references + cxx_thread_local + cxx_variadic_templates + ) + # Compiler feature for `context` same as for `fiber`. + set(_Boost_CONTEXT_COMPILER_FEATURES ${_Boost_FIBER_COMPILER_FEATURES}) + endif() + string(TOUPPER ${component} uppercomponent) + set(${_ret} ${_Boost_${uppercomponent}_COMPILER_FEATURES} PARENT_SCOPE) +endfunction() + +# +# Update library search directory hint variable with paths used by prebuilt boost binaries. +# +# Prebuilt windows binaries (https://sourceforge.net/projects/boost/files/boost-binaries/) +# have library directories named using MSVC compiler version and architecture. +# This function would append corresponding directories if MSVC is a current compiler, +# so having `BOOST_ROOT` would be enough to specify to find everything. +# +function(_Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS componentlibvar basedir) + if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_arch_suffix 64) + else() + set(_arch_suffix 32) + endif() + if(MSVC_TOOLSET_VERSION GREATER_EQUAL 150) + # Not yet known. + elseif(MSVC_TOOLSET_VERSION GREATER_EQUAL 140) + # MSVC toolset 14.x versions are forward compatible. + foreach(v 9 8 7 6 5 4 3 2 1 0) + if(MSVC_TOOLSET_VERSION GREATER_EQUAL 14${v}) + list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-14.${v}) + endif() + endforeach() + elseif(MSVC_TOOLSET_VERSION GREATER_EQUAL 80) + math(EXPR _toolset_major_version "${MSVC_TOOLSET_VERSION} / 10") + list(APPEND ${componentlibvar} ${basedir}/lib${_arch_suffix}-msvc-${_toolset_major_version}.0) + endif() + set(${componentlibvar} ${${componentlibvar}} PARENT_SCOPE) + endif() +endfunction() + +# +# End functions/macros +# +#------------------------------------------------------------------------------- + +#------------------------------------------------------------------------------- +# main. +#------------------------------------------------------------------------------- + + +# If the user sets Boost_LIBRARY_DIR, use it as the default for both +# configurations. +if(NOT Boost_LIBRARY_DIR_RELEASE AND Boost_LIBRARY_DIR) + set(Boost_LIBRARY_DIR_RELEASE "${Boost_LIBRARY_DIR}") +endif() +if(NOT Boost_LIBRARY_DIR_DEBUG AND Boost_LIBRARY_DIR) + set(Boost_LIBRARY_DIR_DEBUG "${Boost_LIBRARY_DIR}") +endif() + +if(NOT DEFINED Boost_USE_DEBUG_LIBS) + set(Boost_USE_DEBUG_LIBS TRUE) +endif() +if(NOT DEFINED Boost_USE_RELEASE_LIBS) + set(Boost_USE_RELEASE_LIBS TRUE) +endif() +if(NOT DEFINED Boost_USE_MULTITHREADED) + set(Boost_USE_MULTITHREADED TRUE) +endif() +if(NOT DEFINED Boost_USE_DEBUG_RUNTIME) + set(Boost_USE_DEBUG_RUNTIME TRUE) +endif() + +# Check the version of Boost against the requested version. +if(Boost_FIND_VERSION AND NOT Boost_FIND_VERSION_MINOR) + message(SEND_ERROR "When requesting a specific version of Boost, you must provide at least the major and minor version numbers, e.g., 1.34") +endif() + +if(Boost_FIND_VERSION_EXACT) + # The version may appear in a directory with or without the patch + # level, even when the patch level is non-zero. + set(_boost_TEST_VERSIONS + "${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}.${Boost_FIND_VERSION_PATCH}" + "${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}") +else() + # The user has not requested an exact version. Among known + # versions, find those that are acceptable to the user request. + # + # Note: When adding a new Boost release, also update the dependency + # information in _Boost_COMPONENT_DEPENDENCIES and + # _Boost_COMPONENT_HEADERS. See the instructions at the top of + # _Boost_COMPONENT_DEPENDENCIES. + set(_Boost_KNOWN_VERSIONS ${Boost_ADDITIONAL_VERSIONS} + "1.70.0" "1.70" "1.69.0" "1.69" + "1.68.0" "1.68" "1.67.0" "1.67" "1.66.0" "1.66" "1.65.1" "1.65.0" "1.65" + "1.64.0" "1.64" "1.63.0" "1.63" "1.62.0" "1.62" "1.61.0" "1.61" "1.60.0" "1.60" + "1.59.0" "1.59" "1.58.0" "1.58" "1.57.0" "1.57" "1.56.0" "1.56" "1.55.0" "1.55" + "1.54.0" "1.54" "1.53.0" "1.53" "1.52.0" "1.52" "1.51.0" "1.51" + "1.50.0" "1.50" "1.49.0" "1.49" "1.48.0" "1.48" "1.47.0" "1.47" "1.46.1" + "1.46.0" "1.46" "1.45.0" "1.45" "1.44.0" "1.44" "1.43.0" "1.43" "1.42.0" "1.42" + "1.41.0" "1.41" "1.40.0" "1.40" "1.39.0" "1.39" "1.38.0" "1.38" "1.37.0" "1.37" + "1.36.1" "1.36.0" "1.36" "1.35.1" "1.35.0" "1.35" "1.34.1" "1.34.0" + "1.34" "1.33.1" "1.33.0" "1.33") + + set(_boost_TEST_VERSIONS) + if(Boost_FIND_VERSION) + set(_Boost_FIND_VERSION_SHORT "${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}") + # Select acceptable versions. + foreach(version ${_Boost_KNOWN_VERSIONS}) + if(NOT "${version}" VERSION_LESS "${Boost_FIND_VERSION}") + # This version is high enough. + list(APPEND _boost_TEST_VERSIONS "${version}") + elseif("${version}.99" VERSION_EQUAL "${_Boost_FIND_VERSION_SHORT}.99") + # This version is a short-form for the requested version with + # the patch level dropped. + list(APPEND _boost_TEST_VERSIONS "${version}") + endif() + endforeach() + else() + # Any version is acceptable. + set(_boost_TEST_VERSIONS "${_Boost_KNOWN_VERSIONS}") + endif() +endif() + +# The reason that we failed to find Boost. This will be set to a +# user-friendly message when we fail to find some necessary piece of +# Boost. +set(Boost_ERROR_REASON) + +if(Boost_DEBUG) + # Output some of their choices + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_TEST_VERSIONS = ${_boost_TEST_VERSIONS}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_USE_MULTITHREADED = ${Boost_USE_MULTITHREADED}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_USE_STATIC_LIBS = ${Boost_USE_STATIC_LIBS}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_USE_STATIC_RUNTIME = ${Boost_USE_STATIC_RUNTIME}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_ADDITIONAL_VERSIONS = ${Boost_ADDITIONAL_VERSIONS}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_NO_SYSTEM_PATHS = ${Boost_NO_SYSTEM_PATHS}") +endif() + +# Supply Boost_LIB_DIAGNOSTIC_DEFINITIONS as a convenience target. It +# will only contain any interface definitions on WIN32, but is created +# on all platforms to keep end user code free from platform dependent +# code. Also provide convenience targets to disable autolinking and +# enable dynamic linking. +if(NOT TARGET Boost::diagnostic_definitions) + add_library(Boost::diagnostic_definitions INTERFACE IMPORTED) + add_library(Boost::disable_autolinking INTERFACE IMPORTED) + add_library(Boost::dynamic_linking INTERFACE IMPORTED) + set_target_properties(Boost::dynamic_linking PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "BOOST_ALL_DYN_LINK") +endif() +if(WIN32) + # In windows, automatic linking is performed, so you do not have + # to specify the libraries. If you are linking to a dynamic + # runtime, then you can choose to link to either a static or a + # dynamic Boost library, the default is to do a static link. You + # can alter this for a specific library "whatever" by defining + # BOOST_WHATEVER_DYN_LINK to force Boost library "whatever" to be + # linked dynamically. Alternatively you can force all Boost + # libraries to dynamic link by defining BOOST_ALL_DYN_LINK. + + # This feature can be disabled for Boost library "whatever" by + # defining BOOST_WHATEVER_NO_LIB, or for all of Boost by defining + # BOOST_ALL_NO_LIB. + + # If you want to observe which libraries are being linked against + # then defining BOOST_LIB_DIAGNOSTIC will cause the auto-linking + # code to emit a #pragma message each time a library is selected + # for linking. + set(Boost_LIB_DIAGNOSTIC_DEFINITIONS "-DBOOST_LIB_DIAGNOSTIC") + set_target_properties(Boost::diagnostic_definitions PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "BOOST_LIB_DIAGNOSTIC") + set_target_properties(Boost::disable_autolinking PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "BOOST_ALL_NO_LIB") +endif() + +_Boost_CHECK_SPELLING(Boost_ROOT) +_Boost_CHECK_SPELLING(Boost_LIBRARYDIR) +_Boost_CHECK_SPELLING(Boost_INCLUDEDIR) + +# Collect environment variable inputs as hints. Do not consider changes. +foreach(v BOOSTROOT BOOST_ROOT BOOST_INCLUDEDIR BOOST_LIBRARYDIR) + set(_env $ENV{${v}}) + if(_env) + file(TO_CMAKE_PATH "${_env}" _ENV_${v}) + else() + set(_ENV_${v} "") + endif() +endforeach() +if(NOT _ENV_BOOST_ROOT AND _ENV_BOOSTROOT) + set(_ENV_BOOST_ROOT "${_ENV_BOOSTROOT}") +endif() + +# Collect inputs and cached results. Detect changes since the last run. +if(NOT BOOST_ROOT AND BOOSTROOT) + set(BOOST_ROOT "${BOOSTROOT}") +endif() +set(_Boost_VARS_DIR + BOOST_ROOT + Boost_NO_SYSTEM_PATHS + ) + +if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Declared as CMake or Environmental Variables:") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " BOOST_ROOT = ${BOOST_ROOT}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " BOOST_INCLUDEDIR = ${BOOST_INCLUDEDIR}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " BOOST_LIBRARYDIR = ${BOOST_LIBRARYDIR}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_TEST_VERSIONS = ${_boost_TEST_VERSIONS}") +endif() + +# ------------------------------------------------------------------------ +# Search for Boost include DIR +# ------------------------------------------------------------------------ + +set(_Boost_VARS_INC BOOST_INCLUDEDIR Boost_INCLUDE_DIR Boost_ADDITIONAL_VERSIONS) +_Boost_CHANGE_DETECT(_Boost_CHANGE_INCDIR ${_Boost_VARS_DIR} ${_Boost_VARS_INC}) +# Clear Boost_INCLUDE_DIR if it did not change but other input affecting the +# location did. We will find a new one based on the new inputs. +if(_Boost_CHANGE_INCDIR AND NOT _Boost_INCLUDE_DIR_CHANGED) + unset(Boost_INCLUDE_DIR CACHE) +endif() + +if(NOT Boost_INCLUDE_DIR) + set(_boost_INCLUDE_SEARCH_DIRS "") + if(BOOST_INCLUDEDIR) + list(APPEND _boost_INCLUDE_SEARCH_DIRS ${BOOST_INCLUDEDIR}) + elseif(_ENV_BOOST_INCLUDEDIR) + list(APPEND _boost_INCLUDE_SEARCH_DIRS ${_ENV_BOOST_INCLUDEDIR}) + endif() + + if( BOOST_ROOT ) + list(APPEND _boost_INCLUDE_SEARCH_DIRS ${BOOST_ROOT}/include ${BOOST_ROOT}) + elseif( _ENV_BOOST_ROOT ) + list(APPEND _boost_INCLUDE_SEARCH_DIRS ${_ENV_BOOST_ROOT}/include ${_ENV_BOOST_ROOT}) + endif() + + if( Boost_NO_SYSTEM_PATHS) + list(APPEND _boost_INCLUDE_SEARCH_DIRS NO_CMAKE_SYSTEM_PATH NO_SYSTEM_ENVIRONMENT_PATH) + else() + if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") + foreach(ver ${_boost_TEST_VERSIONS}) + string(REPLACE "." "_" ver "${ver}") + list(APPEND _boost_INCLUDE_SEARCH_DIRS PATHS "C:/local/boost_${ver}") + endforeach() + endif() + list(APPEND _boost_INCLUDE_SEARCH_DIRS PATHS + C:/boost/include + C:/boost + /sw/local/include + ) + endif() + + # Try to find Boost by stepping backwards through the Boost versions + # we know about. + # Build a list of path suffixes for each version. + set(_boost_PATH_SUFFIXES) + foreach(_boost_VER ${_boost_TEST_VERSIONS}) + # Add in a path suffix, based on the required version, ideally + # we could read this from version.hpp, but for that to work we'd + # need to know the include dir already + set(_boost_BOOSTIFIED_VERSION) + + # Transform 1.35 => 1_35 and 1.36.0 => 1_36_0 + if(_boost_VER MATCHES "([0-9]+)\\.([0-9]+)\\.([0-9]+)") + set(_boost_BOOSTIFIED_VERSION + "${CMAKE_MATCH_1}_${CMAKE_MATCH_2}_${CMAKE_MATCH_3}") + elseif(_boost_VER MATCHES "([0-9]+)\\.([0-9]+)") + set(_boost_BOOSTIFIED_VERSION + "${CMAKE_MATCH_1}_${CMAKE_MATCH_2}") + endif() + + list(APPEND _boost_PATH_SUFFIXES + "boost-${_boost_BOOSTIFIED_VERSION}" + "boost_${_boost_BOOSTIFIED_VERSION}" + "boost/boost-${_boost_BOOSTIFIED_VERSION}" + "boost/boost_${_boost_BOOSTIFIED_VERSION}" + ) + + endforeach() + + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Include debugging info:") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " _boost_INCLUDE_SEARCH_DIRS = ${_boost_INCLUDE_SEARCH_DIRS}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + " _boost_PATH_SUFFIXES = ${_boost_PATH_SUFFIXES}") + endif() + + # Look for a standard boost header file. + find_path(Boost_INCLUDE_DIR + NAMES boost/config.hpp + HINTS ${_boost_INCLUDE_SEARCH_DIRS} + PATH_SUFFIXES ${_boost_PATH_SUFFIXES} + ) +endif() + +# ------------------------------------------------------------------------ +# Extract version information from version.hpp +# ------------------------------------------------------------------------ + +# Set Boost_FOUND based only on header location and version. +# It will be updated below for component libraries. +if(Boost_INCLUDE_DIR) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "location of version.hpp: ${Boost_INCLUDE_DIR}/boost/version.hpp") + endif() + + # Extract Boost_VERSION and Boost_LIB_VERSION from version.hpp + set(Boost_VERSION 0) + set(Boost_LIB_VERSION "") + file(STRINGS "${Boost_INCLUDE_DIR}/boost/version.hpp" _boost_VERSION_HPP_CONTENTS REGEX "#define BOOST_(LIB_)?VERSION ") + set(_Boost_VERSION_REGEX "([0-9]+)") + set(_Boost_LIB_VERSION_REGEX "\"([0-9_]+)\"") + foreach(v VERSION LIB_VERSION) + if("${_boost_VERSION_HPP_CONTENTS}" MATCHES "#define BOOST_${v} ${_Boost_${v}_REGEX}") + set(Boost_${v} "${CMAKE_MATCH_1}") + endif() + endforeach() + unset(_boost_VERSION_HPP_CONTENTS) + + math(EXPR Boost_MAJOR_VERSION "${Boost_VERSION} / 100000") + math(EXPR Boost_MINOR_VERSION "${Boost_VERSION} / 100 % 1000") + math(EXPR Boost_SUBMINOR_VERSION "${Boost_VERSION} % 100") + set(Boost_VERSION_STRING "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") + + string(APPEND Boost_ERROR_REASON + "Boost version: ${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}\nBoost include path: ${Boost_INCLUDE_DIR}") + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "version.hpp reveals boost " + "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") + endif() + + if(Boost_FIND_VERSION) + # Set Boost_FOUND based on requested version. + set(_Boost_VERSION "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") + if("${_Boost_VERSION}" VERSION_LESS "${Boost_FIND_VERSION}") + set(Boost_FOUND 0) + set(_Boost_VERSION_AGE "old") + elseif(Boost_FIND_VERSION_EXACT AND + NOT "${_Boost_VERSION}" VERSION_EQUAL "${Boost_FIND_VERSION}") + set(Boost_FOUND 0) + set(_Boost_VERSION_AGE "new") + else() + set(Boost_FOUND 1) + endif() + if(NOT Boost_FOUND) + # State that we found a version of Boost that is too new or too old. + string(APPEND Boost_ERROR_REASON + "\nDetected version of Boost is too ${_Boost_VERSION_AGE}. Requested version was ${Boost_FIND_VERSION_MAJOR}.${Boost_FIND_VERSION_MINOR}") + if (Boost_FIND_VERSION_PATCH) + string(APPEND Boost_ERROR_REASON + ".${Boost_FIND_VERSION_PATCH}") + endif () + if (NOT Boost_FIND_VERSION_EXACT) + string(APPEND Boost_ERROR_REASON " (or newer)") + endif () + string(APPEND Boost_ERROR_REASON ".") + endif () + else() + # Caller will accept any Boost version. + set(Boost_FOUND 1) + endif() +else() + set(Boost_FOUND 0) + string(APPEND Boost_ERROR_REASON + "Unable to find the Boost header files. Please set BOOST_ROOT to the root directory containing Boost or BOOST_INCLUDEDIR to the directory containing Boost's headers.") +endif() + +# ------------------------------------------------------------------------ +# Prefix initialization +# ------------------------------------------------------------------------ + +set(Boost_LIB_PREFIX "") +if ( (GHSMULTI AND Boost_USE_STATIC_LIBS) OR + (WIN32 AND Boost_USE_STATIC_LIBS AND NOT CYGWIN) ) + set(Boost_LIB_PREFIX "lib") +endif() + +if ( NOT Boost_NAMESPACE ) + set(Boost_NAMESPACE "boost") +endif() + +if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_LIB_PREFIX = ${Boost_LIB_PREFIX}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Boost_NAMESPACE = ${Boost_NAMESPACE}") +endif() + +# ------------------------------------------------------------------------ +# Suffix initialization and compiler suffix detection. +# ------------------------------------------------------------------------ + +set(_Boost_VARS_NAME + Boost_NAMESPACE + Boost_COMPILER + Boost_THREADAPI + Boost_USE_DEBUG_PYTHON + Boost_USE_MULTITHREADED + Boost_USE_STATIC_LIBS + Boost_USE_STATIC_RUNTIME + Boost_USE_STLPORT + Boost_USE_STLPORT_DEPRECATED_NATIVE_IOSTREAMS + ) +_Boost_CHANGE_DETECT(_Boost_CHANGE_LIBNAME ${_Boost_VARS_NAME}) + +# Setting some more suffixes for the library +if (Boost_COMPILER) + set(_boost_COMPILER ${Boost_COMPILER}) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "using user-specified Boost_COMPILER = ${_boost_COMPILER}") + endif() +else() + # Attempt to guess the compiler suffix + # NOTE: this is not perfect yet, if you experience any issues + # please report them and use the Boost_COMPILER variable + # to work around the problems. + _Boost_GUESS_COMPILER_PREFIX(_boost_COMPILER) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "guessed _boost_COMPILER = ${_boost_COMPILER}") + endif() +endif() + +set (_boost_MULTITHREADED "-mt") +if( NOT Boost_USE_MULTITHREADED ) + set (_boost_MULTITHREADED "") +endif() +if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_MULTITHREADED = ${_boost_MULTITHREADED}") +endif() + +#====================== +# Systematically build up the Boost ABI tag for the 'tagged' and 'versioned' layouts +# http://boost.org/doc/libs/1_66_0/more/getting_started/windows.html#library-naming +# http://boost.org/doc/libs/1_66_0/boost/config/auto_link.hpp +# http://boost.org/doc/libs/1_66_0/tools/build/src/tools/common.jam +# http://boost.org/doc/libs/1_66_0/boostcpp.jam +set( _boost_RELEASE_ABI_TAG "-") +set( _boost_DEBUG_ABI_TAG "-") +# Key Use this library when: +# s linking statically to the C++ standard library and +# compiler runtime support libraries. +if(Boost_USE_STATIC_RUNTIME) + set( _boost_RELEASE_ABI_TAG "${_boost_RELEASE_ABI_TAG}s") + set( _boost_DEBUG_ABI_TAG "${_boost_DEBUG_ABI_TAG}s") +endif() +# g using debug versions of the standard and runtime +# support libraries +if(WIN32 AND Boost_USE_DEBUG_RUNTIME) + if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC" + OR "x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang" + OR "x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xIntel") + string(APPEND _boost_DEBUG_ABI_TAG "g") + endif() +endif() +# y using special debug build of python +if(Boost_USE_DEBUG_PYTHON) + string(APPEND _boost_DEBUG_ABI_TAG "y") +endif() +# d using a debug version of your code +string(APPEND _boost_DEBUG_ABI_TAG "d") +# p using the STLport standard library rather than the +# default one supplied with your compiler +if(Boost_USE_STLPORT) + string(APPEND _boost_RELEASE_ABI_TAG "p") + string(APPEND _boost_DEBUG_ABI_TAG "p") +endif() +# n using the STLport deprecated "native iostreams" feature +# removed from the documentation in 1.43.0 but still present in +# boost/config/auto_link.hpp +if(Boost_USE_STLPORT_DEPRECATED_NATIVE_IOSTREAMS) + string(APPEND _boost_RELEASE_ABI_TAG "n") + string(APPEND _boost_DEBUG_ABI_TAG "n") +endif() + +# -x86 Architecture and address model tag +# First character is the architecture, then word-size, either 32 or 64 +# Only used in 'versioned' layout, added in Boost 1.66.0 +if(DEFINED Boost_ARCHITECTURE) + set(_boost_ARCHITECTURE_TAG "${Boost_ARCHITECTURE}") + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "using user-specified Boost_ARCHITECTURE = ${_boost_ARCHITECTURE_TAG}") + endif() +else() + set(_boost_ARCHITECTURE_TAG "") + # {CMAKE_CXX_COMPILER_ARCHITECTURE_ID} is not currently set for all compilers + if(NOT "x${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" STREQUAL "x" AND NOT Boost_VERSION VERSION_LESS 106600) + string(APPEND _boost_ARCHITECTURE_TAG "-") + # This needs to be kept in-sync with the section of CMakePlatformId.h.in + # inside 'defined(_WIN32) && defined(_MSC_VER)' + if(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "IA64") + string(APPEND _boost_ARCHITECTURE_TAG "i") + elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "X86" + OR CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "x64") + string(APPEND _boost_ARCHITECTURE_TAG "x") + elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID MATCHES "^ARM") + string(APPEND _boost_ARCHITECTURE_TAG "a") + elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "MIPS") + string(APPEND _boost_ARCHITECTURE_TAG "m") + endif() + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + string(APPEND _boost_ARCHITECTURE_TAG "64") + else() + string(APPEND _boost_ARCHITECTURE_TAG "32") + endif() + endif() +endif() + +if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_RELEASE_ABI_TAG = ${_boost_RELEASE_ABI_TAG}") + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_DEBUG_ABI_TAG = ${_boost_DEBUG_ABI_TAG}") +endif() + +# ------------------------------------------------------------------------ +# Begin finding boost libraries +# ------------------------------------------------------------------------ + +set(_Boost_VARS_LIB "") +foreach(c DEBUG RELEASE) + set(_Boost_VARS_LIB_${c} BOOST_LIBRARYDIR Boost_LIBRARY_DIR_${c}) + list(APPEND _Boost_VARS_LIB ${_Boost_VARS_LIB_${c}}) + _Boost_CHANGE_DETECT(_Boost_CHANGE_LIBDIR_${c} ${_Boost_VARS_DIR} ${_Boost_VARS_LIB_${c}} Boost_INCLUDE_DIR) + # Clear Boost_LIBRARY_DIR_${c} if it did not change but other input affecting the + # location did. We will find a new one based on the new inputs. + if(_Boost_CHANGE_LIBDIR_${c} AND NOT _Boost_LIBRARY_DIR_${c}_CHANGED) + unset(Boost_LIBRARY_DIR_${c} CACHE) + endif() + + # If Boost_LIBRARY_DIR_[RELEASE,DEBUG] is set, prefer its value. + if(Boost_LIBRARY_DIR_${c}) + set(_boost_LIBRARY_SEARCH_DIRS_${c} ${Boost_LIBRARY_DIR_${c}} NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) + else() + set(_boost_LIBRARY_SEARCH_DIRS_${c} "") + if(BOOST_LIBRARYDIR) + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${BOOST_LIBRARYDIR}) + elseif(_ENV_BOOST_LIBRARYDIR) + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${_ENV_BOOST_LIBRARYDIR}) + endif() + + if(BOOST_ROOT) + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${BOOST_ROOT}/lib ${BOOST_ROOT}/stage/lib) + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${BOOST_ROOT}") + elseif(_ENV_BOOST_ROOT) + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} ${_ENV_BOOST_ROOT}/lib ${_ENV_BOOST_ROOT}/stage/lib) + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${_ENV_BOOST_ROOT}") + endif() + + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} + ${Boost_INCLUDE_DIR}/lib + ${Boost_INCLUDE_DIR}/../lib + ${Boost_INCLUDE_DIR}/stage/lib + ) + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${Boost_INCLUDE_DIR}/..") + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "${Boost_INCLUDE_DIR}") + if( Boost_NO_SYSTEM_PATHS ) + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} NO_CMAKE_SYSTEM_PATH NO_SYSTEM_ENVIRONMENT_PATH) + else() + foreach(ver ${_boost_TEST_VERSIONS}) + string(REPLACE "." "_" ver "${ver}") + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "C:/local/boost_${ver}") + endforeach() + _Boost_UPDATE_WINDOWS_LIBRARY_SEARCH_DIRS_WITH_PREBUILT_PATHS(_boost_LIBRARY_SEARCH_DIRS_${c} "C:/boost") + list(APPEND _boost_LIBRARY_SEARCH_DIRS_${c} PATHS + C:/boost/lib + C:/boost + /sw/local/lib + ) + endif() + endif() +endforeach() + +if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "_boost_LIBRARY_SEARCH_DIRS_RELEASE = ${_boost_LIBRARY_SEARCH_DIRS_RELEASE}" + "_boost_LIBRARY_SEARCH_DIRS_DEBUG = ${_boost_LIBRARY_SEARCH_DIRS_DEBUG}") +endif() + +# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES +if( Boost_USE_STATIC_LIBS ) + set( _boost_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + if(WIN32) + list(INSERT CMAKE_FIND_LIBRARY_SUFFIXES 0 .lib .a) + else() + set(CMAKE_FIND_LIBRARY_SUFFIXES .a) + endif() +endif() + +# We want to use the tag inline below without risking double dashes +if(_boost_RELEASE_ABI_TAG) + if(${_boost_RELEASE_ABI_TAG} STREQUAL "-") + set(_boost_RELEASE_ABI_TAG "") + endif() +endif() +if(_boost_DEBUG_ABI_TAG) + if(${_boost_DEBUG_ABI_TAG} STREQUAL "-") + set(_boost_DEBUG_ABI_TAG "") + endif() +endif() + +# The previous behavior of FindBoost when Boost_USE_STATIC_LIBS was enabled +# on WIN32 was to: +# 1. Search for static libs compiled against a SHARED C++ standard runtime library (use if found) +# 2. Search for static libs compiled against a STATIC C++ standard runtime library (use if found) +# We maintain this behavior since changing it could break people's builds. +# To disable the ambiguous behavior, the user need only +# set Boost_USE_STATIC_RUNTIME either ON or OFF. +set(_boost_STATIC_RUNTIME_WORKAROUND false) +if(WIN32 AND Boost_USE_STATIC_LIBS) + if(NOT DEFINED Boost_USE_STATIC_RUNTIME) + set(_boost_STATIC_RUNTIME_WORKAROUND TRUE) + endif() +endif() + +# On versions < 1.35, remove the System library from the considered list +# since it wasn't added until 1.35. +if(Boost_VERSION AND Boost_FIND_COMPONENTS) + if(Boost_VERSION LESS 103500) + list(REMOVE_ITEM Boost_FIND_COMPONENTS system) + endif() +endif() + +# Additional components may be required via component dependencies. +# Add any missing components to the list. +_Boost_MISSING_DEPENDENCIES(Boost_FIND_COMPONENTS _Boost_EXTRA_FIND_COMPONENTS) + +# If thread is required, get the thread libs as a dependency +if("thread" IN_LIST Boost_FIND_COMPONENTS) + if(Boost_FIND_QUIETLY) + set(_Boost_find_quiet QUIET) + else() + set(_Boost_find_quiet "") + endif() + find_package(Threads ${_Boost_find_quiet}) + unset(_Boost_find_quiet) +endif() + +# If the user changed any of our control inputs flush previous results. +if(_Boost_CHANGE_LIBDIR_DEBUG OR _Boost_CHANGE_LIBDIR_RELEASE OR _Boost_CHANGE_LIBNAME) + foreach(COMPONENT ${_Boost_COMPONENTS_SEARCHED}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + foreach(c DEBUG RELEASE) + set(_var Boost_${UPPERCOMPONENT}_LIBRARY_${c}) + unset(${_var} CACHE) + set(${_var} "${_var}-NOTFOUND") + endforeach() + endforeach() + set(_Boost_COMPONENTS_SEARCHED "") +endif() + +foreach(COMPONENT ${Boost_FIND_COMPONENTS}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + + set( _boost_docstring_release "Boost ${COMPONENT} library (release)") + set( _boost_docstring_debug "Boost ${COMPONENT} library (debug)") + + # Compute component-specific hints. + set(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT "") + if(${COMPONENT} STREQUAL "mpi" OR ${COMPONENT} STREQUAL "mpi_python" OR + ${COMPONENT} STREQUAL "graph_parallel") + foreach(lib ${MPI_CXX_LIBRARIES} ${MPI_C_LIBRARIES}) + if(IS_ABSOLUTE "${lib}") + get_filename_component(libdir "${lib}" PATH) + string(REPLACE "\\" "/" libdir "${libdir}") + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT ${libdir}) + endif() + endforeach() + endif() + + # Handle Python version suffixes + unset(COMPONENT_PYTHON_VERSION_MAJOR) + unset(COMPONENT_PYTHON_VERSION_MINOR) + if(${COMPONENT} MATCHES "^(python|mpi_python|numpy)([0-9])\$") + set(COMPONENT_UNVERSIONED "${CMAKE_MATCH_1}") + set(COMPONENT_PYTHON_VERSION_MAJOR "${CMAKE_MATCH_2}") + elseif(${COMPONENT} MATCHES "^(python|mpi_python|numpy)([0-9])\\.?([0-9])\$") + set(COMPONENT_UNVERSIONED "${CMAKE_MATCH_1}") + set(COMPONENT_PYTHON_VERSION_MAJOR "${CMAKE_MATCH_2}") + set(COMPONENT_PYTHON_VERSION_MINOR "${CMAKE_MATCH_3}") + endif() + + unset(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME) + if (COMPONENT_PYTHON_VERSION_MINOR) + # Boost >= 1.67 + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") + # Debian/Ubuntu (Some versions omit the 2 and/or 3 from the suffix) + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}${COMPONENT_PYTHON_VERSION_MAJOR}-py${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}-py${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") + # Gentoo + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}-${COMPONENT_PYTHON_VERSION_MAJOR}.${COMPONENT_PYTHON_VERSION_MINOR}") + # RPMs + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}-${COMPONENT_PYTHON_VERSION_MAJOR}${COMPONENT_PYTHON_VERSION_MINOR}") + endif() + if (COMPONENT_PYTHON_VERSION_MAJOR AND NOT COMPONENT_PYTHON_VERSION_MINOR) + # Boost < 1.67 + list(APPEND _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME "${COMPONENT_UNVERSIONED}${COMPONENT_PYTHON_VERSION_MAJOR}") + endif() + + # Consolidate and report component-specific hints. + if(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME) + list(REMOVE_DUPLICATES _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Component-specific library search names for ${COMPONENT_NAME}: " + "${_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME}") + endif() + endif() + if(_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT) + list(REMOVE_DUPLICATES _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT) + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Component-specific library search paths for ${COMPONENT}: " + "${_Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT}") + endif() + endif() + + # + # Find headers + # + _Boost_COMPONENT_HEADERS("${COMPONENT}" Boost_${UPPERCOMPONENT}_HEADER_NAME) + # Look for a standard boost header file. + if(Boost_${UPPERCOMPONENT}_HEADER_NAME) + if(EXISTS "${Boost_INCLUDE_DIR}/${Boost_${UPPERCOMPONENT}_HEADER_NAME}") + set(Boost_${UPPERCOMPONENT}_HEADER ON) + else() + set(Boost_${UPPERCOMPONENT}_HEADER OFF) + endif() + else() + set(Boost_${UPPERCOMPONENT}_HEADER ON) + message(WARNING "No header defined for ${COMPONENT}; skipping header check") + endif() + + # + # Find RELEASE libraries + # + unset(_boost_RELEASE_NAMES) + foreach(component IN LISTS _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME COMPONENT) + foreach(compiler IN LISTS _boost_COMPILER) + list(APPEND _boost_RELEASE_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG}${_boost_ARCHITECTURE_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG} ) + endforeach() + list(APPEND _boost_RELEASE_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG}${_boost_ARCHITECTURE_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_ABI_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component} ) + if(_boost_STATIC_RUNTIME_WORKAROUND) + set(_boost_RELEASE_STATIC_ABI_TAG "-s${_boost_RELEASE_ABI_TAG}") + foreach(compiler IN LISTS _boost_COMPILER) + list(APPEND _boost_RELEASE_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG} ) + endforeach() + list(APPEND _boost_RELEASE_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_RELEASE_STATIC_ABI_TAG} ) + endif() + endforeach() + if(Boost_THREADAPI AND ${COMPONENT} STREQUAL "thread") + _Boost_PREPEND_LIST_WITH_THREADAPI(_boost_RELEASE_NAMES ${_boost_RELEASE_NAMES}) + endif() + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Searching for ${UPPERCOMPONENT}_LIBRARY_RELEASE: ${_boost_RELEASE_NAMES}") + endif() + + # if Boost_LIBRARY_DIR_RELEASE is not defined, + # but Boost_LIBRARY_DIR_DEBUG is, look there first for RELEASE libs + if(NOT Boost_LIBRARY_DIR_RELEASE AND Boost_LIBRARY_DIR_DEBUG) + list(INSERT _boost_LIBRARY_SEARCH_DIRS_RELEASE 0 ${Boost_LIBRARY_DIR_DEBUG}) + endif() + + # Avoid passing backslashes to _Boost_FIND_LIBRARY due to macro re-parsing. + string(REPLACE "\\" "/" _boost_LIBRARY_SEARCH_DIRS_tmp "${_boost_LIBRARY_SEARCH_DIRS_RELEASE}") + + if(Boost_USE_RELEASE_LIBS) + _Boost_FIND_LIBRARY(Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE RELEASE + NAMES ${_boost_RELEASE_NAMES} + HINTS ${_boost_LIBRARY_SEARCH_DIRS_tmp} + NAMES_PER_DIR + DOC "${_boost_docstring_release}" + ) + endif() + + # + # Find DEBUG libraries + # + unset(_boost_DEBUG_NAMES) + foreach(component IN LISTS _Boost_FIND_LIBRARY_HINTS_FOR_COMPONENT_NAME COMPONENT) + foreach(compiler IN LISTS _boost_COMPILER) + list(APPEND _boost_DEBUG_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG}${_boost_ARCHITECTURE_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG} ) + endforeach() + list(APPEND _boost_DEBUG_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG}${_boost_ARCHITECTURE_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_ABI_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component} ) + if(_boost_STATIC_RUNTIME_WORKAROUND) + set(_boost_DEBUG_STATIC_ABI_TAG "-s${_boost_DEBUG_ABI_TAG}") + foreach(compiler IN LISTS _boost_COMPILER) + list(APPEND _boost_DEBUG_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${compiler}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG} ) + endforeach() + list(APPEND _boost_DEBUG_NAMES + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG}-${Boost_LIB_VERSION} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG}${_boost_ARCHITECTURE_TAG} + ${Boost_LIB_PREFIX}${Boost_NAMESPACE}_${component}${_boost_MULTITHREADED}${_boost_DEBUG_STATIC_ABI_TAG} ) + endif() + endforeach() + if(Boost_THREADAPI AND ${COMPONENT} STREQUAL "thread") + _Boost_PREPEND_LIST_WITH_THREADAPI(_boost_DEBUG_NAMES ${_boost_DEBUG_NAMES}) + endif() + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] " + "Searching for ${UPPERCOMPONENT}_LIBRARY_DEBUG: ${_boost_DEBUG_NAMES}") + endif() + + # if Boost_LIBRARY_DIR_DEBUG is not defined, + # but Boost_LIBRARY_DIR_RELEASE is, look there first for DEBUG libs + if(NOT Boost_LIBRARY_DIR_DEBUG AND Boost_LIBRARY_DIR_RELEASE) + list(INSERT _boost_LIBRARY_SEARCH_DIRS_DEBUG 0 ${Boost_LIBRARY_DIR_RELEASE}) + endif() + + # Avoid passing backslashes to _Boost_FIND_LIBRARY due to macro re-parsing. + string(REPLACE "\\" "/" _boost_LIBRARY_SEARCH_DIRS_tmp "${_boost_LIBRARY_SEARCH_DIRS_DEBUG}") + + if(Boost_USE_DEBUG_LIBS) + _Boost_FIND_LIBRARY(Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG DEBUG + NAMES ${_boost_DEBUG_NAMES} + HINTS ${_boost_LIBRARY_SEARCH_DIRS_tmp} + NAMES_PER_DIR + DOC "${_boost_docstring_debug}" + ) + endif () + + if(Boost_REALPATH) + _Boost_SWAP_WITH_REALPATH(Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE "${_boost_docstring_release}") + _Boost_SWAP_WITH_REALPATH(Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG "${_boost_docstring_debug}" ) + endif() + + _Boost_ADJUST_LIB_VARS(${UPPERCOMPONENT}) + + # Check if component requires some compiler features + _Boost_COMPILER_FEATURES(${COMPONENT} _Boost_${UPPERCOMPONENT}_COMPILER_FEATURES) + +endforeach() + +# Restore the original find library ordering +if( Boost_USE_STATIC_LIBS ) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${_boost_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) +endif() + +# ------------------------------------------------------------------------ +# End finding boost libraries +# ------------------------------------------------------------------------ + +set(Boost_INCLUDE_DIRS ${Boost_INCLUDE_DIR}) +set(Boost_LIBRARY_DIRS) +if(Boost_LIBRARY_DIR_RELEASE) + list(APPEND Boost_LIBRARY_DIRS ${Boost_LIBRARY_DIR_RELEASE}) +endif() +if(Boost_LIBRARY_DIR_DEBUG) + list(APPEND Boost_LIBRARY_DIRS ${Boost_LIBRARY_DIR_DEBUG}) +endif() +if(Boost_LIBRARY_DIRS) + list(REMOVE_DUPLICATES Boost_LIBRARY_DIRS) +endif() + +# The above setting of Boost_FOUND was based only on the header files. +# Update it for the requested component libraries. +if(Boost_FOUND) + # The headers were found. Check for requested component libs. + set(_boost_CHECKED_COMPONENT FALSE) + set(_Boost_MISSING_COMPONENTS "") + foreach(COMPONENT ${Boost_FIND_COMPONENTS}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + set(_boost_CHECKED_COMPONENT TRUE) + if(NOT Boost_${UPPERCOMPONENT}_FOUND AND Boost_FIND_REQUIRED_${COMPONENT}) + list(APPEND _Boost_MISSING_COMPONENTS ${COMPONENT}) + endif() + endforeach() + if(_Boost_MISSING_COMPONENTS AND _Boost_EXTRA_FIND_COMPONENTS) + # Optional indirect dependencies are not counted as missing. + list(REMOVE_ITEM _Boost_MISSING_COMPONENTS ${_Boost_EXTRA_FIND_COMPONENTS}) + endif() + + if(Boost_DEBUG) + message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] Boost_FOUND = ${Boost_FOUND}") + endif() + + if (_Boost_MISSING_COMPONENTS) + set(Boost_FOUND 0) + # We were unable to find some libraries, so generate a sensible + # error message that lists the libraries we were unable to find. + string(APPEND Boost_ERROR_REASON + "\nCould not find the following") + if(Boost_USE_STATIC_LIBS) + string(APPEND Boost_ERROR_REASON " static") + endif() + string(APPEND Boost_ERROR_REASON + " Boost libraries:\n") + foreach(COMPONENT ${_Boost_MISSING_COMPONENTS}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + string(APPEND Boost_ERROR_REASON + " ${Boost_NAMESPACE}_${COMPONENT}${Boost_ERROR_REASON_${UPPERCOMPONENT}}\n") + endforeach() + + list(LENGTH Boost_FIND_COMPONENTS Boost_NUM_COMPONENTS_WANTED) + list(LENGTH _Boost_MISSING_COMPONENTS Boost_NUM_MISSING_COMPONENTS) + if (${Boost_NUM_COMPONENTS_WANTED} EQUAL ${Boost_NUM_MISSING_COMPONENTS}) + string(APPEND Boost_ERROR_REASON + "No Boost libraries were found. You may need to set BOOST_LIBRARYDIR to the directory containing Boost libraries or BOOST_ROOT to the location of Boost.") + else () + string(APPEND Boost_ERROR_REASON + "Some (but not all) of the required Boost libraries were found. You may need to install these additional Boost libraries. Alternatively, set BOOST_LIBRARYDIR to the directory containing Boost libraries or BOOST_ROOT to the location of Boost.") + endif () + endif () + + if( NOT Boost_LIBRARY_DIRS AND NOT _boost_CHECKED_COMPONENT ) + # Compatibility Code for backwards compatibility with CMake + # 2.4's FindBoost module. + + # Look for the boost library path. + # Note that the user may not have installed any libraries + # so it is quite possible the Boost_LIBRARY_DIRS may not exist. + set(_boost_LIB_DIR ${Boost_INCLUDE_DIR}) + + if("${_boost_LIB_DIR}" MATCHES "boost-[0-9]+") + get_filename_component(_boost_LIB_DIR ${_boost_LIB_DIR} PATH) + endif() + + if("${_boost_LIB_DIR}" MATCHES "/include$") + # Strip off the trailing "/include" in the path. + get_filename_component(_boost_LIB_DIR ${_boost_LIB_DIR} PATH) + endif() + + if(EXISTS "${_boost_LIB_DIR}/lib") + string(APPEND _boost_LIB_DIR /lib) + elseif(EXISTS "${_boost_LIB_DIR}/stage/lib") + string(APPEND _boost_LIB_DIR "/stage/lib") + else() + set(_boost_LIB_DIR "") + endif() + + if(_boost_LIB_DIR AND EXISTS "${_boost_LIB_DIR}") + set(Boost_LIBRARY_DIRS ${_boost_LIB_DIR}) + endif() + + endif() +else() + # Boost headers were not found so no components were found. + foreach(COMPONENT ${Boost_FIND_COMPONENTS}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + set(Boost_${UPPERCOMPONENT}_FOUND 0) + endforeach() +endif() + +# ------------------------------------------------------------------------ +# Add imported targets +# ------------------------------------------------------------------------ + +if(Boost_FOUND) + # For header-only libraries + if(NOT TARGET Boost::boost) + add_library(Boost::boost INTERFACE IMPORTED) + if(Boost_INCLUDE_DIRS) + set_target_properties(Boost::boost PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}") + endif() + endif() + + foreach(COMPONENT ${Boost_FIND_COMPONENTS}) + if(_Boost_IMPORTED_TARGETS AND NOT TARGET Boost::${COMPONENT}) + string(TOUPPER ${COMPONENT} UPPERCOMPONENT) + if(Boost_${UPPERCOMPONENT}_FOUND) + if(Boost_USE_STATIC_LIBS) + add_library(Boost::${COMPONENT} STATIC IMPORTED) + else() + # Even if Boost_USE_STATIC_LIBS is OFF, we might have static + # libraries as a result. + add_library(Boost::${COMPONENT} UNKNOWN IMPORTED) + endif() + if(Boost_INCLUDE_DIRS) + set_target_properties(Boost::${COMPONENT} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}") + endif() + if(EXISTS "${Boost_${UPPERCOMPONENT}_LIBRARY}") + set_target_properties(Boost::${COMPONENT} PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${Boost_${UPPERCOMPONENT}_LIBRARY}") + endif() + if(EXISTS "${Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE}") + set_property(TARGET Boost::${COMPONENT} APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(Boost::${COMPONENT} PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" + IMPORTED_LOCATION_RELEASE "${Boost_${UPPERCOMPONENT}_LIBRARY_RELEASE}") + endif() + if(EXISTS "${Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG}") + set_property(TARGET Boost::${COMPONENT} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(Boost::${COMPONENT} PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX" + IMPORTED_LOCATION_DEBUG "${Boost_${UPPERCOMPONENT}_LIBRARY_DEBUG}") + endif() + if(_Boost_${UPPERCOMPONENT}_DEPENDENCIES) + unset(_Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES) + foreach(dep ${_Boost_${UPPERCOMPONENT}_DEPENDENCIES}) + list(APPEND _Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES Boost::${dep}) + endforeach() + if(COMPONENT STREQUAL "thread") + list(APPEND _Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES Threads::Threads) + endif() + set_target_properties(Boost::${COMPONENT} PROPERTIES + INTERFACE_LINK_LIBRARIES "${_Boost_${UPPERCOMPONENT}_TARGET_DEPENDENCIES}") + endif() + if(_Boost_${UPPERCOMPONENT}_COMPILER_FEATURES) + set_target_properties(Boost::${COMPONENT} PROPERTIES + INTERFACE_COMPILE_FEATURES "${_Boost_${UPPERCOMPONENT}_COMPILER_FEATURES}") + endif() + endif() + endif() + endforeach() +endif() + +# ------------------------------------------------------------------------ +# Notification to end user about what was found +# ------------------------------------------------------------------------ + +set(Boost_LIBRARIES "") +if(Boost_FOUND) + if(NOT Boost_FIND_QUIETLY) + message(STATUS "Boost version: ${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") + if(Boost_FIND_COMPONENTS) + message(STATUS "Found the following Boost libraries:") + endif() + endif() + foreach( COMPONENT ${Boost_FIND_COMPONENTS} ) + string( TOUPPER ${COMPONENT} UPPERCOMPONENT ) + if( Boost_${UPPERCOMPONENT}_FOUND ) + if(NOT Boost_FIND_QUIETLY) + message (STATUS " ${COMPONENT}") + endif() + list(APPEND Boost_LIBRARIES ${Boost_${UPPERCOMPONENT}_LIBRARY}) + if(COMPONENT STREQUAL "thread") + list(APPEND Boost_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) + endif() + endif() + endforeach() +else() + if(Boost_FIND_REQUIRED) + message(SEND_ERROR "Unable to find the requested Boost libraries.\n${Boost_ERROR_REASON}") + else() + if(NOT Boost_FIND_QUIETLY) + # we opt not to automatically output Boost_ERROR_REASON here as + # it could be quite lengthy and somewhat imposing in its requests + # Since Boost is not always a required dependency we'll leave this + # up to the end-user. + if(Boost_DEBUG OR Boost_DETAILED_FAILURE_MSG) + message(STATUS "Could NOT find Boost\n${Boost_ERROR_REASON}") + else() + message(STATUS "Could NOT find Boost") + endif() + endif() + endif() +endif() + +# Configure display of cache entries in GUI. +foreach(v BOOSTROOT BOOST_ROOT ${_Boost_VARS_INC} ${_Boost_VARS_LIB}) + get_property(_type CACHE ${v} PROPERTY TYPE) + if(_type) + set_property(CACHE ${v} PROPERTY ADVANCED 1) + if("x${_type}" STREQUAL "xUNINITIALIZED") + if("x${v}" STREQUAL "xBoost_ADDITIONAL_VERSIONS") + set_property(CACHE ${v} PROPERTY TYPE STRING) + else() + set_property(CACHE ${v} PROPERTY TYPE PATH) + endif() + endif() + endif() +endforeach() + +# Record last used values of input variables so we can +# detect on the next run if the user changed them. +foreach(v + ${_Boost_VARS_INC} ${_Boost_VARS_LIB} + ${_Boost_VARS_DIR} ${_Boost_VARS_NAME} + ) + if(DEFINED ${v}) + set(_${v}_LAST "${${v}}" CACHE INTERNAL "Last used ${v} value.") + else() + unset(_${v}_LAST CACHE) + endif() +endforeach() + +# Maintain a persistent list of components requested anywhere since +# the last flush. +set(_Boost_COMPONENTS_SEARCHED "${_Boost_COMPONENTS_SEARCHED}") +list(APPEND _Boost_COMPONENTS_SEARCHED ${Boost_FIND_COMPONENTS}) +list(REMOVE_DUPLICATES _Boost_COMPONENTS_SEARCHED) +list(SORT _Boost_COMPONENTS_SEARCHED) +set(_Boost_COMPONENTS_SEARCHED "${_Boost_COMPONENTS_SEARCHED}" + CACHE INTERNAL "Components requested for this build tree.") + +# Restore project's policies +cmake_policy(POP) diff --git a/cmake/TestForBug2795.cmake b/cmake/TestForBug2795.cmake new file mode 100644 index 00000000000..162b0335253 --- /dev/null +++ b/cmake/TestForBug2795.cmake @@ -0,0 +1,53 @@ +# This file is a CMake configuration source compile check +# +# G++ 10.0.1 had a regression bug, which causes boost::serialization to fail +# to compile because the underlying type of a strong typedef template did not +# expose its ++/-- post operators. +# +# For more details see: +# https://github.com/freeorion/freeorion/issues/2795 +# https://github.com/boostorg/serialization/issues/192 +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94190 + + +include(CheckCXXSourceCompiles) + +# Enforce compiler to be invoked with C locale to ensure non-localized +# error messages. +set(_locale_vars LC_ALL LC_MESSAGES LANG) +foreach(v IN LISTS _locale_vars) + set(_locale_vars_saved_${v} "$ENV{${v}}") + set(ENV{${v}} C) +endforeach() + +check_cxx_source_compiles(" +struct A +{ + int _i; + operator int&() { return _i; } +}; + +template void f() +{ + A a; + a++; // triggers bug +} + +int main() { f(); } +" NOT_GNU_CXX_BUG_94190 FAIL_REGEX "no post-decrement operator for type") + +if(NOT NOT_GNU_CXX_BUG_94190) + message(FATAL_ERROR + "The found version of the g++ compiler suffers from bug" + " https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94190" + " which triggers a known bug in combination with boost::serialization." + " Please update the compiler to a more recent version of g++ or" + " use an alternative compiler." + " After update rebuild the project from a clean build tree." + ) +endif() + +# Restore locale settings +foreach(v IN LISTS _locale_vars) + set(ENV{${v}} ${_locale_vars_saved_${v}}) +endforeach() diff --git a/cmake/UseCompilerCache.cmake b/cmake/UseCompilerCache.cmake new file mode 100644 index 00000000000..385e303ec92 --- /dev/null +++ b/cmake/UseCompilerCache.cmake @@ -0,0 +1,144 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +# .rst: +# UseCompilerCache +# -------- +# +# This module provides a function to setup a compiler cache tool (currently ``ccache``): +# +# find_compiler_cache([PROGRAM ] [QUIET] [REQUIRED]) +# -- Add the compiler cache tool (default to look for ccache on the path) +# to your build through CMAKE__COMPILER_LAUNCHER variables. Also +# supports XCode. Uses a wrapper for XCode and CCache < 3.3. +# Sets the COMPILER_CACHE_VERSION variable. + +# TODO: Remove this module when cmake 3.11 is the minimum required version +# FreeOrion included this file directly from CMake master. + +function(find_compiler_cache) + set(options + QUIET + REQUIRED + ) + + set(oneValueArgs + PROGRAM + ) + + set(multiValueArgs + ) + + cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT ARGS_PROGRAM) + set(ARGS_PROGRAM ccache) + endif() + + find_program(CCACHE_PROGRAM ${ARGS_PROGRAM}) + + # Quit if required and not found + if(REQUIRED AND NOT CCACHE_PROGRAM) + message(FATAL_ERROR "Failed to find ${CCACHE_PROGRAM} (REQUIRED)") + endif() + + # Only add if program found + if(CCACHE_PROGRAM) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}") + + # Get version number + execute_process(COMMAND "${CCACHE_PROGRAM}" --version OUTPUT_VARIABLE output) + string(REPLACE "\n" ";" output "${output}") + foreach(line ${output}) + string(TOLOWER ${line} line) + string(REGEX REPLACE "ccache version ([\\.0-9]+)$" "\\1" version "${line}") + if(version AND NOT "x${line}" STREQUAL "x${version}") + set(COMPILER_CACHE_VERSION ${version}) + set(COMPILER_CACHE_VERSION ${version} PARENT_SCOPE) + break() + endif() + endforeach() + + if(NOT QUIET) + message(STATUS "CCache version: ${COMPILER_CACHE_VERSION}") + endif() + + # TODO: set PARENT_SCOPE with COMPILER_CACHE_VERSION + + if(NOT ("${CMAKE_GENERATOR}" STREQUAL Xcode OR "${COMPILER_CACHE_VERSION}" VERSION_LESS 3.2.0)) + # Support Unix Makefiles and Ninja + set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") + set(CMAKE_CXX_COMPILER_LAUNCHER "${CMAKE_PROGRAM}") + set(CMAKE_CUDA_COMPILER_LAUNCHER "${CMAKE_PROGRAM}") + message(STATUS "CCache configured for linux: ${CCACHE_PROGRAM}") + endif() + endif() +endfunction() + +# This module provides a function to setup a compiler cache tool for XCode (currently ``ccache``): +# +# use_compiler_cache_with_xcode() +# -- Confiure XCode to use ccache. This must be called after the project() +# statement so that CMAKE_C_COMPILER is already set. + +function(use_compiler_cache_with_xcode) + get_property(RULE_LAUNCH_COMPILE_VAR GLOBAL PROPERTY RULE_LAUNCH_COMPILE) + message(STATUS "Use CCache for ${CMAKE_GENERATOR} called with: ccache program ${RULE_LAUNCH_COMPILE_VAR} ${COMPILER_CACHE_VERSION}") + # This wrapper only is needed for CCache < 3.3 or XCode + if(RULE_LAUNCH_COMPILE_VAR AND ("${CMAKE_GENERATOR}" STREQUAL Xcode OR "${COMPILER_CACHE_VERSION}" VERSION_LESS 3.2.0)) + file(WRITE "${CMAKE_BINARY_DIR}/launch-c" "" + "#!/bin/sh\n" + "\n" + "# Xcode generator doesn't include the compiler as the\n" + "# first argument, Ninja and Makefiles do. Handle both cases.\n" + "if [[ \"$1\" = \"${CMAKE_C_COMPILER}\" ]] ; then\n" + " shift\n" + "fi\n" + "\n" + "export CCACHE_CPP2=true\n" + "echo using \"${RULE_LAUNCH_COMPILE_VAR}\" to compile\n" + "exec \"${RULE_LAUNCH_COMPILE_VAR}\" \"${CMAKE_C_COMPILER}\" \"$@\"\n" + ) + file(WRITE "${CMAKE_BINARY_DIR}/launch-cxx" "" + "#!/bin/sh\n" + "\n" + "# Xcode generator doesn't include the compiler as the\n" + "# first argument, Ninja and Makefiles do. Handle both cases.\n" + "if [[ \"$1\" = \"${CMAKE_CXX_COMPILER}\" ]] ; then\n" + " shift\n" + "fi\n" + "\n" + "export CCACHE_CPP2=true\n" + "echo using \"${RULE_LAUNCH_COMPILE_VAR}\" to compile\n" + "exec \"${RULE_LAUNCH_COMPILE_VAR}\" \"${CMAKE_CXX_COMPILER}\" \"$@\"\n" + ) + # Cuda support only added in CMake 3.10 + file(WRITE "${CMAKE_BINARY_DIR}/launch-cuda" "" + "#!/bin/sh\n" + "\n" + "# Xcode generator doesn't include the compiler as the\n" + "# first argument, Ninja and Makefiles do. Handle both cases.\n" + "if [[ \"$1\" = \"${CMAKE_CUDA_COMPILER}\" ]] ; then\n" + " shift\n" + "fi\n" + "\n" + "export CCACHE_CPP2=true\n" + "echo using \"${RULE_LAUNCH_COMPILE_VAR}\" to compile\n" + "exec \"${RULE_LAUNCH_COMPILE_VAR}\" \"${CMAKE_CUDA_COMPILER}\" \"$@\"\n" + ) + execute_process(COMMAND chmod a+rx + "${CMAKE_BINARY_DIR}/launch-c" + "${CMAKE_BINARY_DIR}/launch-cxx" + "${CMAKE_BINARY_DIR}/launch-cuda" + ) + + # Set Xcode project attributes to route compilation and linking + # through our scripts + set(CMAKE_XCODE_ATTRIBUTE_CC "${CMAKE_BINARY_DIR}/launch-c" PARENT_SCOPE) + set(CMAKE_XCODE_ATTRIBUTE_CXX "${CMAKE_BINARY_DIR}/launch-cxx" PARENT_SCOPE) + set(CMAKE_XCODE_ATTRIBUTE_LD "${CMAKE_BINARY_DIR}/launch-c" PARENT_SCOPE) + set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS "${CMAKE_BINARY_DIR}/launch-cxx" PARENT_SCOPE) + message(STATUS "CCache configured for XCode: ${RULE_LAUNCH_COMPILE_VAR}") + endif() +endfunction() diff --git a/cmake/make_versioncpp.py b/cmake/make_versioncpp.py index c2ab54863db..14f35e8188e 100755 --- a/cmake/make_versioncpp.py +++ b/cmake/make_versioncpp.py @@ -1,5 +1,6 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 +from __future__ import print_function import sys import os @@ -20,13 +21,12 @@ "boost_locale", "boost_log", "boost_log_setup", - "boost_python", + "boost_python%d%d" % (sys.version_info.major, sys.version_info.minor), "boost_regex", "boost_serialization", "boost_signals", "boost_system", "boost_thread", - "boost_zlib" ] @@ -44,21 +44,27 @@ def compile_output(self, template, version, branch, build_no, build_sys): def execute(self, version, branch, build_no, build_sys): if build_no == INVALID_BUILD_NO: - print "WARNING: Can't determine git commit!" + print("WARNING: Can't determine git commit!") - if os.path.isfile(self.outfile) and not build_no == INVALID_BUILD_NO: + if os.path.isfile(self.outfile): with open(self.outfile) as check_file: - if build_no in check_file.read(): + check_file_contents = check_file.read() + if build_no == INVALID_BUILD_NO: + if version in check_file_contents: + print("Version matches version in existing Version.cpp, skip regenerating it") + return + elif build_no in check_file_contents: + print("Build number matches build number in existing Version.cpp, skip regenerating it") return try: with open(self.infile) as template_file: template = Template(template_file.read()) except: - print "WARNING: Can't access %s, %s not updated!" % (self.infile, self.outfile) + print("WARNING: Can't access %s, %s not updated!" % (self.infile, self.outfile)) return - print "Writing file: %s" % self.outfile + print("Writing file: %s" % self.outfile) with open(self.outfile, "w") as generated_file: generated_file.write(self.compile_output(template, version, branch, build_no, build_sys)) @@ -66,10 +72,10 @@ def execute(self, version, branch, build_no, build_sys): class NsisInstScriptGenerator(Generator): def compile_dll_list(self): all_dll_files = glob("*.dll") - accepted_dll_files = set(["GiGi.dll", "GiGiSDL.dll"]) + accepted_dll_files = set(["GiGi.dll"]) for dll_file in all_dll_files: if dll_file.startswith("boost_"): - if dll_file.partition("-")[0] in required_boost_libraries: + if dll_file.partition(".")[0] in required_boost_libraries: accepted_dll_files.add(dll_file) else: accepted_dll_files.add(dll_file) @@ -84,21 +90,24 @@ def compile_output(self, template, version, branch, build_no, build_sys): FreeOrion_BUILD_NO=build_no, FreeOrion_BUILDSYS=build_sys, FreeOrion_DLL_LIST_INSTALL="\n ".join(['File "..\\' + fname + '"' for fname in dll_files]), - FreeOrion_DLL_LIST_UNINSTALL="\n ".join(['Delete "$INSTDIR\\' + fname + '"' for fname in dll_files])) + FreeOrion_DLL_LIST_UNINSTALL="\n ".join(['Delete "$INSTDIR\\' + fname + '"' for fname in dll_files]), + FreeOrion_PYTHON_VERSION="%d%d" % (sys.version_info.major, sys.version_info.minor)) + else: - print "WARNING: no dll files for installer package found" + print("WARNING: no dll files for installer package found") return template.substitute( FreeOrion_VERSION=version, FreeOrion_BRANCH=branch, FreeOrion_BUILD_NO=build_no, FreeOrion_BUILDSYS=build_sys, FreeOrion_DLL_LIST_INSTALL="", - FreeOrion_DLL_LIST_UNINSTALL="") + FreeOrion_DLL_LIST_UNINSTALL="", + FreeOrion_PYTHON_VERSION="") if 3 != len(sys.argv): - print "ERROR: invalid parameters." - print "make_versioncpp.py " + print("ERROR: invalid parameters.") + print("make_versioncpp.py ") quit() os.chdir(sys.argv[1]) @@ -109,28 +118,28 @@ def compile_output(self, template, version, branch, build_no, build_sys): Generator('util/Version.cpp.in', 'util/Version.cpp') ] if system() == 'Windows': - generators.append(NsisInstScriptGenerator('Installer/FreeOrion_Install_Script.nsi.in', - 'Installer/FreeOrion_Install_Script.nsi')) + generators.append(NsisInstScriptGenerator('packaging/windows_installer.nsi.in', + 'packaging/windows_installer.nsi')) if system() == 'Darwin': - generators.append(Generator('Xcode/Info.plist.in', 'Xcode/Info.plist')) + generators.append(Generator('packaging/Info.plist.in', 'packaging/Info.plist')) -version = "0.4.7+" +version = "0.4.9+" branch = "" build_no = INVALID_BUILD_NO try: - branch = check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']).strip() + branch = check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], universal_newlines=True).strip() if (branch == "master") or (branch[:7] == "release"): branch = "" else: branch += " " - commit = check_output(["git", "show", "-s", "--format=%h", "--abbrev=7", "HEAD"]).strip() - timestamp = float(check_output(["git", "show", "-s", "--format=%ct", "HEAD"]).strip()) + commit = check_output(["git", "show", "-s", "--format=%h", "--abbrev=7", "HEAD"], universal_newlines=True).strip() + timestamp = float(check_output(["git", "show", "-s", "--format=%ct", "HEAD"], universal_newlines=True).strip()) build_no = ".".join([datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d"), commit]) except: - print "WARNING: git not installed or not setup correctly" + print("WARNING: git not installed or not setup correctly") for generator in generators: generator.execute(version, branch, build_no, build_sys) -print "Building v%s %sbuild %s" % (version, branch, build_no) +print("Building v%s %sbuild %s" % (version, branch, build_no)) diff --git a/combat/CombatEvent.h b/combat/CombatEvent.h index c1e9529d389..a4134bd1514 100644 --- a/combat/CombatEvent.h +++ b/combat/CombatEvent.h @@ -9,6 +9,7 @@ #include "../util/Export.h" #include +#include #include @@ -40,7 +41,7 @@ struct FO_COMMON_API CombatEvent { /** Generate the combat log details. Describe how it happened in enough detail to avoid a trip to the Pedia. */ virtual std::string CombatLogDetails(int viewing_empire_id) const - { return std::string(""); } + { return ""; } /** If the combat event is composed of smaller events then return a vector of the sub events, otherwise returns an empty vector. @@ -74,7 +75,7 @@ struct FO_COMMON_API CombatEvent { private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/combat/CombatEvents.cpp b/combat/CombatEvents.cpp index 2e7617bcf23..597d0f5f575 100644 --- a/combat/CombatEvents.cpp +++ b/combat/CombatEvents.cpp @@ -11,7 +11,8 @@ #include "../util/Logger.h" #include "../util/AppInterface.h" #include "../util/VarText.h" -#include "../util/MultiplayerCommon.h" +#include "../util/GameRules.h" + #include "../Empire/Empire.h" #include @@ -23,6 +24,9 @@ namespace { // makes all buildings cost 1 PP and take 1 turn to produce rules.Add("RULE_NUM_COMBAT_ROUNDS", "RULE_NUM_COMBAT_ROUNDS_DESC", "", 3, true, RangedValidator(1, 20)); + rules.Add("RULE_AGGRESSIVE_SHIPS_COMBAT_VISIBLE", "RULE_AGGRESSIVE_SHIPS_COMBAT_VISIBLE_DESC", + "", false, true); + } bool temp_bool = RegisterGameRules(&AddRules); @@ -43,6 +47,7 @@ namespace { case OBJ_SYSTEM: return VarText::SYSTEM_ID_TAG; case OBJ_FIELD: + case OBJ_FIGHTER: default: return EMPTY_STRING; } @@ -84,8 +89,7 @@ namespace { /// with the content being the public name from the point of view of empire_id. /// Returns UserString("ENC_COMBAT_UNKNOWN_OBJECT") if object_id is not found. std::string PublicNameLink(int empire_id, int object_id) { - - std::shared_ptr object = GetUniverseObject(object_id); + auto object = Objects().get(object_id); if (object) { const std::string& name = object->PublicName(empire_id); const std::string& tag = LinkTag(object->ObjectType()); @@ -138,11 +142,10 @@ std::string BoutBeginEvent::DebugString() const { return ss.str(); } -std::string BoutBeginEvent::CombatLogDescription(int viewing_empire_id) const { - return str(FlexibleFormat(UserString("ENC_ROUND_BEGIN")) % bout); -} +std::string BoutBeginEvent::CombatLogDescription(int viewing_empire_id) const +{ return str(FlexibleFormat(UserString("ENC_ROUND_BEGIN")) % bout); } -template +template void BoutBeginEvent::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); ar & BOOST_SERIALIZATION_NVP(bout); @@ -167,10 +170,12 @@ void BoutBeginEvent::serialize(freeorion_xml_iarchive& a ///////// BoutEvent ///////////// ////////////////////////////////////////// BoutEvent::BoutEvent(): - bout(-1), events() {} + bout(-1) +{} BoutEvent::BoutEvent(int _bout): - bout(_bout), events() {} + bout(_bout) +{} void BoutEvent::AddEvent(const CombatEventPtr& event) { events.push_back(event); } @@ -187,17 +192,16 @@ std::string BoutEvent::CombatLogDescription(int viewing_empire_id) const { std::vector BoutEvent::SubEvents(int viewing_empire_id) const { std::vector all_events; - for (CombatEventPtr event : events) { + for (CombatEventPtr event : events) all_events.push_back(event); - } return all_events; } -template +template void BoutEvent::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); ar & BOOST_SERIALIZATION_NVP(bout) - & BOOST_SERIALIZATION_NVP(events); + & BOOST_SERIALIZATION_NVP(events); } BOOST_CLASS_VERSION(BoutEvent, 4) @@ -221,7 +225,8 @@ void BoutEvent::serialize(freeorion_xml_iarchive& ar, co ////////////////////////////////////////// SimultaneousEvents::SimultaneousEvents() : - events() {} + events() +{} void SimultaneousEvents::AddEvent(const CombatEventPtr& event) { events.push_back(event); } @@ -232,9 +237,8 @@ std::string SimultaneousEvents::DebugString() const { return ss.str(); } -std::string SimultaneousEvents::CombatLogDescription(int viewing_empire_id) const { - return ""; -} +std::string SimultaneousEvents::CombatLogDescription(int viewing_empire_id) const +{ return ""; } std::vector SimultaneousEvents::SubEvents(int viewing_empire_id) const { // Sort the events by viewing empire, then ALL_EMPIRES and then @@ -246,7 +250,7 @@ std::vector SimultaneousEvents::SubEvents(int viewing_empir for (CombatEventPtr event : events) { boost::optional maybe_faction = event->PrincipalFaction(viewing_empire_id); int faction = maybe_faction.get_value_or(ALL_EMPIRES); - empire_to_event.insert(std::make_pair(faction, event)); + empire_to_event.insert({faction, event}); } std::vector ordered_events; @@ -264,7 +268,7 @@ std::vector SimultaneousEvents::SubEvents(int viewing_empir ordered_events.push_back(it->second); } - for (std::multimap::value_type& entry : empire_to_event) { + for (auto& entry : empire_to_event) { if (entry.first != viewing_empire_id && entry.first != ALL_EMPIRES) ordered_events.push_back(entry.second); } @@ -273,7 +277,7 @@ std::vector SimultaneousEvents::SubEvents(int viewing_empir } -template +template void SimultaneousEvents::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); ar & BOOST_SERIALIZATION_NVP(events); @@ -299,103 +303,72 @@ void SimultaneousEvents::serialize(freeorion_xml_iarchiv ///////// InitialStealthEvent ///////////// ////////////////////////////////////////// -InitialStealthEvent::InitialStealthEvent() : - target_empire_id_to_invisble_obj_id() +InitialStealthEvent::InitialStealthEvent() {} -InitialStealthEvent::InitialStealthEvent(const StealthInvisbleMap &x) : - target_empire_id_to_invisble_obj_id(x) +InitialStealthEvent::InitialStealthEvent(const EmpireToObjectVisibilityMap& x) : + empire_to_object_visibility(x) {} std::string InitialStealthEvent::DebugString() const { std::stringstream ss; ss << "InitialStealthEvent: "; - if (target_empire_id_to_invisble_obj_id.size() > 4) { - ss << target_empire_id_to_invisble_obj_id.size() << " events."; - } else { - for (const StealthInvisbleMap::value_type& attack_empire : target_empire_id_to_invisble_obj_id) { - ss << " Attacking Empire: " << EmpireLink(attack_empire.first) << "\n"; - for (const std::map>>::value_type& target_empire : attack_empire.second) { - ss << " Target Empire: " << EmpireLink(target_empire.first) << " Targets: "; + for (const auto& empire_object_vis : empire_to_object_visibility) { + ss << " Viewing Empire: " << EmpireLink(empire_object_vis.first) << "\n"; - if (target_empire.second.size() > 4) { - ss << target_empire.second.size() << " attackers."; - } else { - for (const std::pair& attacker : target_empire.second) { - ss << FighterOrPublicNameLink(ALL_EMPIRES, attacker.first, target_empire.first); - } - } - ss << "\n"; - } + for (const auto& viewed_object : empire_object_vis.second) { + const auto obj = Objects().get(viewed_object.first); + int owner_id = obj ? obj->Owner() : ALL_EMPIRES; + ss << FighterOrPublicNameLink(ALL_EMPIRES, viewed_object.first, owner_id); } + ss << "\n"; } - return ss.str(); } std::string InitialStealthEvent::CombatLogDescription(int viewing_empire_id) const { - std::string desc = ""; + DebugLogger() << "CombatLogDescription for InitialStealthEvent viewing empire empire: " << viewing_empire_id; - //Viewing empire stealth first - for (const StealthInvisbleMap::value_type& attack_empire : target_empire_id_to_invisble_obj_id) { - if (attack_empire.first == viewing_empire_id) - continue; + std::string desc = ""; - auto target_empire = attack_empire.second.find(viewing_empire_id); - if (target_empire != attack_empire.second.end() && - !target_empire->second.empty()) - { - std::vector cloaked_attackers; - for (auto& attacker : target_empire->second) { - std::string attacker_link = FighterOrPublicNameLink(viewing_empire_id, attacker.first, viewing_empire_id); - // It doesn't matter if targets of viewing empire have no_visibility or basic_visibility - cloaked_attackers.push_back(attacker_link); - } + for (const auto& detector_empire : empire_to_object_visibility) { + int detector_empire_id = detector_empire.first; + DebugLogger() << "CombatLogDescription for InitialStealthEvent for detector empire: " << detector_empire_id; - if (!cloaked_attackers.empty()) { - desc += "\n"; //< Add \n at start of the report and between each empire - std::vector attacker_empire_link(1, EmpireLink(attack_empire.first)); - desc += FlexibleFormatList(attacker_empire_link, cloaked_attackers, - UserString("ENC_COMBAT_INITIAL_STEALTH_LIST")).str(); - } + const auto& visible_objects = detector_empire.second; + if (visible_objects.empty()) { + DebugLogger() << " ... no object info recorded for detector empire: " << detector_empire_id; + continue; } - } - //Viewing empire defending - StealthInvisbleMap::const_iterator attack_empire = - target_empire_id_to_invisble_obj_id.find(viewing_empire_id); - if (attack_empire != target_empire_id_to_invisble_obj_id.end() && - !attack_empire->second.empty()) - { - for (auto& target_empire : attack_empire->second) { - if (target_empire.first == viewing_empire_id) + // Check Visibility of objects, report those that are not visible. + std::vector cloaked_attackers; + for (auto& object_vis : visible_objects) { + const auto obj = Objects().get(object_vis.first); + const auto name = obj ? obj->Name() : UserString("UNKNOWN"); + DebugLogger() << " ... object: " << name << " (" << object_vis.first << ") has vis: " << object_vis.second; + if (object_vis.second > VIS_NO_VISIBILITY) continue; + std::string attacker_link = FighterOrPublicNameLink( + viewing_empire_id, object_vis.first, ALL_EMPIRES); // all empires specifies empire to use for link color if this is a fighter + cloaked_attackers.push_back(attacker_link); + } - std::vector cloaked_attackers; - for (const auto& attacker : target_empire.second) { - std::string attacker_link = FighterOrPublicNameLink(viewing_empire_id, attacker.first, viewing_empire_id); - // Don't even report on targets with no_visibility it is supposed to be a surprise - if (attacker.second >= VIS_BASIC_VISIBILITY ) - cloaked_attackers.push_back(attacker_link); - } - - if (!cloaked_attackers.empty()) { - if (!desc.empty()) - desc += "\n"; - std::vector attacker_empire_link(1, EmpireLink(attack_empire->first)); - desc += FlexibleFormatList(attacker_empire_link, cloaked_attackers, - UserString("ENC_COMBAT_INITIAL_STEALTH_LIST")).str(); - } + if (!cloaked_attackers.empty()) { + desc += "\n"; //< Add \n at start of the report and between each empire + std::vector detector_empire_link(1, EmpireLink(detector_empire.first)); + desc += FlexibleFormatList(detector_empire_link, cloaked_attackers, + UserString("ENC_COMBAT_INITIAL_STEALTH_LIST")).str(); } } return desc; } -template +template void InitialStealthEvent::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); - ar & BOOST_SERIALIZATION_NVP(target_empire_id_to_invisble_obj_id); + ar & BOOST_SERIALIZATION_NVP(empire_to_object_visibility); } BOOST_CLASS_VERSION(InitialStealthEvent, 4) @@ -419,21 +392,18 @@ void InitialStealthEvent::serialize(freeorion_xml_iarchi ////////////////////////////////////////// StealthChangeEvent::StealthChangeEvent() : - bout(-1), - events() + bout(-1) {} StealthChangeEvent::StealthChangeEvent(int bout_) : - bout(bout_), - events() + bout(bout_) {} StealthChangeEvent::StealthChangeEventDetail::StealthChangeEventDetail() : attacker_id(INVALID_OBJECT_ID), target_id(INVALID_OBJECT_ID), attacker_empire_id(INVALID_OBJECT_ID), - target_empire_id(INVALID_OBJECT_ID), - visibility() + target_empire_id(INVALID_OBJECT_ID) {} StealthChangeEvent::StealthChangeEventDetail::StealthChangeEventDetail( @@ -454,7 +424,6 @@ std::string StealthChangeEvent::StealthChangeEventDetail::DebugString() const { } std::string StealthChangeEvent::StealthChangeEventDetail::CombatLogDescription(int viewing_empire_id) const { - std::string attacker_link = FighterOrPublicNameLink(viewing_empire_id, attacker_id, attacker_empire_id); std::string target_link = FighterOrPublicNameLink(viewing_empire_id, target_id, target_empire_id); std::string empire_link = EmpireLink(target_empire_id); @@ -467,8 +436,9 @@ std::string StealthChangeEvent::StealthChangeEventDetail::CombatLogDescription(i } -void StealthChangeEvent::AddEvent(int attacker_id_, int target_id_, int attacker_empire_ - , int target_empire_, Visibility new_visibility_) { +void StealthChangeEvent::AddEvent(int attacker_id_, int target_id_, int attacker_empire_, + int target_empire_, Visibility new_visibility_) +{ events[target_empire_].push_back( std::make_shared( attacker_id_, target_id_, attacker_empire_, target_empire_, new_visibility_)); @@ -480,13 +450,13 @@ std::string StealthChangeEvent::DebugString() const { if (events.size() > 4) { ss << events.size() << " empires."; } else { - for (const std::map>::value_type& target : events) { + for (const auto& target : events) { ss << "Target Empire: " << EmpireLink(target.first) << "\n"; if (target.second.size() > 4) { ss << target.second.size() << " events."; } else { - for (const StealthChangeEventDetailPtr event : target.second) { + for (const auto& event : target.second) { ss << event->DebugString(); } } @@ -500,11 +470,10 @@ std::string StealthChangeEvent::CombatLogDescription(int viewing_empire_id) cons return ""; std::string desc = ""; - for (const std::map>::value_type& target : events) { + for (const auto& target : events) { std::vector uncloaked_attackers; - for (const StealthChangeEventDetailPtr event : target.second) { + for (const auto event : target.second) uncloaked_attackers.push_back(FighterOrPublicNameLink(viewing_empire_id, event->attacker_id, event->attacker_empire_id)); - } if (!uncloaked_attackers.empty()) { if (!desc.empty()) @@ -520,21 +489,18 @@ std::string StealthChangeEvent::CombatLogDescription(int viewing_empire_id) cons return desc; } -bool StealthChangeEvent::AreSubEventsEmpty(int viewing_empire_id) const { - return events.empty(); -} +bool StealthChangeEvent::AreSubEventsEmpty(int viewing_empire_id) const +{ return events.empty(); } + std::vector StealthChangeEvent::SubEvents(int viewing_empire_id) const { std::vector all_events; - for (const std::map>::value_type& target : events) { - - for (const StealthChangeEventDetailPtr event : target.second){ + for (const auto& target : events) + for (const auto event : target.second) all_events.push_back(std::dynamic_pointer_cast(event)); - } - } return all_events; } -template +template void StealthChangeEvent::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); ar & BOOST_SERIALIZATION_NVP(bout) @@ -556,7 +522,7 @@ void StealthChangeEvent::serialize(freeorion_xml_oarchiv template void StealthChangeEvent::serialize(freeorion_xml_iarchive& ar, const unsigned int version); -template +template void StealthChangeEvent::StealthChangeEventDetail::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(attacker_id) & BOOST_SERIALIZATION_NVP(target_id) @@ -592,7 +558,8 @@ WeaponFireEvent::WeaponFireEvent() : power(0.0f), shield(0.0f), damage(0.0f), - attacker_owner_id(ALL_EMPIRES) + attacker_owner_id(ALL_EMPIRES), + target_owner_id(INVALID_OBJECT_ID) {} WeaponFireEvent::WeaponFireEvent( @@ -604,9 +571,6 @@ WeaponFireEvent::WeaponFireEvent( attacker_id(attacker_id_), target_id( target_id_), weapon_name(weapon_name_), - power(), - shield(), - damage(), attacker_owner_id(attacker_owner_id_), target_owner_id(target_owner_id_) { std::tie(power, shield, damage) = power_shield_damage; } @@ -655,27 +619,36 @@ boost::optional WeaponFireEvent::PrincipalFaction(int viewing_empire_id) co } -template +template void WeaponFireEvent::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); - ar & BOOST_SERIALIZATION_NVP(bout) - & BOOST_SERIALIZATION_NVP(round) - & BOOST_SERIALIZATION_NVP(attacker_id) - & BOOST_SERIALIZATION_NVP(target_id) - & BOOST_SERIALIZATION_NVP(weapon_name) - & BOOST_SERIALIZATION_NVP(power) - & BOOST_SERIALIZATION_NVP(shield) - & BOOST_SERIALIZATION_NVP(damage) - & BOOST_SERIALIZATION_NVP(target_owner_id) - & BOOST_SERIALIZATION_NVP(attacker_owner_id); - - if (version < 3) { - int target_destroyed = 0; - ar & BOOST_SERIALIZATION_NVP (target_destroyed); + + if (version < 5) { + ar & BOOST_SERIALIZATION_NVP(bout) + & BOOST_SERIALIZATION_NVP(round) + & BOOST_SERIALIZATION_NVP(attacker_id) + & BOOST_SERIALIZATION_NVP(target_id) + & BOOST_SERIALIZATION_NVP(weapon_name) + & BOOST_SERIALIZATION_NVP(power) + & BOOST_SERIALIZATION_NVP(shield) + & BOOST_SERIALIZATION_NVP(damage) + & BOOST_SERIALIZATION_NVP(target_owner_id) + & BOOST_SERIALIZATION_NVP(attacker_owner_id); + } else { + ar & boost::serialization::make_nvp("b", bout) + & boost::serialization::make_nvp("r", round) + & boost::serialization::make_nvp("a", attacker_id) + & boost::serialization::make_nvp("t", target_id) + & boost::serialization::make_nvp("w", weapon_name) + & boost::serialization::make_nvp("p", power) + & boost::serialization::make_nvp("s", shield) + & boost::serialization::make_nvp("d", damage) + & boost::serialization::make_nvp("to", target_owner_id) + & boost::serialization::make_nvp("ao", attacker_owner_id); } } -BOOST_CLASS_VERSION(WeaponFireEvent, 4) +BOOST_CLASS_VERSION(WeaponFireEvent, 5) BOOST_CLASS_EXPORT(WeaponFireEvent) template @@ -713,7 +686,7 @@ std::string IncapacitationEvent::DebugString() const { std::string IncapacitationEvent::CombatLogDescription(int viewing_empire_id) const { - std::shared_ptr object = GetUniverseObject(object_id); + auto object = Objects().get(object_id); std::string template_str, object_str; int owner_id = object_owner_id; @@ -743,19 +716,25 @@ std::string IncapacitationEvent::CombatLogDescription(int viewing_empire_id) con return str(FlexibleFormat(template_str) % owner_string % object_link); } -boost::optional IncapacitationEvent::PrincipalFaction(int viewing_empire_id) const { - return object_owner_id; -} +boost::optional IncapacitationEvent::PrincipalFaction(int viewing_empire_id) const +{ return object_owner_id; } -template +template void IncapacitationEvent::serialize (Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); - ar & BOOST_SERIALIZATION_NVP(bout) - & BOOST_SERIALIZATION_NVP(object_id) - & BOOST_SERIALIZATION_NVP(object_owner_id); + if (version < 2) { + ar & BOOST_SERIALIZATION_NVP(bout) + & BOOST_SERIALIZATION_NVP(object_id) + & BOOST_SERIALIZATION_NVP(object_owner_id); + } else { + ar & boost::serialization::make_nvp("b", bout) + & boost::serialization::make_nvp("i", object_id) + & boost::serialization::make_nvp("o", object_owner_id); + } } +BOOST_CLASS_VERSION(IncapacitationEvent, 2) BOOST_CLASS_EXPORT(IncapacitationEvent) template @@ -776,13 +755,11 @@ void IncapacitationEvent::serialize(freeorion_xml_iarchi ////////////////////////////////////////// FightersAttackFightersEvent::FightersAttackFightersEvent() : - bout(-1), - events() + bout(-1) {} FightersAttackFightersEvent::FightersAttackFightersEvent(int bout_) : - bout(bout_), - events() + bout(bout_) {} void FightersAttackFightersEvent::AddEvent(int attacker_empire_, int target_empire_) @@ -810,7 +787,8 @@ std::string FightersAttackFightersEvent::CombatLogDescription(int viewing_empire // then the remainder. auto show_events_for_empire = [&ss, &num_events_remaining, &events_to_show, &viewing_empire_id] - (boost::optional show_attacker) { + (boost::optional show_attacker) + { int attacker_empire; int target_empire; for (const auto& index_and_event : events_to_show) { @@ -847,7 +825,7 @@ std::string FightersAttackFightersEvent::CombatLogDescription(int viewing_empire } -template +template void FightersAttackFightersEvent::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); ar & BOOST_SERIALIZATION_NVP(bout) @@ -912,11 +890,10 @@ std::string FighterLaunchEvent::CombatLogDescription(int viewing_empire_id) cons % std::abs(number_launched)); } -boost::optional FighterLaunchEvent::PrincipalFaction(int viewing_empire_id) const { - return fighter_owner_empire_id; -} +boost::optional FighterLaunchEvent::PrincipalFaction(int viewing_empire_id) const +{ return fighter_owner_empire_id; } -template +template void FighterLaunchEvent::serialize (Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); ar & BOOST_SERIALIZATION_NVP(bout) @@ -946,13 +923,11 @@ void FighterLaunchEvent::serialize(freeorion_xml_iarchiv ////////////////////////////////////////// FightersDestroyedEvent::FightersDestroyedEvent() : - bout(-1), - events() + bout(-1) {} FightersDestroyedEvent::FightersDestroyedEvent(int bout_) : - bout(bout_), - events() + bout(bout_) {} void FightersDestroyedEvent::AddEvent(int target_empire_) @@ -980,7 +955,8 @@ std::string FightersDestroyedEvent::CombatLogDescription(int viewing_empire_id) // ALL_EMPIRES and then the remainder. auto show_events_for_empire = [&ss, &num_events_remaining, &events_to_show, &viewing_empire_id] - (boost::optional show_empire_id) { + (boost::optional show_empire_id) + { int count; int target_empire_id; for (const auto& index_and_event : events_to_show) { @@ -1003,7 +979,7 @@ std::string FightersDestroyedEvent::CombatLogDescription(int viewing_empire_id) if (count == 1) { const std::string& template_str = UserString("ENC_COMBAT_FIGHTER_INCAPACITATED_STR"); ss << str(FlexibleFormat(template_str) % target_empire_link % target_link); - }else { + } else { const std::string& template_str = UserString("ENC_COMBAT_FIGHTER_INCAPACITATED_REPEATED_STR"); ss << str(FlexibleFormat(template_str) % count_str % target_empire_link % target_link); } @@ -1021,7 +997,7 @@ std::string FightersDestroyedEvent::CombatLogDescription(int viewing_empire_id) } -template +template void FightersDestroyedEvent::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); ar & BOOST_SERIALIZATION_NVP(bout) @@ -1052,20 +1028,19 @@ void FightersDestroyedEvent::serialize(freeorion_xml_iar WeaponsPlatformEvent::WeaponsPlatformEvent() : bout(-1), attacker_id(INVALID_OBJECT_ID), - attacker_owner_id(INVALID_OBJECT_ID), - events() + attacker_owner_id(INVALID_OBJECT_ID) {} WeaponsPlatformEvent::WeaponsPlatformEvent(int bout_, int attacker_id_, int attacker_owner_id_) : bout(bout_), attacker_id(attacker_id_), - attacker_owner_id(attacker_owner_id_), - events() + attacker_owner_id(attacker_owner_id_) {} void WeaponsPlatformEvent::AddEvent( int round_, int target_id_, int target_owner_id_, std::string const & weapon_name_, - float power_, float shield_, float damage_) { + float power_, float shield_, float damage_) +{ events[target_id_].push_back( std::make_shared( bout, round_, attacker_id, target_id_, weapon_name_, @@ -1077,11 +1052,9 @@ std::string WeaponsPlatformEvent::DebugString() const { std::stringstream desc; desc << "WeaponsPlatformEvent bout = " << bout << " attacker_id = " << attacker_id << " attacker_owner = "<< attacker_owner_id; - for (const std::map>::value_type& target : events) { - for (const WeaponFireEvent::WeaponFireEventPtr attack : target.second) { + for (const auto& target : events) + for (const auto attack : target.second) desc << std::endl << attack->DebugString(); - } - } return desc.str(); } @@ -1092,22 +1065,21 @@ std::string WeaponsPlatformEvent::CombatLogDescription(int viewing_empire_id) co std::vector damaged_target_links; std::vector undamaged_target_links; - for (const std::map>::value_type& target : events) { + for (const auto& target : events) { if (target.second.empty()) continue; - const WeaponFireEvent::WeaponFireEventPtr& fire_event(*target.second.begin()); + const auto& fire_event(*target.second.begin()); std::string target_public_name( - FighterOrPublicNameLink(viewing_empire_id, target.first, fire_event->target_owner_id)); + FighterOrPublicNameLink(viewing_empire_id, target.first, + fire_event->target_owner_id)); double damage = 0.0f; - for (WeaponFireEvent::WeaponFireEventPtr attack_it : target.second) { + for (auto attack_it : target.second) damage += attack_it->damage; - } if (damage <= 0.0f) { undamaged_target_links.push_back(target_public_name); - } else { damaged_target_links.push_back( str(FlexibleFormat(UserString("ENC_COMBAT_PLATFORM_TARGET_AND_DAMAGE")) @@ -1136,31 +1108,27 @@ std::string WeaponsPlatformEvent::CombatLogDescription(int viewing_empire_id) co return desc; } -bool WeaponsPlatformEvent::AreSubEventsEmpty(int viewing_empire_id) const { - return events.empty(); -} +bool WeaponsPlatformEvent::AreSubEventsEmpty(int viewing_empire_id) const +{ return events.empty(); } std::vector WeaponsPlatformEvent::SubEvents(int viewing_empire_id) const { std::vector all_events; - for (const std::map>::value_type& target : events) { - for (WeaponFireEvent::WeaponFireEventPtr event : target.second) { + for (const auto& target : events) + for (auto event : target.second) all_events.push_back(std::dynamic_pointer_cast(event)); - } - } return all_events; } -boost::optional WeaponsPlatformEvent::PrincipalFaction(int viewing_empire_id) const { - return attacker_owner_id; -} +boost::optional WeaponsPlatformEvent::PrincipalFaction(int viewing_empire_id) const +{ return attacker_owner_id; } -template +template void WeaponsPlatformEvent::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(CombatEvent); ar & BOOST_SERIALIZATION_NVP(bout) - & BOOST_SERIALIZATION_NVP(attacker_id) - & BOOST_SERIALIZATION_NVP(attacker_owner_id) - & BOOST_SERIALIZATION_NVP(events); + & BOOST_SERIALIZATION_NVP(attacker_id) + & BOOST_SERIALIZATION_NVP(attacker_owner_id) + & BOOST_SERIALIZATION_NVP(events); } BOOST_CLASS_VERSION(WeaponsPlatformEvent, 4) diff --git a/combat/CombatEvents.h b/combat/CombatEvents.h index 1ba3461dd0b..44de41d8ed8 100644 --- a/combat/CombatEvents.h +++ b/combat/CombatEvents.h @@ -23,21 +23,15 @@ /// Generated when a new bout begins in the battle struct FO_COMMON_API BoutBeginEvent : public CombatEvent { BoutBeginEvent(); - BoutBeginEvent(int bout); - - virtual ~BoutBeginEvent() - {} - std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; int bout; private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -47,18 +41,10 @@ struct FO_COMMON_API BoutEvent : public CombatEvent { typedef std::shared_ptr BoutEventPtr; BoutEvent(); - BoutEvent(int bout); - - virtual ~BoutEvent() - {} - void AddEvent(const CombatEventPtr& event); - std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; - std::vector SubEvents(int viewing_empire_id) const override; bool AreSubEventsEmpty(int viewing_empire_id) const override @@ -76,7 +62,7 @@ struct FO_COMMON_API BoutEvent : public CombatEvent { std::vector events; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -87,16 +73,9 @@ struct FO_COMMON_API SimultaneousEvents : public CombatEvent { typedef std::shared_ptr SimultaneousEventsPtr; SimultaneousEvents(); - - virtual ~SimultaneousEvents() - {} - void AddEvent(const CombatEventPtr& event); - std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; - std::vector SubEvents(int viewing_empire_id) const override; bool AreSubEventsEmpty(int viewing_empire_id) const override @@ -109,7 +88,7 @@ struct FO_COMMON_API SimultaneousEvents : public CombatEvent { std::vector events; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -128,27 +107,19 @@ typedef std::shared_ptr FighterLaunchesEventPtr; Note: Because it is initialized with the unfiltered stealth information it contains information not availble to all empires. */ struct FO_COMMON_API InitialStealthEvent : public CombatEvent { - - /// a map of attacker empire id -> defender empire id -> set of (attacker ids, visibility) - typedef std::map>>> StealthInvisbleMap; + typedef std::map> EmpireToObjectVisibilityMap; InitialStealthEvent(); - - InitialStealthEvent(const StealthInvisbleMap &); - - virtual ~InitialStealthEvent() - {} + InitialStealthEvent(const EmpireToObjectVisibilityMap& x); std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; private: - - StealthInvisbleMap target_empire_id_to_invisble_obj_id; + EmpireToObjectVisibilityMap empire_to_object_visibility;// filled by AutoresolveInfo::ReportInvisibleObjects friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -156,32 +127,24 @@ struct FO_COMMON_API InitialStealthEvent : public CombatEvent { At this time always decloaking.*/ struct FO_COMMON_API StealthChangeEvent : public CombatEvent { StealthChangeEvent(); - StealthChangeEvent(int bout); - virtual ~StealthChangeEvent() - {} - std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; - std::vector SubEvents(int viewing_empire_id) const override; - bool AreSubEventsEmpty(int viewing_empire_id) const override; - - void AddEvent(int attacker_id_, int target_id_, int attacker_empire_, int target_empire_, Visibility new_visibility_); + void AddEvent(int attacker_id_, int target_id_, int attacker_empire_, + int target_empire_, Visibility new_visibility_); struct StealthChangeEventDetail; typedef std::shared_ptr StealthChangeEventDetailPtr; typedef std::shared_ptr ConstStealthChangeEventDetailPtr; struct StealthChangeEventDetail : public CombatEvent { StealthChangeEventDetail(); - - StealthChangeEventDetail(int attacker_id_, int target_id_, int attacker_empire_, int target_empire_, Visibility new_visibility_); + StealthChangeEventDetail(int attacker_id_, int target_id_, int attacker_empire_, + int target_empire_, Visibility new_visibility_); std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; int attacker_id; @@ -191,21 +154,21 @@ struct FO_COMMON_API StealthChangeEvent : public CombatEvent { Visibility visibility; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; private: int bout; - std::map> events; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -/// An event that describes a single attack by one object or fighter against another object or fighter +/// An event that describes a single attack by one object or fighter against +/// another object or fighter struct FO_COMMON_API WeaponFireEvent : public CombatEvent { typedef std::shared_ptr WeaponFireEventPtr; typedef std::shared_ptr ConstWeaponFireEventPtr; @@ -224,19 +187,10 @@ struct FO_COMMON_API WeaponFireEvent : public CombatEvent { WeaponFireEvent(int bout, int round, int attacker_id, int target_id, const std::string &weapon_name, const std::tuple &power_shield_damage, int attacker_owner_id_, int target_owner_id_); - - virtual ~WeaponFireEvent() - {} - std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; - std::string CombatLogDetails(int viewing_empire_id) const override; - - bool AreDetailsEmpty(int viewing_empire_id) const override - { return false; } - + bool AreDetailsEmpty(int viewing_empire_id) const override { return false; } boost::optional PrincipalFaction(int viewing_empire_id) const override; int bout; @@ -252,7 +206,7 @@ struct FO_COMMON_API WeaponFireEvent : public CombatEvent { private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -260,16 +214,9 @@ struct FO_COMMON_API WeaponFireEvent : public CombatEvent { /// eg. a ship is destroyed or a planet loses all defence struct FO_COMMON_API IncapacitationEvent : public CombatEvent { IncapacitationEvent(); - IncapacitationEvent(int bout_, int object_id_, int object_owner_id_); - - virtual ~IncapacitationEvent() - {} - std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; - boost::optional PrincipalFaction(int viewing_empire_id) const override; int bout; @@ -278,7 +225,7 @@ struct FO_COMMON_API IncapacitationEvent : public CombatEvent { private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -286,16 +233,9 @@ struct FO_COMMON_API IncapacitationEvent : public CombatEvent { /** FightersAttackFightersEvent aggregates all the fighter on fighter combat for one bout.*/ struct FO_COMMON_API FightersAttackFightersEvent : public CombatEvent { FightersAttackFightersEvent(); - FightersAttackFightersEvent(int bout); - - virtual ~FightersAttackFightersEvent() - {} - std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; - void AddEvent(int attacker_empire_, int target_empire_); private: @@ -305,7 +245,7 @@ struct FO_COMMON_API FightersAttackFightersEvent : public CombatEvent { std::map, unsigned int> events; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -314,16 +254,9 @@ struct FO_COMMON_API FighterLaunchEvent : public CombatEvent { typedef std::shared_ptr FighterLaunchEventPtr; FighterLaunchEvent(); - FighterLaunchEvent(int bout_, int launched_from_id_, int fighter_owner_empire_id_, int number_launched_); - - virtual ~FighterLaunchEvent() - {} - std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; - boost::optional PrincipalFaction(int viewing_empire_id) const override; int bout; @@ -333,23 +266,16 @@ struct FO_COMMON_API FighterLaunchEvent : public CombatEvent { private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; /** FightersDestroyedEvent aggregates all the fighters destroyed during one combat bout.*/ struct FO_COMMON_API FightersDestroyedEvent : public CombatEvent { FightersDestroyedEvent(); - FightersDestroyedEvent(int bout); - - virtual ~FightersDestroyedEvent() - {} - std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; - void AddEvent(int target_empire_); private: @@ -359,7 +285,7 @@ struct FO_COMMON_API FightersDestroyedEvent : public CombatEvent { std::map events; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -370,23 +296,13 @@ struct FO_COMMON_API WeaponsPlatformEvent : public CombatEvent { typedef std::shared_ptr ConstWeaponsPlatformEventPtr; WeaponsPlatformEvent(); - WeaponsPlatformEvent(int bout, int attacker_id, int attacker_owner_id_); - - virtual ~WeaponsPlatformEvent() - {} - void AddEvent(int round, int target_id, int target_owner_id_, std::string const & weapon_name_, float power_, float shield_, float damage_); - std::string DebugString() const override; - std::string CombatLogDescription(int viewing_empire_id) const override; - std::vector SubEvents(int viewing_empire_id) const override; - bool AreSubEventsEmpty(int viewing_empire_id) const override; - boost::optional PrincipalFaction(int viewing_empire_id) const override; int bout; @@ -397,7 +313,7 @@ struct FO_COMMON_API WeaponsPlatformEvent : public CombatEvent { std::map> events; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/combat/CombatLogManager.cpp b/combat/CombatLogManager.cpp index b5b3da11ff4..26ac3a135ac 100644 --- a/combat/CombatLogManager.cpp +++ b/combat/CombatLogManager.cpp @@ -7,55 +7,50 @@ #include "../util/Logger.h" #include "CombatEvents.h" -#include namespace { static float MaxHealth(const UniverseObject& object) { - if ( object.ObjectType() == OBJ_SHIP ) { + if (object.ObjectType() == OBJ_SHIP) { return object.CurrentMeterValue(METER_MAX_STRUCTURE); + } else if ( object.ObjectType() == OBJ_PLANET ) { const Meter* defense = object.GetMeter(METER_MAX_DEFENSE); const Meter* shield = object.GetMeter(METER_MAX_SHIELD); const Meter* construction = object.UniverseObject::GetMeter(METER_TARGET_CONSTRUCTION); - float ret = 0.0; - if(defense) { + float ret = 0.0f; + if (defense) ret += defense->Current(); - } - if(shield) { + if (shield) ret += shield->Current(); - } - if(construction) { + if (construction) ret += construction->Current(); - } return ret; - } else { - return 0.0; } + + return 0.0f; } static float CurrentHealth(const UniverseObject& object) { - if ( object.ObjectType() == OBJ_SHIP ) { + if (object.ObjectType() == OBJ_SHIP) { return object.CurrentMeterValue(METER_STRUCTURE); - } else if ( object.ObjectType() == OBJ_PLANET ) { + + } else if (object.ObjectType() == OBJ_PLANET) { const Meter* defense = object.GetMeter(METER_DEFENSE); const Meter* shield = object.GetMeter(METER_SHIELD); const Meter* construction = object.UniverseObject::GetMeter(METER_CONSTRUCTION); - float ret = 0.0; - if(defense) { + float ret = 0.0f; + if (defense) ret += defense->Current(); - } - if(shield) { + if (shield) ret += shield->Current(); - } - if(construction) { + if (construction) ret += construction->Current(); - } return ret; - } else { - return 0.0; } + + return 0.0f; } static void FillState(CombatParticipantState& state, const UniverseObject& object) { @@ -88,22 +83,20 @@ CombatLog::CombatLog(const CombatInfo& combat_info) : { // compile all remaining and destroyed objects' ids object_ids = combat_info.destroyed_object_ids; - for (ObjectMap::const_iterator<> it = combat_info.objects.const_begin(); - it != combat_info.objects.const_end(); ++it) - { - object_ids.insert(it->ID()); - participant_states[it->ID()] = CombatParticipantState(**it); + for (const auto& obj : combat_info.objects.all()) { + object_ids.insert(obj->ID()); + participant_states[obj->ID()] = CombatParticipantState(*obj); } } -template +template void CombatParticipantState::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(current_health) & BOOST_SERIALIZATION_NVP(max_health); } -template +template void CombatLog::serialize(Archive& ar, const unsigned int version) { // CombatEvents are serialized only through @@ -118,18 +111,21 @@ void CombatLog::serialize(Archive& ar, const unsigned int version) ar.template register_type(); ar & BOOST_SERIALIZATION_NVP(turn) - & BOOST_SERIALIZATION_NVP(system_id) - & BOOST_SERIALIZATION_NVP(empire_ids) - & BOOST_SERIALIZATION_NVP(object_ids) - & BOOST_SERIALIZATION_NVP(damaged_object_ids) - & BOOST_SERIALIZATION_NVP(destroyed_object_ids) - & BOOST_SERIALIZATION_NVP(combat_events); - - // Store state of fleet at this battle. - // Used to show summaries of past battles. - if (version >= 1) { - ar & BOOST_SERIALIZATION_NVP(participant_states); + & BOOST_SERIALIZATION_NVP(system_id) + & BOOST_SERIALIZATION_NVP(empire_ids) + & BOOST_SERIALIZATION_NVP(object_ids) + & BOOST_SERIALIZATION_NVP(damaged_object_ids) + & BOOST_SERIALIZATION_NVP(destroyed_object_ids); + + if (combat_events.size() > 1) + TraceLogger() << "CombatLog::serialize turn " << turn << " combat at " << system_id << " combat events size: " << combat_events.size(); + try { + ar & BOOST_SERIALIZATION_NVP(combat_events); + } catch (const std::exception& e) { + ErrorLogger() << "combat events serializing failed!: caught exception: " << e.what(); } + + ar & BOOST_SERIALIZATION_NVP(participant_states); } template @@ -147,28 +143,27 @@ void CombatLog::serialize(freeorion_xml_oarchive& ar, co // CombatLogManagerImpl //////////////////////////////////////////////// -class CombatLogManager::Impl -{ +class CombatLogManager::Impl { public: Impl(); /** \name Accessors */ //@{ /** Return the requested combat log or boost::none.*/ - boost::optional GetLog(int log_id) const; + boost::optional GetLog(int log_id) const; /** Return the ids of all incomplete logs or none.*/ boost::optional> IncompleteLogIDs() const; //@} /** \name Mutators */ //@{ - int AddLog(const CombatLog& log); // adds log, returns unique log id + int AddLog(const CombatLog& log); // adds log, returns unique log id /** Replace incomplete log with \p id with \p log. */ - void CompleteLog(int id, const CombatLog& log); - void Clear(); + void CompleteLog(int id, const CombatLog& log); + void Clear(); /** Serialize log headers so that the receiving LogManager can then request complete logs in the background.*/ - template + template void SerializeIncompleteLogs(Archive& ar, const unsigned int version); //@} @@ -176,23 +171,21 @@ class CombatLogManager::Impl void SetLog(int log_id, const CombatLog& log); friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); private: - boost::unordered_map m_logs; - /** Set of logs ids that do not have bodies and need to be fetched from the server. */ - std::set m_incomplete_logs; - int m_latest_log_id; + std::unordered_map m_logs; + std::set m_incomplete_logs; // Set of logs ids that do not have bodies and need to be fetched from the server + int m_latest_log_id; }; CombatLogManager::Impl::Impl() : - m_logs(), m_latest_log_id(-1) {} boost::optional CombatLogManager::Impl::GetLog(int log_id) const { - boost::unordered_map::const_iterator it = m_logs.find(log_id); + auto it = m_logs.find(log_id); if (it != m_logs.end()) return it->second; return boost::none; @@ -205,7 +198,7 @@ int CombatLogManager::Impl::AddLog(const CombatLog& log) { } void CombatLogManager::Impl::CompleteLog(int id, const CombatLog& log) { - std::set::iterator incomplete_it = m_incomplete_logs.find(id); + auto incomplete_it = m_incomplete_logs.find(id); if (incomplete_it == m_incomplete_logs.end()) { ErrorLogger() << "CombatLogManager::Impl::CompleteLog id = " << id << " is not an incomplete log, so log is being discarded."; return; @@ -221,25 +214,24 @@ void CombatLogManager::Impl::CompleteLog(int id, const CombatLog& log) { } } -void CombatLogManager::Impl::Clear() -{ m_logs.clear(); } +void CombatLogManager::Impl::Clear() { + m_logs.clear(); + m_incomplete_logs.clear(); + m_latest_log_id = -1; +} void CombatLogManager::Impl::GetLogsToSerialize( std::map& logs, int encoding_empire) const { // TODO: filter logs by who should have access to them - for (boost::unordered_map::const_iterator it = m_logs.begin(); - it != m_logs.end(); ++it) - { - logs.insert(std::make_pair(it->first, it->second)); - } + for (auto it = m_logs.begin(); it != m_logs.end(); ++it) + logs.insert({it->first, it->second}); } void CombatLogManager::Impl::SetLog(int log_id, const CombatLog& log) { m_logs[log_id] = log; } -boost::optional> CombatLogManager::Impl::IncompleteLogIDs() const -{ +boost::optional> CombatLogManager::Impl::IncompleteLogIDs() const { if (m_incomplete_logs.empty()) return boost::none; @@ -247,15 +239,13 @@ boost::optional> CombatLogManager::Impl::IncompleteLogIDs() con // send one log it is the most recent combat log, which is the one most // likely of interest to the player. std::vector ids; - for (std::set::reverse_iterator rit = m_incomplete_logs.rbegin(); - rit != m_incomplete_logs.rend(); ++rit) - { + for (auto rit = m_incomplete_logs.rbegin(); rit != m_incomplete_logs.rend(); ++rit) ids.push_back(*rit); - } + return ids; } -template +template void CombatLogManager::Impl::SerializeIncompleteLogs(Archive& ar, const unsigned int version) { int old_latest_log_id = m_latest_log_id; @@ -268,7 +258,7 @@ void CombatLogManager::Impl::SerializeIncompleteLogs(Archive& ar, const unsigned m_incomplete_logs.insert(old_latest_log_id); } -template +template void CombatLogManager::Impl::serialize(Archive& ar, const unsigned int version) { std::map logs; @@ -315,7 +305,7 @@ CombatLogManager& CombatLogManager::GetCombatLogManager() { return manager; } -template +template void CombatLogManager::SerializeIncompleteLogs(Archive& ar, const unsigned int version) { m_impl->SerializeIncompleteLogs(ar, version); } @@ -331,7 +321,7 @@ void CombatLogManager::SerializeIncompleteLogs(freeorion template void CombatLogManager::SerializeIncompleteLogs(freeorion_xml_iarchive& ar, const unsigned int version); -template +template void CombatLogManager::serialize(Archive& ar, const unsigned int version) { m_impl->serialize(ar, version); } diff --git a/combat/CombatLogManager.h b/combat/CombatLogManager.h index 5f266d019ea..88a4ca1cd80 100644 --- a/combat/CombatLogManager.h +++ b/combat/CombatLogManager.h @@ -7,6 +7,7 @@ #include "../util/Serialize.h" #include +#include #include @@ -21,7 +22,7 @@ struct FO_COMMON_API CombatParticipantState { CombatParticipantState(const UniverseObject& object); private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -39,7 +40,7 @@ struct FO_COMMON_API CombatLog { std::map participant_states; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -57,15 +58,15 @@ class FO_COMMON_API CombatLogManager { //@} /** \name Mutators */ //@{ - int AddNewLog(const CombatLog& log); // adds log, returns unique log id + int AddNewLog(const CombatLog& log); // adds log, returns unique log id /** Replace incomplete log with \p id with \p log. An incomplete log is a partially downloaded log where only the log id is known.*/ - void CompleteLog(int id, const CombatLog& log); - void Clear(); + void CompleteLog(int id, const CombatLog& log); + void Clear(); /** Serialize log headers so that the receiving LogManager can then request complete logs in the background.*/ - template + template void SerializeIncompleteLogs(Archive& ar, const unsigned int version); //@} @@ -80,7 +81,7 @@ class FO_COMMON_API CombatLogManager { std::unique_ptr const m_impl; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/combat/CombatSystem.cpp b/combat/CombatSystem.cpp index 84b0f379135..2ab4b2c1a76 100644 --- a/combat/CombatSystem.cpp +++ b/combat/CombatSystem.cpp @@ -2,7 +2,7 @@ #include "CombatEvents.h" #include "../universe/Universe.h" -#include "../util/MultiplayerCommon.h" +#include "../util/GameRules.h" #include "../util/OptionsDB.h" #include "../universe/Predicates.h" #include "../universe/Planet.h" @@ -10,8 +10,12 @@ #include "../universe/Ship.h" #include "../universe/Fighter.h" #include "../universe/ShipDesign.h" +#include "../universe/ShipPart.h" #include "../universe/System.h" +#include "../universe/Species.h" #include "../universe/Enums.h" +#include "../universe/Conditions.h" +#include "../universe/ValueRefs.h" #include "../Empire/Empire.h" #include "../Empire/EmpireManager.h" @@ -21,6 +25,8 @@ #include "../network/Message.h" +#include + #include #include @@ -31,108 +37,60 @@ namespace { //////////////////////////////////////////////// // CombatInfo //////////////////////////////////////////////// -CombatInfo::CombatInfo() : - turn(INVALID_GAME_TURN), - system_id(INVALID_OBJECT_ID) -{} - CombatInfo::CombatInfo(int system_id_, int turn_) : turn(turn_), system_id(system_id_) { - std::shared_ptr system = ::GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "CombatInfo constructed with invalid system id: " << system_id; return; } - // add system to full / complete objects in combat - NOTE: changed from copy of system - objects.Insert(system); - + // add system to objects in combat + objects.insert(system); // find ships and their owners in system - std::vector> ships = Objects().FindObjects(system->ShipIDs()); - - for (std::shared_ptr ship : ships) { - // add owner to empires that have assets in this battle + auto ships = Objects().find(system->ShipIDs()); + for (auto& ship : ships) { + // add owner of ships in system to empires that have assets in this battle empire_ids.insert(ship->Owner()); - - objects.Insert(ship); + // add ships to objects in combat + objects.insert(ship); } // find planets and their owners in system - std::vector> planets = - Objects().FindObjects(system->PlanetIDs()); - - for (std::shared_ptr planet : planets) { - // if planet is populated, add owner to empires that have assets in this battle - if (!planet->Unowned() || planet->CurrentMeterValue(METER_POPULATION) > 0.0) + auto planets = Objects().find(system->PlanetIDs()); + for (auto& planet : planets) { + // if planet is populated or has an owner, add owner to empires that have assets in this battle + if (!planet->Unowned() || planet->InitialMeterValue(METER_POPULATION) > 0.0f) empire_ids.insert(planet->Owner()); - - objects.Insert(planet); + // add planets to objects in combat + objects.insert(planet); } - // TODO: should buildings be considered separately? - - // now that all participants in the battle have been found, loop through - // objects again to assemble each participant empire's latest - // known information about all objects in this battle - InitializeObjectVisibility(); - // ships - for (std::shared_ptr ship : ships) { - int ship_id = ship->ID(); - std::shared_ptr fleet = GetFleet(ship->FleetID()); - if (!fleet) { - ErrorLogger() << "CombatInfo::CombatInfo couldn't get fleet with id " - << ship->FleetID() << " in system " << system->Name() << " (" << system_id << ")"; - continue; - } - - std::string ship_known = "At " + system->Name() + " Ship " + std::to_string(ship_id) + " owned by empire " + - std::to_string(ship->Owner()) + " visible to empires: "; - for (int empire_id : empire_ids) { - if (empire_id == ALL_EMPIRES) - continue; - if (GetUniverse().GetObjectVisibilityByEmpire(ship_id, empire_id) > VIS_BASIC_VISIBILITY || - (fleet->Aggressive() && - (empire_id == ALL_EMPIRES || - fleet->Unowned() || - Empires().GetDiplomaticStatus(empire_id, fleet->Owner()) == DIPLO_WAR))) - { - empire_known_objects[empire_id].Insert(GetEmpireKnownShip(ship->ID(), empire_id));} - ship_known += std::to_string(empire_id) + ", "; - } - DebugLogger(combat) << ship_known; - } - - // planets - for (std::shared_ptr planet : planets) { - int planet_id = planet->ID(); - std::string planet_known = "At " + system->Name() + " Planet " + std::to_string(planet_id) + " owned by empire " + - std::to_string(planet->Owner()) + " visible to empires: "; - - for (int empire_id : empire_ids) { - if (empire_id == ALL_EMPIRES) - continue; - if (GetUniverse().GetObjectVisibilityByEmpire(planet_id, empire_id) > VIS_BASIC_VISIBILITY) { - empire_known_objects[empire_id].Insert(GetEmpireKnownPlanet(planet->ID(), empire_id)); - planet_known += std::to_string(empire_id) + ", "; - } - } - DebugLogger(combat) << planet_known; - } - // after battle is simulated, any changes to latest known or actual objects // will be copied back to the main Universe's ObjectMap and the Universe's - // empire latest known objects ObjectMap - NOTE: Using the real thing now + // empire latest known objects ObjectMap } std::shared_ptr CombatInfo::GetSystem() const -{ return this->objects.Object(this->system_id); } +{ return this->objects.get(this->system_id); } std::shared_ptr CombatInfo::GetSystem() -{ return this->objects.Object(this->system_id); } +{ return this->objects.get(this->system_id); } + +float CombatInfo::GetMonsterDetection() const { + float monster_detection = 0.0; + for (const auto& obj : objects.all()) + if (obj->Unowned()) + monster_detection = std::max(monster_detection, obj->InitialMeterValue(METER_DETECTION)); + for (const auto& obj : objects.all()) + if (obj->Unowned()) + monster_detection = std::max(monster_detection, obj->InitialMeterValue(METER_DETECTION)); + return monster_detection; +} void CombatInfo::GetEmpireIdsToSerialize(std::set& filtered_empire_ids, int encoding_empire) const { if (encoding_empire == ALL_EMPIRES) { @@ -147,7 +105,7 @@ void CombatInfo::GetObjectsToSerialize(ObjectMap& filtered_objects, int encoding if (&filtered_objects == &this->objects) return; - filtered_objects.Clear(); + filtered_objects.clear(); if (encoding_empire == ALL_EMPIRES) { filtered_objects = this->objects; @@ -158,29 +116,6 @@ void CombatInfo::GetObjectsToSerialize(ObjectMap& filtered_objects, int encoding filtered_objects = this->objects; // for now, include all objects in battle / system } -void CombatInfo::GetEmpireKnownObjectsToSerialize(std::map& filtered_empire_known_objects, - int encoding_empire) const -{ - if (&filtered_empire_known_objects == &this->empire_known_objects) - return; - - for (std::map::value_type& entry : filtered_empire_known_objects) - { entry.second.Clear(); } - filtered_empire_known_objects.clear(); - - if (encoding_empire == ALL_EMPIRES) { - filtered_empire_known_objects = this->empire_known_objects; - return; - } - - // include only latest known objects for the encoding empire - std::map::const_iterator it = this->empire_known_objects.find(encoding_empire); - if (it != this->empire_known_objects.end()) { - const ObjectMap& map = it->second; - filtered_empire_known_objects[encoding_empire].Copy(map, ALL_EMPIRES); - } -} - void CombatInfo::GetDamagedObjectsToSerialize(std::set& filtered_damaged_objects, int encoding_empire) const { @@ -218,88 +153,263 @@ void CombatInfo::GetDestroyedObjectKnowersToSerialize(std::map& filtered_combat_events, int encoding_empire) const -{ - filtered_combat_events = this->combat_events; -} +{ filtered_combat_events = this->combat_events; } void CombatInfo::GetEmpireObjectVisibilityToSerialize(Universe::EmpireObjectVisibilityMap& filtered_empire_object_visibility, int encoding_empire) const -{ - filtered_empire_object_visibility = this->empire_object_visibility; -} +{ filtered_empire_object_visibility = this->empire_object_visibility; } -/** Requires system_id, empire_ids are initialized*/ -void CombatInfo::InitializeObjectVisibility() -{ - // system and empire visibility of all objects in it - std::shared_ptr system = ::GetSystem(system_id); - std::set< int > local_object_ids = system->ContainedObjectIDs(); - for (int empire_id : empire_ids) { - if (empire_id == ALL_EMPIRES) - continue; - empire_known_objects[empire_id].Insert(GetEmpireKnownSystem(system->ID(), empire_id)); - empire_object_visibility[empire_id][system->ID()] = GetUniverse().GetObjectVisibilityByEmpire(empire_id, system->ID()); - for (int object_id : local_object_ids) { - Visibility obj_vis = GetUniverse().GetObjectVisibilityByEmpire(empire_id, object_id); - if (obj_vis > VIS_NO_VISIBILITY) // to ensure an empire doesn't wrongly get info that an object was present - empire_object_visibility[empire_id][object_id] = obj_vis; +namespace { + // collect detection strengths of all empires (and neutrals) in \a combat_info + std::map GetEmpiresDetectionStrengths(const CombatInfo& combat_info) { + std::map retval; + for (auto empire_id : combat_info.empire_ids) { + const auto empire = GetEmpire(empire_id); + if (!empire) + continue; + const Meter* meter = empire->GetMeter("METER_DETECTION_STRENGTH"); + float strength = meter ? meter->Current() : 0.0f; + retval[empire_id] = strength; } + retval[ALL_EMPIRES] = combat_info.GetMonsterDetection(); + return retval; } } -void CombatInfo::ForceAtLeastBasicVisibility(int attacker_id, int target_id) -{ - std::shared_ptr attacker = objects.Object(attacker_id); - std::shared_ptr target = objects.Object(target_id); - // Also ensure that attacker (and their fleet if attacker was a ship) are - // revealed with at least BASIC_VISIBILITY to the target empire - Visibility old_visibility = empire_object_visibility[target->Owner()][attacker->ID()]; - Visibility new_visibility = std::max(old_visibility, VIS_BASIC_VISIBILITY); - empire_object_visibility[target->Owner()][attacker->ID()] = new_visibility; - if (attacker->ObjectType() == OBJ_SHIP && attacker->ContainerObjectID() != INVALID_OBJECT_ID) { - empire_object_visibility[target->Owner()][attacker->ContainerObjectID()] = - std::max(empire_object_visibility[target->Owner()][attacker->ContainerObjectID()], VIS_BASIC_VISIBILITY); +void CombatInfo::InitializeObjectVisibility() { + // initialize combat-local visibility of objects by empires and combat-local + // empire ObjectMaps with object state info that empires know at start of battle + auto det_strengths = GetEmpiresDetectionStrengths(*this); + + for (int empire_id : empire_ids) { + DebugLogger() << "Initializing CombatInfo object visibility and known objects for empire: " << empire_id; + + float empire_detection = det_strengths[empire_id]; + + for (auto obj : objects.all()) { + + if (obj->ObjectType() == OBJ_SYSTEM) { + // systems always visible to empires with objects in them + empire_object_visibility[empire_id][obj->ID()] = VIS_PARTIAL_VISIBILITY; + DebugLogger() << "System " << obj->Name() << " always visible"; + + } else if (obj->ObjectType() == OBJ_PLANET) { + // planets always at least basically visible to empires with objects in them + Visibility vis = VIS_BASIC_VISIBILITY; + if (empire_id != ALL_EMPIRES) { + Visibility vis_univ = GetUniverse().GetObjectVisibilityByEmpire(obj->ID(), empire_id); + if (vis_univ > vis) { + vis = vis_univ; + DebugLogger() << "Planet " << obj->Name() << " visible from universe state"; + } + } + if (vis < VIS_PARTIAL_VISIBILITY && empire_detection >= obj->CurrentMeterValue(METER_STEALTH)) { + vis = VIS_PARTIAL_VISIBILITY; + DebugLogger() << "Planet " << obj->Name() << " visible empire stealth check: " << empire_detection << " >= " << obj->CurrentMeterValue(METER_STEALTH); + } + if (vis == VIS_BASIC_VISIBILITY) { + DebugLogger() << "Planet " << obj->Name() << " has just basic visibility by default"; + } + + empire_object_visibility[empire_id][obj->ID()] = vis; + + } else if (obj->ObjectType() == OBJ_SHIP) { + // ships only visible if detected or they attack later in combat + Visibility vis = VIS_NO_VISIBILITY; + if (empire_id != ALL_EMPIRES) { + Visibility vis_univ = GetUniverse().GetObjectVisibilityByEmpire(obj->ID(), empire_id); + if (vis_univ > vis) { + vis = vis_univ; + DebugLogger() << "Ship " << obj->Name() << " visible from universe state"; + } + } + if (vis < VIS_PARTIAL_VISIBILITY && empire_detection >= obj->CurrentMeterValue(METER_STEALTH)) { + vis = VIS_PARTIAL_VISIBILITY; + DebugLogger() << "Ship " << obj->Name() << " visible empire stealth check: " << empire_detection << " >= " << obj->CurrentMeterValue(METER_STEALTH); + } + if (vis < VIS_PARTIAL_VISIBILITY && GetGameRules().Get("RULE_AGGRESSIVE_SHIPS_COMBAT_VISIBLE")) { + if (auto ship = std::dynamic_pointer_cast(obj)) { + if (auto fleet = Objects().get(ship->FleetID())) { + if (fleet->Aggressive()) { + vis = VIS_PARTIAL_VISIBILITY; + DebugLogger() << "Ship " << obj->Name() << " visible from aggressive fleet"; + } + } + } + } + + if (vis > VIS_NO_VISIBILITY) { + empire_object_visibility[empire_id][obj->ID()] = vis; + } else { + DebugLogger() << "Ship " << obj->Name() << " initially hidden"; + } + } + } } } + //////////////////////////////////////////////// // AutoResolveCombat //////////////////////////////////////////////// namespace { + // if source is owned by ALL_EMPIRES, match objects owned by an empire + // if source is owned by an empire, match unowned objects and objects owned by enemies of source's owner empire + Condition::Condition* VisibleEnemyOfOwnerCondition() { + return new Condition::Or( + // unowned candidate object case + std::make_unique( + std::make_unique(AFFIL_NONE), // unowned candidate object + + std::make_unique( // when source object is owned (ie. not the same owner as the candidate object) + std::make_unique>( + ValueRef::SOURCE_REFERENCE, "Owner"), + Condition::NOT_EQUAL, + std::make_unique>( + ValueRef::CONDITION_LOCAL_CANDIDATE_REFERENCE, "Owner")), + + std::make_unique( // when source object's owner empire can detect the candidate object + std::make_unique>( // source's owner empire id + ValueRef::SOURCE_REFERENCE, "Owner"))), + + // owned candidate object case + std::make_unique( + std::make_unique(AFFIL_ANY), // candidate is owned by an empire + + std::make_unique( // candidate is owned by enemy of source's owner + std::make_unique>( + ValueRef::SOURCE_REFERENCE, "Owner"), AFFIL_ENEMY), + + std::make_unique( // when source empire can detect the candidate object + std::make_unique>( // source's owner empire id + ValueRef::SOURCE_REFERENCE, "Owner")) + )) + ; + } + + const std::unique_ptr is_enemy_ship_or_fighter = + std::make_unique( + std::make_unique( + std::make_unique( + std::make_unique(OBJ_SHIP), + std::make_unique( + std::make_unique( + METER_STRUCTURE, + nullptr, + std::make_unique>(0.0)))), + std::make_unique(OBJ_FIGHTER)), + std::unique_ptr{VisibleEnemyOfOwnerCondition()}); + + const std::unique_ptr is_enemy_ship = + std::make_unique( + std::make_unique(OBJ_SHIP), + + std::make_unique( + std::make_unique( + METER_STRUCTURE, + nullptr, + std::make_unique>(0.0))), + + std::unique_ptr{VisibleEnemyOfOwnerCondition()}); + + const std::unique_ptr is_enemy_ship_fighter_or_armed_planet = + std::make_unique( + std::unique_ptr{VisibleEnemyOfOwnerCondition()}, // enemies + std::make_unique( + std::make_unique( + std::make_unique( + std::make_unique(OBJ_SHIP), + std::make_unique( + std::make_unique( + METER_STRUCTURE, + nullptr, + std::make_unique>(0.0)))), + std::make_unique(OBJ_FIGHTER)), + + std::make_unique( + std::make_unique(OBJ_PLANET), + std::make_unique( + std::make_unique( + std::make_unique( + METER_DEFENSE, + nullptr, + std::make_unique>(0.0))), + std::make_unique( + std::make_unique( + METER_SHIELD, + nullptr, + std::make_unique>(0.0))), + std::make_unique( + std::make_unique( + METER_CONSTRUCTION, + nullptr, + std::make_unique>(0.0))))))); + + const std::unique_ptr if_source_is_planet_then_ships_else_all = + std::make_unique( + std::make_unique( // if source is a planet, match ships + std::make_unique( + std::make_unique>(1), // minimum objects matching subcondition + nullptr, + std::make_unique( // subcondition: source is a planet + std::make_unique(), + std::make_unique(OBJ_PLANET) + ) + ), + std::make_unique(OBJ_SHIP) + ), + + std::make_unique( // if source is not a planet, match anything + nullptr, + std::make_unique>(0), // maximum objects matching subcondition + std::make_unique( // subcondition: source is a planet + std::make_unique(), + std::make_unique(OBJ_PLANET) + ) + ) + ); + struct PartAttackInfo { - PartAttackInfo(ShipPartClass part_class_, const std::string& part_name_, float part_attack_) : + PartAttackInfo(ShipPartClass part_class_, const std::string& part_name_, + float part_attack_, + const ::Condition::Condition* combat_targets_ = nullptr) : part_class(part_class_), - part_type_name(part_name_), + ship_part_name(part_name_), part_attack(part_attack_), - fighters_launched(0), - fighter_damage(0.0f) + combat_targets(combat_targets_) {} PartAttackInfo(ShipPartClass part_class_, const std::string& part_name_, - int fighters_launched_, float fighter_damage_) : + int fighters_launched_, float fighter_damage_, + const std::string& fighter_type_name_, + const ::Condition::Condition* combat_targets_ = nullptr) : part_class(part_class_), - part_type_name(part_name_), - part_attack(0.0f), + ship_part_name(part_name_), + combat_targets(combat_targets_), fighters_launched(fighters_launched_), - fighter_damage(fighter_damage_) + fighter_damage(fighter_damage_), + fighter_type_name(fighter_type_name_) {} - ShipPartClass part_class; - std::string part_type_name; - float part_attack; // for direct damage parts - int fighters_launched; // for fighter bays, input value should be limited by ship available fighters to launch - float fighter_damage; // for fighter bays, input value should be determined by ship fighter weapon setup + ShipPartClass part_class = INVALID_SHIP_PART_CLASS; + std::string ship_part_name; + float part_attack = 0.0f; // for direct damage parts + const ::Condition::Condition* combat_targets = nullptr; + int fighters_launched = 0; // for fighter bays, input value should be limited by ship available fighters to launch + float fighter_damage = 0.0f; // for fighter bays, input value should be determined by ship fighter weapon setup + std::string fighter_type_name; }; - void AttackShipShip(std::shared_ptr attacker, const PartAttackInfo& weapon, std::shared_ptr target, - CombatInfo& combat_info, int bout, int round, + void AttackShipShip(std::shared_ptr attacker, const PartAttackInfo& weapon, + std::shared_ptr target, CombatInfo& combat_info, + int bout, int round, WeaponsPlatformEvent::WeaponsPlatformEventPtr& combat_event) { if (!attacker || !target) return; float power = weapon.part_attack; - std::set& damaged_object_ids = combat_info.damaged_object_ids; + auto& damaged_object_ids = combat_info.damaged_object_ids; Meter* target_structure = target->UniverseObject::GetMeter(METER_STRUCTURE); if (!target_structure) { @@ -311,7 +421,7 @@ namespace { float shield = (target_shield ? target_shield->Current() : 0.0f); DebugLogger() << "AttackShipShip: attacker: " << attacker->Name() - << "weapon: " << weapon.part_type_name << " power: " << power + << " weapon: " << weapon.ship_part_name << " power: " << power << " target: " << target->Name() << " shield: " << target_shield->Current() << " structure: " << target_structure->Current(); @@ -320,17 +430,21 @@ namespace { if (damage > 0.0f) { target_structure->AddToCurrent(-damage); damaged_object_ids.insert(target->ID()); - DebugLogger(combat) << "COMBAT: Ship " << attacker->Name() << " (" << attacker->ID() << ") does " << damage << " damage to Ship " << target->Name() << " (" << target->ID() << ")"; + DebugLogger(combat) << "COMBAT: Ship " << attacker->Name() << " (" + << attacker->ID() << ") does " << damage << " damage to Ship " + << target->Name() << " (" << target->ID() << ")"; } - combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.part_type_name, power, shield, damage); + combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.ship_part_name, + power, shield, damage); attacker->SetLastTurnActiveInCombat(CurrentTurn()); target->SetLastTurnActiveInCombat(CurrentTurn()); } - void AttackShipPlanet(std::shared_ptr attacker, const PartAttackInfo& weapon, std::shared_ptr target, - CombatInfo& combat_info, int bout, int round, + void AttackShipPlanet(std::shared_ptr attacker, const PartAttackInfo& weapon, + std::shared_ptr target, CombatInfo& combat_info, + int bout, int round, WeaponsPlatformEvent::WeaponsPlatformEventPtr& combat_event) { if (!attacker || !target) return; @@ -377,7 +491,8 @@ namespace { damaged_object_ids.insert(target->ID()); if (defense_damage >= target_defense->Current()) - construction_damage = std::min(target_construction->Current(), power - shield_damage - defense_damage); + construction_damage = std::min(target_construction->Current(), + power - shield_damage - defense_damage); if (shield_damage >= 0) { target_shield->AddToCurrent(-shield_damage); @@ -400,29 +515,35 @@ namespace { //TODO report the planet damage details more clearly float total_damage = shield_damage + defense_damage + construction_damage; - combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.part_type_name, power, 0.0f, total_damage); + combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.ship_part_name, + power, 0.0f, total_damage); attacker->SetLastTurnActiveInCombat(CurrentTurn()); target->SetLastTurnAttackedByShip(CurrentTurn()); } - void AttackShipFighter(std::shared_ptr attacker, const PartAttackInfo& weapon, std::shared_ptr target, - CombatInfo& combat_info, int bout, int round, - AttacksEventPtr &attacks_event, + void AttackShipFighter(std::shared_ptr attacker, const PartAttackInfo& weapon, + std::shared_ptr target, CombatInfo& combat_info, + int bout, int round, WeaponsPlatformEvent::WeaponsPlatformEventPtr& combat_event) { float power = weapon.part_attack; if (attacker->TotalWeaponsDamage(0.0f, false) > 0.0f) { // any damage is enough to kill any fighter + DebugLogger(combat) << "COMBAT: " << attacker->Name() << " of empire " << attacker->Owner() + << " (" << attacker->ID() << ") does " << power + << " damage to " << target->Name() << " (" << target->ID() << ")"; target->SetDestroyed(); } - combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.part_type_name, power, 0.0f, 1.0f); + combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.ship_part_name, + power, 0.0f, 1.0f); attacker->SetLastTurnActiveInCombat(CurrentTurn()); } - void AttackPlanetShip(std::shared_ptr attacker, const PartAttackInfo& weapon, std::shared_ptr target, - CombatInfo& combat_info, int bout, int round, + void AttackPlanetShip(std::shared_ptr attacker, const PartAttackInfo& weapon, + std::shared_ptr target, CombatInfo& combat_info, + int bout, int round, WeaponsPlatformEvent::WeaponsPlatformEventPtr& combat_event) { if (!attacker || !target) return; @@ -457,14 +578,93 @@ namespace { << target->ID() << ")"; } - combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.part_type_name, power, shield, damage); + combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.ship_part_name, + power, shield, damage); target->SetLastTurnActiveInCombat(CurrentTurn()); } - void AttackPlanetFighter(std::shared_ptr attacker, const PartAttackInfo& weapon, std::shared_ptr target, - CombatInfo& combat_info, int bout, int round, - AttacksEventPtr &attacks_event, + void AttackPlanetPlanet(std::shared_ptr attacker, const PartAttackInfo& weapon, + std::shared_ptr target, CombatInfo& combat_info, + int bout, int round, + WeaponsPlatformEvent::WeaponsPlatformEventPtr& combat_event) + { + if (!attacker || !target) return; + + float power = 0.0f; + const Meter* attacker_damage = attacker->UniverseObject::GetMeter(METER_DEFENSE); + if (attacker_damage) + power = attacker_damage->Current(); // planet "Defense" meter is actually its attack power + + std::set& damaged_object_ids = combat_info.damaged_object_ids; + + Meter* target_shield = target->GetMeter(METER_SHIELD); + Meter* target_defense = target->UniverseObject::GetMeter(METER_DEFENSE); + Meter* target_construction = target->UniverseObject::GetMeter(METER_CONSTRUCTION); + if (!target_shield) { + ErrorLogger() << "couldn't get target shield meter"; + return; + } + if (!target_defense) { + ErrorLogger() << "couldn't get target defense meter"; + return; + } + if (!target_construction) { + ErrorLogger() << "couldn't get target construction meter"; + return; + } + + DebugLogger(combat) << "AttackPlanetPlanet: attacker: " << attacker->Name() << " power: " << power + << "\ntarget: " << target->Name() << " shield: " << target_shield->Current() + << " defense: " << target_defense->Current() << " infra: " << target_construction->Current(); + + // damage shields, limited by shield current value and damage amount. + // remaining damage, if any, above shield current value goes to defense. + // remaining damage, if any, above defense current value goes to construction + float shield_damage = std::min(target_shield->Current(), power); + float defense_damage = 0.0f; + float construction_damage = 0.0f; + if (shield_damage >= target_shield->Current()) + defense_damage = std::min(target_defense->Current(), power - shield_damage); + + if (power > 0) + damaged_object_ids.insert(target->ID()); + + if (defense_damage >= target_defense->Current()) + construction_damage = std::min(target_construction->Current(), + power - shield_damage - defense_damage); + + if (shield_damage >= 0) { + target_shield->AddToCurrent(-shield_damage); + DebugLogger(combat) << "COMBAT: Planet " << attacker->Name() << " (" << attacker->ID() << ") does " + << shield_damage << " shield damage to Planet " << target->Name() << " (" + << target->ID() << ")"; + } + if (defense_damage >= 0) { + target_defense->AddToCurrent(-defense_damage); + DebugLogger(combat) << "COMBAT: Planet " << attacker->Name() << " (" << attacker->ID() << ") does " + << defense_damage << " defense damage to Planet " << target->Name() << " (" + << target->ID() << ")"; + } + if (construction_damage >= 0) { + target_construction->AddToCurrent(-construction_damage); + DebugLogger(combat) << "COMBAT: Planet " << attacker->Name() << " (" << attacker->ID() << ") does " + << construction_damage << " instrastructure damage to Planet " << target->Name() + << " (" << target->ID() << ")"; + } + + //TODO report the planet damage details more clearly + float total_damage = shield_damage + defense_damage + construction_damage; + combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.ship_part_name, + power, 0.0f, total_damage); + + //attacker->SetLastTurnActiveInCombat(CurrentTurn()); + //target->SetLastTurnAttackedByShip(CurrentTurn()); + } + + void AttackPlanetFighter(std::shared_ptr attacker, const PartAttackInfo& weapon, + std::shared_ptr target, CombatInfo& combat_info, + int bout, int round, WeaponsPlatformEvent::WeaponsPlatformEventPtr& combat_event) { if (!attacker || !target) return; @@ -476,15 +676,19 @@ namespace { if (power > 0.0f) { // any damage is enough to destroy any fighter + DebugLogger(combat) << "COMBAT: " << attacker->Name() << " of empire " << attacker->Owner() + << " (" << attacker->ID() << ") does " << power + << " damage to " << target->Name() << " (" << target->ID() << ")"; target->SetDestroyed(); } - combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.part_type_name, power, 0.0f, 1.0f); + combat_event->AddEvent(round, target->ID(), target->Owner(), weapon.ship_part_name, + power, 0.0f, 1.0f); } - void AttackFighterShip(std::shared_ptr attacker, const PartAttackInfo& weapon, std::shared_ptr target, - CombatInfo& combat_info, int bout, int round, - AttacksEventPtr &attacks_event) + void AttackFighterShip(std::shared_ptr attacker, const PartAttackInfo& weapon, + std::shared_ptr target, CombatInfo& combat_info, + int bout, int round, AttacksEventPtr& attacks_event) { if (!attacker || !target) return; @@ -501,8 +705,8 @@ namespace { //Meter* target_shield = target->UniverseObject::GetMeter(METER_SHIELD); float shield = 0.0f; //(target_shield ? target_shield->Current() : 0.0f); - DebugLogger() << "AttackFighterShip: Fighter of empire " << attacker->Owner() << " power: " << power - << " target: " << target->Name() //<< " shield: " << target_shield->Current() + DebugLogger() << "AttackFighterShip: " << attacker->Name() << " of empire " << attacker->Owner() + << " power: " << power << " target: " << target->Name() //<< " shield: " << target_shield->Current() << " structure: " << target_structure->Current(); float damage = std::max(0.0f, power - shield); @@ -510,22 +714,101 @@ namespace { if (damage > 0.0f) { target_structure->AddToCurrent(-damage); damaged_object_ids.insert(target->ID()); - DebugLogger(combat) << "COMBAT: Fighter of empire " << attacker->Owner() << " (" << attacker->ID() - << ") does " << damage << " damage to Ship " << target->Name() << " (" - << target->ID() << ")"; + DebugLogger(combat) << "COMBAT: " << attacker->Name() << " of empire " << attacker->Owner() + << " (" << attacker->ID() << ") does " << damage + << " damage to Ship " << target->Name() << " (" << target->ID() << ")"; } float pierced_shield_value(-1.0); CombatEventPtr attack_event = std::make_shared( - bout, round, attacker->ID(), target->ID(), weapon.part_type_name, + bout, round, attacker->ID(), target->ID(), weapon.ship_part_name, std::tie(power, pierced_shield_value, damage), attacker->Owner(), target->Owner()); attacks_event->AddEvent(attack_event); target->SetLastTurnActiveInCombat(CurrentTurn()); } - void AttackFighterFighter(std::shared_ptr attacker, const PartAttackInfo& weapon, std::shared_ptr target, - CombatInfo& combat_info, int bout, int round, + void AttackFighterPlanet(std::shared_ptr attacker, const PartAttackInfo& weapon, + std::shared_ptr target, CombatInfo& combat_info, + int bout, int round, AttacksEventPtr& attacks_event) + { + if (!attacker || !target) return; + + float power = attacker->Damage(); + + std::set& damaged_object_ids = combat_info.damaged_object_ids; + + Meter* target_shield = target->GetMeter(METER_SHIELD); + Meter* target_defense = target->UniverseObject::GetMeter(METER_DEFENSE); + Meter* target_construction = target->UniverseObject::GetMeter(METER_CONSTRUCTION); + if (!target_shield) { + ErrorLogger() << "couldn't get target shield meter"; + return; + } + if (!target_defense) { + ErrorLogger() << "couldn't get target defense meter"; + return; + } + if (!target_construction) { + ErrorLogger() << "couldn't get target construction meter"; + return; + } + + DebugLogger(combat) << "AttackFighterPlanet: attacker: " << attacker->Name() << " power: " << power + << "\ntarget: " << target->Name() << " shield: " << target_shield->Current() + << " defense: " << target_defense->Current() << " infra: " << target_construction->Current(); + + // damage shields, limited by shield current value and damage amount. + // remaining damage, if any, above shield current value goes to defense. + // remaining damage, if any, above defense current value goes to construction + float shield_damage = std::min(target_shield->Current(), power); + float defense_damage = 0.0f; + float construction_damage = 0.0f; + if (shield_damage >= target_shield->Current()) + defense_damage = std::min(target_defense->Current(), power - shield_damage); + + if (power > 0) + damaged_object_ids.insert(target->ID()); + + if (defense_damage >= target_defense->Current()) + construction_damage = std::min(target_construction->Current(), + power - shield_damage - defense_damage); + + if (shield_damage >= 0) { + target_shield->AddToCurrent(-shield_damage); + DebugLogger(combat) << "COMBAT: Ship " << attacker->Name() << " (" << attacker->ID() << ") does " + << shield_damage << " shield damage to Planet " << target->Name() << " (" + << target->ID() << ")"; + } + if (defense_damage >= 0) { + target_defense->AddToCurrent(-defense_damage); + DebugLogger(combat) << "COMBAT: Ship " << attacker->Name() << " (" << attacker->ID() << ") does " + << defense_damage << " defense damage to Planet " << target->Name() << " (" + << target->ID() << ")"; + } + if (construction_damage >= 0) { + target_construction->AddToCurrent(-construction_damage); + DebugLogger(combat) << "COMBAT: Ship " << attacker->Name() << " (" << attacker->ID() << ") does " + << construction_damage << " instrastructure damage to Planet " << target->Name() + << " (" << target->ID() << ")"; + } + + //TODO report the planet damage details more clearly + float total_damage = shield_damage + defense_damage + construction_damage; + + float pierced_shield_value(0.0); + CombatEventPtr attack_event = std::make_shared( + bout, round, attacker->ID(), target->ID(), weapon.ship_part_name, + std::tie(power, pierced_shield_value, total_damage), + attacker->Owner(), target->Owner()); + attacks_event->AddEvent(attack_event); + + target->SetLastTurnAttackedByShip(CurrentTurn()); + } + + void AttackFighterFighter(std::shared_ptr attacker, const PartAttackInfo& weapon, + std::shared_ptr target, CombatInfo& combat_info, + int bout, int round, std::shared_ptr& fighter_on_fighter_event) { if (!attacker || !target) return; @@ -534,6 +817,9 @@ namespace { if (damage > 0.0f) { // any damage is enough to destroy any fighter + DebugLogger(combat) << "COMBAT: " << attacker->Name() << " of empire " << attacker->Owner() + << " (" << attacker->ID() << ") does " << damage + << " damage to " << target->Name() << " (" << target->ID() << ")"; target->SetDestroyed(); } @@ -541,134 +827,45 @@ namespace { } void Attack(std::shared_ptr& attacker, const PartAttackInfo& weapon, - std::shared_ptr& target, CombatInfo& combat_info, int bout, int round, - AttacksEventPtr &attacks_event, + std::shared_ptr& target, CombatInfo& combat_info, + int bout, int round, AttacksEventPtr& attacks_event, WeaponsPlatformEvent::WeaponsPlatformEventPtr platform_event, std::shared_ptr& fighter_on_fighter_event) { - std::shared_ptr attack_ship = std::dynamic_pointer_cast(attacker); - std::shared_ptr attack_planet = std::dynamic_pointer_cast(attacker); - std::shared_ptr attack_fighter = std::dynamic_pointer_cast(attacker); - std::shared_ptr target_ship = std::dynamic_pointer_cast(target); - std::shared_ptr target_planet = std::dynamic_pointer_cast(target); - std::shared_ptr target_fighter = std::dynamic_pointer_cast(target); + auto attack_ship = std::dynamic_pointer_cast(attacker); + auto attack_planet = std::dynamic_pointer_cast(attacker); + auto attack_fighter = std::dynamic_pointer_cast(attacker); + auto target_ship = std::dynamic_pointer_cast(target); + auto target_planet = std::dynamic_pointer_cast(target); + auto target_fighter = std::dynamic_pointer_cast(target); if (attack_ship && target_ship) { - AttackShipShip(attack_ship, weapon, target_ship, combat_info, bout, round, platform_event); + AttackShipShip( attack_ship, weapon, target_ship, combat_info, bout, round, platform_event); } else if (attack_ship && target_planet) { - AttackShipPlanet(attack_ship, weapon, target_planet, combat_info, bout, round, platform_event); + AttackShipPlanet( attack_ship, weapon, target_planet, combat_info, bout, round, platform_event); } else if (attack_ship && target_fighter) { - AttackShipFighter(attack_ship, weapon, target_fighter, combat_info, bout, round, attacks_event, platform_event); + AttackShipFighter( attack_ship, weapon, target_fighter, combat_info, bout, round, platform_event); } else if (attack_planet && target_ship) { - AttackPlanetShip(attack_planet, weapon, target_ship, combat_info, bout, round, platform_event); + AttackPlanetShip( attack_planet, weapon, target_ship, combat_info, bout, round, platform_event); } else if (attack_planet && target_planet) { - // Planets don't attack each other, silly + AttackPlanetPlanet( attack_planet, weapon, target_planet, combat_info, bout, round, platform_event); } else if (attack_planet && target_fighter) { - AttackPlanetFighter(attack_planet, weapon, target_fighter, combat_info, bout, round, attacks_event, platform_event); + AttackPlanetFighter( attack_planet, weapon, target_fighter, combat_info, bout, round, platform_event); } else if (attack_fighter && target_ship) { - AttackFighterShip(attack_fighter, weapon, target_ship, combat_info, bout, round, attacks_event); + AttackFighterShip( attack_fighter, weapon, target_ship, combat_info, bout, round, attacks_event); } else if (attack_fighter && target_planet) { - // Fighters can't attack planets + AttackFighterPlanet( attack_fighter, weapon, target_planet, combat_info, bout, round, attacks_event); } else if (attack_fighter && target_fighter) { - AttackFighterFighter(attack_fighter, weapon, target_fighter, combat_info, bout, round, fighter_on_fighter_event); - } - } - - bool ObjectTypeCanBeAttacked(std::shared_ptr obj) { - if (!obj) - return false; - - UniverseObjectType obj_type = obj->ObjectType(); - - if (obj_type == OBJ_SHIP || obj_type == OBJ_FIGHTER) - return true; - - if (obj_type == OBJ_PLANET) { - if (!obj->Unowned() || obj->CurrentMeterValue(METER_POPULATION) > 0.0f) - return true; - return false; + AttackFighterFighter( attack_fighter, weapon, target_fighter, combat_info, bout, round, fighter_on_fighter_event); } - - return false; - } - - bool ObjectDiplomaticallyAttackableByEmpire(std::shared_ptr obj, int empire_id) { - if (!obj) - return false; - if (obj->OwnedBy(empire_id)) - return false; - if (obj->Unowned() && empire_id == ALL_EMPIRES) - return false; - - if (empire_id != ALL_EMPIRES && !obj->Unowned() && - Empires().GetDiplomaticStatus(empire_id, obj->Owner()) != DIPLO_WAR) - { return false; } - - return true; - } - - bool ObjectTargettableByEmpire(std::shared_ptr obj, int empire_id) { - if (!obj) - return false; - if (obj->ObjectType() != OBJ_FIGHTER && - GetUniverse().GetObjectVisibilityByEmpire(obj->ID(), empire_id) <= VIS_BASIC_VISIBILITY) - { - DebugLogger(combat) << obj->Name() << " not sufficiently visible to empire " << empire_id; - return false; - } - - return true; - } - - bool ObjectAttackableByEmpire(std::shared_ptr obj, int empire_id) { - return ObjectTypeCanBeAttacked(obj) - && ObjectDiplomaticallyAttackableByEmpire(obj, empire_id) - && ObjectTargettableByEmpire(obj, empire_id); - } - - bool ObjectUnTargettableByEmpire(std::shared_ptr obj, int empire_id) { - return ObjectTypeCanBeAttacked(obj) - && ObjectDiplomaticallyAttackableByEmpire(obj, empire_id) - && !ObjectTargettableByEmpire(obj, empire_id); - } - - // monsters / natives can attack any planet, but can only attack - // visible ships or ships that are in aggressive fleets - bool ObjectDiplomaticallyAttackableByMonsters(std::shared_ptr obj) { - return (!obj->Unowned()); - } - - bool ObjectTargettableByMonsters(std::shared_ptr obj, float monster_detection = 0.0f) { - UniverseObjectType obj_type = obj->ObjectType(); - if (obj_type == OBJ_PLANET || obj_type == OBJ_FIGHTER) { - return true; - } else if (obj_type == OBJ_SHIP) { - float stealth = obj->CurrentMeterValue(METER_STEALTH); - if (monster_detection >= stealth) - return true; - } - //DebugLogger() << "... ... is NOT attackable by monsters"; - return false; - } - - bool ObjectAttackableByMonsters(std::shared_ptr obj, float monster_detection = 0.0f) { - return ObjectTypeCanBeAttacked(obj) - && ObjectDiplomaticallyAttackableByMonsters(obj) - && ObjectTargettableByMonsters(obj, monster_detection); - } - - bool ObjectUnTargettableByMonsters(std::shared_ptr obj, float monster_detection = 0.0f) { - return ObjectTypeCanBeAttacked(obj) - && ObjectDiplomaticallyAttackableByMonsters(obj) - && !ObjectTargettableByMonsters(obj, monster_detection); } bool ObjectCanAttack(std::shared_ptr obj) { - if (std::shared_ptr ship = std::dynamic_pointer_cast(obj)) { - return ship->IsArmed() || ship->HasFighters(); - } else if (std::shared_ptr planet = std::dynamic_pointer_cast(obj)) { + if (auto ship = std::dynamic_pointer_cast(obj)) { + return ship->IsArmed(); + } else if (auto planet = std::dynamic_pointer_cast(obj)) { return planet->CurrentMeterValue(METER_DEFENSE) > 0.0f; - } else if (std::shared_ptr fighter = std::dynamic_pointer_cast(obj)) { + } else if (auto fighter = std::dynamic_pointer_cast(obj)) { return fighter->Damage() > 0.0f; } else { return false; @@ -683,33 +880,54 @@ namespace { if (!design) return retval; - std::set seen_hangar_part_types; + std::set seen_hangar_ship_parts; int available_fighters = 0; float fighter_attack = 0.0f; + std::string fighter_name = UserString("OBJ_FIGHTER"); std::map part_fighter_launch_capacities; + const ::Condition::Condition* fighter_combat_targets = nullptr; // determine what ship does during combat, based on parts and their meters... - for (const std::string& part_name : design->Parts()) { - const PartType* part = GetPartType(part_name); + for (const auto& part_name : design->Parts()) { + const ShipPart* part = GetShipPart(part_name); if (!part) continue; ShipPartClass part_class = part->Class(); + const ::Condition::Condition* part_combat_targets = part->CombatTargets(); // direct weapon and fighter-related parts all handled differently... if (part_class == PC_DIRECT_WEAPON) { float part_attack = ship->CurrentPartMeterValue(METER_CAPACITY, part_name); int shots = static_cast(ship->CurrentPartMeterValue(METER_SECONDARY_STAT, part_name)); // secondary stat is shots per attack) if (part_attack > 0.0f && shots > 0) { + if (!part_combat_targets) + part_combat_targets = is_enemy_ship_fighter_or_armed_planet.get(); + // attack for each shot... for (int shot_count = 0; shot_count < shots; ++shot_count) - retval.push_back(PartAttackInfo(part_class, part_name, part_attack)); + retval.push_back(PartAttackInfo(part_class, part_name, part_attack, + part_combat_targets)); + } else { + TraceLogger(combat) << "ShipWeaponsStrengths for ship " << ship->Name() << " (" << ship->ID() << ") " + << " direct weapon part " << part->Name() << " has no shots / zero attack, so is skipped"; } } else if (part_class == PC_FIGHTER_HANGAR) { // hangar max-capacity-modification effects stack, so only add capacity for each hangar type once - if (seen_hangar_part_types.find(part_name) == seen_hangar_part_types.end()) { + if (!seen_hangar_ship_parts.count(part_name)) { available_fighters += ship->CurrentPartMeterValue(METER_CAPACITY, part_name); - seen_hangar_part_types.insert(part_name); + seen_hangar_ship_parts.insert(part_name); + + if (!part_combat_targets) + part_combat_targets = is_enemy_ship_or_fighter.get(); + TraceLogger(combat) << "ShipWeaponsStrengths for ship " << ship->Name() << " (" << ship->ID() << ") " + << "when launching fighters, part " << part->Name() << " with targeting condition: " + << part_combat_targets->Dump(); + if (!fighter_combat_targets) + fighter_combat_targets = part_combat_targets; + + if (UserStringExists(part->Name() + "_FIGHTER")) + fighter_name = UserString(part->Name() + "_FIGHTER"); // should only be one type of fighter per ship as of this writing fighter_attack = ship->CurrentPartMeterValue(METER_SECONDARY_STAT, part_name); // secondary stat is fighter damage @@ -721,14 +939,21 @@ namespace { } if (available_fighters > 0 && !part_fighter_launch_capacities.empty()) { - for (std::map::value_type& launch : part_fighter_launch_capacities) { + for (auto& launch : part_fighter_launch_capacities) { int to_launch = std::min(launch.second, available_fighters); - DebugLogger() << "Ship " << ship->Name() << " launching " << to_launch << " fighters from bay part " << launch.first; + TraceLogger(combat) << "ShipWeaponsStrengths " << ship->Name() << " can launch " << to_launch + << " fighters named \"" << fighter_name << "\" from bay part " << launch.first; + if (fighter_combat_targets) + TraceLogger(combat) << " ... with targeting condition: " << fighter_combat_targets->Dump(); + else + TraceLogger(combat) << " ... with no targeting condition: "; if (to_launch <= 0) continue; - retval.push_back(PartAttackInfo(PC_FIGHTER_BAY, launch.first, to_launch, fighter_attack)); // attack may be 0; that's ok: decoys + retval.push_back(PartAttackInfo(PC_FIGHTER_BAY, launch.first, to_launch, + fighter_attack, fighter_name, + fighter_combat_targets)); // attack may be 0; that's ok: decoys available_fighters -= to_launch; if (available_fighters <= 0) break; @@ -741,21 +966,18 @@ namespace { // Information about a single empire during combat struct EmpireCombatInfo { std::set attacker_ids; - std::set target_ids; - bool HasTargets() const { return !target_ids.empty(); } - bool HasAttackers() const { return !attacker_ids.empty(); } + + bool HasAttackers() const { return !attacker_ids.empty(); } bool HasUnlauchedArmedFighters(const CombatInfo& combat_info) const { // check each ship to see if it has any unlaunched armed fighters... - for (int attacker_id : attacker_ids) { - if (combat_info.destroyed_object_ids.find(attacker_id) != combat_info.destroyed_object_ids.end()) - continue; // destroyed objects can't launch fighters - - std::shared_ptr ship = combat_info.objects.Object(attacker_id); + for (const auto& ship : combat_info.objects.find(attacker_ids)) { if (!ship) - continue; // non-ships can't launch fighters + continue; // discard invalid ship references + if (combat_info.destroyed_object_ids.count(ship->ID())) + continue; // destroyed objects can't launch fighters - std::vector weapons = ShipWeaponsStrengths(ship); + auto weapons = ShipWeaponsStrengths(ship); for (const PartAttackInfo& weapon : weapons) { if (weapon.part_class == PC_FIGHTER_BAY && weapon.fighters_launched > 0 && @@ -768,53 +990,43 @@ namespace { } }; - // Calculate monster detection strength in system - float GetMonsterDetection(const CombatInfo& combat_info) { - float monster_detection = 0.0; - for (ObjectMap::const_iterator<> it = combat_info.objects.const_begin(); it != combat_info.objects.const_end(); ++it) { - std::shared_ptr obj = *it; - if (obj->Unowned() && (obj->ObjectType() == OBJ_SHIP || obj->ObjectType() == OBJ_PLANET )){ - monster_detection = std::max(monster_detection, obj->CurrentMeterValue(METER_DETECTION)); - } - } - return monster_detection; - } - /// A collection of information the autoresolution must keep around struct AutoresolveInfo { - std::set valid_target_object_ids; // all objects that can be attacked std::set valid_attacker_object_ids; // all objects that can attack std::map empire_infos; // empire specific information, indexed by empire id - float monster_detection; // monster (non-player combatants) detection strength - CombatInfo& combat_info; // a reference to the combat info - int next_fighter_id; - - AutoresolveInfo(CombatInfo& combat_info) : - valid_target_object_ids(), - valid_attacker_object_ids(), - empire_infos(), - monster_detection(0.0f), - combat_info(combat_info), - next_fighter_id(-10001) // give fighters negative ids so as to avoid clashes with any positiv-id of persistent UniverseObjects + CombatInfo& combat_info; + int next_fighter_id = -1000001; // give fighters negative ids so as to avoid clashes with any positive-id of persistent UniverseObjects + std::set destroyed_object_ids; // objects that have been destroyed so far during this combat + + explicit AutoresolveInfo(CombatInfo& combat_info_) : + combat_info(combat_info_) { - monster_detection = GetMonsterDetection(combat_info); - PopulateAttackersAndTargets(combat_info); - PopulateEmpireTargets(combat_info); + PopulateAttackers(); } std::vector AddFighters(int number, float damage, int owner_empire_id, - int from_ship_id, const std::string& species) + int from_ship_id, const std::string& species, + const std::string& fighter_name, + const Condition::Condition* combat_targets) { std::vector retval; + if (combat_targets) + TraceLogger(combat) << "Adding " << number << " fighters for empire " << owner_empire_id + << " with targetting condition: " << combat_targets->Dump(); + else + TraceLogger(combat) << "Adding " << number << " fighters for empire " << owner_empire_id + << " with no targetting condition"; + for (int n = 0; n < number; ++n) { // create / insert fighter into combat objectmap - auto fighter_ptr = std::make_shared(owner_empire_id, from_ship_id, species, damage); + auto fighter_ptr = std::make_shared(owner_empire_id, from_ship_id, + species, damage, combat_targets); fighter_ptr->SetID(next_fighter_id--); - fighter_ptr->Rename(UserString("OBJ_FIGHTER")); - combat_info.objects.Insert(fighter_ptr); + fighter_ptr->Rename(fighter_name); + combat_info.objects.insert(fighter_ptr); if (!fighter_ptr) { - ErrorLogger() << "AddFighters unable to create and insert new Fighter object..."; + ErrorLogger(combat) << "AddFighters unable to create and insert new Fighter object..."; break; } @@ -824,64 +1036,60 @@ namespace { if (damage > 0.0f) { valid_attacker_object_ids.insert(fighter_ptr->ID()); empire_infos[fighter_ptr->Owner()].attacker_ids.insert(fighter_ptr->ID()); - DebugLogger() << "Added fighter id: " << fighter_ptr->ID() << " to attackers sets"; + DebugLogger(combat) << "Added fighter id: " << fighter_ptr->ID() << " to attackers sets"; } - // and to targets - valid_target_object_ids.insert(fighter_ptr->ID()); - // add fighter to targets ids for any empire that can attack it - for (const std::map::value_type& entry : empire_infos) { - if (ObjectAttackableByEmpire(fighter_ptr, entry.first)) { - empire_infos[entry.first].target_ids.insert(fighter_ptr->ID()); - DebugLogger() << "Added fighter " << fighter_ptr->ID() - << " owned by empire " << fighter_ptr->Owner() - << " to targets for empire " << entry.first; - } - } - DebugLogger() << "Added fighter id: " << fighter_ptr->ID() << " to valid targets set"; + // mark fighter visible to all empire participants + for (auto viewing_empire_id : combat_info.empire_ids) + combat_info.empire_object_visibility[viewing_empire_id][fighter_ptr->ID()] = VIS_PARTIAL_VISIBILITY; } return retval; } - // Return true if some empire that can attack has some targets that it can attack + // Return true if some empire has ships or fighters that can attack + // Doesn't consider diplomacy or other empires, as parts have arbitrary + // targeting conditions, including friendly fire bool CanSomeoneAttackSomething() const { - for (const std::map::value_type& attacker : empire_infos) { - if (attacker.second.HasTargets() && ( - attacker.second.HasAttackers() || - attacker.second.HasUnlauchedArmedFighters(combat_info) - )) - { return true; } + for (const auto& attacker_info : empire_infos) { + // does empire have something to attack with? + const EmpireCombatInfo& attacker_empire_info = attacker_info.second; + if (!attacker_empire_info.HasAttackers() && !attacker_empire_info.HasUnlauchedArmedFighters(combat_info)) + continue; + + // TODO: check if any of these ships or fighters have targeting + // conditions that match anything present in this combat + return true; } return false; } /// Removes dead units from lists of attackers and defenders - void CullTheDead(int bout, BoutEvent::BoutEventPtr &bout_event) { + void CullTheDead(int bout, BoutEvent::BoutEventPtr& bout_event) { auto fighters_destroyed_event = std::make_shared(bout); bool at_least_one_fighter_destroyed = false; - IncapacitationsEventPtr incaps_event = - std::make_shared(); + IncapacitationsEventPtr incaps_event = std::make_shared(); - for (ObjectMap::const_iterator<> it = combat_info.objects.const_begin(); - it != combat_info.objects.const_end(); ++it) - { - std::shared_ptr obj = *it; - - // There may be objects, for example unowned planets, that were - // not a part of the battle to start with. Ignore them - if (valid_attacker_object_ids.find(obj->ID()) == valid_attacker_object_ids.end() && - valid_target_object_ids.find(obj->ID()) == valid_target_object_ids.end()) - { continue; } + std::vector delete_list; + delete_list.reserve(combat_info.objects.size()); - // Check if the target was destroyed and update lists if yes + for (const auto& obj : combat_info.objects.all()) + { + // Check if object is already noted as destroyed; don't need to re-record this + if (destroyed_object_ids.count(obj->ID())) + continue; + // Check if object is destroyed and update lists if yes if (!CheckDestruction(obj)) continue; + destroyed_object_ids.insert(obj->ID()); if (obj->ObjectType() == OBJ_FIGHTER) { fighters_destroyed_event->AddEvent(obj->Owner()); at_least_one_fighter_destroyed = true; + // delete actual fighter object so that it can't be targeted + // again next round (ships have a minimal structure test instead) + delete_list.push_back(obj->ID()); } else { CombatEventPtr incap_event = std::make_shared( bout, obj->ID(), obj->Owner()); @@ -894,6 +1102,13 @@ namespace { if (!incaps_event->AreSubEventsEmpty(ALL_EMPIRES)) bout_event->AddEvent(incaps_event); + + std::stringstream ss; + for (auto id : delete_list) { + ss << id << " "; + combat_info.objects.erase(id); + } + DebugLogger() << "Removed destroyed objects from combat state with ids: " << ss.str(); } /// Checks if target is destroyed and if it is, update lists of living objects. @@ -903,15 +1118,11 @@ namespace { // check for destruction of target object if (target->ObjectType() == OBJ_FIGHTER) { - const std::shared_ptr fighter = std::dynamic_pointer_cast(target); + auto fighter = std::dynamic_pointer_cast(target); if (fighter && fighter->Destroyed()) { // remove destroyed fighter's ID from lists of valid attackers and targets valid_attacker_object_ids.erase(target_id); - valid_target_object_ids.erase(target_id); // probably not necessary as this set isn't used in this loop - DebugLogger() << "Removed destroyed fighter id: " << fighter->ID() << " from attackers and targets sets"; - - for (std::map::value_type& targeter : empire_infos) - { targeter.second.target_ids.erase(target_id); } + DebugLogger(combat) << "Removed destroyed fighter id: " << fighter->ID() << " from attackers"; // Remove target from its empire's list of attackers empire_infos[target->Owner()].attacker_ids.erase(target_id); @@ -928,17 +1139,13 @@ namespace { for (int empire_id : combat_info.empire_ids) { if (empire_id != ALL_EMPIRES) { DebugLogger(combat) << "Giving knowledge of destroyed object " << target_id - << " to empire " << empire_id; + << " to empire " << empire_id; combat_info.destroyed_object_knowers[empire_id].insert(target_id); } } // remove destroyed ship's ID from lists of valid attackers and targets valid_attacker_object_ids.erase(target_id); - valid_target_object_ids.erase(target_id); // probably not necessary as this set isn't used in this loop - - for (std::map::value_type& targeter : empire_infos) - { targeter.second.target_ids.erase(target_id); } // Remove target from its empire's list of attackers empire_infos[target->Owner()].attacker_ids.erase(target_id); @@ -948,7 +1155,7 @@ namespace { } else if (target->ObjectType() == OBJ_PLANET) { if (!ObjectCanAttack(target) && - valid_attacker_object_ids.find(target_id) != valid_attacker_object_ids.end()) + valid_attacker_object_ids.count(target_id)) { DebugLogger(combat) << "!! Target Planet " << target_id << " knocked out, can no longer attack"; // remove disabled planet's ID from lists of valid attackers @@ -963,19 +1170,13 @@ namespace { // before it has been attacked then it can wrongly get regen // on the next turn, so check that it has been attacked // before excluding it from any remaining battle - if (combat_info.damaged_object_ids.find(target_id) == combat_info.damaged_object_ids.end()) { - DebugLogger(combat) << "!! Planet " << target_id << "has not yet been attacked, " + if (!combat_info.damaged_object_ids.count(target_id)) { + DebugLogger(combat) << "!! Planet " << target_id << " has not yet been attacked, " << "so will not yet be removed from battle, despite being essentially incapacitated"; return false; } DebugLogger(combat) << "!! Target Planet " << target_id << " is entirely knocked out of battle"; - // remove disabled planet's ID from lists of valid targets - valid_target_object_ids.erase(target_id); // probably not necessary as this set isn't used in this loop - - for (std::map::value_type& targeter : empire_infos) - { targeter.second.target_ids.erase(target_id); } - // Remove target from its empire's list of attackers empire_infos[target->Owner()].attacker_ids.erase(target_id); @@ -987,73 +1188,89 @@ namespace { return false; } - /// check if any empire has no remaining target or attacker objects. + /// check if any empire has no remaining objects. /// If so, remove that empire's entry void CleanEmpires() { - std::map temp = empire_infos; - for (std::map::value_type& empire : empire_infos) { - if (!empire.second.HasTargets() && - !empire.second.HasAttackers() && - !empire.second.HasUnlauchedArmedFighters(combat_info)) - { + DebugLogger(combat) << "CleanEmpires"; + auto temp = empire_infos; + + std::set empire_ids_with_objects; + for (const auto obj : combat_info.objects.all()) + empire_ids_with_objects.insert(obj->Owner()); + + for (auto& empire : empire_infos) { + if (!empire_ids_with_objects.count(empire.first)) { temp.erase(empire.first); - DebugLogger(combat) << "No valid attacking objects left for empire with id: " << empire.first; + DebugLogger(combat) << "No objects left for empire with id: " << empire.first; + } + } + empire_infos = std::move(temp); + + if (!empire_infos.empty()) { + DebugLogger(combat) << "Empires with objects remaining:"; + for (auto empire : empire_infos) { + DebugLogger(combat) << " ... " << empire.first; + for (auto obj_id : empire.second.attacker_ids) { + TraceLogger(combat) << " ... ... " << obj_id; + } } } - empire_infos = temp; } - /// Returns the list of attacker ids in a random order + /// Clears and refills \a shuffled with attacker ids in a random order void GetShuffledValidAttackerIDs(std::vector& shuffled) { shuffled.clear(); shuffled.insert(shuffled.begin(), valid_attacker_object_ids.begin(), valid_attacker_object_ids.end()); const unsigned swaps = shuffled.size(); - for (unsigned i = 0; i < swaps; ++i){ + for (unsigned i = 0; i < swaps; ++i) { int pos2 = RandInt(i, swaps - 1); std::swap(shuffled[i], shuffled[pos2]); } } - /**Report for each empire the stealth objects in the combat. */ - InitialStealthEvent::StealthInvisbleMap ReportInvisibleObjects(const CombatInfo& combat_info) const { + float EmpireDetectionStrength(int empire_id) { + if (const auto* empire = GetEmpire(empire_id)) + return empire->GetMeter("METER_DETECTION_STRENGTH")->Current(); + return 0.0f; + } + + /** Report for each empire the stealthy objects in the combat. */ + InitialStealthEvent::EmpireToObjectVisibilityMap ReportInvisibleObjects() const { DebugLogger(combat) << "Reporting Invisible Objects"; - InitialStealthEvent::StealthInvisbleMap report; - for (int object_id : valid_target_object_ids) { - std::shared_ptr obj = combat_info.objects.Object(object_id); - - // for all empires, can they attack this object? - for (int attacking_empire_id : combat_info.empire_ids) { - Visibility visibility = VIS_NO_VISIBILITY; - DebugLogger(combat) << "Target " << obj->Name() << " by empire = "<< attacking_empire_id; - std::map>::const_iterator target_visible_it - = combat_info.empire_object_visibility.find(obj->Owner()); - if (target_visible_it != combat_info.empire_object_visibility.end()) { - std::map::const_iterator target_attacker_visibility_it - = target_visible_it->second.find(obj->ID()); - if (target_attacker_visibility_it != target_visible_it->second.end()) { - visibility = target_attacker_visibility_it->second; - } + InitialStealthEvent::EmpireToObjectVisibilityMap report; + + // loop over all objects, noting which is visible by which empire or neutrals + for (const auto target : combat_info.objects.all()) { + // for all empires, can they detect this object? + for (int viewing_empire_id : combat_info.empire_ids) { + // get visibility of target to attacker empire + auto empire_vis_info_it = combat_info.empire_object_visibility.find(viewing_empire_id); + if (empire_vis_info_it == combat_info.empire_object_visibility.end()) { + DebugLogger() << " ReportInvisibleObjects found no visibility info for viewing empire " << viewing_empire_id; + report[viewing_empire_id][target->ID()] = VIS_NO_VISIBILITY; + continue; + } + auto target_visibility_for_empire_it = empire_vis_info_it->second.find(target->ID()); + if (target_visibility_for_empire_it == empire_vis_info_it->second.end()) { + DebugLogger() << " ReportInvisibleObjects found no visibility record for viewing empire " + << viewing_empire_id << " for object " << target->Name() << " (" << target->ID() << ")"; + report[viewing_empire_id][target->ID()] = VIS_NO_VISIBILITY; + continue; } - if (attacking_empire_id == ALL_EMPIRES) { - if (ObjectUnTargettableByMonsters(obj, monster_detection)) { - // Note: This does put information about invisible and basic visible objects and - // trusts that the combat logger only informs player/ai of what they should know - DebugLogger(combat) << " Monster " << obj->Name() << " " << visibility << " to empire " << attacking_empire_id; - report[attacking_empire_id][obj->Owner()] - .insert(std::make_pair(obj->ID(), visibility)); - } - + Visibility vis = target_visibility_for_empire_it->second; + if (viewing_empire_id == ALL_EMPIRES) { + DebugLogger(combat) << " Target " << target->Name() << " (" << target->ID() << "): " + << vis << " to monsters and neutrals"; } else { - if (ObjectUnTargettableByEmpire(obj, attacking_empire_id)) { - // Note: This does put information about invisible and basic visible objects and - // trusts that the combat logger only informs player/ai of what they should know - DebugLogger(combat) << " Ship " << obj->Name() << " " << visibility << " to empire " << attacking_empire_id; - report[attacking_empire_id][obj->Owner()] - .insert(std::make_pair(obj->ID(), visibility)); - } + DebugLogger(combat) << " Target " << target->Name() << " (" << target->ID() << "): " + << vis << " to empire " << viewing_empire_id; } + + // This adds information about invisible and basic visible objects and + // trusts that the combat logger only informs player/ai of what they should know + report[viewing_empire_id][target->ID()] = vis; } } return report; @@ -1062,206 +1279,160 @@ namespace { private: typedef std::set::const_iterator const_id_iterator; - // Populate lists of things that can attack and be attacked. List attackers also by empire. - void PopulateAttackersAndTargets(const CombatInfo& combat_info) { - for (ObjectMap::const_iterator<> it = combat_info.objects.const_begin(); - it != combat_info.objects.const_end(); ++it) + // Populate lists of things that can attack. List attackers also by empire. + void PopulateAttackers() { + for (const auto& obj : combat_info.objects.all()) { - std::shared_ptr obj = *it; bool can_attack{ObjectCanAttack(obj)}; if (can_attack) { - valid_attacker_object_ids.insert(it->ID()); - empire_infos[obj->Owner()].attacker_ids.insert(it->ID()); + valid_attacker_object_ids.insert(obj->ID()); + empire_infos[obj->Owner()].attacker_ids.insert(obj->ID()); } - bool can_be_attacked{ObjectTypeCanBeAttacked(obj)}; - if (can_be_attacked) { - valid_target_object_ids.insert(it->ID()); - } - DebugLogger(combat) << "Considerting object " + obj->Name() + " owned by " + std::to_string(obj->Owner()) - << (can_attack ? "... can attack" : "") - << (can_be_attacked ? "... can be attacked": ""); + DebugLogger(combat) << "Considering object " << obj->Name() << " (" << obj->ID() << ")" + << " owned by " << std::to_string(obj->Owner()) + << (can_attack ? "... can attack" : ""); } } + }; - // Get a map from empire to set of IDs of objects that empire's objects - // could potentially target. - void PopulateEmpireTargets(const CombatInfo& combat_info) { - for (int object_id : valid_target_object_ids) { - std::shared_ptr obj = combat_info.objects.Object(object_id); - for (int attacking_empire_id : combat_info.empire_ids) { - if (attacking_empire_id == ALL_EMPIRES) { - if (ObjectAttackableByMonsters(obj, monster_detection)) - empire_infos[ALL_EMPIRES].target_ids.insert(object_id); - } else if (ObjectAttackableByEmpire(obj, attacking_empire_id)) { - empire_infos[attacking_empire_id].target_ids.insert(object_id); - } - } + std::vector GetWeapons(std::shared_ptr attacker) { + // Loop over weapons of attacking object. Each gets a shot at a + // randomly selected target object, from the objects in the combat + // that match the weapon's targetting condition. + std::vector weapons; - DebugLogger(combat) << ReportAttackabilityOfTarget(combat_info, obj); - } - } + auto attack_ship = std::dynamic_pointer_cast(attacker); + auto attack_planet = std::dynamic_pointer_cast(attacker); + auto attack_fighter = std::dynamic_pointer_cast(attacker); - // Return a log report of attackability of \p obj - std::string ReportAttackabilityOfTarget(const CombatInfo& combat_info, - const std::shared_ptr& obj) - { - std::stringstream ss; - ss << "Considering attackability of object " << obj->Name() - << " owned by " << std::to_string(obj->Owner()) - << " attackable by "; - - bool no_one{true}; - for (int attacking_empire_id : combat_info.empire_ids) { - if (attacking_empire_id == ALL_EMPIRES) { - if (ObjectAttackableByMonsters(obj, monster_detection)) { - ss << "monsters and "; - no_one = false; - } - } else if (ObjectAttackableByEmpire(obj, attacking_empire_id)) { - if (!no_one) - ss << ", "; - ss << std::to_string(attacking_empire_id); - no_one = false; + if (attack_ship) { + weapons = ShipWeaponsStrengths(attack_ship); // includes info about fighter launches with PC_FIGHTER_BAY part class, and direct fire weapons with PC_DIRECT_WEAPON part class + for (PartAttackInfo& part : weapons) { + if (part.part_class == PC_DIRECT_WEAPON) { + DebugLogger(combat) << "Attacker Ship has direct weapon: " << part.ship_part_name + << " attack: " << part.part_attack; + } else if (part.part_class == PC_FIGHTER_BAY) { + DebugLogger(combat) << "Attacker Ship can fighter launch: " << part.fighters_launched + << " damage: " << part.fighter_damage; + if (part.combat_targets) + TraceLogger(combat) << " ... fighter targeting condition: " << part.combat_targets->Dump(); + else + TraceLogger(combat) << " ... fighter has no targeting condition"; } } - if (no_one) - ss << "none of the empires present"; - return ss.str(); - } - }; - - - // Return a report of invalid targets - std::string ReportInvalidTargets(const std::shared_ptr& attacker, - const std::set& potential_target_ids, - const AutoresolveInfo& combat_state) - { - std::stringstream ss; - ss << "Attacker " << attacker->ID() << " can't attack potential targets: "; - - for (int target_id : potential_target_ids) { - std::shared_ptr target = combat_state.combat_info.objects.Object(target_id); - if (!target) - continue; - // planets can only attack ships (not other planets or fighters) - if (attacker->ObjectType() == OBJ_PLANET && target->ObjectType() != OBJ_SHIP) - ss << std::to_string(target_id) << " "; + } else if (attack_planet) { // treat planet defenses as direct fire weapon that only target ships + weapons.push_back(PartAttackInfo(PC_DIRECT_WEAPON, UserStringNop("DEF_DEFENSE"), + attack_planet->CurrentMeterValue(METER_DEFENSE), + is_enemy_ship.get())); - // fighters can't attack planets - else if (attacker->ObjectType() == OBJ_FIGHTER && target->ObjectType() == OBJ_PLANET) - ss << std::to_string(target_id) << " "; + } else if (attack_fighter) { // treat fighter damage as direct fire weapon + weapons.push_back(PartAttackInfo(PC_DIRECT_WEAPON, UserStringNop("FT_WEAPON_1"), + attack_fighter->Damage(), + attack_fighter->CombatTargets())); } - - return ss.str(); + return weapons; } - const std::set ValidTargetsForAttackerType(std::shared_ptr& attacker, - AutoresolveInfo& combat_state, - const std::set& potential_target_ids) - { - if (attacker->ObjectType() == OBJ_SHIP) - return potential_target_ids; // ships can attack anything! + const Condition::Condition* SpeciesTargettingCondition(const std::shared_ptr& attacker) { + if (!attacker) + return if_source_is_planet_then_ships_else_all.get(); - std::set valid_target_ids; - bool any_invalid_targets{false}; + const Species* species = nullptr; + if (auto attack_ship = std::dynamic_pointer_cast(attacker)) + species = GetSpecies(attack_ship->SpeciesName()); + else if (auto attack_planet = std::dynamic_pointer_cast(attacker)) + species = GetSpecies(attack_planet->SpeciesName()); + else if (auto attack_fighter = std::dynamic_pointer_cast(attacker)) + species = GetSpecies(attack_fighter->SpeciesName()); - for (int target_id : potential_target_ids) { - std::shared_ptr target = combat_state.combat_info.objects.Object(target_id); - if (!target) { - ErrorLogger() << "AutoResolveCombat couldn't get target object with id " << target_id; - continue; - } + if (!species || !species->CombatTargets()) + return if_source_is_planet_then_ships_else_all.get(); - if (attacker->ObjectType() == OBJ_PLANET) { - // planets can only attack ships (not other planets or fighters) - if (target->ObjectType() == OBJ_SHIP) { - valid_target_ids.insert(target_id); - } else { - any_invalid_targets = true; - } - } else if (attacker->ObjectType() == OBJ_FIGHTER) { - // fighters can't attack planets - if (target->ObjectType() != OBJ_PLANET) { - valid_target_ids.insert(target_id); - } else { - any_invalid_targets = true; - } - } - } + return species->CombatTargets(); + } - if (any_invalid_targets) - DebugLogger(combat) << ReportInvalidTargets(attacker, potential_target_ids, combat_state); + void AddAllObjectsSet(ObjectMap& obj_map, Condition::ObjectSet& condition_non_targets) { + condition_non_targets.reserve(condition_non_targets.size() + obj_map.ExistingObjects().size()); + std::transform(obj_map.ExistingObjects().begin(), obj_map.ExistingObjects().end(), // ExistingObjects() here does not consider whether objects have been destroyed during this combat + std::back_inserter(condition_non_targets), [](const std::map>::value_type& p) { + return std::const_pointer_cast(p.second); + }); - return valid_target_ids; } - void ShootAllWeapons(std::shared_ptr& attacker, const std::vector& weapons, - AutoresolveInfo& combat_state, int bout, int round, - AttacksEventPtr &attacks_event, - WeaponsPlatformEvent::WeaponsPlatformEventPtr &platform_event, + void ShootAllWeapons(std::shared_ptr attacker, + AutoresolveInfo& combat_state, int round, + AttacksEventPtr& attacks_event, + WeaponsPlatformEvent::WeaponsPlatformEventPtr& platform_event, std::shared_ptr& fighter_on_fighter_event) { + auto weapons = GetWeapons(attacker); if (weapons.empty()) { - DebugLogger(combat) << "no weapons' can't attack"; + DebugLogger(combat) << "Attacker " << attacker->Name() << " (" + << attacker->ID() << ") has no weapons, so can't attack"; return; // no ability to attack! } + const auto* species_targetting_condition = SpeciesTargettingCondition(attacker); + if (!species_targetting_condition) { + ErrorLogger(combat) << "Null Species Targetting Condition...!?"; + return; + } + TraceLogger(combat) << "Species targeting condition: " << species_targetting_condition->Dump(); + for (const PartAttackInfo& weapon : weapons) { // skip non-direct-fire weapons (as only direct fire weapons can "shoot"). // fighter launches handled separately if (weapon.part_class != PC_DIRECT_WEAPON) continue; - // select object from valid targets for this object's owner TODO: with this weapon... - DebugLogger(combat) << "Attacking with weapon " << weapon.part_type_name << " with power " << weapon.part_attack; + // select object from valid targets for this object's owner + DebugLogger(combat) << "Attacker " << attacker->Name() << " (" + << attacker->ID() << ") attacks with weapon " + << weapon.ship_part_name << " with power " << weapon.part_attack; - // get valid targets set for attacker owner. need to do this for - // each weapon that is attacking, as the previous shot might have - // destroyed something - int attacker_owner_id = attacker->Owner(); - - std::map::iterator target_vec_it = combat_state.empire_infos.find(attacker_owner_id); - if (target_vec_it == combat_state.empire_infos.end() || !target_vec_it->second.HasTargets()) { - DebugLogger(combat) << "No targets for empire: " << attacker_owner_id; - break; + if (!weapon.combat_targets) { + DebugLogger(combat) << "Weapon has no targeting condition?? Should have been set when initializing PartAttackInfo"; + continue; } + TraceLogger(combat) << "Weapon targeting condition: " << weapon.combat_targets->Dump(); - const std::set valid_target_ids = ValidTargetsForAttackerType(attacker, combat_state, target_vec_it->second.target_ids); - if (valid_target_ids.empty()) { - DebugLogger(combat) << "No valid targets for attacker " << attacker->ID(); - break; - } - //const std::set& valid_target_ids = target_vec_it->second.target_ids; - DebugLogger(combat) << [&valid_target_ids, &attacker, &attacker_owner_id]() { - std::stringstream ss; - ss << "Valid targets for attacker with id: " << attacker->ID() - << " owned by empire: " << attacker_owner_id - << " : "; - for (int target_id : valid_target_ids) - { ss << std::to_string(target_id) + " "; } - return ss.str(); - }(); + Condition::ObjectSet targets, rejected_targets; + AddAllObjectsSet(combat_state.combat_info.objects, targets); + // attacker is source object for condition evaluation. use combat-specific vis info. + ScriptingContext context(attacker, combat_state.combat_info); - // END DEBUG + // apply species targeting condition and then weapon targeting condition + species_targetting_condition->Eval(context, targets, rejected_targets, Condition::MATCHES); + weapon.combat_targets->Eval(context, targets, rejected_targets, Condition::MATCHES); - // select target object - int target_idx = RandInt(0, valid_target_ids.size() - 1); - int target_id = *std::next(valid_target_ids.begin(), target_idx); - std::shared_ptr target = combat_state.combat_info.objects.Object(target_id); + if (targets.empty()) { + DebugLogger(combat) << "No objects matched targeting condition!"; + continue; + } + DebugLogger(combat) << targets.size() << " objects matched targeting condition"; + for (const auto& match : targets) + TraceLogger(combat) << " ... " << match->Name() << " (" << match->ID() << ")"; + + // select target object from matches + int target_idx = RandInt(0, targets.size() - 1); + auto target = *std::next(targets.begin(), target_idx); + if (!target) { - ErrorLogger() << "AutoResolveCombat couldn't get target object with id " << target_id; + ErrorLogger(combat) << "AutoResolveCombat selected null target object?"; continue; } - DebugLogger(combat) << "Target: " << target->Name(); + DebugLogger(combat) << "Selected target: " << target->Name() << " (" << target->ID() << ")"; + auto targetx = std::const_pointer_cast(target); // do actual attacks - Attack(attacker, weapon, target, combat_state.combat_info, bout, round, attacks_event, - platform_event, fighter_on_fighter_event); + Attack(attacker, weapon, targetx, combat_state.combat_info, combat_state.combat_info.bout, round, + attacks_event, platform_event, fighter_on_fighter_event); } // end for over weapons } @@ -1272,28 +1443,28 @@ namespace { // get how many fighters are initialy in each part type... // may be multiple hangar part types, each with different capacity (number of stored fighters) - std::map part_type_fighter_hangar_capacities; + std::map ship_part_fighter_hangar_capacities; const ShipDesign* design = ship->Design(); if (!design) { - ErrorLogger() << "ReduceStoredFighterCount couldn't get ship design with id " << ship->DesignID();; + ErrorLogger(combat) << "ReduceStoredFighterCount couldn't get ship design with id " << ship->DesignID();; return; } // get hangar part meter values for (const std::string& part_name : design->Parts()) { - const PartType* part = GetPartType(part_name); + const ShipPart* part = GetShipPart(part_name); if (!part) continue; if (part->Class() != PC_FIGHTER_HANGAR) continue; - part_type_fighter_hangar_capacities[part_name] = ship->GetPartMeter(METER_CAPACITY, part_name); + ship_part_fighter_hangar_capacities[part_name] = ship->GetPartMeter(METER_CAPACITY, part_name); } // reduce meters until requested fighter reduction is achived // doesn't matter which part's capacity meters are reduced, as all // fighters are the same on the ship - for (std::map::value_type& part : part_type_fighter_hangar_capacities) { + for (auto& part : ship_part_fighter_hangar_capacities) { if (!part.second) continue; float reduction = std::min(part.second->Current(), launched_fighters); @@ -1306,45 +1477,60 @@ namespace { } } - void LaunchFighters(std::shared_ptr& attacker, const std::vector& weapons, - AutoresolveInfo& combat_state, int bout, int round, - FighterLaunchesEventPtr &launches_event) + void LaunchFighters(std::shared_ptr attacker, + const std::vector& weapons, + AutoresolveInfo& combat_state, int round, + FighterLaunchesEventPtr& launches_event) { if (weapons.empty()) { DebugLogger(combat) << "no weapons' can't launch figters!"; return; // no ability to attack! } - static const std::string EMPTY_STRING; - std::shared_ptr attacker_ship = std::dynamic_pointer_cast(attacker); - const std::string& species_name = attacker_ship ? attacker_ship->SpeciesName() : EMPTY_STRING; + std::string species_name; + auto attacker_ship = std::dynamic_pointer_cast(attacker); + if (attacker_ship) + species_name = attacker_ship->SpeciesName(); + else if (auto attacker_planet = std::dynamic_pointer_cast(attacker)) + species_name = attacker_planet->SpeciesName(); - for (const PartAttackInfo& weapon : weapons) { + for (const auto& weapon : weapons) { // skip non-fighter weapons // direct fire weapons handled separately if (weapon.part_class != PC_FIGHTER_BAY) // passed in weapons container should have already checked for adequate supply of fighters to launch and the available bays, and condensed into a single entry... continue; int attacker_owner_id = attacker->Owner(); + const auto empire = GetEmpire(attacker_owner_id); + const auto& empire_name = (empire ? empire->Name() : UserString("ENC_COMBAT_ROGUE")); + + std::string fighter_name = boost::io::str(FlexibleFormat(UserString("ENC_COMBAT_EMPIRE_FIGHTER_NAME")) + % empire_name % species_name % weapon.fighter_type_name); DebugLogger(combat) << "Launching " << weapon.fighters_launched << " with damage " << weapon.fighter_damage << " for empire id: " << attacker_owner_id << " from ship id: " << attacker->ID(); + if (weapon.combat_targets) + TraceLogger(combat) << " ... with targeting condition: " << weapon.combat_targets->Dump(); + else + TraceLogger(combat) << " ... with no targeting condition!"; std::vector new_fighter_ids = combat_state.AddFighters(weapon.fighters_launched, weapon.fighter_damage, - attacker_owner_id, attacker->ID(), species_name); + attacker_owner_id, attacker->ID(), species_name, + fighter_name, weapon.combat_targets); // combat event - CombatEventPtr launch_event = - std::make_shared(bout, attacker->ID(), attacker_owner_id, new_fighter_ids.size()); + CombatEventPtr launch_event = std::make_shared( + combat_state.combat_info.bout, attacker->ID(), attacker_owner_id, new_fighter_ids.size()); launches_event->AddEvent(launch_event); // reduce hangar capacity (contents) corresponding to launched fighters int num_launched = new_fighter_ids.size(); - ReduceStoredFighterCount(attacker_ship, num_launched); + if (attacker_ship) + ReduceStoredFighterCount(attacker_ship, num_launched); // launching fighters counts as a ship being active in combat if (!new_fighter_ids.empty()) @@ -1360,61 +1546,80 @@ namespace { // get how many fighters are initialy in each part type... // may be multiple hangar part types, each with different capacity (number of stored fighters) - std::map> part_type_fighter_hangar_capacities; + std::map> ship_part_fighter_hangar_capacities; const ShipDesign* design = ship->Design(); if (!design) { - ErrorLogger() << "ReduceStoredFighterCount couldn't get ship design with id " << ship->DesignID();; + ErrorLogger(combat) << "IncreaseStoredFighterCount couldn't get ship design with id " << ship->DesignID();; return; } // get hangar part meter values for (const std::string& part_name : design->Parts()) { - const PartType* part = GetPartType(part_name); + const ShipPart* part = GetShipPart(part_name); if (!part) continue; if (part->Class() != PC_FIGHTER_HANGAR) continue; - part_type_fighter_hangar_capacities[part_name].first = ship->GetPartMeter(METER_CAPACITY, part_name); - part_type_fighter_hangar_capacities[part_name].second = ship->GetPartMeter(METER_MAX_CAPACITY, part_name); + ship_part_fighter_hangar_capacities[part_name].first = ship->GetPartMeter(METER_CAPACITY, part_name); + ship_part_fighter_hangar_capacities[part_name].second = ship->GetPartMeter(METER_MAX_CAPACITY, part_name); } // increase capacity meters until requested fighter allocation is - // achived doesn't matter which part's capacity meters are increased, - // as allfighters are the same on the ship - for (std::map>::value_type& part : part_type_fighter_hangar_capacities) { + // recovered. ioesn't matter which part's capacity meters are increased, + // since all fighters are the same on the ship + for (auto& part : ship_part_fighter_hangar_capacities) { if (!part.second.first || !part.second.second) continue; float space = part.second.second->Current() - part.second.first->Current(); float increase = std::min(space, recovered_fighters); recovered_fighters -= increase; + + DebugLogger() << "Increasing stored fighter count of ship " << ship->Name() + << " (" << ship->ID() << ") from " << part.second.first->Current() + << " by " << increase << " towards max of " + << part.second.second->Current(); + part.second.first->AddToCurrent(increase); - // stop if all fighters launched + // stop if all fighters recovered if (recovered_fighters <= 0.0f) break; } } void RecoverFighters(CombatInfo& combat_info, int bout, - FighterLaunchesEventPtr &launches_event) { + FighterLaunchesEventPtr& launches_event) + { std::map ships_fighters_to_add_back; + DebugLogger() << "Recovering fighters at end of combat..."; - for (ObjectMap::iterator<> obj_it = combat_info.objects.begin(); - obj_it != combat_info.objects.end() && obj_it->ID() < 0; ++obj_it) + // count still-existing and not destroyed fighters at end of combat + for (const auto& obj : combat_info.objects.all()) { - std::shared_ptr fighter = std::dynamic_pointer_cast(*obj_it); + if (obj->ID() >= 0) + continue; + auto fighter = std::dynamic_pointer_cast(obj); if (!fighter || fighter->Destroyed()) continue; // destroyed fighters can't return - if (combat_info.destroyed_object_ids.find(fighter->LaunchedFrom()) != combat_info.destroyed_object_ids.end()) + if (combat_info.destroyed_object_ids.count(fighter->LaunchedFrom())) { + DebugLogger() << " ... Fighter " << fighter->Name() << " (" << fighter->ID() + << ") is from destroyed ship id" << fighter->LaunchedFrom() + << " so can't be recovered"; continue; // can't return to a destroyed ship + } ships_fighters_to_add_back[fighter->LaunchedFrom()]++; } + DebugLogger() << "Fighters left at end of combat:"; + for (auto ship_fighter_count_pair : ships_fighters_to_add_back) + DebugLogger() << " ... from ship id " << ship_fighter_count_pair.first << " : " << ship_fighter_count_pair.second; + - for (std::map::value_type& entry : ships_fighters_to_add_back) { - std::shared_ptr ship = combat_info.objects.Object(entry.first); + DebugLogger() << "Returning fighters to ships:"; + for (auto& entry : ships_fighters_to_add_back) { + auto ship = combat_info.objects.get(entry.first); if (!ship) { - ErrorLogger() << "Couldn't get ship with id " << entry.first << " for fighter to return to..."; + ErrorLogger(combat) << "Couldn't get ship with id " << entry.first << " for fighter to return to..."; continue; } IncreaseStoredFighterCount(ship, entry.second); @@ -1424,41 +1629,13 @@ namespace { } } - std::vector GetWeapons(std::shared_ptr& attacker) { - // loop over weapons of attacking object. each gets a shot at a - // randomly selected target object - std::vector weapons; - - std::shared_ptr attack_ship = std::dynamic_pointer_cast(attacker); - std::shared_ptr attack_planet = std::dynamic_pointer_cast(attacker); - std::shared_ptr attack_fighter = std::dynamic_pointer_cast(attacker); - - if (attack_ship) { - weapons = ShipWeaponsStrengths(attack_ship); // includes info about fighter launches with PC_FIGHTER_BAY part class, and direct fire weapons with PC_DIRECT_WEAPON part class - for (PartAttackInfo& part : weapons) { - if (part.part_class == PC_DIRECT_WEAPON) { - DebugLogger(combat) << "Attacker Ship direct weapon: " << part.part_type_name - << " attack: " << part.part_attack; - } else if (part.part_class == PC_FIGHTER_BAY) { - DebugLogger(combat) << "Attacker Ship fighter launch: " << part.fighters_launched - << " damage: " << part.fighter_damage; - } - } - - } else if (attack_planet) { // treat planet defenses as direct fire weapon - weapons.push_back(PartAttackInfo(PC_DIRECT_WEAPON, UserStringNop("DEF_DEFENSE"), attack_planet->CurrentMeterValue(METER_DEFENSE))); + void CombatRound(AutoresolveInfo& combat_state) { + CombatInfo& combat_info = combat_state.combat_info; - } else if (attack_fighter) { // treat fighter damage as direct fire weapon - weapons.push_back(PartAttackInfo(PC_DIRECT_WEAPON, UserStringNop("FT_WEAPON_1"), attack_fighter->Damage())); - } - return weapons; - } - - void CombatRound(int bout, CombatInfo& combat_info, AutoresolveInfo& combat_state) { - BoutEvent::BoutEventPtr bout_event = std::make_shared(bout); + auto bout_event = std::make_shared(combat_info.bout); combat_info.combat_events.push_back(bout_event); if (combat_state.valid_attacker_object_ids.empty()) { - DebugLogger(combat) << "Combat bout " << bout << " aborted due to no remaining attackers."; + DebugLogger(combat) << "Combat bout " << combat_info.bout << " aborted due to no remaining attackers."; return; } @@ -1467,54 +1644,51 @@ namespace { DebugLogger() << [&shuffled_attackers](){ std::stringstream ss; - ss << "Attacker IDs:"; + ss << "Attacker IDs: ["; for (int attacker : shuffled_attackers) - { ss << attacker; } + { ss << attacker << " "; } + ss << "]"; return ss.str(); }(); - AttacksEventPtr attacks_event = std::make_shared(); + auto attacks_event = std::make_shared(); bout_event->AddEvent(attacks_event); - auto fighter_on_fighter_event = std::make_shared(bout); + auto fighter_on_fighter_event = std::make_shared(combat_info.bout); bout_event->AddEvent(fighter_on_fighter_event); int round = 1; // counter of events during the current combat bout - // Planets are processed first so that they still have full power as intended, + + // todo: cache results of GetWeapons(attacker) to avoid re-calling multiple times per combat. + // todo: and pass into ShootAllWeapons which also calls that function + + + // Process planets attacks first so that they still have full power, // despite their attack power depending on something (their defence meter) // that processing shots at them may reduce. - for (int attacker_id : shuffled_attackers) { - std::shared_ptr attacker = combat_info.objects.Object(attacker_id); - - if (!attacker) { - ErrorLogger() << "CombatRound couldn't get object with id " << attacker_id; + for (const auto& planet : combat_info.objects.find(shuffled_attackers)) { + if (!planet) continue; - } - if (attacker->ObjectType() != OBJ_PLANET) { - continue; // fighter and ship attacks processed below - } - if (!ObjectCanAttack(attacker)) { - DebugLogger() << "Planet " << attacker->Name() << " could not attack."; + if (!ObjectCanAttack(planet)) { + DebugLogger() << "Planet " << planet->Name() << " could not attack."; continue; } - DebugLogger(combat) << "Planet: " << attacker->Name(); + DebugLogger(combat) << "Planet: " << planet->Name(); - std::vector weapons = GetWeapons(attacker); // includes info about fighter launches with PC_FIGHTER_BAY part class, and direct fire weapons (ships, planets, or fighters) with PC_DIRECT_WEAPON part class - WeaponsPlatformEvent::WeaponsPlatformEventPtr platform_event - = std::make_shared(bout, attacker_id, attacker->Owner()); + auto platform_event = std::make_shared( + combat_info.bout, planet->ID(), planet->Owner()); attacks_event->AddEvent(platform_event); - ShootAllWeapons(attacker, weapons, combat_state, bout, round++, attacks_event, platform_event, fighter_on_fighter_event); + + ShootAllWeapons(planet, combat_state, round++, + attacks_event, platform_event, fighter_on_fighter_event); } - // now process ship and fighter attacks - for (int attacker_id : shuffled_attackers) { - std::shared_ptr attacker = combat_info.objects.Object(attacker_id); - if (!attacker) { - ErrorLogger() << "CombatRound couldn't get object with id " << attacker_id; + // Process ship and fighter attacks + for (const auto& attacker : combat_info.objects.find(shuffled_attackers)) { + if (!attacker) continue; - } if (attacker->ObjectType() == OBJ_PLANET) { continue; // planet attacks processed above } @@ -1524,95 +1698,124 @@ namespace { } DebugLogger(combat) << "Attacker: " << attacker->Name(); - // loop over weapons of the attacking object. each gets a shot at a - // randomly selected target object - std::vector weapons = GetWeapons(attacker); // includes info about fighter launches with PC_FIGHTER_BAY part class, and direct fire weapons (ships, planets, or fighters) with PC_DIRECT_WEAPON part class - WeaponsPlatformEvent::WeaponsPlatformEventPtr platform_event - = std::make_shared(bout, attacker_id, attacker->Owner()); - ShootAllWeapons(attacker, weapons, combat_state, bout, round++, attacks_event, platform_event, fighter_on_fighter_event); + auto platform_event = std::make_shared( + combat_info.bout, attacker->ID(), attacker->Owner()); + + ShootAllWeapons(attacker, combat_state, round++, + attacks_event, platform_event, fighter_on_fighter_event); + if (!platform_event->AreSubEventsEmpty(attacker->Owner())) attacks_event->AddEvent(platform_event); } - // now launch fighters (which can attack in any subsequent combat bouts) - // no point is launching fighters during the last bout, as they will not - // get any chance to attack during this combat - if (bout <= GetGameRules().Get("RULE_NUM_COMBAT_ROUNDS") - 1) { - FighterLaunchesEventPtr launches_event = std::make_shared(); - for (int attacker_id : shuffled_attackers) { - std::shared_ptr attacker = combat_info.objects.Object(attacker_id); + auto stealth_change_event = std::make_shared(combat_info.bout); - if (!attacker) { - ErrorLogger() << "CombatRound couldn't get object with id " << attacker_id; + // Launch fighters (which can attack in any subsequent combat bouts). + // There is no point to launching fighters during the last bout, since + // they won't get any chance to attack during this combat + if (combat_info.bout < GetGameRules().Get("RULE_NUM_COMBAT_ROUNDS")) { + auto launches_event = std::make_shared(); + for (const auto& attacker : combat_info.objects.find(shuffled_attackers)) { + if (!attacker) continue; - } - if (attacker->ObjectType() != OBJ_SHIP) { - continue; // currently only ships can launch fighters - } if (!ObjectCanAttack(attacker)) { DebugLogger() << "Attacker " << attacker->Name() << " could not attack."; continue; } - std::vector weapons = GetWeapons(attacker); // includes info about fighter launches with PC_FIGHTER_BAY part class, and direct fire weapons (ships, planets, or fighters) with PC_DIRECT_WEAPON part class + auto weapons = GetWeapons(attacker); // includes info about fighter launches with PC_FIGHTER_BAY part class, and direct fire weapons (ships, planets, or fighters) with PC_DIRECT_WEAPON part class - LaunchFighters(attacker, weapons, combat_state, bout, round++, launches_event); + LaunchFighters(attacker, weapons, combat_state, round++, + launches_event); DebugLogger(combat) << "Attacker: " << attacker->Name(); + + // Set launching carrier as at least basically visible to other empires. + if (!launches_event->AreSubEventsEmpty(ALL_EMPIRES)) { + for (auto detector_empire_id : combat_info.empire_ids) { + Visibility initial_vis = combat_info.empire_object_visibility[detector_empire_id][attacker->ID()]; + TraceLogger(combat) << "Pre-attack visibility of launching carrier id: " << attacker->ID() + << " by empire: " << detector_empire_id << " was: " << initial_vis; + + if (initial_vis >= VIS_BASIC_VISIBILITY) + continue; + + combat_info.empire_object_visibility[detector_empire_id][attacker->ID()] = VIS_BASIC_VISIBILITY; + + DebugLogger(combat) << " ... Setting post-attack visability to " << VIS_BASIC_VISIBILITY; + + // record visibility change event due to attack + // FIXME attacker, TARGET, attacker empire, target empire, visibility + stealth_change_event->AddEvent(attacker->ID(), + attacker->ID(), + attacker->Owner(), + detector_empire_id, + VIS_BASIC_VISIBILITY); + } + } + } if (!launches_event->AreSubEventsEmpty(ALL_EMPIRES)) bout_event->AddEvent(launches_event); } - // Stealthed attackers have now revealed themselves to their targets. - // Process this for each new combat event. - std::shared_ptr stealth_change_event = std::make_shared(bout); - std::vector attacks_this_bout = attacks_event->SubEvents(ALL_EMPIRES); - for (ConstCombatEventPtr this_event : attacks_this_bout) { - // mark attacker as valid target for attacked object's owner, so that regardless - // of visibility the attacker can be counter-attacked in subsequent rounds if it - // was not already attackable + + // Create weapon fire events and mark attackers as visible to other battle participants + auto attacks_this_bout = attacks_event->SubEvents(ALL_EMPIRES); + for (auto this_event : attacks_this_bout) { + // Generate attack events std::vector> weapon_fire_events; - if (std::shared_ptr naked_fire_event - = std::dynamic_pointer_cast(this_event)) { + if (auto naked_fire_event = std::dynamic_pointer_cast(this_event)) { weapon_fire_events.push_back(naked_fire_event); - } else if (std::shared_ptr weapons_platform - = std::dynamic_pointer_cast(this_event)) { - for (ConstCombatEventPtr more_event : weapons_platform->SubEvents(ALL_EMPIRES)){ - if (std::shared_ptr this_attack - = std::dynamic_pointer_cast(more_event)) { + + } else if (auto weapons_platform = std::dynamic_pointer_cast(this_event)) { + for (auto more_event : weapons_platform->SubEvents(ALL_EMPIRES)) { + if (auto this_attack = std::dynamic_pointer_cast(more_event)) weapon_fire_events.push_back(this_attack); - } } } - for (std::shared_ptr this_attack : weapon_fire_events) { - combat_info.ForceAtLeastBasicVisibility(this_attack->attacker_id, this_attack->target_id); - int target_empire = combat_info.objects.Object(this_attack->target_id)->Owner(); - std::set::iterator attacker_targettable_it - = combat_state.empire_infos[target_empire].target_ids.find(this_attack->attacker_id); - if (attacker_targettable_it == combat_state.empire_infos[target_empire].target_ids.end()) { - combat_state.empire_infos[target_empire].target_ids.insert( - attacker_targettable_it, this_attack->attacker_id); - int attacker_empire = combat_info.objects.Object(this_attack->attacker_id)->Owner(); - stealth_change_event->AddEvent(this_attack->attacker_id, this_attack->target_id, - attacker_empire, target_empire, VIS_BASIC_VISIBILITY); + // Set attacker as at least basically visible to other empires. + for (auto this_attack : weapon_fire_events) { + for (auto detector_empire_id : combat_info.empire_ids) { + Visibility initial_vis = combat_info.empire_object_visibility[detector_empire_id][this_attack->attacker_id]; + TraceLogger(combat) << "Pre-attack visibility of attacker id: " << this_attack->attacker_id + << " by empire: " << detector_empire_id << " was: " << initial_vis; + + if (initial_vis >= VIS_BASIC_VISIBILITY) + continue; + + combat_info.empire_object_visibility[detector_empire_id][this_attack->attacker_id] = VIS_BASIC_VISIBILITY; + + DebugLogger(combat) << " ... Setting post-attack visability to " << VIS_BASIC_VISIBILITY; + + // record visibility change event due to attack + stealth_change_event->AddEvent(this_attack->attacker_id, + this_attack->target_id, + this_attack->attacker_owner_id, + this_attack->target_owner_id, + VIS_BASIC_VISIBILITY); } } } + if (!stealth_change_event->AreSubEventsEmpty(ALL_EMPIRES)) combat_info.combat_events.push_back(stealth_change_event); /// Remove all who died in the bout - combat_state.CullTheDead(bout, bout_event); + combat_state.CullTheDead(combat_info.bout, bout_event); + + // Backpropagate meters so that next round tests can use the results of the previous round + for (auto obj : combat_info.objects.all()) + obj->BackPropagateMeters(); } } void AutoResolveCombat(CombatInfo& combat_info) { - if (combat_info.objects.Empty()) + if (combat_info.objects.empty()) return; - std::shared_ptr system = combat_info.objects.Object(combat_info.system_id); + auto system = combat_info.objects.get(combat_info.system_id); if (!system) ErrorLogger() << "AutoResolveCombat couldn't get system with id " << combat_info.system_id; else @@ -1625,8 +1828,8 @@ void AutoResolveCombat(CombatInfo& combat_info) { int base_seed = 0; if (GetGameRules().Get("RULE_RESEED_PRNG_SERVER")) { //static boost::hash cs_string_hash; - // todo: salt further with galaxy setup seed - base_seed = combat_info.objects.begin()->ID() + CurrentTurn(); + // TODO: salt further with galaxy setup seed + base_seed = (*combat_info.objects.all().begin())->ID() + CurrentTurn(); } // compile list of valid objects to attack or be attacked in this combat @@ -1634,7 +1837,7 @@ void AutoResolveCombat(CombatInfo& combat_info) { combat_info.combat_events.push_back( std::make_shared( - combat_state.ReportInvisibleObjects(combat_info))); + combat_state.ReportInvisibleObjects())); // run multiple combat "bouts" during which each combat object can take // action(s) such as shooting at target(s) or launching fighters @@ -1650,29 +1853,22 @@ void AutoResolveCombat(CombatInfo& combat_info) { break; } - DebugLogger(combat) << "Combat at " << system->Name() << " (" << combat_info.system_id << ") Bout " << bout; - - CombatRound(bout, combat_info, combat_state); + DebugLogger(combat) << "Combat at " << system->Name() << " (" + << combat_info.system_id << ") Bout " << bout; + combat_info.bout = bout; + CombatRound(combat_state); last_bout = bout; } // end for over combat arounds - FighterLaunchesEventPtr launches_event = std::make_shared(); + auto launches_event = std::make_shared(); combat_info.combat_events.push_back(launches_event); RecoverFighters(combat_info, last_bout, launches_event); - // ensure every participant knows what happened. - // TODO: assemble list of objects to copy for each empire. this should - // include objects the empire already knows about with standard - // visibility system, and also any objects the empire knows are - // destroyed during this combat... - for (std::map::value_type& entry : combat_info.empire_known_objects) - { entry.second.Copy(combat_info.objects); } - DebugLogger(combat) << "AutoResolveCombat objects after resolution: " << combat_info.objects.Dump(); DebugLogger(combat) << "combat event log start:"; - for (CombatEventPtr event : combat_info.combat_events) - { DebugLogger(combat) << event->DebugString(); } + for (auto event : combat_info.combat_events) + DebugLogger(combat) << event->DebugString(); DebugLogger(combat) << "combat event log end:"; } diff --git a/combat/CombatSystem.h b/combat/CombatSystem.h index 6a1e23024e4..9d7366992e5 100644 --- a/combat/CombatSystem.h +++ b/combat/CombatSystem.h @@ -2,17 +2,21 @@ #define _CombatSystem_h_ #include "../universe/Universe.h" +#include "../universe/ScriptingContext.h" #include "../util/AppInterface.h" #include "CombatEvent.h" +#include +#include #include + /** Contains information about the state of a combat before or after the combat * occurs. */ -struct CombatInfo { +struct CombatInfo : public ScriptingCombatInfo { public: /** \name Structors */ //@{ - CombatInfo(); + CombatInfo() = default; CombatInfo(int system_id_, int turn_); ///< ctor taking system id where combat occurs and game turn on which combat occurs //@} @@ -23,44 +27,35 @@ struct CombatInfo { //@} /** \name Mutators */ //@{ - //void Clear(); ///< cleans up contents - /** Returns System object in this CombatInfo's objects if one exists with id system_id. */ std::shared_ptr GetSystem(); - /** Reveal stealthed attacker to their target's empires. */ - void ForceAtLeastBasicVisibility(int attacker_id, int target_id); - - //@} + int turn = INVALID_GAME_TURN; ///< main game turn + int system_id = INVALID_OBJECT_ID; ///< ID of system where combat is occurring (could be INVALID_OBJECT_ID ?) + std::set empire_ids; ///< IDs of empires involved in combat + std::set damaged_object_ids; ///< ids of objects damaged during this battle + std::set destroyed_object_ids; ///< ids of objects destroyed during this battle + std::map> destroyed_object_knowers; ///< indexed by empire ID, the set of ids of objects the empire knows were destroyed during the combat + std::vector combat_events; ///< list of combat attack events that occur in combat - int turn; ///< main game turn - int system_id; ///< ID of system where combat is occurring (could be INVALID_OBJECT_ID ?) - std::set empire_ids; ///< IDs of empires involved in combat - ObjectMap objects; ///< actual state of objects relevant to combat - std::map empire_known_objects; ///< each empire's latest known state of objects relevant to combat - std::set damaged_object_ids; ///< ids of objects damaged during this battle - std::set destroyed_object_ids; ///< ids of objects destroyed during this battle - std::map> destroyed_object_knowers; ///< indexed by empire ID, the set of ids of objects the empire knows were destroyed during the combat - Universe::EmpireObjectVisibilityMap empire_object_visibility; ///< indexed by empire id and object id, the visibility level the empire has of each object. may be increased during battle - std::vector combat_events; ///< list of combat attack events that occur in combat + float GetMonsterDetection() const; private: void GetEmpireIdsToSerialize( std::set& filtered_empire_ids, int encoding_empire) const; void GetObjectsToSerialize( ObjectMap& filtered_objects, int encoding_empire) const; - void GetEmpireKnownObjectsToSerialize( std::map& filtered_empire_known_objects, int encoding_empire) const; void GetDamagedObjectsToSerialize( std::set& filtered_damaged_objects, int encoding_empire) const; void GetDestroyedObjectsToSerialize( std::set& filtered_destroyed_objects, int encoding_empire) const; void GetDestroyedObjectKnowersToSerialize(std::map>& filtered_destroyed_object_knowers, int encoding_empire) const; void GetEmpireObjectVisibilityToSerialize(Universe::EmpireObjectVisibilityMap& filtered_empire_object_visibility, int encoding_empire) const; void GetCombatEventsToSerialize( std::vector& filtered_combat_events, int encoding_empire) const; - void InitializeObjectVisibility(); + void InitializeObjectVisibility(); friend class boost::serialization::access; - template + template void save(Archive & ar, const unsigned int version) const; - template + template void load(Archive & ar, const unsigned int version); BOOST_SERIALIZATION_SPLIT_MEMBER() }; @@ -68,12 +63,11 @@ struct CombatInfo { /** Auto-resolves a battle. */ void AutoResolveCombat(CombatInfo& combat_info); -template +template void CombatInfo::save(Archive & ar, const unsigned int version) const { std::set filtered_empire_ids; ObjectMap filtered_objects; - std::map filtered_empire_known_objects; std::set filtered_damaged_object_ids; std::set filtered_destroyed_object_ids; std::map> filtered_destroyed_object_knowers; @@ -82,7 +76,6 @@ void CombatInfo::save(Archive & ar, const unsigned int version) const GetEmpireIdsToSerialize( filtered_empire_ids, GetUniverse().EncodingEmpire()); GetObjectsToSerialize( filtered_objects, GetUniverse().EncodingEmpire()); - GetEmpireKnownObjectsToSerialize( filtered_empire_known_objects, GetUniverse().EncodingEmpire()); GetDamagedObjectsToSerialize( filtered_damaged_object_ids, GetUniverse().EncodingEmpire()); GetDestroyedObjectsToSerialize( filtered_destroyed_object_ids, GetUniverse().EncodingEmpire()); GetDestroyedObjectKnowersToSerialize( filtered_destroyed_object_knowers, GetUniverse().EncodingEmpire()); @@ -93,7 +86,6 @@ void CombatInfo::save(Archive & ar, const unsigned int version) const & BOOST_SERIALIZATION_NVP(system_id) & BOOST_SERIALIZATION_NVP(filtered_empire_ids) & BOOST_SERIALIZATION_NVP(filtered_objects) - & BOOST_SERIALIZATION_NVP(filtered_empire_known_objects) & BOOST_SERIALIZATION_NVP(filtered_damaged_object_ids) & BOOST_SERIALIZATION_NVP(filtered_destroyed_object_ids) & BOOST_SERIALIZATION_NVP(filtered_destroyed_object_knowers) @@ -101,12 +93,11 @@ void CombatInfo::save(Archive & ar, const unsigned int version) const & BOOST_SERIALIZATION_NVP(filtered_combat_events); } -template +template void CombatInfo::load(Archive & ar, const unsigned int version) { std::set filtered_empire_ids; ObjectMap filtered_objects; - std::map filtered_empire_known_objects; std::set filtered_damaged_object_ids; std::set filtered_destroyed_object_ids; std::map> filtered_destroyed_object_knowers; @@ -117,7 +108,6 @@ void CombatInfo::load(Archive & ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(system_id) & BOOST_SERIALIZATION_NVP(filtered_empire_ids) & BOOST_SERIALIZATION_NVP(filtered_objects) - & BOOST_SERIALIZATION_NVP(filtered_empire_known_objects) & BOOST_SERIALIZATION_NVP(filtered_damaged_object_ids) & BOOST_SERIALIZATION_NVP(filtered_destroyed_object_ids) & BOOST_SERIALIZATION_NVP(filtered_destroyed_object_knowers) @@ -126,7 +116,6 @@ void CombatInfo::load(Archive & ar, const unsigned int version) empire_ids.swap( filtered_empire_ids); objects.swap( filtered_objects); - empire_known_objects.swap( filtered_empire_known_objects); damaged_object_ids.swap( filtered_damaged_object_ids); destroyed_object_ids.swap( filtered_destroyed_object_ids); destroyed_object_knowers.swap(filtered_destroyed_object_knowers); diff --git a/default/README.md b/default/README.md index 15ed793d02c..b2c73f50f3d 100644 --- a/default/README.md +++ b/default/README.md @@ -14,7 +14,7 @@ This file should give a guideline for any entries or files required by FreeOrion if a new resource directory is created. An alternate resource directory can be specified with the ---resource-dir command line flag, or by changing the resource-dir node in the +--resource.path command line flag, or by changing the resource.path node in the user's config.xml. See http://www.freeorion.org/index.php/Config.xml for details on the config.xml file. @@ -43,5 +43,3 @@ content changes. See file for details. **Required file and content.** * empire_colors.xml - Color options available for empires to select from. **This file containing at least one GG::Clr node is required.** -* global_settings.txt - Contains entries which control the game mechanics. -See file for details. **This file and the entries it contains are required.** diff --git a/default/content_specific_parameters.txt b/default/content_specific_parameters.txt index 0b7071f16e7..b04a6c49d15 100644 --- a/default/content_specific_parameters.txt +++ b/default/content_specific_parameters.txt @@ -18,16 +18,12 @@ SUPERCONDUCTOR_SPECIAL ''' - -## FUNCTIONAL_CENSUS_TAG_ORDER is a whitespace separated list, acts as a filter for which Tags will be presented -## in Census pop-up, and specifies order in which they will be displayed. -## This list should not include any non-species tags, since those won't be displayed in the census anyway. - -FUNCTIONAL_CENSUS_TAG_ORDER -'''LITHIC -ORGANIC -PHOTOTROPHIC -ROBOTIC -SELF_SUSTAINING -TELEPATHIC''' -##STYLISH // add to tag order list if ever this tag is given a function +## FUNCTIONAL_HULL_DESC_TAGS_LIST is an ordered, whitespace separated list, +# defining which hull tags on HULLs which are shown in the given order in the hull description. +# There should be corresponding HULL_DESC_* stringtable entries (e.g. HULL_DESC_GREAT_FUEL_EFFICIENCY) +FUNCTIONAL_HULL_DESC_TAGS_LIST +'''BAD_FUEL_EFFICIENCY +AVERAGE_FUEL_EFFICIENCY +GOOD_FUEL_EFFICIENCY +GREAT_FUEL_EFFICIENCY +''' diff --git a/default/credits.xml b/default/credits.xml index 8b8f1305fb6..afd0c30db0c 100644 --- a/default/credits.xml +++ b/default/credits.xml @@ -3,7 +3,6 @@ - @@ -15,6 +14,7 @@ + @@ -59,13 +59,18 @@ - + + + + + + @@ -86,6 +91,9 @@ + + + @@ -109,6 +117,7 @@ + @@ -135,6 +144,8 @@ + + - + - + diff --git a/default/global_settings.txt b/default/global_settings.txt deleted file mode 100644 index 9b635a44869..00000000000 --- a/default/global_settings.txt +++ /dev/null @@ -1,27 +0,0 @@ - -# Note: The longer term vision is for these kinds of parameters to mostly or entirely be set as part of the Game Setup dialog rather than here. -# To minimize potential conflict with stringtable tags, all keys here should begin with "FUNCTIONAL_" - - -## FUNCTIONAL_PRODUCTION_QUEUE_FRONTLOAD_FACTOR and FUNCTIONAL_PRODUCTION_QUEUE_TOPPING_UP_FACTOR specify settings that affect how the ProductionQueue will limit -## allocation towards building a given item on a given turn. The base amount of maximum allocation per turn (if the player has enough PP available) is the item's total -## cost, divided over its minimum build time. Sometimes complications arise, though, which unexpectedly delay the completion even if the item had been fully-funded -## every turn, because costs have risen partway through (such as due to increasing ship costs resulting from recent ship constructoin completion and ensuing increase -## of Fleet Maintenance costs. These two settings provide a mechanism for some allocation leeway to deal with mid-build cost increases without causing the project -## completion to take an extra turn because of the small bit of increased cost. The settings differ in the timing of the extra allocation allowed. -## Both factors have a minimum value of 0.0 and a maximum value of 0.3. - -## Making the frontloaded factor greater than zero increases the per-turn allocation cap by the specified percentage (so it always spreads the extra allocation across all turns). -## Making the topping-up option nonzero allows the final turn allocation cap to be increased by the specified percentage of the total cost, if needed (and then subject to -## availability of course). They can both be nonzero, although to avoid that introducing too much interaction complexity into the minimum build time safeguard for topping-up, -## the topping-up percentage will be reduced by the frontloading setting. - -## Note that for very small values of the options (less than 5%), when dealing with very low cost items the effect/protection may be noticeably less than expected because of -## interactions with the ProductionQueue Epsilon value (0.01) - -FUNCTIONAL_PRODUCTION_QUEUE_FRONTLOAD_FACTOR -0.0 - -FUNCTIONAL_PRODUCTION_QUEUE_TOPPING_UP_FACTOR -0.20 - diff --git a/default/python/AI/AIDependencies.py b/default/python/AI/AIDependencies.py index 148f9e6c2ed..6be11a223a7 100644 --- a/default/python/AI/AIDependencies.py +++ b/default/python/AI/AIDependencies.py @@ -20,8 +20,6 @@ my_industry = AIDependencies.INDUSTRY_PER_POP * my_population """ import freeOrionAIInterface as fo # interface used to interact with FreeOrion AI client # pylint: disable=import-error -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) # Note re common dictionary lookup structure, "PlanetSize-Dependent-Lookup": # Many dictionaries herein (a prime example being the building_supply dictionary) have a primary key (such as @@ -48,6 +46,7 @@ PROT_FOCUS_MULTIPLIER = 2.0 TECH_COST_MULTIPLIER = 2.0 +FOCUS_CHANGE_PENALTY = 1 # Colonization details COLONY_POD_COST = 120 # TODO Query directly from part @@ -69,6 +68,19 @@ # Constants defined by the C++ game engine INVALID_ID = -1 +ALL_EMPIRES = -1 + + +class Tags: + POPULATION = "POPULATION" + INDUSTRY = "INDUSTRY" + WEAPONS = "WEAPONS" + RESEARCH = "RESEARCH" + SUPPLY = "SUPPLY" + ATTACKTROOPS = "ATTACKTROOPS" + STEALTH = "STEALTH" + SHIELDS = "SHIELDS" + FUEL = "FUEL" # @@ -83,7 +95,7 @@ "environment_bonus": [0, -4, -2, 0, 3], "GRO_SYMBIOTIC_BIO": [0, 0, 1, 1, 1], "GRO_XENO_GENETICS": [0, 1, 2, 2, 0], - "GRO_XENO_HYBRID": [0, 2, 1, 0, 0], + "GRO_XENO_HYBRIDS": [0, 2, 1, 0, 0], "GRO_CYBORG": [0, 2, 0, 0, 0], } @@ -91,7 +103,7 @@ # - [[LATE_PRIORITY]] or later: POP_SIZE_MOD_MAP_NOT_MODIFIED_BY_SPECIES = { "GRO_SUBTER_HAB": [0, 1, 1, 1, 1], - "CON_NDIM_STRUC": [0, 2, 2, 2, 2], + "CON_NDIM_STRC": [0, 2, 2, 2, 2], "CON_ORBITAL_HAB": [0, 1, 1, 1, 1], } @@ -101,8 +113,55 @@ "GRO_PLANET_ECOL": [0, 0, 0, 1, 1], "GRO_SYMBIOTIC_BIO": [0, 0, 0, -1, -1], } + +# phototrophic star coefficients +POP_MOD_PHOTOTROPHIC_STAR_MAP = {fo.starType.blue: 3, fo.starType.white: 1.5, fo.starType.red: -1, + fo.starType.neutron: -1, fo.starType.blackHole: -10, fo.starType.noStar: -10} + +# lightsensitive star coefficients +TAG_LIGHT_SENSITIVE = "LIGHT_SENSITIVE" +POP_MOD_LIGHTSENSITIVE_STAR_MAP = {fo.starType.blue: -2, fo.starType.white: -1} + +# the percentage of normal population that a species with Gaseous tag has +GASEOUS_POP_FACTOR = 0.50 + # +# +DETECTION_TECH_STRENGTHS = { + "SPY_DETECT_1": 10, + "SPY_DETECT_2": 30, + "SPY_DETECT_3": 50, + "SPY_DETECT_4": 70, + "SPY_DETECT_5": 200, +} +# + +# +PRIMARY_FOCS_STEALTH_LEVELS = { + "LOW_STEALTH": 20, + "MEDIUM_STEALTH": 40, + "HIGH_STEALTH": 60, + "VERY_HIGH_STEALTH": 80, +} + +STEALTH_SPECIAL_STRENGTHS = { + "CLOUD_COVER_SLAVE_SPECIAL": PRIMARY_FOCS_STEALTH_LEVELS["LOW_STEALTH"], + "VOLCANIC_ASH_SLAVE_SPECIAL": PRIMARY_FOCS_STEALTH_LEVELS["MEDIUM_STEALTH"], + "DIM_RIFT_SLAVE_SPECIAL": PRIMARY_FOCS_STEALTH_LEVELS["HIGH_STEALTH"], + "VOID_SLAVE_SPECIAL": PRIMARY_FOCS_STEALTH_LEVELS["VERY_HIGH_STEALTH"], +} + +BASE_PLANET_STEALTH = 5 + +STEALTH_STRENGTHS_BY_SPECIES_TAG = { + "BAD": -PRIMARY_FOCS_STEALTH_LEVELS["LOW_STEALTH"], + "GOOD": PRIMARY_FOCS_STEALTH_LEVELS["LOW_STEALTH"], + "GREAT": PRIMARY_FOCS_STEALTH_LEVELS["MEDIUM_STEALTH"], + "ULTIMATE": PRIMARY_FOCS_STEALTH_LEVELS["HIGH_STEALTH"], + "INFINITE": 500, # used by super testers +} +# # # @@ -167,7 +226,10 @@ # # +COMPUTRONIUM_SPECIAL = "COMPUTRONIUM_SPECIAL" COMPUTRONIUM_RES_MULTIPLIER = 1.0 + +ANCIENT_RUINS_SPECIAL = "ANCIENT_RUINS_SPECIAL" # # @@ -184,6 +246,19 @@ } # +# +PANOPTICON_SPECIAL = "PANOPTICON_SPECIAL" +PANOPTICON_DETECTION_BONUS = 10 +# + +# +# (special_name, defense&shield_bonus) +TECH_NATIVE_SPECIALS = { + "MODERATE_TECH_NATIVES_SPECIAL": {'defense': 10, 'shield': 10}, + "HIGH_TECH_NATIVES_SPECIAL": {'defense': 30, 'shield': 30}, +} +# + # @@ -215,6 +290,13 @@ SPY_STEALTH_1 = "SPY_STEALTH_1" SPY_STEALTH_2 = "SPY_STEALTH_2" + +EXOBOT_TECH_NAME = "PRO_EXOBOTS" + +SHP_WEAPON_ARC_DISRUPTOR_1 = "SHP_WEAPON_ARC_DISRUPTOR_1" +SHP_WEAPON_ARC_DISRUPTOR_2 = "SHP_WEAPON_ARC_DISRUPTOR_2" +SHP_WEAPON_ARC_DISRUPTOR_3 = "SHP_WEAPON_ARC_DISRUPTOR_3" + # @@ -274,6 +356,19 @@ ] # +# +FUEL_TANK_UPGRADE_DICT = { + # "PARTNAME": tuple((tech_name, fuel_upgrade), (tech_name2, fuel_upgrade2), ...) + "FU_BASIC_TANK": (("SHP_DEUTERIUM_TANK", 0.5), ("SHP_ANTIMATTER_TANK", 1.5)), + "FU_RAMSCOOP": (), + "FU_ZERO_FUEL": (), +} + +# DO NOT TOUCH THIS ENTRY BUT UPDATE FUEL_TANK_UPGRADE_DICT INSTEAD! +FUEL_UPGRADE_TECHS = frozenset(tech_name for _dict in (FUEL_TANK_UPGRADE_DICT, ) + for tups in _dict.values() for (tech_name, _) in tups) +# + # # TODO (Morlic): Consider using only 1 dict with (capacity, secondaryStat) tuple as entries WEAPON_UPGRADE_DICT = { @@ -283,6 +378,7 @@ "SR_WEAPON_2_1": tuple(("SHP_WEAPON_2_%d" % i, 2) for i in [2, 3, 4]), "SR_WEAPON_3_1": tuple(("SHP_WEAPON_3_%d" % i, 3) for i in [2, 3, 4]), "SR_WEAPON_4_1": tuple(("SHP_WEAPON_4_%d" % i, 5) for i in [2, 3, 4]), + "SR_ARC_DISRUPTOR": tuple(("SHP_WEAPON_ARC_DISRUPTOR_%d" % i, i) for i in [2, 3]), "SR_SPINAL_ANTIMATTER": (), } @@ -293,27 +389,31 @@ "SR_WEAPON_2_1": (), "SR_WEAPON_3_1": (), "SR_WEAPON_4_1": (), + "SR_ARC_DISRUPTOR": (), "SR_SPINAL_ANTIMATTER": (), } FIGHTER_DAMAGE_UPGRADE_DICT = { # "PARTNAME": tuple((tech_name, dmg_upgrade), (tech_name2, dmg_upgrade2), ...) - "FT_HANGAR_1": (("SHP_FIGHTERS_2", 1), ("SHP_FIGHTERS_3", 1), ("SHP_FIGHTERS_4", 1)), - "FT_HANGAR_2": (("SHP_FIGHTERS_2", 2), ("SHP_FIGHTERS_3", 3), ("SHP_FIGHTERS_4", 5)), - "FT_HANGAR_3": (("SHP_FIGHTERS_2", 3), ("SHP_FIGHTERS_3", 4), ("SHP_FIGHTERS_4", 7)), - "FT_HANGAR_4": (), + "FT_HANGAR_1": (("SHP_FIGHTERS_2", 0), ("SHP_FIGHTERS_3", 0), ("SHP_FIGHTERS_4", 0)), + "FT_HANGAR_2": (("SHP_FIGHTERS_2", 2), ("SHP_FIGHTERS_3", 2), ("SHP_FIGHTERS_4", 2)), + "FT_HANGAR_3": (("SHP_FIGHTERS_2", 3), ("SHP_FIGHTERS_3", 3), ("SHP_FIGHTERS_4", 3)), + "FT_HANGAR_4": (("SHP_FIGHTERS_2", 6), ("SHP_FIGHTERS_3", 6), ("SHP_FIGHTERS_4", 6)), } FIGHTER_CAPACITY_UPGRADE_DICT = { # "PARTNAME": tuple((tech_name, capacity_upgrade), (tech_name2, capacity_upgrade2), ...) - "FT_HANGAR_1": (), + "FT_HANGAR_1": (("SHP_FIGHTERS_2", 1), ("SHP_FIGHTERS_3", 1), ("SHP_FIGHTERS_4", 1)), "FT_HANGAR_2": (), "FT_HANGAR_3": (), "FT_HANGAR_4": (), } # DO NOT TOUCH THIS ENTRY BUT UPDATE WEAPON_UPGRADE_DICT INSTEAD! -WEAPON_UPGRADE_TECHS = [tech_name for tups in WEAPON_UPGRADE_DICT.values() for (tech_name, _) in tups] +WEAPON_UPGRADE_TECHS = frozenset(tech_name for _dict in (WEAPON_UPGRADE_DICT, WEAPON_ROF_UPGRADE_DICT) + for tups in _dict.values() for (tech_name, _) in tups) +FIGHTER_UPGRADE_TECHS = frozenset(tech_name for _dict in (FIGHTER_DAMAGE_UPGRADE_DICT, FIGHTER_CAPACITY_UPGRADE_DICT) + for tups in _dict.values() for (tech_name, _) in tups) # # @@ -538,13 +638,14 @@ # # species names -SP_LAMBALALAM = "SP_LEMBALALAM" +SP_LEMBALALAM = "SP_LEMBALALAM" # Species modifiers SPECIES_RESEARCH_MODIFIER = {'NO': 0.0, 'BAD': 0.75, 'GOOD': 1.5, 'GREAT': 2.0, 'ULTIMATE': 3.0} SPECIES_INDUSTRY_MODIFIER = {'NO': 0.0, 'BAD': 0.75, 'GOOD': 1.5, 'GREAT': 2.0, 'ULTIMATE': 3.0} SPECIES_POPULATION_MODIFIER = {'BAD': 0.75, 'GOOD': 1.25} -SPECIES_SUPPLY_MODIFIER = {'BAD': 0, 'AVERAGE': 1, 'GREAT': 2, 'ULTIMATE': 3} +SPECIES_SUPPLY_MODIFIER = {'VERY_BAD': -1, 'BAD': 0, 'AVERAGE': 1, 'GREAT': 2, 'ULTIMATE': 3} +SPECIES_FUEL_MODIFIER = {'NO': -100, 'BAD': -0.5, 'AVERAGE': 0, 'GOOD': 0.5, 'GREAT': 1, 'ULTIMATE': 1.5} # EXTINCT_SPECIES = [ @@ -576,11 +677,12 @@ PILOT_FIGHTERDAMAGE_MODIFIER_DICT = { # TRAIT: {hangar_name: effect, hangar_name2: effect2,...} + # TODO FT_HANGAR_1 fighters are not able to attack ships so pilot damage modifier does not apply "NO": {}, - "BAD": {"FT_HANGAR_1": -1, "FT_HANGAR_2": -2, "FT_HANGAR_3": -3, "FT_HANGAR_4": -4}, - "GOOD": {"FT_HANGAR_1": 1, "FT_HANGAR_2": 2, "FT_HANGAR_3": 3, "FT_HANGAR_4": 4}, - "GREAT": {"FT_HANGAR_1": 2, "FT_HANGAR_2": 4, "FT_HANGAR_3": 6, "FT_HANGAR_4": 8}, - "ULTIMATE": {"FT_HANGAR_1": 3, "FT_HANGAR_2": 6, "FT_HANGAR_3": 9, "FT_HANGAR_4": 12}, + "BAD": {"FT_HANGAR_1": 0, "FT_HANGAR_2": -1, "FT_HANGAR_3": -1, "FT_HANGAR_4": -1}, + "GOOD": {"FT_HANGAR_1": 0, "FT_HANGAR_2": 1, "FT_HANGAR_3": 1, "FT_HANGAR_4": 1}, + "GREAT": {"FT_HANGAR_1": 0, "FT_HANGAR_2": 2, "FT_HANGAR_3": 2, "FT_HANGAR_4": 2}, + "ULTIMATE": {"FT_HANGAR_1": 0, "FT_HANGAR_2": 3, "FT_HANGAR_3": 3, "FT_HANGAR_4": 3}, } PILOT_FIGHTER_CAPACITY_MODIFIER_DICT = { @@ -591,6 +693,11 @@ "GREAT": {}, "ULTIMATE": {}, } + +HANGAR_LAUNCH_CAPACITY_MODIFIER_DICT = { + # hangar_name: {bay_name: ((tech_name, effect), ...), bay_name2: ((tech_name, effect), ...} + "FT_HANGAR_1": {"FT_BAY_1": (("SHP_FIGHTERS_1", 1), ("SHP_FIGHTERS_2", 1), ("SHP_FIGHTERS_3", 1), ("SHP_FIGHTERS_4", 1))}, +} # # @@ -600,13 +707,13 @@ # some species have a fixed population SPECIES_FIXED_POPULATION = { # species_name: fixed_population_size - SP_LAMBALALAM: 5.0, + SP_LEMBALALAM: 5.0, } # techs that are unlocked if conquering a planet of a species SPECIES_TECH_UNLOCKS = { # species: [tech1, tech2, ...] - SP_LAMBALALAM: [GRO_LIFE_CYCLE, SPY_STEALTH_1, SPY_STEALTH_2] + SP_LEMBALALAM: [GRO_LIFE_CYCLE, SPY_STEALTH_1, SPY_STEALTH_2] } # @@ -630,12 +737,13 @@ fo.planetSize.medium: 3, fo.planetSize.large: 4, fo.planetSize.huge: 5, - fo.planetSize.gasGiant: 4, + fo.planetSize.gasGiant: 3, }, } # # +BLD_SHIPYARD_ORBITAL_DRYDOCK = "BLD_SHIPYARD_ORBITAL_DRYDOCK" # ship facilities info, dict keyed by building name, value is (min_aggression, prereq_bldg, base_cost, time) # not currently determined dynamically because it is initially used in a location-independent fashion # note that BLD_SHIPYARD_BASE is not an absolute prereq for BLD_NEUTRONIUM_FORGE, but is a practical one @@ -669,6 +777,46 @@ # PART_KRILL_SPAWNER = "SP_KRILL_SPAWNER" + +HULL_EXCLUDED_SHIP_PART_CLASSES = { + "SH_COLONY_BASE": (fo.shipPartClass.fuel, fo.shipPartClass.speed) +} + + +# +# TODO: Inherit from enum.Flag after switch to Python 3.6+ +class CombatTarget: + NONE = 0 + SHIP = 1 << 0 + PLANET = 1 << 1 + FIGHTER = 1 << 2 + ANY = SHIP | PLANET | FIGHTER + + # map from weapon to allowed targets + PART_ALLOWED_TARGETS = { + "SR_WEAPON_0_1": FIGHTER, + "SR_WEAPON_1_1": SHIP | PLANET, + "SR_WEAPON_2_1": SHIP | PLANET, + "SR_WEAPON_3_1": SHIP | PLANET, + "SR_WEAPON_4_1": SHIP | PLANET, + "SR_SPINAL_ANTIMATTER": SHIP | PLANET, + "FT_HANGAR_0": NONE, + "FT_HANGAR_1": FIGHTER, + "FT_HANGAR_2": SHIP | FIGHTER, + "FT_HANGAR_3": SHIP, + "FT_HANGAR_4": SHIP | PLANET, + # monster weapons + "SR_ARC_DISRUPTOR": ANY, + "SR_ICE_BEAM": ANY, + "SR_JAWS": ANY, + "SR_PLASMA_DISCHARGE": ANY, + "SR_SPINES": ANY, + "SR_TENTACLE": ANY, + "FT_HANGAR_KRILL": SHIP | FIGHTER, + } +# + + # # # known tokens the AI can handle @@ -679,6 +827,7 @@ SOLAR_STEALTH = "SOLAR_STEALTH" SPEED = "SPEED" FUEL = "FUEL" +FUEL_EFFICIENCY = "FUEL_EFFICIENCY" SHIELDS = "SHIELDS" STRUCTURE = "STRUCTURE" DETECTION = "DETECTION" # do only specify for hulls if irregular detection @@ -698,7 +847,23 @@ "SHP_FLEET_REPAIR": {REPAIR_PER_TURN: (STRUCTURE, 0.1)}, # 10% of max structure "SHP_ADV_DAM_CONT": {REPAIR_PER_TURN: (STRUCTURE, 0.1)}, # 10% of max structure "SHP_INTSTEL_LOG": {SPEED: 20}, # technically not correct, but as approximation good enough... - "GRO_ENERGY_META": {FUEL: 2}, + "GRO_ENERGY_META": {FUEL: 1}, +} + +DEFAULT_FUEL_EFFICIENCY = 1 +HULL_TAG_EFFECTS = { + "GREAT_FUEL_EFFICIENCY": { + FUEL_EFFICIENCY: 4, + }, + "GOOD_FUEL_EFFICIENCY": { + FUEL_EFFICIENCY: 2, + }, + "AVERAGE_FUEL_EFFICIENCY": { + FUEL_EFFICIENCY: 1, + }, + "BAD_FUEL_EFFICIENCY": { + FUEL_EFFICIENCY: 0.6, + }, } HULL_EFFECTS = { @@ -707,6 +872,9 @@ "SH_ROBOTIC": { REPAIR_PER_TURN: 2, }, + "SH_SPACE_FLUX_BUBBLE": { + STEALTH_MODIFIER: -30, + }, "SH_SPATIAL_FLUX": { STEALTH_MODIFIER: -30, }, diff --git a/default/python/AI/AIFleetMission.py b/default/python/AI/AIFleetMission.py index 463898e2239..d30e6979b55 100644 --- a/default/python/AI/AIFleetMission.py +++ b/default/python/AI/AIFleetMission.py @@ -1,24 +1,25 @@ -import sys +from logging import debug, warning import freeOrionAIInterface as fo # pylint: disable=import-error -from fleet_orders import OrderMove, OrderOutpost, OrderColonize, OrderMilitary, OrderInvade, OrderDefend +# the following import is used for type hinting, which pylint seems not to recognize +from fleet_orders import AIFleetOrder # pylint: disable=unused-import # noqa: F401 +from fleet_orders import OrderMove, OrderPause, OrderOutpost, OrderColonize, OrderMilitary, OrderInvade, OrderDefend import AIstate +import EspionageAI import FleetUtilsAI -import FreeOrionAI as foAI import MoveUtilsAI import MilitaryAI import InvasionAI import CombatRatingsAI -from universe_object import UniverseObject, System, Fleet, Planet +from aistate_interface import get_aistate +from target import TargetSystem, TargetFleet, TargetPlanet from EnumsAI import MissionType from AIDependencies import INVALID_ID - -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) +from freeorion_tools import get_partial_visibility_turn, assertion_fails ORDERS_FOR_MISSION = { - MissionType.EXPLORATION: OrderMove, + MissionType.EXPLORATION: OrderPause, MissionType.OUTPOST: OrderOutpost, MissionType.COLONISATION: OrderColonize, MissionType.INVASION: OrderInvade, @@ -26,55 +27,79 @@ # SECURE is mostly same as MILITARY, but waits for system removal from all targeted system lists # (invasion, colonization, outpost, blockade) before clearing MissionType.SECURE: OrderMilitary, + MissionType.PROTECT_REGION: OrderPause, MissionType.ORBITAL_INVASION: OrderInvade, MissionType.ORBITAL_OUTPOST: OrderOutpost, - MissionType.ORBITAL_DEFENSE: OrderDefend + MissionType.ORBITAL_DEFENSE: OrderDefend, } COMBAT_MISSION_TYPES = ( MissionType.MILITARY, - MissionType.SECURE + MissionType.SECURE, + MissionType.PROTECT_REGION, ) MERGEABLE_MISSION_TYPES = ( MissionType.MILITARY, MissionType.INVASION, + MissionType.PROTECT_REGION, MissionType.ORBITAL_INVASION, MissionType.SECURE, MissionType.ORBITAL_DEFENSE, ) -COMPATIBLE_ROLES_MAP = { - MissionType.ORBITAL_DEFENSE: [MissionType.ORBITAL_DEFENSE], - MissionType.MILITARY: [MissionType.MILITARY], - MissionType.ORBITAL_INVASION: [MissionType.ORBITAL_INVASION], - MissionType.INVASION: [MissionType.INVASION], -} - -class AIFleetMission(object): +class AIFleetMission: """ Stores information about AI mission. Every mission has fleetID and AI targets depending upon AI fleet mission type. - :type target: UniverseObject | None + :type orders: list[AIFleetOrder] + :type target: target.Target | None """ def __init__(self, fleet_id): self.orders = [] - self.fleet = Fleet(fleet_id) + self.fleet = TargetFleet(fleet_id) self.type = None self.target = None + def __setstate__(self, state): + target_type = state.pop("target_type") + if state["target"] is not None: + object_map = {TargetPlanet.object_name: TargetPlanet, + TargetSystem.object_name: TargetSystem, + TargetFleet.object_name: TargetFleet} + state["target"] = object_map[target_type](state["target"]) + + state["fleet"] = TargetFleet(state["fleet"]) + self.__dict__ = state + + def __getstate__(self): + retval = dict(self.__dict__) + # do only store the fleet id not the Fleet object + retval["fleet"] = self.fleet.id + + # store target type and id rather than the object + if self.target is not None: + retval["target_type"] = self.target.object_name + retval["target"] = self.target.id + else: + retval["target_type"] = None + retval["target"] = None + + return retval + def set_target(self, mission_type, target): """ Set mission and target for this fleet. :type mission_type: MissionType - :type target: UniverseObject + :type target: target.Target """ if self.type == mission_type and self.target == target: return if self.type or self.target: - print "Change mission assignment from %s:%s to %s:%s" % (self.type, self.target, mission_type, target) + debug("%s: change mission assignment from %s:%s to %s:%s" % ( + self.fleet, self.type, self.target, mission_type, target)) self.type = mission_type self.target = target @@ -88,7 +113,7 @@ def has_target(self, mission_type, target): Check if fleet has specified mission_type and target. :type mission_type: MissionType - :type target: UniverseObject + :type target: target.Target :rtype: bool """ return self.type == mission_type and self.target == target @@ -102,85 +127,67 @@ def _get_fleet_order_from_target(self, mission_type, target): Get a fleet order according to mission type and target. :type mission_type: MissionType - :type target: UniverseObject + :type target: target.Target :rtype: AIFleetOrder """ - fleet_target = Fleet(self.fleet.id) + fleet_target = TargetFleet(self.fleet.id) return ORDERS_FOR_MISSION[mission_type](fleet_target, target) def check_mergers(self, context=""): """ - If possible and reasonable, merge this fleet with others. + Merge local fleets with same mission into this fleet. :param context: Context of the function call for logging purposes :type context: str """ + debug("Considering to merge %s", self.__str__()) if self.type not in MERGEABLE_MISSION_TYPES: + debug("Mission type does not allow merging.") + return + + if not self.target: + debug("Mission has no valid target - do not merge.") return + universe = fo.getUniverse() empire_id = fo.empireID() + fleet_id = self.fleet.id main_fleet = universe.getFleet(fleet_id) - system_id = main_fleet.systemID - if system_id == INVALID_ID: - return # can't merge fleets in middle of starlane - system_status = foAI.foAIstate.systemStatus[system_id] - destroyed_list = list(universe.destroyedObjectIDs(empire_id)) + main_fleet_system_id = main_fleet.systemID + if main_fleet_system_id == INVALID_ID: + debug("Can't merge: fleet in middle of starlane.") + return + + # only merge PROTECT_REGION if there is any threat near target + if self.type == MissionType.PROTECT_REGION: + neighbor_systems = universe.getImmediateNeighbors(self.target.id, empire_id) + if not any(MilitaryAI.get_system_local_threat(sys_id) + for sys_id in neighbor_systems): + debug("Not merging PROTECT_REGION fleet - no threat nearby.") + return + + destroyed_list = set(universe.destroyedObjectIDs(empire_id)) + aistate = get_aistate() + system_status = aistate.systemStatus[main_fleet_system_id] other_fleets_here = [fid for fid in system_status.get('myFleetsAccessible', []) if fid != fleet_id and fid not in destroyed_list and universe.getFleet(fid).ownedBy(empire_id)] if not other_fleets_here: + debug("No other fleets here") return - target_id = self.target.id if self.target else None - main_fleet_role = foAI.foAIstate.get_fleet_role(fleet_id) for fid in other_fleets_here: - fleet_role = foAI.foAIstate.get_fleet_role(fid) - if fleet_role not in COMPATIBLE_ROLES_MAP[main_fleet_role]: + fleet_mission = aistate.get_fleet_mission(fid) + if fleet_mission.type != self.type or fleet_mission.target != self.target: + debug("Local candidate %s does not have same mission." % fleet_mission) continue - fleet = universe.getFleet(fid) - - if not fleet or fleet.systemID != system_id or len(fleet.shipIDs) == 0: - continue - if not (fleet.speed > 0 or main_fleet.speed == 0): # TODO(Cjkjvfnby) Check this condition - continue - fleet_mission = foAI.foAIstate.get_fleet_mission(fid) - do_merge = False - need_left = 0 - if (main_fleet_role == MissionType.ORBITAL_DEFENSE) or (fleet_role == MissionType.ORBITAL_DEFENSE): - if main_fleet_role == fleet_role: - do_merge = True - elif (main_fleet_role == MissionType.ORBITAL_INVASION) or (fleet_role == MissionType.ORBITAL_INVASION): - if main_fleet_role == fleet_role: - do_merge = False # TODO: could allow merger if both orb invaders and both same target - elif not fleet_mission and (main_fleet.speed > 0) and (fleet.speed > 0): - do_merge = True - else: - if not self.target and (main_fleet.speed > 0 or fleet.speed == 0): - do_merge = True - else: - target = fleet_mission.target.id if fleet_mission.target else None - if target == target_id: - print "Military fleet %d has same target as %s fleet %d. Merging." % (fid, fleet_role, fleet_id) - do_merge = True # TODO: should probably ensure that fleetA has aggression on now - elif main_fleet.speed > 0: - neighbors = foAI.foAIstate.systemStatus.get(system_id, {}).get('neighbors', []) - if target == system_id and target_id in neighbors and self.type == MissionType.SECURE: - # consider 'borrowing' for work in neighbor system # TODO check condition - need_left = 1.5 * sum(foAI.foAIstate.systemStatus.get(nid, {}).get('fleetThreat', 0) - for nid in neighbors if nid != target_id) - fleet_rating = CombatRatingsAI.get_fleet_rating(fid) - if need_left < fleet_rating: - do_merge = True - if do_merge: - FleetUtilsAI.merge_fleet_a_into_b(fid, fleet_id, need_left, - context="Order %s of mission %s" % (context, self)) - return + FleetUtilsAI.merge_fleet_a_into_b(fid, fleet_id, context="Order %s of mission %s" % (context, self)) def _is_valid_fleet_mission_target(self, mission_type, target): if not target: return False if mission_type == MissionType.EXPLORATION: - if isinstance(target, System): + if isinstance(target, TargetSystem): empire = fo.getEmpire() if not empire.hasExploredSystem(target.id): return True @@ -188,7 +195,7 @@ def _is_valid_fleet_mission_target(self, mission_type, target): fleet = self.fleet.get_object() if not fleet.hasOutpostShips: return False - if isinstance(target, Planet): + if isinstance(target, TargetPlanet): planet = target.get_object() if planet.unowned: return True @@ -196,22 +203,23 @@ def _is_valid_fleet_mission_target(self, mission_type, target): fleet = self.fleet.get_object() if not fleet.hasColonyShips: return False - if isinstance(target, Planet): + if isinstance(target, TargetPlanet): planet = target.get_object() - population = planet.currentMeterValue(fo.meterType.population) + population = planet.initialMeterValue(fo.meterType.population) if planet.unowned or (planet.owner == fleet.owner and population == 0): return True elif mission_type in [MissionType.INVASION, MissionType.ORBITAL_INVASION]: fleet = self.fleet.get_object() if not fleet.hasTroopShips: return False - if isinstance(target, Planet): + if isinstance(target, TargetPlanet): planet = target.get_object() # TODO remove latter portion of next check in light of invasion retargeting, or else correct logic if not planet.unowned or planet.owner != fleet.owner: return True - elif mission_type in [MissionType.MILITARY, MissionType.SECURE, MissionType.ORBITAL_DEFENSE]: - if isinstance(target, System): + elif mission_type in [MissionType.MILITARY, MissionType.SECURE, + MissionType.ORBITAL_DEFENSE, MissionType.PROTECT_REGION]: + if isinstance(target, TargetSystem): return True # TODO: implement other mission types return False @@ -224,10 +232,23 @@ def clean_invalid_targets(self): def _check_abort_mission(self, fleet_order): """ checks if current mission (targeting a planet) should be aborted""" - if fleet_order.target and isinstance(fleet_order.target, Planet): + planet_stealthed = False + target_is_planet = fleet_order.target and isinstance(fleet_order.target, TargetPlanet) + planet = None + if target_is_planet: planet = fleet_order.target.get_object() + # Check visibility prediction, but if somehow still have current visibility, don't + # abort the mission yet + if not EspionageAI.colony_detectable_by_empire(planet.id, empire=fo.empireID()): + if get_partial_visibility_turn(planet.id) == fo.currentTurn(): + debug("EspionageAI predicts planet id %d to be stealthed" % planet.id + + ", but somehow have current visibity anyway, so won't trigger mission abort") + else: + debug("EspionageAI predicts we can no longer detect %s, will abort mission" % fleet_order.target) + planet_stealthed = True + if target_is_planet and not planet_stealthed: if isinstance(fleet_order, OrderColonize): - if (planet.currentMeterValue(fo.meterType.population) == 0 and + if (planet.initialMeterValue(fo.meterType.population) == 0 and (planet.ownedBy(fo.empireID()) or planet.unowned)): return False elif isinstance(fleet_order, OrderOutpost): @@ -239,8 +260,8 @@ def _check_abort_mission(self, fleet_order): return False # canceling fleet orders - print " %s" % fleet_order - print "Fleet %d had a target planet that is no longer valid for this mission; aborting." % self.fleet.id + debug(" %s" % fleet_order) + debug("Fleet %d had a target planet that is no longer valid for this mission; aborting." % self.fleet.id) self.clear_fleet_orders() self.clear_target() FleetUtilsAI.split_fleet(self.fleet.id) @@ -266,13 +287,14 @@ def _check_retarget_invasion(self): return # TODO: check for best local target open_targets = [] already_targeted = InvasionAI.get_invasion_targeted_planet_ids(system.planetIDs, MissionType.INVASION) + aistate = get_aistate() for pid in system.planetIDs: - if pid in already_targeted or (pid in foAI.foAIstate.qualifyingTroopBaseTargets): + if pid in already_targeted or (pid in aistate.qualifyingTroopBaseTargets): continue planet = universe.getPlanet(pid) if planet.unowned or (planet.owner == empire_id): continue - if (planet.currentMeterValue(fo.meterType.shield)) <= 0: + if (planet.initialMeterValue(fo.meterType.shield)) <= 0: open_targets.append(pid) if not open_targets: return @@ -292,7 +314,7 @@ def _check_retarget_invasion(self): if target_id == INVALID_ID: return - print "\t AIFleetMission._check_retarget_invasion: splitting and retargetting fleet %d" % fleet_id + debug("\t Splitting and retargetting fleet %d" % fleet_id) new_fleets = FleetUtilsAI.split_fleet(fleet_id) self.clear_target() # TODO: clear from foAIstate self.clear_fleet_orders() @@ -301,11 +323,12 @@ def _check_retarget_invasion(self): target_stats = {'rating': 10, 'troopCapacity': troops_needed} found_fleets = [] # TODO check if next statement does not mutate any global states and can be removed - _ = FleetUtilsAI.get_fleets_for_mission(target_stats, min_stats, {}, starting_system=fleet.systemID, + + _ = FleetUtilsAI.get_fleets_for_mission(target_stats, min_stats, {}, starting_system=fleet.systemID, # noqa: F841 fleet_pool_set=set(new_fleets), fleet_list=found_fleets) for fid in found_fleets: FleetUtilsAI.merge_fleet_a_into_b(fid, fleet_id) - target = Planet(target_id) + target = TargetPlanet(target_id) self.set_target(MissionType.INVASION, target) self.generate_fleet_orders() @@ -344,90 +367,98 @@ def issue_fleet_orders(self): """issues AIFleetOrders which can be issued in system and moves to next one if is possible""" # TODO: priority order_completed = True - print - print "Checking orders for fleet %s (on turn %d), with mission type %s" % ( - self.fleet.get_object(), fo.currentTurn(), self.type or 'No mission') + + debug("\nChecking orders for fleet %s (on turn %d), with mission type %s and target %s", + self.fleet.get_object(), fo.currentTurn(), self.type or 'No mission', self.target or 'No Target') if MissionType.INVASION == self.type: self._check_retarget_invasion() just_issued_move_order = False last_move_target_id = INVALID_ID + # Note: the following abort check somewhat assumes only one major mission type + for fleet_order in self.orders: + if (isinstance(fleet_order, (OrderColonize, OrderOutpost, OrderInvade)) and + self._check_abort_mission(fleet_order)): + return + aistate = get_aistate() for fleet_order in self.orders: if just_issued_move_order and self.fleet.get_object().systemID != last_move_target_id: # having just issued a move order, we will normally stop issuing orders this turn, except that if there # are consecutive move orders we will consider moving through the first destination rather than stopping + # Without the below noinspection directive, PyCharm is concerned about the 2nd part of the test + # noinspection PyTypeChecker if (not isinstance(fleet_order, OrderMove) or self.need_to_pause_movement(last_move_target_id, fleet_order)): break - print "Checking order: %s" % fleet_order - if isinstance(fleet_order, (OrderColonize, OrderOutpost, OrderInvade)): # TODO: invasion? - if self._check_abort_mission(fleet_order): - print "Aborting fleet order %s" % fleet_order - return + debug("Checking order: %s" % fleet_order) self.check_mergers(context=str(fleet_order)) if fleet_order.can_issue_order(verbose=False): - if isinstance(fleet_order, OrderMove) and order_completed: # only move if all other orders completed - print "Issuing fleet order %s" % fleet_order + # only move if all other orders completed + if isinstance(fleet_order, OrderMove) and order_completed: + debug("Issuing fleet order %s" % fleet_order) fleet_order.issue_order() just_issued_move_order = True last_move_target_id = fleet_order.target.id elif not isinstance(fleet_order, OrderMove): - print "Issuing fleet order %s" % fleet_order + debug("Issuing fleet order %s" % fleet_order) fleet_order.issue_order() else: - print "NOT issuing (even though can_issue) fleet order %s" % fleet_order - print "Order issued: %s" % fleet_order.order_issued - if not fleet_order.order_issued: + debug("NOT issuing (even though can_issue) fleet order %s" % fleet_order) + status_words = tuple(["not", ""][_s] for _s in [fleet_order.order_issued, fleet_order.executed]) + debug("Order %s issued and %s fully executed." % status_words) + if not fleet_order.executed: order_completed = False else: # check that we're not held up by a Big Monster if fleet_order.order_issued: - # It's unclear why we'd really get to this spot, but it has been observed to happen, perhaps due to - # game being reloaded after code changes. + # A previously issued order that wasn't instantly executed must have had cirumstances change so that + # the order can't currently be reissued (or perhaps simply a savegame has been reloaded on the same + # turn the order was issued). + if not fleet_order.executed: + order_completed = False # Go on to the next order. continue - print "CAN'T issue fleet order %s" % fleet_order + debug("CAN'T issue fleet order %s because:" % fleet_order) + fleet_order.can_issue_order(verbose=True) if isinstance(fleet_order, OrderMove): this_system_id = fleet_order.target.id - this_status = foAI.foAIstate.systemStatus.setdefault(this_system_id, {}) + this_status = aistate.systemStatus.setdefault(this_system_id, {}) threat_threshold = fo.currentTurn() * MilitaryAI.cur_best_mil_ship_rating() / 4.0 if this_status.get('monsterThreat', 0) > threat_threshold: # if this move order is not this mil fleet's final destination, and blocked by Big Monster, # release and hope for more effective reassignment if (self.type not in (MissionType.MILITARY, MissionType.SECURE) or fleet_order != self.orders[-1]): - print "Aborting mission due to being blocked by Big Monster at system %d, threat %d" % ( - this_system_id, foAI.foAIstate.systemStatus[this_system_id]['monsterThreat']) - print "Full set of orders were:" + debug("Aborting mission due to being blocked by Big Monster at system %d, threat %d" % ( + this_system_id, aistate.systemStatus[this_system_id]['monsterThreat'])) + debug("Full set of orders were:") for this_order in self.orders: - print " - %s" % this_order + debug(" - %s" % this_order) self.clear_fleet_orders() self.clear_target() return break # do not order the next order until this one is finished. else: # went through entire order list if order_completed: - print "Final order is completed" + debug("Final order is completed") orders = self.orders last_order = orders[-1] if orders else None universe = fo.getUniverse() if last_order and isinstance(last_order, OrderColonize): planet = universe.getPlanet(last_order.target.id) - sys_partial_vis_turn = universe.getVisibilityTurnsMap(planet.systemID, fo.empireID()).get( - fo.visibility.partial, -9999) - planet_partial_vis_turn = universe.getVisibilityTurnsMap(planet.id, fo.empireID()).get( - fo.visibility.partial, -9999) + sys_partial_vis_turn = get_partial_visibility_turn(planet.systemID) + planet_partial_vis_turn = get_partial_visibility_turn(planet.id) if (planet_partial_vis_turn == sys_partial_vis_turn and - not planet.currentMeterValue(fo.meterType.population)): - print >> sys.stderr, ("Fleet %d has tentatively completed its " - "colonize mission but will wait to confirm population.") % self.fleet.id - print " Order details are %s" % last_order - print " Order is valid: %s; issued: %s; executed: %s" % ( - last_order.is_valid(), last_order.order_issued, last_order.executed) + not planet.initialMeterValue(fo.meterType.population)): + warning("Fleet %s has tentatively completed its " + "colonize mission but will wait to confirm population.", self.fleet) + debug(" Order details are %s" % last_order) + debug(" Order is valid: %s; issued: %s; executed: %s" % ( + last_order.is_valid(), last_order.order_issued, last_order.executed)) if not last_order.is_valid(): source_target = last_order.fleet target_target = last_order.target - print " source target validity: %s; target target validity: %s " % ( - bool(source_target), bool(target_target)) + debug(" source target validity: %s; target target validity: %s " % ( + bool(source_target), bool(target_target))) return # colonize order must not have completed yet clear_all = True last_sys_target = INVALID_ID @@ -437,8 +468,7 @@ def issue_fleet_orders(self): # if (MissionType.SECURE == self.type) or secure_targets = set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + - AIstate.invasionTargetedSystemIDs + - AIstate.blockadeTargetedSystemIDs) + AIstate.invasionTargetedSystemIDs) if last_sys_target in secure_targets: # consider a secure mission if last_sys_target in AIstate.colonyTargetedSystemIDs: secure_type = "Colony" @@ -446,25 +476,31 @@ def issue_fleet_orders(self): secure_type = "Outpost" elif last_sys_target in AIstate.invasionTargetedSystemIDs: secure_type = "Invasion" - elif last_sys_target in AIstate.blockadeTargetedSystemIDs: - secure_type = "Blockade" else: secure_type = "Unidentified" - print ("Fleet %d has completed initial stage of its mission " - "to secure system %d (targeted for %s), " - "may release a portion of ships" % (self.fleet.id, last_sys_target, secure_type)) + debug("Fleet %d has completed initial stage of its mission " + "to secure system %d (targeted for %s), " + "may release a portion of ships" % (self.fleet.id, last_sys_target, secure_type)) clear_all = False + + # for PROTECT_REGION missions, only release fleet if no more threat + if self.type == MissionType.PROTECT_REGION: + # use military logic code below to determine if can release + # any or even all of the ships. + clear_all = False + last_sys_target = self.target.id + debug("Check if PROTECT_REGION mission with target %d is finished.", last_sys_target) + fleet_id = self.fleet.id if clear_all: if orders: - print "Fleet %d has completed its mission; clearing all orders and targets." % self.fleet.id - print "Full set of orders were:" + debug("Fleet %d has completed its mission; clearing all orders and targets." % self.fleet.id) + debug("Full set of orders were:") for this_order in orders: - print "\t\t %s" % this_order + debug("\t\t %s" % this_order) self.clear_fleet_orders() self.clear_target() - if foAI.foAIstate.get_fleet_role(fleet_id) in (MissionType.MILITARY, - MissionType.SECURE): + if aistate.get_fleet_role(fleet_id) in (MissionType.MILITARY, MissionType.SECURE): allocations = MilitaryAI.get_military_fleets(mil_fleets_ids=[fleet_id], try_reset=False, thisround="Fleet %d Reassignment" % fleet_id) @@ -472,12 +508,14 @@ def issue_fleet_orders(self): MilitaryAI.assign_military_fleets_to_systems(use_fleet_id_list=[fleet_id], allocations=allocations) else: # no orders - print "No Current Orders" + debug("No Current Orders") else: - # TODO: evaluate releasing a smaller portion or none of the ships - system_status = foAI.foAIstate.systemStatus.setdefault(last_sys_target, {}) - new_fleets = [] - threat_present = system_status.get('totalThreat', 0) + system_status.get('neighborThreat', 0) > 0 + potential_threat = CombatRatingsAI.combine_ratings( + MilitaryAI.get_system_local_threat(last_sys_target), + MilitaryAI.get_system_neighbor_threat(last_sys_target) + ) + threat_present = potential_threat > 0 + debug("Fleet threat present? %s", threat_present) target_system = universe.getSystem(last_sys_target) if not threat_present and target_system: for pid in target_system.planetIDs: @@ -485,18 +523,62 @@ def issue_fleet_orders(self): if (planet and planet.owner != fo.empireID() and planet.currentMeterValue(fo.meterType.maxDefense) > 0): + debug("Found local planetary threat: %s", planet) threat_present = True break if not threat_present: - print "No current threat in target system; releasing a portion of ships." + debug("No current threat in target system; releasing a portion of ships.") # at least first stage of current task is done; # release extra ships for potential other deployments new_fleets = FleetUtilsAI.split_fleet(self.fleet.id) + if self.type == MissionType.PROTECT_REGION: + self.clear_fleet_orders() + self.clear_target() + new_fleets.append(self.fleet.id) else: - print "Threat remains in target system; NOT releasing any ships." + debug("Threat remains in target system; Considering to release some ships.") + new_fleets = [] + fleet_portion_to_remain = self._portion_of_fleet_needed_here() + if fleet_portion_to_remain >= 1: + debug("Can not release fleet yet due to large threat.") + elif fleet_portion_to_remain > 0: + debug("Not all ships are needed here - considering releasing a few") + # TODO: Rate against specific enemy threat cause + fleet_remaining_rating = CombatRatingsAI.get_fleet_rating(fleet_id) + fleet_min_rating = fleet_portion_to_remain * fleet_remaining_rating + debug("Starting rating: %.1f, Target rating: %.1f", + fleet_remaining_rating, fleet_min_rating) + allowance = CombatRatingsAI.rating_needed(fleet_remaining_rating, fleet_min_rating) + debug("May release ships with total rating of %.1f", allowance) + ship_ids = list(self.fleet.get_object().shipIDs) + for ship_id in ship_ids: + ship_rating = CombatRatingsAI.get_ship_rating(ship_id) + debug("Considering to release ship %d with rating %.1f", ship_id, ship_rating) + if ship_rating > allowance: + debug("Remaining rating insufficient. Not released.") + continue + debug("Splitting from fleet.") + new_fleet_id = FleetUtilsAI.split_ship_from_fleet(fleet_id, ship_id) + if assertion_fails(new_fleet_id and new_fleet_id != INVALID_ID): + break + new_fleets.append(new_fleet_id) + fleet_remaining_rating = CombatRatingsAI.rating_difference( + fleet_remaining_rating, ship_rating) + allowance = CombatRatingsAI.rating_difference( + fleet_remaining_rating, fleet_min_rating) + debug("Remaining fleet rating: %.1f - Allowance: %.1f", + fleet_remaining_rating, allowance) + if new_fleets: + aistate.get_fleet_role(fleet_id, force_new=True) + aistate.update_fleet_rating(fleet_id) + aistate.ensure_have_fleet_missions(new_fleets) + else: + debug("Planetary defenses are deemed sufficient. Release fleet.") + new_fleets = FleetUtilsAI.split_fleet(self.fleet.id) + new_military_fleets = [] for fleet_id in new_fleets: - if foAI.foAIstate.get_fleet_role(fleet_id) in COMBAT_MISSION_TYPES: + if aistate.get_fleet_role(fleet_id) in COMBAT_MISSION_TYPES: new_military_fleets.append(fleet_id) allocations = [] if new_military_fleets: @@ -509,6 +591,40 @@ def issue_fleet_orders(self): MilitaryAI.assign_military_fleets_to_systems(use_fleet_id_list=new_military_fleets, allocations=allocations) + def _portion_of_fleet_needed_here(self): + """Calculate the portion of the fleet needed in target system considering enemy forces.""" + # TODO check rating against planets + if assertion_fails(self.type in COMBAT_MISSION_TYPES, msg=str(self)): + return 0 + if assertion_fails(self.target and self.target.id != INVALID_ID, msg=str(self)): + return 0 + system_id = self.target.id + aistate = get_aistate() + local_defenses = MilitaryAI.get_my_defense_rating_in_system(system_id) + potential_threat = CombatRatingsAI.combine_ratings( + MilitaryAI.get_system_local_threat(system_id), + MilitaryAI.get_system_neighbor_threat(system_id) + ) + universe = fo.getUniverse() + system = universe.getSystem(system_id) + + # tally planetary defenses + total_defense = total_shields = 0 + for planet_id in system.planetIDs: + planet = universe.getPlanet(planet_id) + total_defense += planet.currentMeterValue(fo.meterType.defense) + total_shields += planet.currentMeterValue(fo.meterType.shield) + planetary_ratings = total_defense * (total_shields + total_defense) + potential_threat += planetary_ratings # TODO: rewrite to return min rating vs planets as well + + # consider safety factor just once here rather than everywhere below + safety_factor = aistate.character.military_safety_factor() + potential_threat *= safety_factor + + # TODO: Rate against specific threat here + fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) + return CombatRatingsAI.rating_needed(potential_threat, local_defenses) / float(max(fleet_rating, 1.)) + def generate_fleet_orders(self): """generates AIFleetOrders from fleets targets to accomplish""" universe = fo.getUniverse() @@ -516,7 +632,7 @@ def generate_fleet_orders(self): fleet = universe.getFleet(fleet_id) if (not fleet) or fleet.empty or (fleet_id in universe.destroyedObjectIDs(fo.empireID())): # fleet was probably merged into another or was destroyed - foAI.foAIstate.delete_fleet_info(fleet_id) + get_aistate().delete_fleet_info(fleet_id) return # TODO: priority @@ -530,8 +646,7 @@ def generate_fleet_orders(self): # if (not self.hasAnyAIMissionTypes()): if not self.target and (system_id not in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + - AIstate.invasionTargetedSystemIDs + - AIstate.blockadeTargetedSystemIDs)): + AIstate.invasionTargetedSystemIDs)): if self._need_repair(): repair_fleet_order = MoveUtilsAI.get_repair_fleet_order(self.fleet, start_sys_id) if repair_fleet_order and repair_fleet_order.is_valid(): @@ -546,14 +661,20 @@ def generate_fleet_orders(self): if self.target: # for some targets fleet has to visit systems and therefore fleet visit them - system_to_visit = self.target.get_system() + + system_to_visit = (self.target.get_system() if not self.type == MissionType.PROTECT_REGION + else TargetSystem(self._get_target_for_protection_mission())) + if not system_to_visit: + return orders_to_visit_systems = MoveUtilsAI.create_move_orders_to_system(self.fleet, system_to_visit) # TODO: if fleet doesn't have enough fuel to get to final target, consider resetting Mission for fleet_order in orders_to_visit_systems: self.orders.append(fleet_order) # also generate appropriate final orders - fleet_order = self._get_fleet_order_from_target(self.type, self.target) + fleet_order = self._get_fleet_order_from_target(self.type, + self.target if not self.type == MissionType.PROTECT_REGION + else system_to_visit) self.orders.append(fleet_order) def _need_repair(self, repair_limit=0.70): @@ -569,7 +690,6 @@ def _need_repair(self, repair_limit=0.70): :rtype: bool """ # TODO: More complex evaluation if fleet needs repair (consider self-repair, distance, threat, mission...) - universe = fo.getUniverse() fleet_id = self.fleet.id # if we are already at a system where we can repair, make sure we use it... system = self.fleet.get_system() @@ -578,17 +698,11 @@ def _need_repair(self, repair_limit=0.70): if nearest_dock == system.id: repair_limit = 0.99 # if combat fleet, use military repair check - if foAI.foAIstate.get_fleet_role(fleet_id) in COMBAT_MISSION_TYPES: + if get_aistate().get_fleet_role(fleet_id) in COMBAT_MISSION_TYPES: return fleet_id in MilitaryAI.avail_mil_needing_repair([fleet_id], on_mission=bool(self.orders), repair_limit=repair_limit)[0] # TODO: Allow to split fleet to send only damaged ships to repair - fleet = universe.getFleet(fleet_id) - ships_cur_health = 0 - ships_max_health = 0 - for ship_id in fleet.shipIDs: - this_ship = universe.getShip(ship_id) - ships_cur_health += this_ship.currentMeterValue(fo.meterType.structure) - ships_max_health += this_ship.currentMeterValue(fo.meterType.maxStructure) + ships_cur_health, ships_max_health = FleetUtilsAI.get_current_and_max_structure(fleet_id) return ships_cur_health < repair_limit * ships_max_health def get_location_target(self): @@ -597,13 +711,16 @@ def get_location_target(self): fleet = fo.getUniverse().getFleet(self.fleet.id) system_id = fleet.systemID if system_id >= 0: - return System(system_id) + return TargetSystem(system_id) else: # in starlane, so return next system - return System(fleet.nextSystemID) + return TargetSystem(fleet.nextSystemID) def __eq__(self, other): return isinstance(other, self.__class__) and self.fleet == other.target + def __hash__(self): + return hash(self.fleet) + def __str__(self): fleet = self.fleet.get_object() @@ -613,3 +730,84 @@ def __str__(self): (fleet and len(fleet.shipIDs)) or 0, CombatRatingsAI.get_fleet_rating(fleet_id), self.target or 'no target') + + def _get_target_for_protection_mission(self): + """Get a target for a PROTECT_REGION mission. + + 1) If primary target (system target of this mission) is under attack, move to primary target. + 2) If neighbors of primary target have local enemy forces weaker than this fleet, may move to attack + 3) If no neighboring fleets or strongest enemy force is too strong, move to defend primary target + """ + # TODO: Also check fleet rating vs planets in decision making below not only vs fleets + universe = fo.getUniverse() + primary_objective = self.target.id + # TODO: Rate against specific threats + fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) + debug("%s finding target for protection mission (primary target %s). Fleet Rating: %.1f", + self.fleet, self.target, fleet_rating) + immediate_threat = MilitaryAI.get_system_local_threat(primary_objective) + if immediate_threat: + debug(" Immediate threat! Moving to primary mission target") + return primary_objective + else: + debug(" No immediate threats.") + # Try to eliminate neighbouring fleets + neighbors = universe.getImmediateNeighbors(primary_objective, fo.empireID()) + threat_list = sorted(map( + lambda x: (MilitaryAI.get_system_local_threat(x), x), + neighbors + ), reverse=True) + + if not threat_list: + debug(" No neighbors (?!). Moving to primary mission target") + return primary_objective + else: + debug(" Neighboring threats:") + for threat, sys_id in threat_list: + debug(" %s - %.1f", TargetSystem(sys_id), threat) + top_threat, candidate_system = threat_list[0] + if not top_threat: + # TODO: Move into second ring but needs more careful evaluation + # For now, consider staying at the current location if enemy + # owns a planet here which we can bombard. + current_system_id = self.fleet.get_current_system_id() + if current_system_id in neighbors: + system = universe.getSystem(current_system_id) + if assertion_fails(system is not None): + return primary_objective + empire_id = fo.empireID() + for planet_id in system.planetIDs: + planet = universe.getPlanet(planet_id) + if (planet and + not planet.ownedBy(empire_id) and + not planet.unowned): + debug("Currently no neighboring threats. " + "Staying for bombardment of planet %s", planet) + return current_system_id + + # TODO consider attacking neighboring, non-military fleets + # - needs more careful evaluation against neighboring threats + # empire_id = fo.empireID() + # for sys_id in neighbors: + # system = universe.getSystem(sys_id) + # if assertion_fails(system is not None): + # continue + # local_fleets = system.fleetIDs + # for fleet_id in local_fleets: + # fleet = universe.getFleet(fleet_id) + # if not fleet or fleet.ownedBy(empire_id): + # continue + # return sys_id + + debug("No neighboring threats. Moving to primary mission target") + return primary_objective + + # TODO rate against threat in target system + # TODO only engage if can reach in 1 turn or leaves sufficient defense behind + safety_factor = get_aistate().character.military_safety_factor() + if fleet_rating < safety_factor*top_threat: + debug(" Neighboring threat is too powerful. Moving to primary mission target") + return primary_objective # do not engage! + + debug(" Engaging neighboring threat: %s", TargetSystem(candidate_system)) + return candidate_system diff --git a/default/python/AI/AIstate.py b/default/python/AI/AIstate.py index 5cbfd938ef9..6347f0cbb2b 100755 --- a/default/python/AI/AIstate.py +++ b/default/python/AI/AIstate.py @@ -1,49 +1,34 @@ import copy -import sys -from collections import OrderedDict as odict +from collections import Counter, OrderedDict as odict +from logging import error, info, warning, debug +from operator import itemgetter from time import time import freeOrionAIInterface as fo # pylint: disable=import-error from common.print_utils import Table, Text, Float import AIFleetMission +import ColonisationAI import ExplorationAI import FleetUtilsAI -import ResourcesAI from EnumsAI import MissionType, ShipRoleType import CombatRatingsAI import MilitaryAI import PlanetUtilsAI -from freeorion_tools import dict_from_map -from universe_object import System -from AIDependencies import INVALID_ID +from freeorion_tools import get_partial_visibility_turn +from AIDependencies import INVALID_ID, TECH_NATIVE_SPECIALS from character.character_module import create_character, Aggression -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) - - # moving ALL or NEARLY ALL 'global' variables into AIState object rather than module # in general, leaving items as a module attribute if they are recalculated each turn without reference to prior values # global variables colonyTargetedSystemIDs = [] outpostTargetedSystemIDs = [] opponentPlanetIDs = [] -opponentSystemIDs = [] # TODO: Currently never filled but some (uncommented) code in MilitaryAI refers to this... invasionTargets = [] invasionTargetedSystemIDs = [] -blockadeTargetedSystemIDs = [] # TODO also never filled atm... either implement this or remove redundant code -militarySystemIDs = [] -colonyFleetIDs = [] -outpostFleetIDs = [] -invasionFleetIDs = [] fleetsLostBySystem = {} # keys are system_ids, values are ratings for the fleets lost -popCtrSystemIDs = [] -colonizedSystems = {} empireStars = {} -popCtrIDs = [] -outpostIDs = [] -outpostSystemIDs = [] class ConversionError(Exception): @@ -51,6 +36,7 @@ class ConversionError(Exception): Automatically logs and chats to the host if raised. """ + def __init__(self, msg=""): error(msg, exc_info=True) @@ -58,14 +44,12 @@ def __init__(self, msg=""): def convert_to_version(state, version): """Convert a savegame AIstate to the next version. - :param state: savegame state, modified in function - :type state: dict - :param version: Version to convert to - :type version: int + :param dict state: savegame state, modified in function + :param int version: Version to convert to """ - print "Trying to convert savegame state to version %d..." % version + debug("Trying to convert savegame state to version %d..." % version) current_version = state.get("version", -1) - print " Current version: %d" % current_version + debug(" Current version: %d" % current_version) if current_version == version: raise ConversionError("Can't convert AI savegame to the same compatibility version.") @@ -75,38 +59,51 @@ def convert_to_version(state, version): if version != current_version + 1: raise ConversionError("Can't skip a compatibility version when converting AI savegame.") - if version == 0: - pass # only version number added - elif version == 1: - try: - state['_aggression'] = state['character'].get_trait(Aggression).key - except Exception as e: - raise ConversionError("Error when converting to compatibility version 1: " - "Can't find aggression in character module. Exception thrown was: " + e.message) - elif version == 2: - # some dicts which used only the keys were changed to sets - for var_name in ['visInteriorSystemIDs', 'exploredSystemIDs', 'visBorderSystemIDs', 'unexploredSystemIDs']: - state[var_name] = set(state.get(var_name, {}).keys()) + # Starting with version 3, we switched from pickle to json-style encoding + # Do not try to load an older savegame even if it magically passed the encoder. + if version <= 3: + raise ConversionError("The AI savegame version is no longer supported.") + + if version == 4: + del state['qualifyingOutpostBaseTargets'] + del state['qualifyingColonyBaseTargets'] + state['orbital_colonization_manager'] = ColonisationAI.OrbitalColonizationManager() + + if version == 5: + state['last_turn_played'] = 0 + + if version == 6: + # Anti-fighter and anti-planet stats were added to CombatRatingAI + state['_AIstate__empire_standard_enemy'] = state['_AIstate__empire_standard_enemy'] + (0, False) + (0, False) # state["some_new_member"] = some_default_value # del state["some_removed_member"] # state["list_changed_to_set"] = set(state["list_changed_to_set"]) - print " All updates set. Setting new version number." + debug(" All updates set. Setting new version number.") state["version"] = version -class AIstate(object): +class AIstate: """Stores AI game state. IMPORTANT: - If class members are redefined, added or deleted, then the + (i) If class members are redefined, added or deleted, then the version number must be increased by 1 and the convert_to_version() function must be updated so a saved state from the previous version is playable with this AIstate version, i.e. new members must be added and outdated members must be modified and / or deleted. + + (ii) The AIstate is stored as an encoded string in save game files + (currently via the pickle module). The attributes of the AIstate must + therefore be compatible with the encoding method, which currently generally + means that they must be native python data types (or other data types the + encoder is augmented to handle), not objects such as UniverseObject + instances or C++ enum values brought over from the C++ side + via boost. If desiring to store a reference to a UniverseObject store its + object id instead; for enum values store their int conversion value. """ - version = 2 + version = 6 def __init__(self, aggression): # Do not allow to create AIstate instances with an invalid version number. @@ -127,7 +124,8 @@ def __init__(self, aggression): # unique ids for turns. {turn: uid} self.turn_uids = {} - self._aggression = aggression + # see AIstate docstring re importance of int cast for aggression + self._aggression = int(aggression) # 'global' (?) variables self.colonisablePlanetIDs = odict() @@ -149,48 +147,61 @@ def __init__(self, aggression): self.exploredSystemIDs = set() self.unexploredSystemIDs = {self.__origin_home_system_id} self.fleetStatus = {} # keys: 'sysID', 'nships', 'rating' - # systemStatus keys: 'name', 'neighbors' (sysIDs), '2jump_ring' (sysIDs), '3jump_ring', '4jump_ring', 'enemy_ship_count' - # 'fleetThreat', 'planetThreat', 'monsterThreat' (specifically, immobile nonplanet threat), 'totalThreat', 'localEnemyFleetIDs', - # 'neighborThreat', 'max_neighbor_threat', 'jump2_threat' (up to 2 jumps away), 'jump3_threat', 'jump4_threat', 'regional_threat' - # 'myDefenses' (planet rating), 'myfleets', 'myFleetsAccessible'(not just next desitination), 'myFleetRating' - # 'my_neighbor_rating' (up to 1 jump away), 'my_jump2_rating', 'my_jump3_rating', my_jump4_rating' - # 'local_fleet_threats', 'regional_fleet_threats' <== these are only for mobile fleet threats + # systemStatus keys: + # 'name', 'neighbors' (sysIDs), '2jump_ring' (sysIDs), '3jump_ring', '4jump_ring', 'enemy_ship_count', + # 'fleetThreat', 'planetThreat', 'monsterThreat' (specifically, immobile nonplanet threat), 'totalThreat', + # 'localEnemyFleetIDs', 'neighborThreat', 'max_neighbor_threat', 'jump2_threat' (up to 2 jumps away), + # 'jump3_threat', 'jump4_threat', 'regional_threat', 'myDefenses' (planet rating), 'myfleets', + # 'myFleetsAccessible'(not just next desitination), 'myFleetRating', 'my_neighbor_rating' (up to 1 jump away), + # 'my_jump2_rating', 'my_jump3_rating', my_jump4_rating', 'local_fleet_threats', + # 'regional_fleet_threats' <== these are only for mobile fleet threats self.systemStatus = {} self.needsEmergencyExploration = [] self.newlySplitFleets = {} self.militaryRating = 0 self.shipCount = 4 - self.misc = {} - self.qualifyingColonyBaseTargets = {} - self.qualifyingOutpostBaseTargets = {} + self.misc = {} # Keys: "enemies_sighted" (dict[turn: list[fleetIDs]]), + # "observed_empires" (set[enemy empire IDs]), + # "ReassignedFleetMissions" (list[FleetMissions]) + self.orbital_colonization_manager = ColonisationAI.OrbitalColonizationManager() self.qualifyingTroopBaseTargets = {} - self.__empire_standard_enemy = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) # TODO: track on a per-empire basis + # TODO: track on a per-empire basis + self.__empire_standard_enemy = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) self.empire_standard_enemy_rating = 0 # TODO: track on a per-empire basis self.character = create_character(aggression, self.empireID) + self.last_turn_played = 0 def __setstate__(self, state): try: for v in range(state.get("version", -1), AIstate.version): convert_to_version(state, v+1) - self.__dict__ = state except ConversionError: if '_aggression' in state: aggression = state['_aggression'] else: try: aggression = state['character'].get_trait(Aggression).key - except Exception as e: - print >> sys.stderr, "Could not find the aggression level of the AI, defaulting to typical." - print >> sys.stderr, e + except Exception: + error("Could not find the aggression level of the AI, defaulting to typical.", exc_info=True) aggression = fo.aggression.typical self.__init__(aggression) + return + + # build the ordered dict with sorted entries from the (unsorted) dict + # that is contained in the savegame state. + for content in ("colonisablePlanetIDs", "colonisableOutpostIDs"): + sorted_planets = sorted(state[content].items(), + key=itemgetter(1), reverse=True) + state[content] = odict(sorted_planets) + + self.__dict__ = state def generate_uid(self, first=False): """ Generates unique identifier. It is hexed number of milliseconds. To set self.uid use flag first=True result will be - number of mils between current time and some recent date + number of mils between current time and some recent date For turn result is mils between uid and current time """ time_delta = (time() - 1433809768) * 1000 @@ -222,45 +233,39 @@ def get_prev_turn_uid(self): """ return self.turn_uids.get(fo.currentTurn() - 1, '0') - def refresh(self): + def __refresh(self): """Turn start AIstate cleanup/refresh.""" - universe = fo.getUniverse() - # checks exploration border & clears roles/missions of missing fleets & updates fleet locs & threats fleetsLostBySystem.clear() invasionTargets[:] = [] + + def __border_exploration_update(self): + universe = fo.getUniverse() exploration_center = PlanetUtilsAI.get_capital_sys_id() - if exploration_center == INVALID_ID: # a bad state probably from an old savegame, or else empire has lost (or almost has) + # a bad state probably from an old savegame, or else empire has lost (or almost has) + if exploration_center == INVALID_ID: exploration_center = self.__origin_home_system_id - - for system_id, info in sorted(self.systemStatus.items()): - self.systemStatus[system_id]['enemy_ship_count'] = 0 # clear now in prep for update_system_status() ExplorationAI.graph_flags.clear() if fo.currentTurn() < 50: - print "-------------------------------------------------" - print "Border Exploration Update (relative to %s)" % (PlanetUtilsAI.sys_name_ids([exploration_center, INVALID_ID])[0]) - print "-------------------------------------------------" + debug("-------------------------------------------------") + debug("Border Exploration Update (relative to %s)" % universe.getSystem(exploration_center)) + debug("-------------------------------------------------") if self.visBorderSystemIDs == {INVALID_ID}: self.visBorderSystemIDs.clear() self.visBorderSystemIDs.add(exploration_center) for sys_id in list(self.visBorderSystemIDs): # This set is modified during iteration. if fo.currentTurn() < 50: - print "Considering border system %s" % (PlanetUtilsAI.sys_name_ids([sys_id, INVALID_ID])[0]) + debug("Considering border system %s" % universe.getSystem(sys_id)) ExplorationAI.follow_vis_system_connections(sys_id, exploration_center) newly_explored = ExplorationAI.update_explored_systems() nametags = [] for sys_id in newly_explored: newsys = universe.getSystem(sys_id) - nametags.append("ID:%4d -- %-20s" % (sys_id, (newsys and newsys.name) or "name unknown")) # an explored system *should* always be able to be gotten + # an explored system *should* always be able to be gotten + nametags.append("ID:%4d -- %-20s" % (sys_id, (newsys and newsys.name) or "name unknown")) if newly_explored: - print "-------------------------------------------------" - print "Newly explored systems:\n%s" % "\n".join(nametags) - print "-------------------------------------------------" - # cleanup fleet roles - # self.update_fleet_locs() - self.__clean_fleet_roles() - self.__clean_fleet_missions(FleetUtilsAI.get_empire_fleet_ids()) - print "Fleets lost by system: %s" % fleetsLostBySystem - self.update_system_status() + debug("-------------------------------------------------") + debug("Newly explored systems:\n%s" % "\n".join(nametags)) + debug("-------------------------------------------------") def delete_fleet_info(self, fleet_id): if fleet_id in self.__aiMissionsByFleetID: @@ -269,25 +274,23 @@ def delete_fleet_info(self, fleet_id): del self.fleetStatus[fleet_id] if fleet_id in self.__fleetRoleByID: del self.__fleetRoleByID[fleet_id] + for sys_status in self.systemStatus.values(): + for fleet_list in [sys_status.get('myfleets', []), sys_status.get('myFleetsAccessible', [])]: + if fleet_id in fleet_list: + fleet_list.remove(fleet_id) - def report_system_threats(self): - if fo.currentTurn() >= 100: - return - universe = fo.getUniverse() - sys_id_list = sorted(universe.systemIDs) # will normally look at this, the list of all known systems + def __report_system_threats(self): + """Print a table with system threats to the logfile.""" current_turn = fo.currentTurn() - # assess fleet and planet threats + if current_turn >= 100: + return threat_table = Table([ Text('System'), Text('Vis.'), Float('Total'), Float('by Monsters'), Float('by Fleets'), Float('by Planets'), Float('1 jump away'), Float('2 jumps'), Float('3 jumps')], table_name="System Threat Turn %d" % current_turn ) - defense_table = Table([ - Text('System Defenses'), Float('Total'), Float('by Planets'), Float('by Fleets'), - Float('Fleets 1 jump away'), Float('2 jumps'), Float('3 jumps')], - table_name="System Defenses Turn %d" % current_turn - ) - for sys_id in sys_id_list: + universe = fo.getUniverse() + for sys_id in universe.systemIDs: sys_status = self.systemStatus.get(sys_id, {}) system = universe.getSystem(sys_id) threat_table.add_row([ @@ -301,6 +304,22 @@ def report_system_threats(self): sys_status.get('jump2_threat', 0.0), sys_status.get('jump3_threat', 0.0), ]) + info(threat_table) + + def __report_system_defenses(self): + """Print a table with system defenses to the logfile.""" + current_turn = fo.currentTurn() + if current_turn >= 100: + return + defense_table = Table([ + Text('System Defenses'), Float('Total'), Float('by Planets'), Float('by Fleets'), + Float('Fleets 1 jump away'), Float('2 jumps'), Float('3 jumps')], + table_name="System Defenses Turn %d" % current_turn + ) + universe = fo.getUniverse() + for sys_id in universe.systemIDs: + sys_status = self.systemStatus.get(sys_id, {}) + system = universe.getSystem(sys_id) defense_table.add_row([ system, sys_status.get('all_local_defenses', 0.0), @@ -310,29 +329,45 @@ def report_system_threats(self): sys_status.get('my_jump2_rating', 0.0), sys_status.get('my_jump3_rating', 0.0), ]) - threat_table.print_table() - defense_table.print_table() + info(defense_table) def assess_planet_threat(self, pid, sighting_age=0): - sighting_age += 1 # play it safe + if sighting_age > 5: + sighting_age += 1 # play it safe universe = fo.getUniverse() planet = universe.getPlanet(pid) if not planet: return {'overall': 0, 'attack': 0, 'health': 0} - current_shields = planet.currentMeterValue(fo.meterType.shield) + init_shields = planet.initialMeterValue(fo.meterType.shield) + next_shields = planet.currentMeterValue(fo.meterType.shield) # always assumes regen will occur max_shields = planet.currentMeterValue(fo.meterType.maxShield) - current_defense = planet.currentMeterValue(fo.meterType.defense) + init_defense = planet.initialMeterValue(fo.meterType.defense) + next_defense = planet.currentMeterValue(fo.meterType.defense) # always assumes regen will occur max_defense = planet.currentMeterValue(fo.meterType.maxDefense) - shields = min(max_shields, current_shields + 2 * sighting_age) # TODO: base off regen tech - defense = min(max_defense, current_defense + 2 * sighting_age) # TODO: base off regen tech + for special, bonuses in TECH_NATIVE_SPECIALS.items(): + if special in planet.specials and sighting_age > 0: + shield_bonus = bonuses.get('shields', 0) + defense_bonus = bonuses.get('defense', 0) + max_shields = max(max_shields, shield_bonus) + max_defense = max(max_defense, defense_bonus) + next_shields, init_shields = max(next_shields, shield_bonus), max(init_shields, shield_bonus) + next_defense, init_defense = max(next_defense, defense_bonus), max(init_defense, defense_bonus) + # TODO: get regens from knowledge of possessed tech + # note the max below is because sometimes the next value will be less than init + # (e.g. shields just after invasion) + shield_regen = max(1, next_shields - init_shields) + defense_regen = max(1, next_defense - init_defense) + shields = min(max_shields, init_shields + sighting_age * shield_regen) + defense = min(max_defense, init_defense + sighting_age * defense_regen) return {'overall': defense * (defense + shields), 'attack': defense, 'health': (defense + shields)} def assess_enemy_supply(self): """ Assesses where enemy empires have Supply - :return:a tuple of 2 dicts, each of which is keyed by system id, and each of which is a list of empire ids - 1st dict-- enemies that actually have supply at this system - 2nd dict-- enemies that have supply within 2 jumps from this system (if they clear obstructions) + :return: a tuple of 2 dicts, each of which is keyed by system id, and each of which is a list of empire ids + 1st dict -- enemies that actually have supply at this system + 2nd dict -- enemies that have supply within 2 jumps from this system (if they clear obstructions) + :rtype: (dict[int, list[int]], dict[int, list[int]]) """ enemy_ids = [_id for _id in fo.allEmpireIDs() if _id != fo.empireID()] actual_supply = {} @@ -340,7 +375,7 @@ def assess_enemy_supply(self): for enemy_id in enemy_ids: this_enemy = fo.getEmpire(enemy_id) if not this_enemy: - print "Could not retrieve empire for empire id %d" % enemy_id # do not spam chat_error with this + debug("Could not retrieve empire for empire id %d" % enemy_id) # do not spam chat_error with this continue for sys_id in this_enemy.fleetSupplyableSystemIDs: actual_supply.setdefault(sys_id, []).append(enemy_id) @@ -349,8 +384,41 @@ def assess_enemy_supply(self): near_supply.setdefault(sys_id, []).append(enemy_id) return actual_supply, near_supply - def update_system_status(self): - print 10 * "=", "Updating System Threats", 10 * "=" + def __update_empire_standard_enemy(self): + """Update the empire's standard enemy. + + The standard enemy is the enemy that is most often seen. + """ + # TODO: If no current information available, rate against own fighters + universe = fo.getUniverse() + empire_id = fo.empireID() + + # assess enemy fleets that may have been momentarily visible (start with dummy entries) + dummy_stats = CombatRatingsAI.default_ship_stats().get_stats(hashable=True) + cur_e_fighters = Counter() # actual visible enemies + old_e_fighters = Counter({dummy_stats: 0}) # destroyed enemies TODO: consider seen but out of sight enemies + + for fleet_id in universe.fleetIDs: + fleet = universe.getFleet(fleet_id) + if (not fleet or fleet.empty or fleet.ownedBy(empire_id) or fleet.unowned or + not (fleet.hasArmedShips or fleet.hasFighterShips)): + continue + + # track old/dead enemy fighters for rating assessments in case not enough current info + ship_stats = CombatRatingsAI.FleetCombatStats(fleet_id).get_ship_stats(hashable=True) + dead_fleet = fleet_id in universe.destroyedObjectIDs(empire_id) + e_f_dict = old_e_fighters if dead_fleet else cur_e_fighters + for stats in ship_stats: + # log only ships that are armed + if stats[0]: + e_f_dict[stats] += 1 + + e_f_dict = cur_e_fighters or old_e_fighters + self.__empire_standard_enemy = sorted([(v, k) for k, v in e_f_dict.items()])[-1][1] + self.empire_standard_enemy_rating = self.get_standard_enemy().get_rating() + + def __update_system_status(self): + debug('{0} Updating System Threats {0}'.format(10 * "=")) universe = fo.getUniverse() empire = fo.getEmpire() empire_id = fo.empireID() @@ -358,69 +426,64 @@ def update_system_status(self): supply_unobstructed_systems = set(empire.supplyUnobstructedSystems) min_hidden_attack = 4 min_hidden_health = 8 - system_id_list = universe.systemIDs # will normally look at this, the list of all known systems + observed_empires = self.misc.setdefault("observed_empires", set()) + + # TODO: Variables that are recalculated each turn from scratch should not be stored in AIstate + # clear previous game state + for sys_id in self.systemStatus: + self.systemStatus[sys_id]['enemy_ship_count'] = 0 + self.systemStatus[sys_id]['myFleetRating'] = 0 + self.systemStatus[sys_id]['myFleetRatingVsPlanets'] = 0 # for use in debugging verbose = False # assess enemy fleets that may have been momentarily visible - cur_e_fighters = {CombatRatingsAI.default_ship_stats().get_stats(hashable=True): [0]} # start with a dummy entry - old_e_fighters = {CombatRatingsAI.default_ship_stats().get_stats(hashable=True): [0]} # start with a dummy entry - enemy_fleet_ids = [] enemies_by_system = {} my_fleets_by_system = {} fleet_spot_position = {} - saw_enemies_at_system = {} - my_milship_rating = MilitaryAI.cur_best_mil_ship_rating() current_turn = fo.currentTurn() for fleet_id in universe.fleetIDs: fleet = universe.getFleet(fleet_id) - if fleet is None: + if not fleet or fleet.empty: + self.delete_fleet_info(fleet_id) # this is safe even if fleet wasn't mine + continue + # TODO: check if currently in system and blockaded before accepting destination as location + this_system_id = fleet.nextSystemID if fleet.nextSystemID != INVALID_ID else fleet.systemID + dead_fleet = fleet_id in destroyed_object_ids + if dead_fleet: + self.delete_fleet_info(fleet_id) + + if fleet.ownedBy(empire_id): + if not dead_fleet: + my_fleets_by_system.setdefault(this_system_id, []).append(fleet_id) + fleet_spot_position.setdefault(fleet.systemID, []).append(fleet_id) continue - if not fleet.empty: - # TODO: check if currently in system and blockaded before accepting destination as location - this_system_id = (fleet.nextSystemID != INVALID_ID and fleet.nextSystemID) or fleet.systemID - if fleet.ownedBy(empire_id): - if fleet_id not in destroyed_object_ids: - my_fleets_by_system.setdefault(this_system_id, []).append(fleet_id) - fleet_spot_position.setdefault(fleet.systemID, []).append(fleet_id) - else: - dead_fleet = fleet_id in destroyed_object_ids - if not fleet.ownedBy(-1) and (fleet.hasArmedShips or fleet.hasFighterShips): - ship_stats = CombatRatingsAI.FleetCombatStats(fleet_id).get_ship_stats(hashable=True) - e_f_dict = [cur_e_fighters, old_e_fighters][dead_fleet] # track old/dead enemy fighters for rating assessments in case not enough current info - for stats in ship_stats: - attacks = stats[0] - if attacks: - e_f_dict.setdefault(stats, [0])[0] += 1 - partial_vis_turn = universe.getVisibilityTurnsMap(fleet_id, empire_id).get(fo.visibility.partial, -9999) - if not dead_fleet: - # TODO: consider checking death of individual ships. If ships had been moved from this fleet - # into another fleet, we might have witnessed their death in that other fleet but if this fleet - # had not been seen since before that transfer then the ships might also still be listed here. - sys_status = self.systemStatus.setdefault(this_system_id, {}) - sys_status['enemy_ship_count'] = sys_status.get('enemy_ship_count', 0) + len(fleet.shipIDs) - if partial_vis_turn >= current_turn - 1: # only interested in immediately recent data - saw_enemies_at_system[fleet.systemID] = True - enemy_fleet_ids.append(fleet_id) - enemies_by_system.setdefault(this_system_id, []).append(fleet_id) - if not fleet.ownedBy(-1): - self.misc.setdefault('enemies_sighted', {}).setdefault(current_turn, []).append(fleet_id) - rating = CombatRatingsAI.get_fleet_rating(fleet_id, enemy_stats=CombatRatingsAI.get_empire_standard_fighter()) - if rating > 0.25 * my_milship_rating: - self.misc.setdefault('dangerous_enemies_sighted', {}).setdefault(current_turn, []).append(fleet_id) - e_f_dict = [cur_e_fighters, old_e_fighters][len(cur_e_fighters) == 1] - std_fighter = sorted([(v, k) for k, v in e_f_dict.items()])[-1][1] - self.__empire_standard_enemy = std_fighter - self.empire_standard_enemy_rating = self.get_standard_enemy().get_rating() - # TODO: If no current information available, rate against own fighters + + # TODO: consider checking death of individual ships. If ships had been moved from this fleet + # into another fleet, we might have witnessed their death in that other fleet but if this fleet + # had not been seen since before that transfer then the ships might also still be listed here. + if dead_fleet: + continue + + # we are only interested in immediately recent data + if get_partial_visibility_turn(fleet_id) < (current_turn - 1): + continue + + sys_status = self.systemStatus.setdefault(this_system_id, {}) + sys_status['enemy_ship_count'] = sys_status.get('enemy_ship_count', 0) + len(fleet.shipIDs) + enemies_by_system.setdefault(this_system_id, []).append(fleet_id) + + if not fleet.unowned: + self.misc.setdefault('enemies_sighted', {}).setdefault(current_turn, []).append(fleet_id) + observed_empires.add(fleet.owner) # assess fleet and planet threats & my local fleets - for sys_id in system_id_list: + for sys_id in universe.systemIDs: sys_status = self.systemStatus.setdefault(sys_id, {}) system = universe.getSystem(sys_id) if verbose: - print "AIState threat evaluation for %s" % system + debug("AIState threat evaluation for %s" % system) # update fleets sys_status['myfleets'] = my_fleets_by_system.get(sys_id, []) sys_status['myFleetsAccessible'] = fleet_spot_position.get(sys_id, []) @@ -428,118 +491,159 @@ def update_system_status(self): sys_status['localEnemyFleetIDs'] = local_enemy_fleet_ids if system: sys_status['name'] = system.name - for fid in system.fleetIDs: - if fid in destroyed_object_ids: # TODO: double check are these checks/deletes necessary? - self.delete_fleet_info(fid) # this is safe even if fleet wasn't mine - continue - fleet = universe.getFleet(fid) - if not fleet or fleet.empty: - self.delete_fleet_info(fid) # this is safe even if fleet wasn't mine - continue + + # update my fleet rating versus planets so that planet ratings can be more accurate + my_ratings_against_planets_list = [] + for fid in sys_status['myfleets']: + my_ratings_against_planets_list.append(self.get_rating(fid, against_planets=True)) + sys_status['myFleetRatingVsPlanets'] = CombatRatingsAI.combine_ratings_list( + my_ratings_against_planets_list) # update threats - sys_vis_dict = universe.getVisibilityTurnsMap(sys_id, fo.empireID()) - partial_vis_turn = sys_vis_dict.get(fo.visibility.partial, -9999) - mob_ratings = [] # for mobile unowned monster fleets - lost_fleet_rating = 0 - enemy_ratings = [] - monster_ratings = [] - mobile_fleets = [] + monster_ratings = [] # immobile + enemy_ratings = [] # owned & mobile + mob_ratings = [] # mobile & unowned + mobile_fleets = [] # mobile and either owned or unowned for fid in local_enemy_fleet_ids: - fleet = universe.getFleet(fid) - if not fleet: - continue - fleet_rating = CombatRatingsAI.get_fleet_rating(fid, enemy_stats=CombatRatingsAI.get_empire_standard_fighter()) + fleet = universe.getFleet(fid) # ensured to exist + fleet_rating = CombatRatingsAI.get_fleet_rating( + fid, enemy_stats=CombatRatingsAI.get_empire_standard_fighter()) if fleet.speed == 0: monster_ratings.append(fleet_rating) if verbose: - print "\t immobile enemy fleet %s has rating %.1f" % (fleet, fleet_rating) + debug("\t immobile enemy fleet %s has rating %.1f" % (fleet, fleet_rating)) + continue + + if verbose: + debug("\t mobile enemy fleet %s has rating %.1f" % (fleet, fleet_rating)) + mobile_fleets.append(fid) + if fleet.unowned: + mob_ratings.append(fleet_rating) else: - if verbose: - print "\t mobile enemy fleet %s has rating %.1f" % (fleet, fleet_rating) - mobile_fleets.append(fid) - if fleet.unowned: - mob_ratings.append(fleet_rating) - else: - enemy_ratings.append(fleet_rating) + enemy_ratings.append(fleet_rating) + enemy_rating = CombatRatingsAI.combine_ratings_list(enemy_ratings) monster_rating = CombatRatingsAI.combine_ratings_list(monster_ratings) mob_rating = CombatRatingsAI.combine_ratings_list(mob_ratings) - if fleetsLostBySystem.get(sys_id, []): - lost_fleet_rating = CombatRatingsAI.combine_ratings_list(fleetsLostBySystem[sys_id]) - if not system or partial_vis_turn == -9999: # under current visibility rules should not be possible to have any losses or other info here, but just in case... + lost_fleets = fleetsLostBySystem.get(sys_id, []) + lost_fleet_rating = CombatRatingsAI.combine_ratings_list(lost_fleets) + if lost_fleet_rating: + debug("Just lost fleet rating %.1f in system %s", lost_fleet_rating, system) + + # under current visibility rules should not be possible to have any losses or other info here, + # but just in case... + partial_vis_turn = get_partial_visibility_turn(sys_id) + if not system or partial_vis_turn < 0: if verbose: - print "Have never had partial vis for system %d ( %s ) -- basing threat assessment on old info and lost ships" % (sys_id, sys_status.get('name', "name unknown")) + debug("Never had partial vis for %s - basing threat assessment on old info and lost ships" % system) sys_status.setdefault('local_fleet_threats', set()) sys_status['planetThreat'] = 0 - sys_status['fleetThreat'] = int(max(CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), 0.98 * sys_status.get('fleetThreat', 0), 1.1 * lost_fleet_rating - monster_rating)) - sys_status['monsterThreat'] = int(max(monster_rating, 0.98 * sys_status.get('monsterThreat', 0), 1.1 * lost_fleet_rating - enemy_rating - mob_rating)) - sys_status['enemy_threat'] = int(max(enemy_rating, 0.98 * sys_status.get('enemy_threat', 0), 1.1 * lost_fleet_rating - monster_rating - mob_rating)) + sys_status['fleetThreat'] = max( + CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), + 0.98 * sys_status.get('fleetThreat', 0), + 1.1*lost_fleet_rating - monster_rating) + sys_status['monsterThreat'] = max( + monster_rating, + 0.98 * sys_status.get('monsterThreat', 0), + 1.1*lost_fleet_rating - enemy_rating - mob_rating) + sys_status['enemy_threat'] = max( + enemy_rating, + 0.98 * sys_status.get('enemy_threat', 0), + 1.1*lost_fleet_rating - monster_rating - mob_rating) sys_status['mydefenses'] = {'overall': 0, 'attack': 0, 'health': 0} sys_status['totalThreat'] = sys_status['fleetThreat'] sys_status['regional_fleet_threats'] = sys_status['local_fleet_threats'].copy() continue # have either stale or current info - pattack = 0 - phealth = 0 - mypattack, myphealth = 0, 0 + pattack = phealth = 0 + mypattack = myphealth = 0 for pid in system.planetIDs: - prating = self.assess_planet_threat(pid, sighting_age=current_turn - partial_vis_turn) planet = universe.getPlanet(pid) if not planet: continue - if planet.owner == self.empireID: # TODO: check for diplomatic status + sighting_age = current_turn - get_partial_visibility_turn(pid) + prating = self.assess_planet_threat(pid, sighting_age) + if planet.ownedBy(empire_id): # TODO: check for diplomatic status mypattack += prating['attack'] myphealth += prating['health'] else: - if [special for special in planet.specials if "_NEST_" in special]: - sys_status['nest_threat'] = 100 pattack += prating['attack'] phealth += prating['health'] + if any("_NEST_" in special for special in planet.specials): + sys_status['nest_threat'] = 100 sys_status['planetThreat'] = pattack * phealth sys_status['mydefenses'] = {'overall': mypattack * myphealth, 'attack': mypattack, 'health': myphealth} - if max(sys_status.get('totalThreat', 0), pattack * phealth) >= 0.6 * lost_fleet_rating: # previous threat assessment could account for losses, ignore the losses now + # previous threat assessment could account for losses, ignore the losses now + if (lost_fleet_rating and + lost_fleet_rating < max(sys_status.get('totalThreat', 0), pattack * phealth)): + debug("In system %s: Ignoring lost fleets since known threats could cause it.", system) lost_fleet_rating = 0 # TODO use sitrep combat info rather than estimating stealthed enemies by fleets lost to them # TODO also only consider past stealthed fleet threat to still be present if the system is still obstructed # TODO: track visibility across turns in order to distinguish the blip of visibility in (losing) combat, - # which FO currently treats as being for the previous turn, partially superseding the previous visibility for that turn - if not partial_vis_turn == current_turn: # (universe.getVisibility(sys_id, self.empire_id) >= fo.visibility.partial): + # which FO currently treats as being for the previous turn, + # partially superseding the previous visibility for that turn + + if not partial_vis_turn == current_turn: sys_status.setdefault('local_fleet_threats', set()) sys_status['currently_visible'] = False - # print "Stale visibility for system %d ( %s ) -- last seen %d, current Turn %d -- basing threat assessment on old info and lost ships"%(sys_id, sys_status.get('name', "name unknown"), partial_vis_turn, currentTurn) - sys_status['fleetThreat'] = int(max(CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), 0.98 * sys_status.get('fleetThreat', 0), 2.0 * lost_fleet_rating - max(sys_status.get('monsterThreat', 0), monster_rating))) - sys_status['enemy_threat'] = int(max(enemy_rating, 0.98 * sys_status.get('enemy_threat', 0), 1.1 * lost_fleet_rating - max(sys_status.get('monsterThreat', 0), monster_rating))) - sys_status['monsterThreat'] = int(max(monster_rating, 0.98 * sys_status.get('monsterThreat', 0))) - # sys_status['totalThreat'] = ((pattack + enemy_attack + monster_attack) ** 0.8) * ((phealth + enemy_health + monster_health)** 0.6) # reevaluate this - sys_status['totalThreat'] = max(CombatRatingsAI.combine_ratings_list([enemy_rating, mob_rating, monster_rating, pattack * phealth]), 2 * lost_fleet_rating, 0.98 * sys_status.get('totalThreat', 0)) - else: # system considered visible #TODO: reevaluate as visibility rules change + # print ("Stale visibility for system %d ( %s ) -- last seen %d, " + # "current Turn %d -- basing threat assessment on old info and lost ships") % ( + # sys_id, sys_status.get('name', "name unknown"), partial_vis_turn, currentTurn) + sys_status['fleetThreat'] = max( + CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), + 0.98 * sys_status.get('fleetThreat', 0), + 2.0 * lost_fleet_rating - max(sys_status.get('monsterThreat', 0), monster_rating)) + sys_status['enemy_threat'] = max( + enemy_rating, + 0.98 * sys_status.get('enemy_threat', 0), + 1.1*lost_fleet_rating - max(sys_status.get('monsterThreat', 0), monster_rating)) + sys_status['monsterThreat'] = max(monster_rating, 0.98 * sys_status.get('monsterThreat', 0)) + # sys_status['totalThreat'] = ((pattack + enemy_attack + monster_attack) ** 0.8)\ + # * ((phealth + enemy_health + monster_health)** 0.6) # reevaluate this + sys_status['totalThreat'] = max( + CombatRatingsAI.combine_ratings_list([enemy_rating, mob_rating, monster_rating, pattack * phealth]), + 2 * lost_fleet_rating, + 0.98 * sys_status.get('totalThreat', 0)) + else: # system considered visible sys_status['currently_visible'] = True sys_status['local_fleet_threats'] = set(mobile_fleets) - sys_status['fleetThreat'] = int(max(CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), 2 * lost_fleet_rating - monster_rating)) # includes mobile monsters + # includes mobile monsters + sys_status['fleetThreat'] = max( + CombatRatingsAI.combine_ratings(enemy_rating, mob_rating), 2*lost_fleet_rating - monster_rating) if verbose: - print "enemy threat calc parts: enemy rating %.1f, lost fleet rating %.1f, monster_rating %.1f" % (enemy_rating, lost_fleet_rating, monster_rating) - sys_status['enemy_threat'] = int(max(enemy_rating, 2 * lost_fleet_rating - monster_rating)) # does NOT include mobile monsters + debug("enemy threat calc parts: enemy rating %.1f, lost fleet rating %.1f, monster_rating %.1f" % ( + enemy_rating, lost_fleet_rating, monster_rating)) + # does NOT include mobile monsters + sys_status['enemy_threat'] = max(enemy_rating, 2*lost_fleet_rating - monster_rating) sys_status['monsterThreat'] = monster_rating - sys_status['totalThreat'] = CombatRatingsAI.combine_ratings_list([enemy_rating, mob_rating, monster_rating, pattack * phealth]) + sys_status['totalThreat'] = CombatRatingsAI.combine_ratings_list([ + sys_status['fleetThreat'], + sys_status['monsterThreat'], + pattack * phealth, + ]) sys_status['regional_fleet_threats'] = sys_status['local_fleet_threats'].copy() sys_status['fleetThreat'] = max(sys_status['fleetThreat'], sys_status.get('nest_threat', 0)) sys_status['totalThreat'] = max(sys_status['totalThreat'], sys_status.get('nest_threat', 0)) - if partial_vis_turn > 0 and sys_id not in supply_unobstructed_systems: # has been seen with Partial Vis, but is currently supply-blocked + # has been seen with Partial Vis, but is currently supply-blocked + if partial_vis_turn > 0 and sys_id not in supply_unobstructed_systems: sys_status['fleetThreat'] = max(sys_status['fleetThreat'], min_hidden_attack * min_hidden_health) - sys_status['totalThreat'] = max(sys_status['totalThreat'], ((pattack + min_hidden_attack) ** 0.8) * ((phealth + min_hidden_health) ** 0.6)) + sys_status['totalThreat'] = max(sys_status['totalThreat'], + CombatRatingsAI.combine_ratings(sys_status.get('planetThreat', 0), + (min_hidden_attack*min_hidden_health))) if verbose and sys_status['fleetThreat'] > 0: - print "%s intermediate status: %s" % (system, sys_status) + debug("%s intermediate status: %s" % (system, sys_status)) enemy_supply, enemy_near_supply = self.assess_enemy_supply() # TODO: assess change in enemy supply over time # assess secondary threats (threats of surrounding systems) and update my fleet rating - for sys_id in system_id_list: + for sys_id in universe.systemIDs: sys_status = self.systemStatus[sys_id] sys_status['enemies_supplied'] = enemy_supply.get(sys_id, []) + observed_empires.update(enemy_supply.get(sys_id, [])) sys_status['enemies_nearly_supplied'] = enemy_near_supply.get(sys_id, []) my_ratings_list = [] my_ratings_against_planets_list = [] @@ -549,16 +653,19 @@ def update_system_status(self): my_ratings_against_planets_list.append(self.get_rating(fid, against_planets=True)) if sys_id != INVALID_ID: sys_status['myFleetRating'] = CombatRatingsAI.combine_ratings_list(my_ratings_list) - sys_status['myFleetRatingVsPlanets'] = CombatRatingsAI.combine_ratings_list(my_ratings_against_planets_list) - sys_status['all_local_defenses'] = CombatRatingsAI.combine_ratings(sys_status['myFleetRating'], sys_status['mydefenses']['overall']) - sys_status['neighbors'] = set(dict_from_map(universe.getSystemNeighborsMap(sys_id, self.empireID))) + sys_status['myFleetRatingVsPlanets'] = CombatRatingsAI.combine_ratings_list( + my_ratings_against_planets_list) + sys_status['all_local_defenses'] = CombatRatingsAI.combine_ratings( + sys_status['myFleetRating'], sys_status['mydefenses']['overall']) + sys_status['neighbors'] = set(universe.getImmediateNeighbors(sys_id, self.empireID)) - for sys_id in system_id_list: + for sys_id in universe.systemIDs: sys_status = self.systemStatus[sys_id] neighbors = sys_status.get('neighbors', set()) - this_system = fo.getUniverse().getSystem(sys_id) + this_system = universe.getSystem(sys_id) if verbose: - print "Regional Assessment for %s with local fleet threat %.1f" % (this_system, sys_status.get('fleetThreat', 0)) + debug("Regional Assessment for %s with local fleet threat %.1f" % ( + this_system, sys_status.get('fleetThreat', 0))) jumps2 = set() jumps3 = set() jumps4 = set() @@ -567,46 +674,40 @@ def update_system_status(self): setb.update(self.systemStatus.get(sys2id, {}).get('neighbors', set())) jump2ring = jumps2 - neighbors - {sys_id} jump3ring = jumps3 - jumps2 - neighbors - {sys_id} - jump4ring = jumps4 - jumps3 - jumps2 - neighbors - {sys_id} + jump4ring = jumps4 - jumps3 - jumps2 - neighbors - {sys_id} sys_status['2jump_ring'] = jump2ring sys_status['3jump_ring'] = jump3ring sys_status['4jump_ring'] = jump4ring - threat, max_threat, myrating, j1_threats = self.area_ratings(neighbors, ref_sys_name="neighbors %s" % this_system) if verbose else self.area_ratings(neighbors) + threat, max_threat, myrating, j1_threats = self.area_ratings(neighbors) sys_status['neighborThreat'] = threat sys_status['max_neighbor_threat'] = max_threat sys_status['my_neighbor_rating'] = myrating - threat, max_threat, myrating, j2_threats = self.area_ratings(jump2ring, ref_sys_name="jump2 %s" % this_system) if verbose else self.area_ratings(jump2ring) + threat, max_threat, myrating, j2_threats = self.area_ratings(jump2ring) sys_status['jump2_threat'] = threat sys_status['my_jump2_rating'] = myrating threat, max_threat, myrating, j3_threats = self.area_ratings(jump3ring) sys_status['jump3_threat'] = threat sys_status['my_jump3_rating'] = myrating - threat_keys = ['fleetThreat', 'neighborThreat', 'jump2_threat'] # for local system includes both enemies and mobs - sys_status['regional_threat'] = CombatRatingsAI.combine_ratings_list(map(lambda x: sys_status.get(x, 0), threat_keys)) + # for local system includes both enemies and mobs + threat_keys = ['fleetThreat', 'neighborThreat', 'jump2_threat'] + sys_status['regional_threat'] = CombatRatingsAI.combine_ratings_list( + [sys_status.get(x, 0) for x in threat_keys]) # TODO: investigate cases where regional_threat has been nonzero but no regional_threat_fleets # (probably due to attenuating history of past threats) sys_status.setdefault('regional_fleet_threats', set()).update(j1_threats, j2_threats) - # threat, max_threat, myrating, j4_threats = self.area_ratings(jump4ring) - # sys_status['jump4_threat'] = threat - # sys_status['my_jump4_rating'] = myrating def area_ratings(self, system_ids): """Returns (fleet_threat, max_threat, myFleetRating, threat_fleets) compiled over a group of systems.""" - max_threat = 0 - threat = 0 - myrating = 0 + myrating = threat = max_threat = 0 threat_fleets = set() - threat_detail = [] for sys_id in system_ids: sys_status = self.systemStatus.get(sys_id, {}) # TODO: have distinct treatment for both enemy_threat and fleetThreat, respectively fthreat = sys_status.get('enemy_threat', 0) - if fthreat > max_threat: - max_threat = fthreat + max_threat = max(max_threat, fthreat) threat = CombatRatingsAI.combine_ratings(threat, fthreat) myrating = CombatRatingsAI.combine_ratings(myrating, sys_status.get('myFleetRating', 0)) # myrating = FleetUtilsAI.combine_ratings(myrating, sys_status.get('all_local_defenses', 0)) - threat_detail.append((sys_id, fthreat, sys_status.get('local_fleet_threats', []))) threat_fleets.update(sys_status.get('local_fleet_threats', [])) return threat, max_threat, myrating, threat_fleets @@ -636,9 +737,11 @@ def get_fleet_missions_with_any_mission_types(self, mission_types): return result def __add_fleet_mission(self, fleet_id): - """Add new AIFleetMission with fleetID if it already not exists.""" - if self.get_fleet_mission(fleet_id) is None: - self.__aiMissionsByFleetID[fleet_id] = AIFleetMission.AIFleetMission(fleet_id) + """Add a new dummy AIFleetMission for the passed fleet_id if it has no mission yet.""" + if self.get_fleet_mission(fleet_id) is not None: + warning("Tried to add a new fleet mission for fleet that already had a mission.") + return + self.__aiMissionsByFleetID[fleet_id] = AIFleetMission.AIFleetMission(fleet_id) def __remove_fleet_mission(self, fleet_id): """Remove invalid AIFleetMission with fleetID if it exists.""" @@ -651,22 +754,27 @@ def ensure_have_fleet_missions(self, fleet_ids): if self.get_fleet_mission(fleet_id) is None: self.__add_fleet_mission(fleet_id) - def __clean_fleet_missions(self, fleet_ids): - """Cleanup of AIFleetMissions.""" - for fleet_id in fleet_ids: + def __clean_fleet_missions(self): + """Assign a new dummy mission to new fleets and clean up existing, now invalid missions.""" + current_empire_fleets = FleetUtilsAI.get_empire_fleet_ids() + + # assign a new (dummy) mission to new fleets + for fleet_id in current_empire_fleets: if self.get_fleet_mission(fleet_id) is None: self.__add_fleet_mission(fleet_id) + # Check all fleet missions for validity and clear invalid targets. + # If a fleet does not exist anymore, mark mission for deletion. + # Deleting only after the loop allows us to avoid an expensive copy. deleted_fleet_ids = [] for mission in self.get_all_fleet_missions(): - if mission.fleet.id not in fleet_ids: + if mission.fleet.id not in current_empire_fleets: deleted_fleet_ids.append(mission.fleet.id) + else: + mission.clean_invalid_targets() for deleted_fleet_id in deleted_fleet_ids: self.__remove_fleet_mission(deleted_fleet_id) - for mission in self.get_all_fleet_missions(): - mission.clean_invalid_targets() - def has_target(self, mission_type, target): for mission in self.get_fleet_missions_with_any_mission_types([mission_type]): if mission.has_target(mission_type, target): @@ -693,7 +801,9 @@ def update_fleet_rating(self, fleet_id): def get_ship_role(self, ship_design_id): """Returns ship role for given designID, assesses and adds as needed.""" - if ship_design_id in self.__shipRoleByDesignID and self.__shipRoleByDesignID[ship_design_id] != ShipRoleType.INVALID: # if thought was invalid, recheck to be sure + # if thought was invalid, recheck to be sure + if (ship_design_id in self.__shipRoleByDesignID and + self.__shipRoleByDesignID[ship_design_id] != ShipRoleType.INVALID): return self.__shipRoleByDesignID[ship_design_id] else: role = FleetUtilsAI.assess_ship_design_role(fo.getShipDesign(ship_design_id)) @@ -738,95 +848,72 @@ def session_start_cleanup(self): self.ensure_have_fleet_missions([fleetID]) self.__clean_fleet_roles(just_resumed=True) fleetsLostBySystem.clear() - popCtrSystemIDs[:] = [] # resets without detroying existing references - colonizedSystems.clear() empireStars.clear() - popCtrIDs[:] = [] - outpostIDs[:] = [] - outpostSystemIDs[:] = [] - ResourcesAI.lastFociCheck[0] = 0 - self.qualifyingColonyBaseTargets.clear() - self.qualifyingOutpostBaseTargets.clear() self.qualifyingTroopBaseTargets.clear() def __clean_fleet_roles(self, just_resumed=False): """Removes fleetRoles if a fleet has been lost, and update fleet Ratings.""" - for sys_id in self.systemStatus: - self.systemStatus[sys_id]['myFleetRating'] = 0 - self.systemStatus[sys_id]['myFleetRatingVsPlanets'] = 0 - universe = fo.getUniverse() - ok_fleets = FleetUtilsAI.get_empire_fleet_ids() - fleet_list = sorted(list(self.__fleetRoleByID)) - ship_count = 0 - destroyed_object_ids = universe.destroyedObjectIDs(fo.empireID()) + current_empire_fleets = FleetUtilsAI.get_empire_fleet_ids() + self.shipCount = 0 fleet_table = Table([ Text('Fleet'), Float('Rating'), Float('Troops'), Text('Location'), Text('Destination')], table_name="Fleet Summary Turn %d" % fo.currentTurn() ) - for fleet_id in fleet_list: - status = self.fleetStatus.setdefault(fleet_id, {}) - rating = CombatRatingsAI.get_fleet_rating(fleet_id, self.get_standard_enemy()) - troops = FleetUtilsAI.count_troops_in_fleet(fleet_id) - old_sys_id = status.get('sysID', -2) + # need to loop over a copy as entries are deleted in loop + for fleet_id in list(self.__fleetRoleByID): + fleet_status = self.fleetStatus.setdefault(fleet_id, {}) + rating = CombatRatingsAI.get_fleet_rating(fleet_id) + old_sys_id = fleet_status.get('sysID', -2) # TODO: Introduce helper function instead fleet = universe.getFleet(fleet_id) if fleet: sys_id = fleet.systemID if old_sys_id in [-2, -1]: old_sys_id = sys_id - status['nships'] = len(fleet.shipIDs) - ship_count += status['nships'] + fleet_status['nships'] = len(fleet.shipIDs) # TODO: Introduce helper function instead + self.shipCount += fleet_status['nships'] else: - sys_id = old_sys_id # can still retrieve a fleet object even if fleet was just destroyed, so shouldn't get here + # can still retrieve a fleet object even if fleet was just destroyed, so shouldn't get here # however,this has been observed happening, and is the reason a fleet check was added a few lines below. # Not at all sure how this came about, but was throwing off threat assessments - if fleet_id not in ok_fleets: # or fleet.empty: - if (fleet and self.__fleetRoleByID.get(fleet_id, -1) != -1 and fleet_id not in destroyed_object_ids and - [ship_id for ship_id in fleet.shipIDs if ship_id not in destroyed_object_ids]): - if not just_resumed: - fleetsLostBySystem.setdefault(old_sys_id, []).append(max(rating, MilitaryAI.MinThreat)) - if fleet_id in self.__fleetRoleByID: - del self.__fleetRoleByID[fleet_id] - if fleet_id in self.__aiMissionsByFleetID: - del self.__aiMissionsByFleetID[fleet_id] - if fleet_id in self.fleetStatus: - del self.fleetStatus[fleet_id] + sys_id = old_sys_id + + # check if fleet is destroyed and if so, delete stored information + if fleet_id not in current_empire_fleets: # or fleet.empty: + debug("Just lost %s", fleet) + if not just_resumed: + fleetsLostBySystem.setdefault(old_sys_id, []).append( + max(rating, fleet_status.get('rating', 0.), MilitaryAI.MinThreat)) + + self.delete_fleet_info(fleet_id) continue - else: # fleet in ok fleets - this_sys = universe.getSystem(sys_id) - next_sys = universe.getSystem(fleet.nextSystemID) - - fleet_table.add_row( - [ - fleet, - rating, - troops, - this_sys or 'starlane', - next_sys or '-', - ]) - - status['rating'] = rating - if next_sys: - status['sysID'] = next_sys.id - elif this_sys: - status['sysID'] = this_sys.id - else: - main_mission = self.get_fleet_mission(fleet_id) - main_mission_type = (main_mission.getAIMissionTypes() + [-1])[0] - if main_mission_type != -1: - targets = main_mission.getAITargets(main_mission_type) - if targets: - m_mt0 = targets[0] - if isinstance(m_mt0.target_type, System): - status['sysID'] = m_mt0.target.id # hmm, but might still be a fair ways from here - fleet_table.print_table() - self.shipCount = ship_count + + # if reached here, the fleet does still exist + this_sys = universe.getSystem(sys_id) + next_sys = universe.getSystem(fleet.nextSystemID) + + fleet_table.add_row([ + fleet, + rating, + FleetUtilsAI.count_troops_in_fleet(fleet_id), + this_sys or 'starlane', + next_sys or '-', + ]) + + fleet_status['rating'] = rating + if next_sys: + fleet_status['sysID'] = next_sys.id + elif this_sys: + fleet_status['sysID'] = this_sys.id + else: + error("Fleet %s has no valid system." % fleet) + info(fleet_table) # Next string used in charts. Don't modify it! - print "Empire Ship Count: ", ship_count - print "Empire standard fighter summary: ", CombatRatingsAI.get_empire_standard_fighter().get_stats() - print "------------------------" + debug("Empire Ship Count: %s" % self.shipCount) + debug("Empire standard fighter summary: %s", (CombatRatingsAI.get_empire_standard_fighter().get_stats(), )) + debug("------------------------") def get_explored_system_ids(self): return list(self.exploredSystemIDs) @@ -845,11 +932,12 @@ def get_priority(self, priority_type): return copy.deepcopy(self.__priorityByType[priority_type]) return 0 - def split_new_fleets(self): - """Split any new fleets (at new game creation, can have unplanned mix of ship roles).""" + def __report_last_turn_fleet_missions(self): + """Print a table reviewing last turn fleet missions to the log file.""" universe = fo.getUniverse() - mission_table = Table([Text('Fleet'), Text('Mission'), Text('Ships'), Float('Rating'), Float('Troops'), Text('Target')], - table_name="Turn %d: Fleet Mission Review from Last Turn" % fo.currentTurn()) + mission_table = Table( + [Text('Fleet'), Text('Mission'), Text('Ships'), Float('Rating'), Float('Troops'), Text('Target')], + table_name="Turn %d: Fleet Mission Review from Last Turn" % fo.currentTurn()) for fleet_id, mission in self.get_fleet_missions_map().items(): fleet = universe.getFleet(fleet_id) if not fleet: @@ -865,27 +953,76 @@ def split_new_fleets(self): FleetUtilsAI.count_troops_in_fleet(fleet_id), mission.target or "-" ]) - mission_table.print_table() - # TODO: check length of fleets for losses or do in AIstat.__cleanRoles + info(mission_table) + + def __split_new_fleets(self): + """Split any new fleets. + + This function is supposed to be called once at the beginning of the turn. + Splitting the auto generated fleets at game start or those created by + recently built ships allows the AI to assign correct roles to all ships. + """ + # TODO: check length of fleets for losses or do in AIstate.__cleanRoles + universe = fo.getUniverse() known_fleets = self.get_fleet_roles_map() self.newlySplitFleets.clear() fleets_to_split = [fleet_id for fleet_id in FleetUtilsAI.get_empire_fleet_ids() if fleet_id not in known_fleets] - if fleets_to_split: - print "Splitting new fleets" - for fleet_id in fleets_to_split: - fleet = universe.getFleet(fleet_id) - if not fleet: - print >> sys.stderr, "After splitting fleet: resulting fleet ID %d appears to not exist" % fleet_id - continue - fleet_len = len(list(fleet.shipIDs)) - if fleet_len == 1: - continue - new_fleets = FleetUtilsAI.split_fleet(fleet_id) # try splitting fleet - print "\t from splitting fleet ID %4d with %d ships, got %d new fleets:" % (fleet_id, fleet_len, len(new_fleets)) - # old fleet may have different role after split, later will be again identified - # self.remove_fleet_role(fleet_id) # in current system, orig new fleet will not yet have been assigned a role + debug("Trying to split %d new fleets" % len(fleets_to_split)) + for fleet_id in fleets_to_split: + fleet = universe.getFleet(fleet_id) + if not fleet: + warning("Trying to split fleet %d but seemingly does not exist" % fleet_id) + continue + fleet_len = len(fleet.shipIDs) + if fleet_len == 1: + continue + new_fleets = FleetUtilsAI.split_fleet(fleet_id) + debug("Split fleet %d with %d ships into %d new fleets:" % (fleet_id, fleet_len, len(new_fleets))) + # old fleet may have different role after split, later will be again identified + # in current system, orig new fleet will not yet have been assigned a role + # self.remove_fleet_role(fleet_id) + + def __cleanup_qualifiying_base_targets(self): + """Cleanup invalid entries in qualifying base targets.""" + universe = fo.getUniverse() + empire_id = fo.empireID() + for dct in [self.qualifyingTroopBaseTargets]: + for pid in list(dct.keys()): + planet = universe.getPlanet(pid) + if planet and planet.ownedBy(empire_id): + del dct[pid] + + def prepare_for_new_turn(self): + self.__report_last_turn_fleet_missions() + self.__split_new_fleets() + self.__refresh() # TODO: Use turn_state instead + self.__border_exploration_update() + self.__cleanup_qualifiying_base_targets() + self.orbital_colonization_manager.turn_start_cleanup() + self.__clean_fleet_roles() + self.__clean_fleet_missions() + debug("Fleets lost by system: %s" % fleetsLostBySystem) + self.__update_empire_standard_enemy() + self.__update_system_status() + self.__report_system_threats() + self.__report_system_defenses() + self.__report_exploration_status() + + def __report_exploration_status(self): + universe = fo.getUniverse() + explored_system_ids = self.get_explored_system_ids() + debug("Unexplored Systems: %s " % [universe.getSystem(sys_id) for sys_id in self.get_unexplored_system_ids()]) + debug("Explored SystemIDs: %s" % [universe.getSystem(sys_id) for sys_id in explored_system_ids]) + debug("Explored PlanetIDs: %s" % PlanetUtilsAI.get_planets_in__systems_ids(explored_system_ids)) + + def log_alliance_request(self, initiating_empire_id, recipient_empire_id): + """Keep a record of alliance requests made or received by this empire.""" + + alliance_requests = self.diplomatic_logs.setdefault('alliance_requests', {}) + log_index = (initiating_empire_id, recipient_empire_id) + alliance_requests.setdefault(log_index, []).append(fo.currentTurn()) def log_peace_request(self, initiating_empire_id, recipient_empire_id): """Keep a record of peace requests made or received by this empire.""" diff --git a/default/python/AI/ColonisationAI.py b/default/python/AI/ColonisationAI.py index d4343014f24..64e2161996d 100644 --- a/default/python/AI/ColonisationAI.py +++ b/default/python/AI/ColonisationAI.py @@ -1,24 +1,27 @@ -import sys +from logging import debug, warning, error, info +from operator import itemgetter import freeOrionAIInterface as fo # pylint: disable=import-error + from common.print_utils import Table, Text, Sequence, Bool, Float import AIDependencies -import universe_object import AIstate +import EspionageAI import FleetUtilsAI -import FreeOrionAI as foAI +import InvasionAI import PlanetUtilsAI +import PriorityAI import ProductionAI -import TechsListsAI import MilitaryAI +from aistate_interface import get_aistate +from target import TargetPlanet from turn_state import state from EnumsAI import MissionType, FocusType, EmpireProductionTypes, ShipRoleType, PriorityType -from freeorion_tools import dict_from_map, tech_is_complete, get_ai_tag_grade, cache_by_turn, AITimer -from AIDependencies import (INVALID_ID, POP_CONST_MOD_MAP, POP_SIZE_MOD_MAP_MODIFIED_BY_SPECIES, - POP_SIZE_MOD_MAP_NOT_MODIFIED_BY_SPECIES) - -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) +from freeorion_tools import (tech_is_complete, get_species_tag_grade, cache_by_turn_persistent, + AITimer, get_partial_visibility_turn, cache_for_current_turn, cache_for_session) +from AIDependencies import (INVALID_ID, OUTPOSTING_TECH, POP_CONST_MOD_MAP, + POP_SIZE_MOD_MAP_MODIFIED_BY_SPECIES, POP_SIZE_MOD_MAP_NOT_MODIFIED_BY_SPECIES, + Tags) colonization_timer = AITimer('getColonyFleets()') @@ -26,57 +29,66 @@ empire_colonizers = {} empire_ship_builders = {} empire_shipyards = {} -empire_dry_docks = {} available_growth_specials = {} active_growth_specials = {} empire_metabolisms = {} -annexable_system_ids = set() -annexable_ring1 = set() -annexable_ring2 = set() -annexable_ring3 = set() -systems_by_supply_tier = {} -system_supply = {} planet_supply_cache = {} # includes system supply all_colony_opportunities = {} pilot_ratings = {} colony_status = {} -empire_status = {'industrialists': 0, 'researchers': 0} -unowned_empty_planet_ids = set() -empire_outpost_ids = set() facilities_by_species_grade = {} system_facilities = {} -ENVIRONS = {str(fo.planetEnvironment.uninhabitable): 0, str(fo.planetEnvironment.hostile): 1, - str(fo.planetEnvironment.poor): 2, str(fo.planetEnvironment.adequate): 3, str(fo.planetEnvironment.good): 4} -PHOTO_MAP = {fo.starType.blue: 3, fo.starType.white: 1.5, fo.starType.red: -1, fo.starType.neutron: -1, - fo.starType.blackHole: -10, fo.starType.noStar: -10} +NEST_VAL_MAP = { + "SNOWFLAKE_NEST_SPECIAL": 15, + "KRAKEN_NEST_SPECIAL": 40, + "JUGGERNAUT_NEST_SPECIAL": 80, +} + +AVG_PILOT_RATING = 2.0 +GOOD_PILOT_RATING = 4.0 +GREAT_PILOT_RATING = 6.0 +ULT_PILOT_RATING = 12.0 + +# minimum evaluation score that a planet must reach so it is considered for outposting or colonizing +MINIMUM_COLONY_SCORE = 60 -def _get_planet_size(planet): - """Get the colonisation-relevant planet size. - :param planet: Planet whose size should be find - :type planet: fo.Planet - :return: size of the planet - :rtype: int - """ +def colony_pod_cost(): + return AIDependencies.COLONY_POD_COST * (1 + state.get_number_of_colonies()*AIDependencies.COLONY_POD_UPKEEP) - if planet.size == fo.planetSize.asteroids: - planet_size = 3 - elif planet.size == fo.planetSize.gasGiant: - planet_size = 6 - else: - planet_size = planet.size - return planet_size +def outpod_pod_cost(): + return AIDependencies.OUTPOST_POD_COST * (1 + state.get_number_of_colonies()*AIDependencies.COLONY_POD_UPKEEP) def calc_max_pop(planet, species, detail): - planet_size = _get_planet_size(planet) - planet_env = ENVIRONS[str(species.getPlanetEnvironment(planet.type))] + planet_size = planet.habitableSize + planet_env = species.getPlanetEnvironment(planet.type) + if planet_env == fo.planetEnvironment.uninhabitable: + detail.append("Uninhabitable.") + return 0 + + for bldg_id in planet.buildingIDs: + building = fo.getUniverse().getBuilding(bldg_id) + if not building: + continue + if building.buildingTypeName == "BLD_GATEWAY_VOID": + detail.append("Gateway to the void: Uninhabitable.") + return 0 + tag_list = list(species.tags) if species else [] - pop_tag_mod = AIDependencies.SPECIES_POPULATION_MODIFIER.get(get_ai_tag_grade(tag_list, "POPULATION"), 1.0) + pop_tag_mod = AIDependencies.SPECIES_POPULATION_MODIFIER.get( + get_species_tag_grade(species.name, Tags.POPULATION), 1.0) + if planet.type == fo.planetType.gasGiant and "GASEOUS" in tag_list: + gaseous_adjustment = AIDependencies.GASEOUS_POP_FACTOR + detail.append("GASEOUS adjustment: %.2f" % gaseous_adjustment) + else: + gaseous_adjustment = 1.0 + + planet_specials = set(planet.specials) base_pop_modified_by_species = 0 base_pop_not_modified_by_species = 0 @@ -103,20 +115,20 @@ def calc_max_pop(planet, species, detail): pop_const_mod += POP_CONST_MOD_MAP[tech][planet_env] detail.append("%s_PCM(%d)" % (tech, POP_CONST_MOD_MAP[tech][planet_env])) - for _special in set(planet.specials).intersection(AIDependencies.POP_FIXED_MOD_SPECIALS): + for _special in planet_specials.intersection(AIDependencies.POP_FIXED_MOD_SPECIALS): this_mod = sum(AIDependencies.POP_FIXED_MOD_SPECIALS[_special].get(int(psize), 0) for psize in [-1, planet.size]) detail.append("%s_PCM(%d)" % (_special, this_mod)) pop_const_mod += this_mod - for _special in set(planet.specials).intersection(AIDependencies.POP_PROPORTIONAL_MOD_SPECIALS): + for _special in planet_specials.intersection(AIDependencies.POP_PROPORTIONAL_MOD_SPECIALS): this_mod = sum(AIDependencies.POP_PROPORTIONAL_MOD_SPECIALS[_special].get(int(psize), 0) for psize in [-1, planet.size]) detail.append("%s (maxPop%+.1f)" % (_special, this_mod)) base_pop_not_modified_by_species += this_mod # exobots can't ever get to good environ so no gaia bonus, for others we'll assume they'll get there - if "GAIA_SPECIAL" in planet.specials and species.name != "SP_EXOBOT": + if "GAIA_SPECIAL" in planet_specials and species.name != "SP_EXOBOT": base_pop_not_modified_by_species += 3 detail.append("Gaia_PSM_late(3)") @@ -132,7 +144,7 @@ def calc_max_pop(planet, species, detail): applicable_boosts.add(key) detail.append("%s boost active" % key) for boost in metab_boosts: - if boost in planet.specials: + if boost in planet_specials: applicable_boosts.add(boost) detail.append("%s boost present" % boost) @@ -142,170 +154,99 @@ def calc_max_pop(planet, species, detail): detail.append("boosts_PSM(%d from %s)" % (n_boosts, applicable_boosts)) if planet.id in species.homeworlds: + detail.append('Homeworld (2)') base_pop_not_modified_by_species += 2 + if AIDependencies.TAG_LIGHT_SENSITIVE in tag_list: + star_type = fo.getUniverse().getSystem(planet.systemID).starType + star_pop_mod = AIDependencies.POP_MOD_LIGHTSENSITIVE_STAR_MAP.get(star_type, 0) + base_pop_not_modified_by_species += star_pop_mod + detail.append("Lightsensitive Star Bonus_PSM_late(%.1f)" % star_pop_mod) + def max_pop_size(): species_effect = (pop_tag_mod - 1) * abs(base_pop_modified_by_species) - base_pop = base_pop_not_modified_by_species + base_pop_modified_by_species + species_effect + gaseous_effect = (gaseous_adjustment - 1) * abs(base_pop_modified_by_species) + base_pop = (base_pop_not_modified_by_species + base_pop_modified_by_species + + species_effect + gaseous_effect) return planet_size * base_pop + pop_const_mod - if "PHOTOTROPHIC" in tag_list and max_pop_size() > 0: + target_pop = max_pop_size() + if "PHOTOTROPHIC" in tag_list and target_pop > 0: star_type = fo.getUniverse().getSystem(planet.systemID).starType - star_pop_mod = PHOTO_MAP.get(star_type, 0) + star_pop_mod = AIDependencies.POP_MOD_PHOTOTROPHIC_STAR_MAP.get(star_type, 0) base_pop_not_modified_by_species += star_pop_mod detail.append("Phototropic Star Bonus_PSM_late(%0.1f)" % star_pop_mod) + target_pop = max_pop_size() - detail.append("MaxPop = baseMaxPop + size*(psm_early + (species_mod - 1)*abs(psm_early) + psm_late)" - " = %d + %d * (%d + (%.2f-1)*abs(%d) + %d) = %.2f" % ( + detail.append("max_pop = base + size*[psm_early + species_mod*abs(psm_early) + psm_late]") + detail.append(" = %.2f + %d * [%.2f + %.2f*abs(%.2f) + %.2f]" % ( pop_const_mod, planet_size, base_pop_modified_by_species, - pop_tag_mod, base_pop_modified_by_species, base_pop_not_modified_by_species, max_pop_size())) - detail.append("maxPop %.1f" % max_pop_size()) - print detail - return max_pop_size() - - -NEST_VAL_MAP = { - "SNOWFLAKE_NEST_SPECIAL": 15, - "KRAKEN_NEST_SPECIAL": 40, - "JUGGERNAUT_NEST_SPECIAL": 80, -} - -AVG_PILOT_RATING = 2.0 -GOOD_PILOT_RATING = 4.0 -GREAT_PILOT_RATING = 6.0 -ULT_PILOT_RATING = 12.0 + (pop_tag_mod+gaseous_adjustment-2), base_pop_modified_by_species, + base_pop_not_modified_by_species)) + detail.append(" = %.2f" % target_pop) + return target_pop def galaxy_is_sparse(): setup_data = fo.getGalaxySetupData() - avg_empire_systems = setup_data.size / len(fo.allEmpireIDs()) + avg_empire_systems = setup_data.size // len(fo.allEmpireIDs()) return ((setup_data.monsterFrequency <= fo.galaxySetupOption.low) and ((avg_empire_systems >= 40) or ((avg_empire_systems >= 35) and (setup_data.shape != fo.galaxyShape.elliptical)))) -def rate_piloting_tag(tag_list): +@cache_for_session +def rate_piloting_tag(species_name): grades = {'NO': 1e-8, 'BAD': 0.75, 'GOOD': GOOD_PILOT_RATING, 'GREAT': GREAT_PILOT_RATING, 'ULTIMATE': ULT_PILOT_RATING} - return grades.get(get_ai_tag_grade(tag_list, "WEAPONS"), 1.0) + return grades.get(get_species_tag_grade(species_name, Tags.WEAPONS), 1.0) def rate_planetary_piloting(pid): universe = fo.getUniverse() planet = universe.getPlanet(pid) - if not planet or not planet.speciesName: + if not planet: return 0.0 - this_spec = fo.getSpecies(planet.speciesName) - if not this_spec: - return 0.0 - return rate_piloting_tag(this_spec.tags) + return rate_piloting_tag(planet.speciesName) -@cache_by_turn +@cache_by_turn_persistent # helpful to cache history to debug AI supply progress def get_supply_tech_range(): - return sum(_range for _tech, _range in AIDependencies.supply_range_techs.iteritems() if tech_is_complete(_tech)) - - -def check_supply(): - print "\n", 10 * "=", "Supply calculations", 10 * "=", "\n" - universe = fo.getUniverse() - empire = fo.getEmpire() - - colonization_timer.start('Getting Empire Supply Info') - systems_by_supply_tier.clear() - system_supply.clear() - system_supply.update(empire.supplyProjections()) # number of jumps away from fleet-supplied system (0 = in supply) - for sys_id, supply_val in system_supply.items(): - systems_by_supply_tier.setdefault(min(0, supply_val), []).append(sys_id) - - print "Base Supply:", dict_from_map(empire.systemSupplyRanges) - print "Supply connected systems: ", ', '.join(PlanetUtilsAI.sys_name_ids(systems_by_supply_tier.get(0, []))) - print - - colonization_timer.start('Determining Annexable Systems') - annexable_system_ids.clear() # TODO: distinguish colony-annexable systems and outpost-annexable systems - annexable_ring1.clear() - annexable_ring2.clear() - annexable_ring3.clear() - annexable_ring1.update(systems_by_supply_tier.get(-1, [])) - annexable_ring2.update(systems_by_supply_tier.get(-2, [])) - annexable_ring3.update(systems_by_supply_tier.get(-3, [])) - - print "First Ring of annexable systems: ", ', '.join(PlanetUtilsAI.sys_name_ids(systems_by_supply_tier.get(-1, []))) - print "Second Ring of annexable systems: ", ', '.join(PlanetUtilsAI.sys_name_ids(systems_by_supply_tier.get(-2, []))) - print "Third Ring of annexable systems: ", ', '.join(PlanetUtilsAI.sys_name_ids(systems_by_supply_tier.get(-3, []))) - - supply_distance = get_supply_tech_range() - # extra potential supply contributions: - # 3 for up to Ultimate supply species - # 2 for possible tiny planets - # 1 for World Tree - # TODO: +3 to consider capturing planets with Elevators - # TODO consider that this should not be more then maximal value in empire.systemSupplyRanges - supply_distance += 6 # should not be more than max value in supplyProjections - - # we should not rely on constant here, for system, supply in supplyProjections need to add systems in supply range - for jumps in range(-supply_distance, 1): # [-supply_distance, ..., -2, -1, 0] - annexable_system_ids.update(systems_by_supply_tier.get(jumps, [])) - colonization_timer.stop() + return sum(_range for _tech, _range in AIDependencies.supply_range_techs.items() if tech_is_complete(_tech)) def survey_universe(): - check_supply() colonization_timer.start("Categorizing Visible Planets") universe = fo.getUniverse() - empire = fo.getEmpire() empire_id = fo.empireID() current_turn = fo.currentTurn() - # get outpost and colonization planets - explored_system_ids = foAI.foAIstate.get_explored_system_ids() - un_ex_sys_ids = foAI.foAIstate.get_unexplored_system_ids() - - print "Unexplored Systems: %s " % map(universe.getSystem, un_ex_sys_ids) - print "Explored SystemIDs: %s" % map(universe.getSystem, explored_system_ids) - print "Explored PlanetIDs: %s" % PlanetUtilsAI.get_planets_in__systems_ids(explored_system_ids) - print - # set up / reset various variables; the 'if' is purely for code folding convenience if True: colony_status['colonies_under_attack'] = [] colony_status['colonies_under_threat'] = [] - empire_status.clear() - empire_status.update({'industrialists': 0, 'researchers': 0}) AIstate.empireStars.clear() - empire_outpost_ids.clear() empire_colonizers.clear() empire_ship_builders.clear() empire_shipyards.clear() - empire_dry_docks.clear() empire_metabolisms.clear() available_growth_specials.clear() active_growth_specials.clear() - if tech_is_complete(TechsListsAI.EXOBOT_TECH_NAME): + if tech_is_complete(AIDependencies.EXOBOT_TECH_NAME): empire_colonizers["SP_EXOBOT"] = [] # get it into colonizer list even if no colony yet for spec_name in AIDependencies.EXTINCT_SPECIES: if tech_is_complete("TECH_COL_" + spec_name): empire_colonizers["SP_" + spec_name] = [] # get it into colonizer list even if no colony yet - AIstate.popCtrIDs[:] = [] - AIstate.popCtrSystemIDs[:] = [] - AIstate.outpostIDs[:] = [] - AIstate.outpostSystemIDs[:] = [] - AIstate.colonizedSystems.clear() pilot_ratings.clear() - unowned_empty_planet_ids.clear() facilities_by_species_grade.clear() system_facilities.clear() - state.update() # var setup done - + aistate = get_aistate() for sys_id in universe.systemIDs: system = universe.getSystem(sys_id) if not system: continue - empire_has_colony_in_sys = False - empire_has_pop_ctr_in_sys = False local_ast = False local_gg = False empire_has_qualifying_planet = False @@ -316,7 +257,7 @@ def survey_universe(): planet = universe.getPlanet(pid) if not planet: continue - if pid in foAI.foAIstate.colonisablePlanetIDs: + if pid in aistate.colonisablePlanetIDs: empire_has_qualifying_planet = True if planet.size == fo.planetSize.asteroids: local_ast = True @@ -330,20 +271,12 @@ def survey_universe(): ship_facilities = set(AIDependencies.SHIP_FACILITIES).intersection(buildings_here) weapons_grade = "WEAPONS_0.0" if owner_id == empire_id: - empire_has_colony_in_sys = True - AIstate.colonizedSystems.setdefault(sys_id, []).append( - pid) # track these to plan Solar Generators and Singularity Generators, etc. - if planet_population <= 0.0: - empire_outpost_ids.add(pid) - AIstate.outpostIDs.append(pid) - elif this_spec: + if planet_population > 0.0 and this_spec: empire_has_qualifying_planet = True - AIstate.popCtrIDs.append(pid) - empire_has_pop_ctr_in_sys = True for metab in [tag for tag in this_spec.tags if tag in AIDependencies.metabolismBoostMap]: - empire_metabolisms[metab] = empire_metabolisms.get(metab, 0.0) + planet.size + empire_metabolisms[metab] = (empire_metabolisms.get(metab, 0.0) + planet.habitableSize) if this_spec.canProduceShips: - pilot_val = rate_piloting_tag(list(this_spec.tags)) + pilot_val = rate_piloting_tag(spec_name) if spec_name == "SP_ACIREMA": pilot_val += 1 weapons_grade = "WEAPONS_%.1f" % pilot_val @@ -356,22 +289,6 @@ def survey_universe(): yard_here = [pid] if this_spec.canColonize and planet.currentMeterValue(fo.meterType.targetPopulation) >= 3: empire_colonizers.setdefault(spec_name, []).extend(yard_here) - if "COMPUTRONIUM_SPECIAL" in planet.specials: # only counting it if planet is populated - state.set_have_computronium() - else: - # Logic says this should not happen, but it seems to happen some time for a single turm - # TODO What causes this? - empire_outpost_ids.add(pid) - AIstate.outpostIDs.append(pid) - print - print >> sys.stderr, "+ + +" - print >> sys.stderr, "DEBUG: ColonisationAI.survey_universe()" - print >> sys.stderr, "Found a planet we own that has pop > 0 but has no species" - print >> sys.stderr, "Planet: ", universe.getPlanet(pid) - print >> sys.stderr, "Species: ", spec_name, " ", this_spec - print >> sys.stderr, "Population: ", planet_population - print >> sys.stderr, "+ + +" - print this_grade_facilities = facilities_by_species_grade.setdefault(weapons_grade, {}) for facility in ship_facilities: @@ -381,12 +298,6 @@ def survey_universe(): this_facility_dict.setdefault("systems", set()).add(sys_id) this_facility_dict.setdefault("planets", set()).add(pid) - if planet.focus == FocusType.FOCUS_INDUSTRY: - empire_status['industrialists'] += planet_population - elif planet.focus == FocusType.FOCUS_RESEARCH: - empire_status['researchers'] += planet_population - if "ANCIENT_RUINS_SPECIAL" in planet.specials: - state.set_have_ruins() for special in planet.specials: if special in NEST_VAL_MAP: state.set_have_nest() @@ -394,15 +305,9 @@ def survey_universe(): available_growth_specials.setdefault(special, []).append(pid) if planet.focus == FocusType.FOCUS_GROWTH: active_growth_specials.setdefault(special, []).append(pid) - if "BLD_SHIPYARD_ORBITAL_DRYDOCK" in buildings_here: - empire_dry_docks.setdefault(planet.systemID, []).append(pid) - elif owner_id == -1: - if spec_name == "": - unowned_empty_planet_ids.add(pid) - else: - partial_vis_turn = universe.getVisibilityTurnsMap(pid, empire_id).get(fo.visibility.partial, -9999) - if partial_vis_turn >= current_turn - 1: # only interested in immediately recent data - foAI.foAIstate.misc.setdefault('enemies_sighted', {}).setdefault(current_turn, []).append(pid) + elif owner_id != -1: + if get_partial_visibility_turn(pid) >= current_turn - 1: # only interested in immediately recent data + aistate.misc.setdefault('enemies_sighted', {}).setdefault(current_turn, []).append(pid) if empire_has_qualifying_planet: if local_ast: @@ -410,13 +315,9 @@ def survey_universe(): elif local_gg: state.set_have_gas_giant() - if empire_has_colony_in_sys: - if empire_has_pop_ctr_in_sys: - AIstate.popCtrSystemIDs.append(sys_id) - else: - AIstate.outpostSystemIDs.append(sys_id) + if sys_id in state.get_empire_planets_by_system(): AIstate.empireStars.setdefault(system.starType, []).append(sys_id) - sys_status = foAI.foAIstate.systemStatus.setdefault(sys_id, {}) + sys_status = aistate.systemStatus.setdefault(sys_id, {}) if sys_status.get('fleetThreat', 0) > 0: colony_status['colonies_under_attack'].append(sys_id) if sys_status.get('neighborThreat', 0) > 0: @@ -424,14 +325,14 @@ def survey_universe(): _print_empire_species_roster() - if len(pilot_ratings) != 0: + if pilot_ratings: rating_list = sorted(pilot_ratings.values(), reverse=True) state.set_best_pilot_rating(rating_list[0]) if len(pilot_ratings) == 1: state.set_medium_pilot_rating(rating_list[0]) else: - state.set_medium_pilot_rating(rating_list[1 + int(len(rating_list) / 5)]) - # the idea behind this was to note systems that the empire has claimed-- either has a current colony or has targetted + state.set_medium_pilot_rating(rating_list[1 + int(len(rating_list) // 5)]) + # the idea behind this was to note systems that the empire has claimed-- either has a current colony or has targeted # for making/invading a colony # claimedStars = {} # for sType in AIstate.empireStars: @@ -440,7 +341,7 @@ def survey_universe(): # tSys = universe.getSystem(sysID) # if not tSys: continue # claimedStars.setdefault( tSys.starType, []).append(sysID) - # foAI.foAIstate.misc['claimedStars'] = claimedStars + # get_aistate().misc['claimedStars'] = claimedStars colonization_timer.stop() @@ -449,9 +350,6 @@ def get_colony_fleets(): universe = fo.getUniverse() empire = fo.getEmpire() - colonization_timer.start('Getting avail colony fleets') - all_colony_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.COLONISATION) - colonization_timer.start('Identify Existing colony/outpost targets') colony_targeted_planet_ids = FleetUtilsAI.get_targeted_planet_ids(universe.planetIDs, MissionType.COLONISATION) all_colony_targeted_system_ids = PlanetUtilsAI.get_systems(colony_targeted_planet_ids) @@ -465,21 +363,20 @@ def get_colony_fleets(): outpost_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.OUTPOST) num_outpost_fleets = len(FleetUtilsAI.extract_fleet_ids_without_mission_types(outpost_fleet_ids)) - print "Colony Targeted SystemIDs: %s" % all_colony_targeted_system_ids - print "Colony Targeted PlanetIDs: %s" % colony_targeted_planet_ids - print colony_fleet_ids and "Colony Fleet IDs: %s" % colony_fleet_ids or "Available Colony Fleets: 0" - print "Colony Fleets Without Missions: %s" % num_colony_fleets - print - print "Outpost Targeted SystemIDs:", all_outpost_targeted_system_ids - print "Outpost Targeted PlanetIDs:", outpost_targeted_planet_ids - print outpost_fleet_ids and "Outpost Fleet IDs: %s" % outpost_fleet_ids or "Available Outpost Fleets: 0" - print "Outpost Fleets Without Missions: %s" % num_outpost_fleets - print + debug("Colony Targeted SystemIDs: %s" % all_colony_targeted_system_ids) + debug("Colony Targeted PlanetIDs: %s" % colony_targeted_planet_ids) + debug(colony_fleet_ids and "Colony Fleet IDs: %s" % colony_fleet_ids or "Available Colony Fleets: 0") + debug("Colony Fleets Without Missions: %s" % num_colony_fleets) + debug('') + debug("Outpost Targeted SystemIDs: %s" % all_outpost_targeted_system_ids) + debug("Outpost Targeted PlanetIDs: %s" % outpost_targeted_planet_ids) + debug(outpost_fleet_ids and "Outpost Fleet IDs: %s" % outpost_fleet_ids or "Available Outpost Fleets: 0") + debug("Outpost Fleets Without Missions: %s" % num_outpost_fleets) + debug('') # export targeted systems for other AI modules AIstate.colonyTargetedSystemIDs = all_colony_targeted_system_ids AIstate.outpostTargetedSystemIDs = all_outpost_targeted_system_ids - AIstate.colonyFleetIDs[:] = FleetUtilsAI.extract_fleet_ids_without_mission_types(all_colony_fleet_ids) colonization_timer.start('Identify colony base targets') # keys are sets of ints; data is doubles @@ -488,97 +385,34 @@ def get_colony_fleets(): avail_pp_by_sys = {} for p_set in available_pp: avail_pp_by_sys.update([(sys_id, available_pp[p_set]) for sys_id in set(PlanetUtilsAI.get_systems(p_set))]) - colony_cost = AIDependencies.COLONY_POD_COST * (1 + AIDependencies.COLONY_POD_UPKEEP * len(list(AIstate.popCtrIDs))) - outpost_cost = AIDependencies.OUTPOST_POD_COST * ( - 1 + AIDependencies.COLONY_POD_UPKEEP * len(list(AIstate.popCtrIDs))) - production_queue = empire.productionQueue - queued_outpost_bases = [] - queued_colony_bases = [] - for queue_index in range(0, len(production_queue)): - element = production_queue[queue_index] - if element.buildType == EmpireProductionTypes.BT_SHIP and element.turnsLeft != -1: - if foAI.foAIstate.get_ship_role(element.designID) in [ShipRoleType.BASE_OUTPOST]: - build_planet = universe.getPlanet(element.locationID) - queued_outpost_bases.append(build_planet.systemID) - elif foAI.foAIstate.get_ship_role(element.designID) in [ShipRoleType.BASE_COLONISATION]: - build_planet = universe.getPlanet(element.locationID) - queued_colony_bases.append(build_planet.systemID) - - evaluated_colony_planet_ids = list(unowned_empty_planet_ids.union(AIstate.outpostIDs) - set( + + evaluated_colony_planet_ids = list(state.get_unowned_empty_planets().union(state.get_empire_outposts()) - set( colony_targeted_planet_ids)) # places for possible colonyBase - # foAI.foAIstate.qualifyingOutpostBaseTargets.clear() #don't want to lose the info by clearing, but #TODO: should double check if still own colonizer planet - # foAI.foAIstate.qualifyingColonyBaseTargets.clear() - cost_ratio = 120 # TODO: temp ratio; reest to 12 *; consider different ratio + aistate = get_aistate() + outpost_base_manager = aistate.orbital_colonization_manager + for pid in evaluated_colony_planet_ids: # TODO: reorganize planet = universe.getPlanet(pid) if not planet: continue sys_id = planet.systemID - for pid2 in state.get_empire_inhabited_planets_by_system().get(sys_id, []): + for pid2 in state.get_empire_planets_by_system(sys_id, include_outposts=False): planet2 = universe.getPlanet(pid2) if not (planet2 and planet2.speciesName in empire_colonizers): continue - if pid not in foAI.foAIstate.qualifyingOutpostBaseTargets: - if planet.unowned: - foAI.foAIstate.qualifyingOutpostBaseTargets.setdefault(pid, [pid2, -1]) - if ((pid not in foAI.foAIstate.qualifyingColonyBaseTargets) and - (colony_cost < cost_ratio * avail_pp_by_sys.get(sys_id, 0)) and - (planet.unowned or pid in empire_outpost_ids)): - # TODO: enable actual building, remove from outpostbases, check other local colonizers for better score - foAI.foAIstate.qualifyingColonyBaseTargets.setdefault(pid, [pid2, -1]) + if planet.unowned: + outpost_base_manager.create_new_plan(pid, pid2) colonization_timer.start('Initiate outpost base construction') - reserved_outpost_base_targets = foAI.foAIstate.qualifyingOutpostBaseTargets.keys() - max_queued_outpost_bases = max(1, int(2 * empire.productionPoints / outpost_cost)) - considered_outpost_base_targets = (set(reserved_outpost_base_targets) - set(outpost_targeted_planet_ids)) - if tech_is_complete(AIDependencies.OUTPOSTING_TECH) and considered_outpost_base_targets: - print "Considering to build outpost bases for %s" % reserved_outpost_base_targets - for pid in considered_outpost_base_targets: - if len(queued_outpost_bases) >= max_queued_outpost_bases: - print "Too many queued outpost bases to build any more now" - break - if pid not in unowned_empty_planet_ids: - continue - if foAI.foAIstate.qualifyingOutpostBaseTargets[pid][1] != -1: - continue # already building for here - loc = foAI.foAIstate.qualifyingOutpostBaseTargets[pid][0] - this_score = evaluate_planet(pid, MissionType.OUTPOST, None, []) - for species in empire_colonizers: - this_score = max(this_score, evaluate_planet(pid, MissionType.COLONISATION, species, [])) - planet = universe.getPlanet(pid) - if this_score == 0: - # print "Potential outpost base (rejected) for %s to be built at planet id(%d); outpost score %.1f" % ( ((planet and planet.name) or "unknown"), loc, this_score) - continue - print "Potential outpost base for %s to be built at planet id(%d); outpost score %.1f" % ( - ((planet and planet.name) or "unknown"), loc, this_score) - if this_score < 100: - print "Potential outpost base (rejected) for %s to be built at planet id(%d); outpost score %.1f" % ( - ((planet and planet.name) or "unknown"), loc, this_score) - continue - best_ship, col_design, build_choices = ProductionAI.get_best_ship_info( - PriorityType.PRODUCTION_ORBITAL_OUTPOST, loc) - if best_ship is None: - print >> sys.stderr, "Can't get standard best outpost base design that can be built at ", PlanetUtilsAI.planet_name_ids([loc]) - outpost_base_design_ids = [design for design in empire.availableShipDesigns if "SD_OUTPOST_BASE" == fo.getShipDesign(design).name] - if outpost_base_design_ids: - print "trying fallback outpost base design SD_OUTPOST_BASE" - best_ship = outpost_base_design_ids.pop() - else: - continue - retval = fo.issueEnqueueShipProductionOrder(best_ship, loc) - print "Enqueueing Outpost Base at %s for %s with result %s" % ( - PlanetUtilsAI.planet_name_ids([loc]), PlanetUtilsAI.planet_name_ids([pid]), retval) - if retval: - foAI.foAIstate.qualifyingOutpostBaseTargets[pid][1] = loc - queued_outpost_bases.append((planet and planet.systemID) or INVALID_ID) - # res=fo.issueRequeueProductionOrder(production_queue.size -1, 0) # TODO: evaluate move to front + reserved_outpost_base_targets = outpost_base_manager.get_targets() + debug("Current qualifyingOutpostBaseTargets: %s" % reserved_outpost_base_targets) + outpost_base_manager.build_bases() colonization_timer.start('Evaluate Primary Colony Opportunities') - evaluated_outpost_planet_ids = list( - unowned_empty_planet_ids - set(outpost_targeted_planet_ids) - set(colony_targeted_planet_ids) - set( - reserved_outpost_base_targets)) + evaluated_outpost_planet_ids = list(state.get_unowned_empty_planets() - set(outpost_targeted_planet_ids) - set( + colony_targeted_planet_ids) - set(reserved_outpost_base_targets)) evaluated_colony_planets = assign_colonisation_values(evaluated_colony_planet_ids, MissionType.COLONISATION, None) colonization_timer.stop('Evaluate %d Primary Colony Opportunities' % (len(evaluated_colony_planet_ids))) @@ -588,19 +422,15 @@ def get_colony_fleets(): assign_colonisation_values(evaluated_colony_planet_ids, MissionType.COLONISATION, None, [], True)) colonization_timer.start('Evaluate Outpost Opportunities') - sorted_planets = evaluated_colony_planets.items() - sorted_planets.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) + sorted_planets = list(evaluated_colony_planets.items()) + sorted_planets.sort(key=itemgetter(1), reverse=True) - _print_colony_candidate_table(sorted_planets) + _print_colony_candidate_table(sorted_planets, show_detail=False) sorted_planets = [(planet_id, score[:2]) for planet_id, score in sorted_planets if score[0] > 0] # export planets for other AI modules - foAI.foAIstate.colonisablePlanetIDs.clear() - foAI.foAIstate.colonisablePlanetIDs.update(sorted_planets) - - # get outpost fleets - all_outpost_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.OUTPOST) - AIstate.outpostFleetIDs = FleetUtilsAI.extract_fleet_ids_without_mission_types(all_outpost_fleet_ids) + aistate.colonisablePlanetIDs.clear() + aistate.colonisablePlanetIDs.update(sorted_planets) evaluated_outpost_planets = assign_colonisation_values(evaluated_outpost_planet_ids, MissionType.OUTPOST, None) # if outposted planet would be in supply range, let outpost value be best of outpost value or colonization value @@ -611,21 +441,22 @@ def get_colony_fleets(): colonization_timer.stop() - sorted_outposts = evaluated_outpost_planets.items() - sorted_outposts.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) + sorted_outposts = list(evaluated_outpost_planets.items()) + sorted_outposts.sort(key=itemgetter(1), reverse=True) _print_outpost_candidate_table(sorted_outposts) sorted_outposts = [(planet_id, score[:2]) for planet_id, score in sorted_outposts if score[0] > 0] # export outposts for other AI modules - foAI.foAIstate.colonisableOutpostIDs.clear() - foAI.foAIstate.colonisableOutpostIDs.update(sorted_outposts) + aistate.colonisableOutpostIDs.clear() + aistate.colonisableOutpostIDs.update(sorted_outposts) colonization_timer.stop_print_and_clear() # TODO: clean up suppliable versus annexable def assign_colonisation_values(planet_ids, mission_type, species, detail=None, return_all=False): """Creates a dictionary that takes planetIDs as key and their colonisation score as value.""" + empire_research_list = tuple(element.tech for element in fo.getEmpire().researchQueue) if detail is None: detail = [] orig_detail = detail @@ -649,7 +480,7 @@ def assign_colonisation_values(planet_ids, mission_type, species, detail=None, r for spec_name in try_species: detail = orig_detail[:] # appends (score, species_name, detail) - pv.append((evaluate_planet(planet_id, mission_type, spec_name, detail), spec_name, detail)) + pv.append((evaluate_planet(planet_id, mission_type, spec_name, detail, empire_research_list), spec_name, detail)) all_sorted = sorted(pv, reverse=True) best = all_sorted[:1] if best: @@ -662,7 +493,10 @@ def assign_colonisation_values(planet_ids, mission_type, species, detail=None, r def next_turn_pop_change(cur_pop, target_pop): - """Population change calc taken from PopCenter.cpp.""" + """Population change calc taken from PopCenter.cpp. + :type cur_pop: float + :type target_pop: float + """ if target_pop > cur_pop: pop_change = cur_pop * (target_pop + 1 - cur_pop) / 100 return min(pop_change, target_pop - cur_pop) @@ -671,6 +505,7 @@ def next_turn_pop_change(cur_pop, target_pop): return max(pop_change, target_pop - cur_pop) +@cache_for_session def project_ind_val(init_pop, max_pop_size, init_industry, max_ind_factor, flat_industry, discount_multiplier): """returns a discouted value for a projected industry stream over time with changing population""" discount_factor = 0.95 @@ -688,7 +523,7 @@ def project_ind_val(init_pop, max_pop_size, init_industry, max_ind_factor, flat_ return ind_val -@cache_by_turn +@cache_by_turn_persistent def get_base_outpost_defense_value(): """ :return:planet defenses contribution towards planet evaluations @@ -711,7 +546,7 @@ def get_base_outpost_defense_value(): return result -@cache_by_turn +@cache_by_turn_persistent def get_base_colony_defense_value(): """ :return:planet defenses contribution towards planet evaluations @@ -735,8 +570,8 @@ def get_defense_value(species_name): """ :param species_name: :type species_name: str - :return:planet defenses contribution towards planet evaluations - :rtype float + :return: planet defenses contribution towards planet evaluations + :rtype: float """ # TODO: assess species defense characteristics if species_name: @@ -745,19 +580,30 @@ def get_defense_value(species_name): return get_base_outpost_defense_value() -def evaluate_planet(planet_id, mission_type, spec_name, detail=None): +def _base_asteroid_mining_val(): + return 5 if tech_is_complete("PRO_MICROGRAV_MAN") else 3 + + +def evaluate_planet(planet_id, mission_type, spec_name, detail=None, empire_research_list=None): """returns the colonisation value of a planet""" empire = fo.getEmpire() if detail is None: detail = [] + + if spec_name is None: + spec_name = "" + + if empire_research_list is None: + empire_research_list = tuple(element.tech for element in empire.researchQueue) retval = 0 - discount_multiplier = foAI.foAIstate.character.preferred_discount_multiplier([30.0, 40.0]) + character = get_aistate().character + discount_multiplier = character.preferred_discount_multiplier([30.0, 40.0]) species = fo.getSpecies(spec_name or "") # in case None is passed as specName species_foci = [] and species and list(species.foci) tag_list = list(species.tags) if species else [] pilot_val = pilot_rating = 0 if species and species.canProduceShips: - pilot_val = pilot_rating = rate_piloting_tag(species.tags) + pilot_val = pilot_rating = rate_piloting_tag(spec_name) if pilot_val > state.best_pilot_rating: pilot_val *= 2 if pilot_val > 2: @@ -775,11 +621,13 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): capital_id = PlanetUtilsAI.get_capital() homeworld = universe.getPlanet(capital_id) planet = universe.getPlanet(planet_id) + prospective_invasion_targets = [pid for pid, pscore, trp in + AIstate.invasionTargets[:PriorityAI.allotted_invasion_targets()] + if pscore > InvasionAI.MIN_INVASION_SCORE] if spec_name != planet.speciesName and planet.speciesName and mission_type != MissionType.INVASION: return 0 - planet_size = planet.size this_sysid = planet.systemID if homeworld: home_system_id = homeworld.systemID @@ -792,35 +640,37 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): claimed_stars = get_claimed_stars() - empire_research_list = [element.tech for element in empire.researchQueue] if planet is None: vis_map = universe.getVisibilityTurnsMap(planet_id, empire.empireID) - print "Planet %d object not available; visMap: %s" % (planet_id, vis_map) + debug("Planet %d object not available; visMap: %s" % (planet_id, vis_map)) return 0 # only count existing presence if not target planet # TODO: consider neighboring sytems for smaller contribution, and bigger contributions for # local colonies versus local outposts - locally_owned_planets = [lpid for lpid in AIstate.colonizedSystems.get(this_sysid, []) if lpid != planet_id] + locally_owned_planets = [lpid for lpid in state.get_empire_planets_by_system(this_sysid) if lpid != planet_id] planets_with_species = state.get_inhabited_planets() locally_owned_pop_ctrs = [lpid for lpid in locally_owned_planets if lpid in planets_with_species] # triple count pop_ctrs existing_presence = len(locally_owned_planets) + 2 * len(locally_owned_pop_ctrs) system = universe.getSystem(this_sysid) - sys_status = foAI.foAIstate.systemStatus.get(this_sysid, {}) - sys_supply = system_supply.get(this_sysid, -99) - planet_supply = AIDependencies.supply_by_size.get(int(planet_size), 0) + sys_supply = state.get_system_supply(this_sysid) + planet_supply = AIDependencies.supply_by_size.get(planet.size, 0) bld_types = set(universe.getBuilding(bldg).buildingTypeName for bldg in planet.buildingIDs).intersection( AIDependencies.building_supply) planet_supply += sum(AIDependencies.building_supply[bld_type].get(int(psize), 0) for psize in [-1, planet.size] for bld_type in bld_types) + + supply_specials = set(planet.specials).union(system.specials).intersection(AIDependencies.SUPPLY_MOD_SPECIALS) planet_supply += sum(AIDependencies.SUPPLY_MOD_SPECIALS[_special].get(int(psize), 0) - for psize in [-1, planet.size] for _special in - set(planet.specials).union(system.specials).intersection(AIDependencies.SUPPLY_MOD_SPECIALS)) + for _special in supply_specials for psize in [-1, planet.size]) - ind_tag_mod = AIDependencies.SPECIES_INDUSTRY_MODIFIER.get(get_ai_tag_grade(tag_list, "INDUSTRY"), 1.0) - res_tag_mod = AIDependencies.SPECIES_RESEARCH_MODIFIER.get(get_ai_tag_grade(tag_list, "RESEARCH"), 1.0) - supply_tag_mod = AIDependencies.SPECIES_SUPPLY_MODIFIER.get(get_ai_tag_grade(tag_list, "SUPPLY"), 1) + ind_tag_mod = AIDependencies.SPECIES_INDUSTRY_MODIFIER.get(get_species_tag_grade(spec_name, Tags.INDUSTRY), 1.0) + res_tag_mod = AIDependencies.SPECIES_RESEARCH_MODIFIER.get(get_species_tag_grade(spec_name, Tags.RESEARCH), 1.0) + if species: + supply_tag_mod = AIDependencies.SPECIES_SUPPLY_MODIFIER.get(get_species_tag_grade(spec_name, Tags.SUPPLY), 1) + else: + supply_tag_mod = 0 # determine the potential supply provided by owning this planet, and if the planet is currently populated by # the evaluated species, then save this supply value in a cache. @@ -829,37 +679,14 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): # least a portion of the existing empire) if the planet becomes owned by the AI. planet_supply += supply_tag_mod - if planet.speciesName == (spec_name or ""): # adding or "" in case None was passed as spec_name + planet_supply = max(planet_supply, 0) # planets can't have negative supply + if planet.speciesName == spec_name: planet_supply_cache[planet_id] = planet_supply + sys_supply - myrating = sys_status.get('myFleetRating', 0) - cur_best_mil_ship_rating = max(MilitaryAI.cur_best_mil_ship_rating(), 0.001) - fleet_threat_ratio = (sys_status.get('fleetThreat', 0) - myrating) / float(cur_best_mil_ship_rating) - monster_threat_ratio = sys_status.get('monsterThreat', 0) / float(cur_best_mil_ship_rating) - neighbor_threat_ratio = ((sys_status.get('neighborThreat', 0)) / float(cur_best_mil_ship_rating)) + \ - min(0, fleet_threat_ratio) # last portion gives credit for inner extra defenses - myrating = sys_status.get('my_neighbor_rating', 0) - jump2_threat_ratio = ((max(0, sys_status.get('jump2_threat', 0) - myrating) / float(cur_best_mil_ship_rating)) + - min(0, neighbor_threat_ratio)) # last portion gives credit for inner extra defenses - - thrt_factor = 1.0 - ship_limit = 2 * (2 ** (fo.currentTurn() / 40.0)) - threat_tally = fleet_threat_ratio + neighbor_threat_ratio + monster_threat_ratio - if existing_presence: - threat_tally += 0.3 * jump2_threat_ratio - threat_tally *= 0.8 - else: - threat_tally += 0.6 * jump2_threat_ratio - if threat_tally > ship_limit: - thrt_factor = 0.1 - elif fleet_threat_ratio + neighbor_threat_ratio + monster_threat_ratio > 0.6 * ship_limit: - thrt_factor = 0.4 - elif fleet_threat_ratio + neighbor_threat_ratio + monster_threat_ratio > 0.2 * ship_limit: - thrt_factor = 0.8 - - sys_partial_vis_turn = universe.getVisibilityTurnsMap(this_sysid, empire.empireID).get(fo.visibility.partial, -9999) - planet_partial_vis_turn = universe.getVisibilityTurnsMap(planet_id, empire.empireID).get(fo.visibility.partial, - -9999) + threat_factor = determine_colony_threat_factor(planet_id, spec_name, existing_presence) + + sys_partial_vis_turn = get_partial_visibility_turn(this_sysid) + planet_partial_vis_turn = get_partial_visibility_turn(planet_id) if planet_partial_vis_turn < sys_partial_vis_turn: # last time we had partial vis of the system, the planet was stealthed to us @@ -873,7 +700,7 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): fixed_ind = 0 fixed_res = 0 if system: - already_got_this_one = this_sysid in (AIstate.popCtrSystemIDs + AIstate.outpostSystemIDs) + already_got_this_one = this_sysid in state.get_empire_planets_by_system() # TODO: Should probably consider pilot rating also for Phototropic species if "PHOTOTROPHIC" not in tag_list and pilot_rating >= state.best_pilot_rating: if system.starType == fo.starType.red and tech_is_complete("LRN_STELLAR_TOMOGRAPHY"): @@ -916,8 +743,8 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): detail.append("Black Hole Backup %.1f" % (5 * discount_multiplier * backup_factor)) if tech_is_complete(AIDependencies.PRO_SOL_ORB_GEN): # start valuing as soon as PRO_SOL_ORB_GEN done if system.starType in [fo.starType.blackHole]: - this_val = 0.5 * max(empire_status.get('industrialists', 0), - 20) * discount_multiplier # pretty rare planets, good for generator + # pretty rare planets, good for generator + this_val = 0.5 * max(state.population_with_industry_focus(), 20) * discount_multiplier if not claimed_stars.get(fo.starType.blackHole, []): star_bonus += this_val detail.append("PRO_SINGULAR_GEN %.1f" % this_val) @@ -976,15 +803,11 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): detail.append("%s %.1f" % (special, fort_val)) elif special == "HONEYCOMB_SPECIAL": honey_val = 0.3 * (AIDependencies.HONEYCOMB_IND_MULTIPLIER * AIDependencies.INDUSTRY_PER_POP * - empire_status['industrialists'] * discount_multiplier) + state.population_with_industry_focus() * discount_multiplier) retval += honey_val detail.append("%s %.1f" % (special, honey_val)) if planet.size == fo.planetSize.asteroids: ast_val = 0 - if tech_is_complete("PRO_MICROGRAV_MAN"): - per_ast = 5 - else: - per_ast = 3 if system: for pid in system.planetIDs: other_planet = universe.getPlanet(pid) @@ -994,8 +817,14 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): elif pid < planet_id and planet.unowned: ast_val = 0 break - elif other_planet.speciesName: # and otherPlanet.owner==empire.empireID - ast_val += per_ast * discount_multiplier + elif other_planet.speciesName: + if other_planet.owner == empire.empireID: + ownership_factor = 1.0 + elif pid in prospective_invasion_targets: + ownership_factor = character.secondary_valuation_factor_for_invasion_targets() + else: + ownership_factor = 0.0 + ast_val += ownership_factor * _base_asteroid_mining_val() * discount_multiplier retval += ast_val if ast_val > 0: detail.append("AsteroidMining %.1f" % ast_val) @@ -1015,17 +844,31 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): elif pid < planet_id and planet.unowned: ast_val = 0 break - elif other_planet.speciesName: # and otherPlanet.owner==empire.empireID + elif other_planet.speciesName: other_species = fo.getSpecies(other_planet.speciesName) if other_species and other_species.canProduceShips: - ast_val += per_ast * discount_multiplier + if other_planet.owner == empire.empireID: + ownership_factor = 1.0 + elif pid in prospective_invasion_targets: + ownership_factor = character.secondary_valuation_factor_for_invasion_targets() + else: + ownership_factor = 0.0 + ast_val += ownership_factor * per_ast * discount_multiplier retval += ast_val if ast_val > 0: detail.append("AsteroidShipBuilding %.1f" % ast_val) - if planet.size == fo.planetSize.gasGiant and tech_is_complete("PRO_ORBITAL_GEN"): - per_gg = 20 - elif planet.size == fo.planetSize.gasGiant and tech_is_complete("CON_ORBITAL_CON"): - per_gg = 10 + # We will assume that if any GG in the system is populated, they all will wind up populated; cannot then hope + # to build a GGG on a non-populated GG + populated_gg_factor = 1.0 + per_gg = 0 + if planet.size == fo.planetSize.gasGiant: + # TODO: Given current industry calc approach, consider bringing this max val down to actual max val of 10 + if tech_is_complete("PRO_ORBITAL_GEN"): + per_gg = 20 + elif tech_is_complete("CON_ORBITAL_CON"): + per_gg = 10 + if spec_name: + populated_gg_factor = 0.5 else: per_gg = 5 if system: @@ -1036,18 +879,24 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): other_planet = universe.getPlanet(pid) if other_planet.size == fo.planetSize.gasGiant: gg_list.append(pid) + if other_planet.speciesName: + populated_gg_factor = 0.5 if pid != planet_id and other_planet.owner == empire.empireID and FocusType.FOCUS_INDUSTRY in list( other_planet.availableFoci) + [other_planet.focus]: orb_gen_val += per_gg * discount_multiplier - gg_detail.append("GGG for %s %.1f" % (other_planet.name, discount_multiplier * per_gg)) + # Note, this reported value may not take into account a later adjustment from a populated gg + gg_detail.append("GGG for %s %.1f" % (other_planet.name, discount_multiplier * per_gg * + populated_gg_factor)) if planet_id in sorted(gg_list)[:max_gggs]: - retval += orb_gen_val + retval += orb_gen_val * populated_gg_factor detail.extend(gg_detail) else: detail.append("Won't GGG") if existing_presence: detail.append("preexisting system colony") retval = (retval + existing_presence * get_defense_value(spec_name)) * 1.5 + + # Fixme - sys_supply is always <= 0 leading to incorrect supply bonus score supply_val = 0 if sys_supply < 0: if sys_supply + planet_supply >= 0: @@ -1058,9 +907,11 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): supply_val += 25 * (planet_supply - sys_supply) detail.append("sys_supply: %d, planet_supply: %d, supply_val: %.0f" % (sys_supply, planet_supply, supply_val)) retval += supply_val - if thrt_factor < 1.0: - retval *= thrt_factor - detail.append("threat reducing value by %3d %%" % (100 * (1 - thrt_factor))) + + if threat_factor < 1.0: + threat_factor = revise_threat_factor(threat_factor, retval, this_sysid, MINIMUM_COLONY_SCORE) + retval *= threat_factor + detail.append("threat reducing value by %3d %%" % (100 * (1 - threat_factor))) return int(retval) else: # colonization mission if not species: @@ -1071,17 +922,21 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): detail.append("Undepleted Ruins %.1f" % discount_multiplier * 50) if "HONEYCOMB_SPECIAL" in planet.specials: honey_val = (AIDependencies.HONEYCOMB_IND_MULTIPLIER * AIDependencies.INDUSTRY_PER_POP * - empire_status['industrialists'] * discount_multiplier) + state.population_with_industry_focus() * discount_multiplier) if FocusType.FOCUS_INDUSTRY not in species_foci: honey_val *= -0.3 # discourage settlement by colonizers not able to use Industry Focus retval += honey_val detail.append("%s %.1f" % ("HONEYCOMB_SPECIAL", honey_val)) + # Fixme - sys_supply is always <= 0 leading to incorrect supply bonus score if sys_supply <= 0: if sys_supply + planet_supply >= 0: supply_val = 40 * (planet_supply - max(-3, sys_supply)) else: supply_val = 200 * (planet_supply + sys_supply) # a penalty + if spec_name == "SP_SLY": + # Sly are essentially stuck with lousy supply, so don't penalize for that + supply_val = 0 elif planet_supply > sys_supply == 1: # TODO: check min neighbor supply supply_val = 20 * (planet_supply - sys_supply) detail.append("sys_supply: %d, planet_supply: %d, supply_val: %.0f" % (sys_supply, planet_supply, supply_val)) @@ -1097,46 +952,47 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): mining_bonus = 0 per_ggg = 10 - got_asteroids = False - got_owned_asteroids = False - local_gg = False - got_owned_gg = False + asteroid_factor = 0.0 + gg_factor = 0.0 ast_shipyard_name = "" if system and FocusType.FOCUS_INDUSTRY in species.foci: - for pid in [temp_id for temp_id in system.planetIDs if temp_id != planet_id]: + for pid in system.planetIDs: + if pid == planet_id: + continue p2 = universe.getPlanet(pid) if p2: if p2.size == fo.planetSize.asteroids: - got_asteroids = True - ast_shipyard_name = p2.name - if p2.owner != -1: - got_owned_asteroids = True + this_factor = 0.0 + if p2.owner == empire.empireID: + this_factor = 1.0 + elif p2.unowned: + this_factor = 0.5 + elif pid in prospective_invasion_targets: + this_factor = character.secondary_valuation_factor_for_invasion_targets() + if this_factor > asteroid_factor: + asteroid_factor = this_factor + ast_shipyard_name = p2.name if p2.size == fo.planetSize.gasGiant: - local_gg = True - if p2.owner != -1: - got_owned_gg = True - if got_asteroids: + if p2.owner == empire.empireID: + gg_factor = max(gg_factor, 1.0) + elif p2.unowned: + gg_factor = max(gg_factor, 0.5) + elif pid in prospective_invasion_targets: + gg_factor = max(gg_factor, character.secondary_valuation_factor_for_invasion_targets()) + if asteroid_factor > 0.0: if tech_is_complete("PRO_MICROGRAV_MAN") or "PRO_MICROGRAV_MAN" in empire_research_list[:10]: - if got_owned_asteroids: # can be quickly captured - flat_industry += 5 # will go into detailed industry projection - detail.append("Asteroid mining ~ %.1f" % (5 * discount_multiplier)) - else: # uncertain when can be outposted - asteroid_bonus = 2.5 * discount_multiplier # give partial value - detail.append("Asteroid mining %.1f" % (5 * discount_multiplier)) + flat_industry += 5 * asteroid_factor # will go into detailed industry projection + detail.append("Asteroid mining ~ %.1f" % (5 * asteroid_factor * discount_multiplier)) if tech_is_complete("SHP_ASTEROID_HULLS") or "SHP_ASTEROID_HULLS" in empire_research_list[:11]: if species and species.canProduceShips: - asteroid_bonus += 30 * discount_multiplier * pilot_val + asteroid_bonus = 30 * discount_multiplier * pilot_val detail.append( "Asteroid ShipBuilding from %s %.1f" % ( - ast_shipyard_name, discount_multiplier * 20 * pilot_val)) - if local_gg: + ast_shipyard_name, discount_multiplier * 30 * pilot_val)) + if gg_factor > 0.0: if tech_is_complete("PRO_ORBITAL_GEN") or "PRO_ORBITAL_GEN" in empire_research_list[:5]: - if got_owned_gg: - flat_industry += per_ggg # will go into detailed industry projection - detail.append("GGG ~ %.1f" % (per_ggg * discount_multiplier)) - else: - gas_giant_bonus = 0.5 * per_ggg * discount_multiplier - detail.append("GGG %.1f" % (0.5 * per_ggg * discount_multiplier)) + flat_industry += per_ggg * gg_factor # will go into detailed industry projection + detail.append("GGG ~ %.1f" % (per_ggg * gg_factor * discount_multiplier)) # calculate the maximum population of the species on that planet. if planet.speciesName not in AIDependencies.SPECIES_FIXED_POPULATION: @@ -1204,14 +1060,14 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): if "TEMPORAL_ANOMALY_SPECIAL" in planet.specials: research_bonus += discount_multiplier * 2 * AIDependencies.RESEARCH_PER_POP * max_pop_size * 25 detail.append("Temporal Anomaly Research") - if "COMPUTRONIUM_SPECIAL" in planet.specials: + if AIDependencies.COMPUTRONIUM_SPECIAL in planet.specials: comp_bonus = (0.5 * AIDependencies.TECH_COST_MULTIPLIER * AIDependencies.RESEARCH_PER_POP * - AIDependencies.COMPUTRONIUM_RES_MULTIPLIER * empire_status['researchers'] * + AIDependencies.COMPUTRONIUM_RES_MULTIPLIER * state.population_with_research_focus() * discount_multiplier) if state.have_computronium: comp_bonus *= backup_factor research_bonus += comp_bonus - detail.append("COMPUTRONIUM_SPECIAL") + detail.append(AIDependencies.COMPUTRONIUM_SPECIAL) retval += max(ind_val + asteroid_bonus + gas_giant_bonus, research_bonus, growth_val) + fixed_ind + fixed_res + supply_val @@ -1219,13 +1075,79 @@ def evaluate_planet(planet_id, mission_type, spec_name, detail=None): if existing_presence: detail.append("preexisting system colony") retval = (retval + existing_presence * get_defense_value(spec_name)) * 2 - if thrt_factor < 1.0: - retval *= thrt_factor - detail.append("threat reducing value by %3d %%" % (100 * (1 - thrt_factor))) + if threat_factor < 1.0: + threat_factor = revise_threat_factor(threat_factor, retval, this_sysid, MINIMUM_COLONY_SCORE) + retval *= threat_factor + detail.append("threat reducing value by %3d %%" % (100 * (1 - threat_factor))) return retval -@cache_by_turn +def revise_threat_factor(threat_factor, planet_value, system_id, min_planet_value=MINIMUM_COLONY_SCORE): + """ + Check if the threat_factor should be made less severe. + + If the AI does have enough total military to secure this system, and the target has more than minimal value, + don't let the threat_factor discount the adjusted value below min_planet_value +1, so that if there are no + other targets the AI could still pursue this one. Otherwise, scoring pressure from + MilitaryAI.get_preferred_max_military_portion_for_single_battle might prevent the AI from pursuing a heavily + defended but still obtainable target even if it has no softer locations available. + + :param threat_factor: the base threat factor + :type threat_factor: float + :param planet_value: the planet score + :type planet_value: float + :param system_id: the system ID of subject planet + :type system_id: int + :param min_planet_value: a floor planet value if the AI has enough military to secure the system + :type min_planet_value: float + :return: the (potentially) revised threat_factor + :rtype: float + """ + + # the default value below for fleetThreat shouldn't come in to play, but is just to be absolutely sure we don't + # send colony ships into some system for which we have not evaluated fleetThreat + system_status = get_aistate().systemStatus.get(system_id, {}) + system_fleet_treat = system_status.get('fleetThreat', 1000) + # TODO: consider taking area threat into account here. Arguments can be made both ways, see discussion in PR2069 + sys_total_threat = system_fleet_treat + system_status.get('monsterThreat', 0) + system_status.get('planetThreat', 0) + if (MilitaryAI.get_concentrated_tot_mil_rating() > sys_total_threat) and (planet_value > 2 * min_planet_value): + threat_factor = max(threat_factor, (min_planet_value + 1) / planet_value) + return threat_factor + + +def determine_colony_threat_factor(planet_id, spec_name, existing_presence): + universe = fo.getUniverse() + planet = universe.getPlanet(planet_id) + if not planet: # should have been checked previously, but belt-and-suspenders + error("Can't retrieve planet ID %d" % planet_id) + return 0 + sys_status = get_aistate().systemStatus.get(planet.systemID, {}) + cur_best_mil_ship_rating = max(MilitaryAI.cur_best_mil_ship_rating(), 0.001) + local_defenses = sys_status.get('all_local_defenses', 0) + local_threat = sys_status.get('fleetThreat', 0) + sys_status.get('monsterThreat', 0) + neighbor_threat = sys_status.get('neighborThreat', 0) + jump2_threat = 0.6 * max(0, sys_status.get('jump2_threat', 0) - sys_status.get('my_neighbor_rating', 0)) + area_threat = neighbor_threat + jump2_threat + area_threat *= 2.0 / (existing_presence + 2) # once we have a foothold be less scared off by area threats + # TODO: evaluate detectability by specific source of area threat, also consider if the subject AI already has + # researched techs that would grant a stealth bonus + local_enemies = sys_status.get("enemies_nearly_supplied", []) + # could more conservatively base detection on get_aistate().misc.get("observed_empires") + if not EspionageAI.colony_detectable_by_empire(planet_id, spec_name, empire=local_enemies, default_result=True): + area_threat *= 0.05 + net_threat = max(0, local_threat + area_threat - local_defenses) + # even if our military has lost all warships, rate planets as if we have at least one + reference_rating = max(cur_best_mil_ship_rating, MilitaryAI.get_preferred_max_military_portion_for_single_battle() * + MilitaryAI.get_concentrated_tot_mil_rating()) + threat_factor = min(1.0, reference_rating / (net_threat + 0.001)) ** 2 + if threat_factor < 0.5: + mil_ref_string = "Military rating reference: %.1f" % reference_rating + debug("Significant threat discounting %2d%% at %s, local defense: %.1f, local threat %.1f, area threat %.1f" % + (100 * (1 - threat_factor), planet.name, local_defenses, local_threat, area_threat) + mil_ref_string) + return threat_factor + + +@cache_for_current_turn def get_claimed_stars(): """ Return dictionary of star type: list of colonised and planned to be colonized systems. @@ -1245,39 +1167,20 @@ def get_claimed_stars(): def assign_colony_fleets_to_colonise(): - universe = fo.getUniverse() - all_outpost_base_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( - MissionType.ORBITAL_OUTPOST) - avail_outpost_base_fleet_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(all_outpost_base_fleet_ids) - for fid in avail_outpost_base_fleet_ids: - fleet = universe.getFleet(fid) - if not fleet: - continue - sys_id = fleet.systemID - system = universe.getSystem(sys_id) - avail_planets = set(system.planetIDs).intersection(set(foAI.foAIstate.qualifyingOutpostBaseTargets.keys())) - targets = [pid for pid in avail_planets if foAI.foAIstate.qualifyingOutpostBaseTargets[pid][1] != -1] - if not targets: - print >> sys.stderr, "Found no valid target for outpost base in system %s (%d)" % (system.name, sys_id) - continue - target_id = INVALID_ID - best_score = -1 - for pid, rating in assign_colonisation_values(targets, MissionType.OUTPOST, None).items(): - if rating[0] > best_score: - best_score = rating[0] - target_id = pid - if target_id != INVALID_ID: - foAI.foAIstate.qualifyingOutpostBaseTargets[target_id][1] = -1 # TODO: should probably delete - ai_target = universe_object.Planet(target_id) - ai_fleet_mission = foAI.foAIstate.get_fleet_mission(fid) - ai_fleet_mission.set_target(MissionType.ORBITAL_OUTPOST, ai_target) + + aistate = get_aistate() + aistate.orbital_colonization_manager.assign_bases_to_colonize() # assign fleet targets to colonisable planets - send_colony_ships(AIstate.colonyFleetIDs, foAI.foAIstate.colonisablePlanetIDs.items(), + all_colony_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.COLONISATION) + send_colony_ships(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_colony_fleet_ids), + list(aistate.colonisablePlanetIDs.items()), MissionType.COLONISATION) # assign fleet targets to colonisable outposts - send_colony_ships(AIstate.outpostFleetIDs, foAI.foAIstate.colonisableOutpostIDs.items(), + all_outpost_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.OUTPOST) + send_colony_ships(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_outpost_fleet_ids), + list(aistate.colonisableOutpostIDs.items()), MissionType.OUTPOST) @@ -1286,29 +1189,29 @@ def send_colony_ships(colony_fleet_ids, evaluated_planets, mission_type): fleet_pool = colony_fleet_ids[:] try_all = False if mission_type == MissionType.OUTPOST: - cost = 20 + AIDependencies.OUTPOST_POD_COST * (1 + len(AIstate.popCtrIDs) * AIDependencies.COLONY_POD_UPKEEP) + cost = 20 + outpod_pod_cost() else: try_all = True - cost = 20 + AIDependencies.COLONY_POD_COST * (1 + len(AIstate.popCtrIDs) * AIDependencies.COLONY_POD_UPKEEP) + cost = 20 + colony_pod_cost() if fo.currentTurn() < 50: cost *= 0.4 # will be making fast tech progress so value is underestimated elif fo.currentTurn() < 80: cost *= 0.8 # will be making fast-ish tech progress so value is underestimated potential_targets = [(pid, (score, specName)) for (pid, (score, specName)) in evaluated_planets if - score > (0.8 * cost)] + score > (0.8 * cost) and score > MINIMUM_COLONY_SCORE] - print "Colony/outpost ship matching: fleets %s to planets %s" % (fleet_pool, evaluated_planets) + debug("Colony/outpost ship matching: fleets %s to planets %s" % (fleet_pool, evaluated_planets)) if try_all: - print "Trying best matches to current colony ships" + debug("Trying best matches to current colony ships") best_scores = dict(evaluated_planets) potential_targets = [] for pid, ratings in all_colony_opportunities.items(): for rating in ratings: if rating[0] >= 0.75 * best_scores.get(pid, [9999])[0]: potential_targets.append((pid, rating)) - potential_targets.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) + potential_targets.sort(key=itemgetter(1), reverse=True) # added a lot of checking because have been getting mysterious exception, after too many recursions to get info fleet_pool = set(fleet_pool) @@ -1316,7 +1219,7 @@ def send_colony_ships(colony_fleet_ids, evaluated_planets, mission_type): for fid in fleet_pool: fleet = universe.getFleet(fid) if not fleet or fleet.empty: - print >> sys.stderr, "Bad fleet ( ID %d ) given to colonization routine; will be skipped" % fid + warning("Bad fleet ( ID %d ) given to colonization routine; will be skipped" % fid) fleet_pool.remove(fid) continue report_str = "Fleet ID (%d): %d ships; species: " % (fid, fleet.numShips) @@ -1326,10 +1229,11 @@ def send_colony_ships(colony_fleet_ids, evaluated_planets, mission_type): report_str += "NoShip, " else: report_str += "%s, " % ship.speciesName - print report_str - print + debug(report_str) + debug('') already_targeted = [] # for planetID_value_pair in evaluatedPlanets: + aistate = get_aistate() while fleet_pool and potential_targets: target = potential_targets.pop(0) if target in already_targeted: @@ -1339,10 +1243,10 @@ def send_colony_ships(colony_fleet_ids, evaluated_planets, mission_type): continue planet = universe.getPlanet(planet_id) sys_id = planet.systemID - if foAI.foAIstate.systemStatus.setdefault(sys_id, {}).setdefault('monsterThreat', 0) > 2000 \ - or fo.currentTurn() < 20 and foAI.foAIstate.systemStatus[sys_id]['monsterThreat'] > 200: - print "Skipping colonization of system %s due to Big Monster, threat %d" % ( - PlanetUtilsAI.sys_name_ids([sys_id]), foAI.foAIstate.systemStatus[sys_id]['monsterThreat']) + if aistate.systemStatus.setdefault(sys_id, {}).setdefault('monsterThreat', 0) > 2000 \ + or fo.currentTurn() < 20 and aistate.systemStatus[sys_id]['monsterThreat'] > 200: + debug("Skipping colonization of system %s due to Big Monster, threat %d" % ( + universe.getSystem(sys_id), aistate.systemStatus[sys_id]['monsterThreat'])) already_targeted.append(planet_id) continue this_spec = target[1][1] @@ -1359,69 +1263,367 @@ def send_colony_ships(colony_fleet_ids, evaluated_planets, mission_type): continue # must have no compatible colony/outpost ships fleet_id = this_fleet_list[0] already_targeted.append(planet_id) - ai_target = universe_object.Planet(planet_id) - foAI.foAIstate.get_fleet_mission(fleet_id).set_target(mission_type, ai_target) + ai_target = TargetPlanet(planet_id) + aistate.get_fleet_mission(fleet_id).set_target(mission_type, ai_target) def _print_empire_species_roster(): """Print empire species roster in table format to log.""" grade_map = {"ULTIMATE": "+++", "GREAT": "++", "GOOD": "+", "AVERAGE": "o", "BAD": "-", "NO": "---"} - grade_tags = {'INDUSTRY': "Ind.", 'RESEARCH': "Res.", 'POPULATION': "Pop.", - 'SUPPLY': "Supply", 'WEAPONS': "Pilots", 'ATTACKTROOPS': "Troops"} + grade_tags = {Tags.INDUSTRY: "Ind.", Tags.RESEARCH: "Res.", Tags.POPULATION: "Pop.", + Tags.SUPPLY: "Supply", Tags.WEAPONS: "Pilots", Tags.ATTACKTROOPS: "Troops"} header = [Text('species'), Sequence('Planets'), Bool('Colonizer'), Text('Shipyards')] header.extend(Text(v) for v in grade_tags.values()) header.append(Sequence('Tags')) species_table = Table(header, table_name="Empire species roster Turn %d" % fo.currentTurn()) for species_name, planet_ids in state.get_empire_planets_by_species().items(): species_tags = fo.getSpecies(species_name).tags - grade_symbol = lambda x: grade_map.get(get_ai_tag_grade(species_tags, x).upper(), "o") is_colonizer = species_name in empire_colonizers number_of_shipyards = len(empire_ship_builders.get(species_name, [])) this_row = [species_name, planet_ids, is_colonizer, number_of_shipyards] - this_row.extend(grade_symbol(tag) for tag in grade_tags) + this_row.extend(grade_map.get(get_species_tag_grade(species_name, tag).upper(), "o") for tag in grade_tags) this_row.append([tag for tag in species_tags if not any(s in tag for s in grade_tags) and 'PEDIA' not in tag]) species_table.add_row(this_row) - print - species_table.print_table() + print() + info(species_table) -def _print_outpost_candidate_table(candidates): +def _print_outpost_candidate_table(candidates, show_detail=False): """Print a summary for the outpost candidates in a table format to log. :param candidates: list of (planet_id, (score, species, details)) tuples """ - __print_candidate_table(candidates, mission='Outposts') + __print_candidate_table(candidates, mission='Outposts', show_detail=show_detail) -def _print_colony_candidate_table(candidates): +def _print_colony_candidate_table(candidates, show_detail=False): """Print a summary for the colony candidates in a table format to log. :param candidates: list of (planet_id, (score, species, details)) tuples """ - __print_candidate_table(candidates, mission='Colonization') + __print_candidate_table(candidates, mission='Colonization', show_detail=show_detail) -def __print_candidate_table(candidates, mission): +def __print_candidate_table(candidates, mission, show_detail=False): universe = fo.getUniverse() if mission == 'Colonization': - col1 = Text('(Score, Species)') - col1_value = lambda x: (round(x[0], 2), x[1]) + first_column = Text('(Score, Species)') + + def get_first_column_value(x): + return round(x[0], 2), x[1] elif mission == 'Outposts': - col1 = Float('Score') - col1_value = lambda x: x[0] + first_column = Float('Score') + get_first_column_value = itemgetter(0) else: - print >> sys.stderr, "__print_candidate_table(%s, %s): Invalid mission type" % (candidates, mission) + warning("__print_candidate_table(%s, %s): Invalid mission type" % (candidates, mission)) return - candidate_table = Table([col1, Text('Planet'), Text('System'), Sequence('Specials')], + columns = [first_column, Text('Planet'), Text('System'), Sequence('Specials')] + if show_detail: + columns.append(Sequence('Detail')) + candidate_table = Table(columns, table_name='Potential Targets for %s in Turn %d' % (mission, fo.currentTurn())) for planet_id, score_tuple in candidates: if score_tuple[0] > 0.5: planet = universe.getPlanet(planet_id) - candidate_table.add_row([ - col1_value(score_tuple), - planet, - universe.getSystem(planet.systemID), - planet.specials, - ]) - print - candidate_table.print_table() + entries = [get_first_column_value(score_tuple), + planet, + universe.getSystem(planet.systemID), + planet.specials, + ] + if show_detail: + entries.append(score_tuple[-1]) + candidate_table.add_row(entries) + info(candidate_table) + + +class OrbitalColonizationPlan: + def __init__(self, target_id, source_id): + """ + :param target_id: id of the target planet to colonize + :type target_id: int + :param source_id: id of the planet which should build the colony base + :type source_id: int + """ + self.target = target_id + self.source = source_id + self.base_enqueued = False + self.fleet_id = INVALID_ID + self.__score = 0 + self.__last_score_update = -1 + + def assign_base(self, fleet_id): + """ + Assign an outpost base fleet to execute the plan. + + It is expected that the fleet consists of only that one outpost base. + + :type fleet_id: int + :return: True on success, False on failure + :rtype: bool + """ + if self.base_assigned: + warning("Assigned a base to a plan that was already assigned a base to.") + return False + # give orders to perform the mission + target = TargetPlanet(self.target) + fleet_mission = get_aistate().get_fleet_mission(fleet_id) + fleet_mission.set_target(MissionType.ORBITAL_OUTPOST, target) + self.fleet_id = fleet_id + return True + + def enqueue_base(self): + """ + Enqueue the base according to the plan. + + :return: True on success, False on failure + :rtype: bool + """ + if self.base_enqueued: + warning("Tried to enqueue a base eventhough already done that.") + return False + + # find the best possible base design for the source planet + universe = fo.getUniverse() + best_ship, _, _ = ProductionAI.get_best_ship_info(PriorityType.PRODUCTION_ORBITAL_OUTPOST, self.source) + if best_ship is None: + warning("Can't find optimized outpost base design at %s" % (universe.getPlanet(self.source))) + try: + best_ship = next(design for design in fo.getEmpire().availableShipDesigns + if "SD_OUTPOST_BASE" == fo.getShipDesign(design).name) + debug("Falling back to base design SD_OUTPOST_BASE") + except StopIteration: + # fallback design not available + return False + + # enqueue the design at the source planet + retval = fo.issueEnqueueShipProductionOrder(best_ship, self.source) + debug("Enqueueing Outpost Base at %s for %s with result %s" % ( + universe.getPlanet(self.source), universe.getPlanet(self.target), retval)) + + if not retval: + warning("Failed to enqueue outpost base at %s" % universe.getPlanet(self.source)) + return False + + self.base_enqueued = True + return True + + @property + def base_assigned(self): + if self.fleet_id == INVALID_ID: + return False + + fleet = fo.getUniverse().getFleet(self.fleet_id) + if fleet: + return True + + debug("The fleet assigned to the OrbitalColonizationPlan doesn't exist anymore.") + self.fleet_id = INVALID_ID + return False + + @property + def score(self): + if self.__last_score_update != fo.currentTurn(): + self.__update_score() + return self.__score + + def __update_score(self): + planet_score = evaluate_planet(self.target, MissionType.OUTPOST, None) + for species in empire_colonizers: + this_score = evaluate_planet(self.target, MissionType.COLONISATION, species) + planet_score = max(planet_score, this_score) + self.__last_score_update = fo.currentTurn() + self.__score = planet_score + + def is_valid(self): + """ + Check the colonization plan for validity, i.e. if it could be executed in the future. + + The plan is valid if it is possible to outpust the target planet + and if the planet envisioned to build the outpost bases can still do so. + + :rtype: bool + """ + universe = fo.getUniverse() + + # make sure target is valid + target = universe.getPlanet(self.target) + if target is None or (not target.unowned) or target.speciesName: + return False + + # make sure source is valid + source = universe.getPlanet(self.source) + if not (source and source.ownedBy(fo.empireID()) and source.speciesName in empire_colonizers): + return False + + # appears to be valid + return True + + +class OrbitalColonizationManager: + """ + The OrbitalColonizationManager handles orbital colonization for the AI. + + :type _colonization_plans: dict[int, OrbitalColonizationPlan] + :type num_enqueued_bases: int + """ + def __init__(self): + self._colonization_plans = {} + self.num_enqueued_bases = 0 + + def get_targets(self): + """ + Return all planets for which an orbital colonization plan exists. + + :rtype: list[int] + """ + return list(self._colonization_plans.keys()) + + def create_new_plan(self, target_id, source_id): + """ + Create and keep track of a new colonization plan for a target planet. + + :param target_id: id of the target planet + :type target_id: int + :param source_id: id of the planet which is supposed to build the base + :type source_id: int + """ + if target_id in self._colonization_plans: + warning("Already have a colonization plan for this planet. Doing nothing.") + return + self._colonization_plans[target_id] = OrbitalColonizationPlan(target_id, source_id) + + def turn_start_cleanup(self): + universe = fo.getUniverse() + # clean up invalid or finished plans + for pid in list(self._colonization_plans.keys()): + if not self._colonization_plans[pid].is_valid(): + del self._colonization_plans[pid] + + # parse the production queue and find bases which no longer have valid + # targets (e.g. the planet was colonized already). + self.num_enqueued_bases = 0 + unaccounted_plans = dict(self._colonization_plans) + + # Check which plans still have valid bases assigned (possibly interrupted by combat last turn) + for pid in list(unaccounted_plans.keys()): + if unaccounted_plans[pid].base_assigned: + del unaccounted_plans[pid] + + # find enqueued bases which are no longer needed and dequeue those. + items_to_dequeue = [] + aistate = get_aistate() + for idx, element in enumerate(fo.getEmpire().productionQueue): + if element.buildType != EmpireProductionTypes.BT_SHIP or element.turnsLeft == -1: + continue + + role = aistate.get_ship_role(element.designID) + if role != ShipRoleType.BASE_OUTPOST: + continue + + self.num_enqueued_bases += 1 + # check if a target for this base remains + original_target = next((target for target, plan in unaccounted_plans.items() if + plan.source == element.locationID and plan.base_enqueued), None) + if original_target: + debug("Base built at %d still has its original target." % element.locationID) + del unaccounted_plans[original_target] + continue + + # the original target may be no longer valid but maybe there is another + # orbital colonization plan which wasn't started yet and has the same source planet + alternative_target = next((target for target, plan in unaccounted_plans.items() + if plan.source == element.locationID), None) + if alternative_target: + debug("Reassigning base built at %d to new target %d as old target is no longer valid" % ( + element.locationID, alternative_target)) + self._colonization_plans[alternative_target].base_enqueued = True + del unaccounted_plans[alternative_target] + continue + + # final try: unstarted plans with source in the same system + target_system = universe.getSystem(universe.getPlanet(element.locationID).systemID) + alternative_plan = next((plan for target, plan in unaccounted_plans.items() + if plan.source in target_system.planetIDs and not plan.base_enqueued + and not plan.base_assigned), None) + if alternative_plan: + debug("Reassigning base enqueued at %d to new plan with target %d. Previous source was %d" % ( + element.locationID, alternative_plan.target, alternative_plan.source)) + alternative_plan.source = element.locationID + alternative_plan.base_enqueued = True + del unaccounted_plans[alternative_plan.target] + continue + + debug("Could not find a target for the outpost base enqueued at %s" % + universe.getPlanet(element.locationID)) + items_to_dequeue.append(idx) + + # TODO: Stop Building for targets with now insufficient colonization score + + # delete last items first so that queue index of remaining items + # does not have to be adjusted + # TODO: Only pause the production if could become valid again + items_to_dequeue.sort(reverse=True) + for idx in items_to_dequeue: + fo.issueDequeueProductionOrder(idx) + self.num_enqueued_bases -= 1 + + def build_bases(self): + empire = fo.getEmpire() + if not empire.techResearched(OUTPOSTING_TECH): + return + + considered_plans = [plan for plan in self._colonization_plans.values() + if not plan.base_enqueued and plan.score > MINIMUM_COLONY_SCORE] + queue_limit = max(1, int(2*empire.productionPoints / outpod_pod_cost())) + for colonization_plan in sorted(considered_plans, key=lambda x: x.score, reverse=True): + if self.num_enqueued_bases >= queue_limit: + debug("Base enqueue limit (%d) reached." % queue_limit) + return + + success = colonization_plan.enqueue_base() + if success: + self.num_enqueued_bases += 1 + + def assign_bases_to_colonize(self): + universe = fo.getUniverse() + all_outpost_base_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.ORBITAL_OUTPOST) + avail_outpost_base_fleet_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(all_outpost_base_fleet_ids) + for fid in avail_outpost_base_fleet_ids: + fleet = universe.getFleet(fid) + if not fleet: + continue + sys_id = fleet.systemID + system = universe.getSystem(sys_id) + + avail_plans = [plan for plan in self._colonization_plans.values() + if plan.target in system.planetIDs and not plan.base_assigned] + avail_plans.sort(key=lambda x: x.score, reverse=True) + for plan in avail_plans: + success = plan.assign_base(fid) + if success: + break + + +def test_calc_max_pop(): + """ + Verify AI calculation of max population by comparing it with actual client + queried values. + + This function may be called in debug mode in a running game and will compare + the actual target population meters on all planets owned by this AI with the + predicted maximum population. Any mismatch will be reported in chat. + """ + from freeorion_tools import chat_human + chat_human("Verifying calculation of ColonisationAI.calc_max_pop()") + universe = fo.getUniverse() + for spec_name, planets in state.get_empire_planets_by_species().items(): + species = fo.getSpecies(spec_name) + for pid in planets: + planet = universe.getPlanet(pid) + detail = [] + predicted = calc_max_pop(planet, species, detail) + actual = planet.initialMeterValue(fo.meterType.targetPopulation) + if actual != predicted: + error("Predicted pop of %.2f on %s but actually is %.2f; Details: %s" % + (predicted, planet, actual, "\n".join(detail))) + chat_human("Finished verification of ColonisationAI.calc_max_pop()") diff --git a/default/python/AI/CombatRatingsAI.py b/default/python/AI/CombatRatingsAI.py index eae13f19d75..9fb3c89f568 100644 --- a/default/python/AI/CombatRatingsAI.py +++ b/default/python/AI/CombatRatingsAI.py @@ -1,16 +1,32 @@ from collections import Counter -import sys +from functools import reduce +from logging import warning, error import freeOrionAIInterface as fo import FleetUtilsAI +from aistate_interface import get_aistate from EnumsAI import MissionType -from freeorion_tools import get_ai_tag_grade, dict_to_tuple, tuple_to_dict, cache_by_session -from ShipDesignAI import get_part_type -from AIDependencies import INVALID_ID +from freeorion_tools import dict_to_tuple, tuple_to_dict, cache_for_current_turn +from ShipDesignAI import get_ship_part +from AIDependencies import INVALID_ID, CombatTarget -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) +_issued_errors = [] + + +def get_allowed_targets(partname: str) -> int: + """Return the allowed targets for a given hangar or shortrange weapon""" + try: + return CombatTarget.PART_ALLOWED_TARGETS[partname] + except KeyError: + if partname not in _issued_errors: + error("AI has no targeting information for weapon part %s. Will assume any target allowed." + "Please update CombatTarget.PART_ALLOWED_TARGETS in AIDependencies.py ") + _issued_errors.append(partname) + return CombatTarget.ANY + + +@cache_for_current_turn def get_empire_standard_fighter(): """Get the current empire standard fighter stats, i.e. the most common shiptype within the empire. @@ -35,33 +51,40 @@ def default_ship_stats(): :return: Some weak standard ship :rtype: ShipCombatStats """ - attacks = (4.0, 1) + attacks = (6.0, 1) structure = 15 shields = 0 fighters = 0 launch_rate = 0 fighter_damage = 0 - return ShipCombatStats(stats=(attacks, structure, shields, fighters, launch_rate, fighter_damage)) + flak_shots = 0 + has_interceptors = False + damage_vs_planets = 0 + has_bomber = False + return ShipCombatStats(stats=(attacks, structure, shields, + fighters, launch_rate, fighter_damage, + flak_shots, has_interceptors, + damage_vs_planets, has_bomber)) -class ShipCombatStats(object): +class ShipCombatStats: """Stores all relevant stats of a ship for combat strength evaluation.""" - class BasicStats(object): + class BasicStats: """Stores non-fighter-related stats.""" def __init__(self, attacks, structure, shields): """ :param attacks: - :type attacks: dict|None + :type attacks: dict[float, int]|None :param structure: :type structure: int|None :param shields: :type shields: int|None :return: """ - self.structure = 1 if structure is None else structure - self.shields = 0 if shields is None else shields - self.attacks = {} if attacks is None else tuple_to_dict(attacks) + self.structure = 1.0 if structure is None else structure + self.shields = 0.0 if shields is None else shields + self.attacks = {} if attacks is None else tuple_to_dict(attacks) # type: dict[float, int] def get_stats(self, hashable=False): """ @@ -77,15 +100,12 @@ def get_stats(self, hashable=False): def __str__(self): return str(self.get_stats()) - class FighterStats(object): + class FighterStats: """ Stores fighter-related stats """ - def __init__(self, stat_tuple=None, capacity=0, launch_rate=0, damage=0): - if stat_tuple and isinstance(stat_tuple, tuple): - self.capacity, self.launch_rate, self.damage = stat_tuple - else: - self.capacity = capacity - self.launch_rate = launch_rate - self.damage = damage + def __init__(self, capacity, launch_rate, damage): + self.capacity = capacity + self.launch_rate = launch_rate + self.damage = damage def __str__(self): return str(self.get_stats()) @@ -96,15 +116,48 @@ def get_stats(self): """ return self.capacity, self.launch_rate, self.damage + class AntiFighterStats: + def __init__(self, flak_shots: int, has_interceptors: bool): + """ + :param flak_shots: number of shots per bout with flak weapon part + :param has_interceptors: true if mounted hangar parts have interceptor ability (interceptors/fighters) + """ + self.flak_shots = flak_shots + self.has_interceptors = has_interceptors + + def __str__(self): + return str(self.get_stats()) + + def get_stats(self): + """ + :return: flak_shots, has_interceptors + """ + return self.flak_shots, self.has_interceptors + + class AntiPlanetStats: + def __init__(self, damage_vs_planets, has_bomber): + self.damage_vs_planets = damage_vs_planets + self.has_bomber = has_bomber + + def get_stats(self): + return self.damage_vs_planets, self.has_bomber + + def __str__(self): + return str(self.get_stats()) + def __init__(self, ship_id=INVALID_ID, consider_refuel=False, stats=None): self.__ship_id = ship_id self._consider_refuel = consider_refuel if stats: self._basic_stats = self.BasicStats(*stats[0:3]) # TODO: Should probably determine size dynamically - self._fighter_stats = self.FighterStats(*stats[3:]) + self._fighter_stats = self.FighterStats(*stats[3:6]) + self._anti_fighter_stats = self.AntiFighterStats(*stats[6:8]) + self._anti_planet_stats = self.AntiPlanetStats(*stats[8:]) else: self._basic_stats = self.BasicStats(None, None, None) self._fighter_stats = self.FighterStats(None, None, None) + self._anti_fighter_stats = self.AntiFighterStats(0, False) + self._anti_planet_stats = self.AntiPlanetStats(0, False) self.__get_stats_from_ship() def __hash__(self): @@ -121,44 +174,65 @@ def __get_stats_from_ship(self): return # TODO: Add some estimate for stealthed ships if self._consider_refuel: - structure = ship.currentMeterValue(fo.meterType.maxStructure) - shields = ship.currentMeterValue(fo.meterType.maxShield) + structure = ship.initialMeterValue(fo.meterType.maxStructure) + shields = ship.initialMeterValue(fo.meterType.maxShield) else: - structure = ship.currentMeterValue(fo.meterType.structure) - shields = ship.currentMeterValue(fo.meterType.shield) + structure = ship.initialMeterValue(fo.meterType.structure) + shields = ship.initialMeterValue(fo.meterType.shield) attacks = {} fighter_launch_rate = 0 fighter_capacity = 0 fighter_damage = 0 + flak_shots = 0 + has_bomber = False + has_interceptors = False + damage_vs_planets = 0 design = ship.design if design and (ship.isArmed or ship.hasFighters): meter_choice = fo.meterType.maxCapacity if self._consider_refuel else fo.meterType.capacity for partname in design.parts: if not partname: continue - pc = get_part_type(partname).partClass + pc = get_ship_part(partname).partClass if pc == fo.shipPartClass.shortRange: + allowed_targets = get_allowed_targets(partname) damage = ship.currentPartMeterValue(meter_choice, partname) - attacks[damage] = attacks.get(damage, 0) + 1 + shots = ship.currentPartMeterValue(fo.meterType.secondaryStat, partname) + if allowed_targets & CombatTarget.SHIP: + attacks[damage] = attacks.get(damage, 0) + shots + if allowed_targets & CombatTarget.FIGHTER: + flak_shots += 1 + if allowed_targets & CombatTarget.PLANET: + damage_vs_planets += damage * shots elif pc == fo.shipPartClass.fighterBay: fighter_launch_rate += ship.currentPartMeterValue(fo.meterType.capacity, partname) elif pc == fo.shipPartClass.fighterHangar: - fighter_capacity += ship.currentPartMeterValue(meter_choice, partname) + allowed_targets = get_allowed_targets(partname) + # for hangars, capacity meter is already counting contributions from ALL hangars. + fighter_capacity = ship.currentPartMeterValue(meter_choice, partname) part_damage = ship.currentPartMeterValue(fo.meterType.secondaryStat, partname) if part_damage != fighter_damage and fighter_damage > 0: # the C++ code fails also in this regard, so FOCS content *should* not allow this. # TODO: Depending on future implementation, might actually need to handle this case. - print "WARNING: Multiple hangar types present on one ship, estimates expected to be wrong." - fighter_damage = max(fighter_damage, part_damage) + warning("Multiple hangar types present on one ship, estimates expected to be wrong.") + if allowed_targets & CombatTarget.SHIP: + fighter_damage = max(fighter_damage, part_damage) + if allowed_targets & CombatTarget.PLANET: + has_bomber = True + if allowed_targets & CombatTarget.FIGHTER: + has_interceptors = True + self._basic_stats = self.BasicStats(attacks, structure, shields) self._fighter_stats = self.FighterStats(fighter_capacity, fighter_launch_rate, fighter_damage) + self._anti_fighter_stats = self.AntiFighterStats(flak_shots, has_interceptors) + self._anti_planet_stats = self.AntiPlanetStats(damage_vs_planets, has_bomber) def get_basic_stats(self, hashable=False): """Get non-fighter-related combat stats of the ship. :param hashable: if true, returns tuple instead of attacks-dict :return: attacks, structure, shields - :rtype: (dict|tuple, int, int) + :rtype: (dict|tuple, float, float) """ return self._basic_stats.get_stats(hashable=hashable) @@ -168,10 +242,21 @@ def get_fighter_stats(self): """ return self._fighter_stats.get_stats() + def get_anti_fighter_stats(self): + """Get anti-fighter related stats + :return: flak_shots, has_interceptors + """ + return self._anti_fighter_stats.get_stats() + + def get_anti_planet_stats(self): + return self._anti_planet_stats.get_stats() + def get_rating(self, enemy_stats=None, ignore_fighters=False): """Calculate a rating against specified enemy. - :param enemy_stats: Enemy stats to be rated against + If no enemy is specified, will rate against the empire standard enemy + + :param enemy_stats: Enemy stats to be rated against - if None :type enemy_stats: ShipCombatStats :param ignore_fighters: If True, acts as if fighters are not launched :type ignore_fighters: bool @@ -182,43 +267,50 @@ def get_rating(self, enemy_stats=None, ignore_fighters=False): def _rating(): return my_total_attack * my_structure + # The fighter rating calculations are heavily based upon the enemy stats. + # So, for now, we compare at least against a certain standard enemy. + enemy_stats = enemy_stats or get_aistate().get_standard_enemy() + my_attacks, my_structure, my_shields = self.get_basic_stats() - e_avg_attack = 1 + # e_avg_attack = 1 if enemy_stats: e_attacks, e_structure, e_shields = enemy_stats.get_basic_stats() if e_attacks: - e_num_attacks = sum(n for n in e_attacks.values()) - e_total_attack = sum(n*dmg for dmg, n in e_attacks.iteritems()) - e_avg_attack = e_total_attack / e_num_attacks - e_net_attack = sum(n*max(dmg - my_shields, .001) for dmg, n in e_attacks.iteritems()) + # e_num_attacks = sum(n for n in e_attacks.values()) + e_total_attack = sum(n*dmg for dmg, n in e_attacks.items()) + # e_avg_attack = e_total_attack / e_num_attacks + e_net_attack = sum(n*max(dmg - my_shields, .001) for dmg, n in e_attacks.items()) e_net_attack = max(e_net_attack, .1*e_total_attack) shield_factor = e_total_attack / e_net_attack my_structure *= max(1, shield_factor) - my_total_attack = sum(n*max(dmg - e_shields, .001) for dmg, n in my_attacks.iteritems()) + my_total_attack = sum(n*max(dmg - e_shields, .001) for dmg, n in my_attacks.items()) else: - my_total_attack = sum(n*dmg for dmg, n in my_attacks.iteritems()) + my_total_attack = sum(n*dmg for dmg, n in my_attacks.items()) my_structure += my_shields if ignore_fighters: return _rating() - # consider fighter attacks + my_total_attack += self.estimate_fighter_damage() + + # TODO: Consider enemy fighters + + return _rating() + + def estimate_fighter_damage(self): capacity, launch_rate, fighter_damage = self.get_fighter_stats() launched_1st_bout = min(capacity, launch_rate) launched_2nd_bout = min(max(capacity - launch_rate, 0), launch_rate) survival_rate = .2 # chance of a fighter launched in bout 1 to live in turn 3 TODO Actual estimation - total_fighter_damage = fighter_damage * (launched_1st_bout * (1+survival_rate) + launched_2nd_bout) - fighter_damage_per_bout = total_fighter_damage / 3 - my_total_attack += fighter_damage_per_bout - - # consider fighter protection factor - fighters_shot_down = (1-survival_rate**2) * launched_1st_bout + (1-survival_rate) * launched_2nd_bout - damage_prevented = fighters_shot_down * e_avg_attack - my_structure += damage_prevented - return _rating() + total_fighter_damage = fighter_damage * (launched_1st_bout * (1 + survival_rate) + launched_2nd_bout) + return total_fighter_damage / 3 def get_rating_vs_planets(self): - return self.get_rating(ignore_fighters=True) + """Heuristic to estimate combat strength against planets""" + damage = self._anti_planet_stats.damage_vs_planets + if self._anti_planet_stats.has_bomber: + damage += self.estimate_fighter_damage() + return damage * (self._basic_stats.structure + self._basic_stats.shields) def get_stats(self, hashable=False): """ Get all combat related stats of the ship. @@ -226,10 +318,11 @@ def get_stats(self, hashable=False): :param hashable: if true, return tuple instead of dict for attacks :return: attacks, structure, shields, fighter-capacity, fighter-launch_rate, fighter-damage """ - return self.get_basic_stats(hashable=hashable) + self.get_fighter_stats() + return (self.get_basic_stats(hashable=hashable) + self.get_fighter_stats() + + self.get_anti_fighter_stats() + self.get_anti_planet_stats()) -class FleetCombatStats(object): +class FleetCombatStats: """Stores combat related stats of the fleet.""" def __init__(self, fleet_id=INVALID_ID, consider_refuel=False): self.__fleet_id = fleet_id @@ -261,10 +354,10 @@ def get_rating(self, enemy_stats=None, ignore_fighters=False): :return: Rating of the fleet :rtype: float """ - return combine_ratings_list(map(lambda x: x.get_rating(enemy_stats, ignore_fighters), self.__ship_stats)) + return combine_ratings_list([x.get_rating(enemy_stats, ignore_fighters) for x in self.__ship_stats]) def get_rating_vs_planets(self): - return self.get_rating(ignore_fighters=True) + return combine_ratings_list([x.get_rating_vs_planets() for x in self.__ship_stats]) def __get_stats_from_fleet(self): """Calculate fleet combat stats (i.e. the stats of all its ships).""" @@ -293,43 +386,8 @@ def get_fleet_rating_against_planets(fleet_id): return FleetCombatStats(fleet_id, consider_refuel=False).get_rating_vs_planets() -@cache_by_session -def _get_species_grades(species_name, grade_type): - spec_tags = [] - if species_name: - species = fo.getSpecies(species_name) - if species: - spec_tags = species.tags - else: - print >> sys.stderr, "get_species_grades() couldn't retrieve species '%s'\n" % species_name - return get_ai_tag_grade(spec_tags, grade_type) - - -def get_pilot_weapons_grade(species_name): - """ - Return pilot grade string. - - :rtype str - """ - return _get_species_grades(species_name, 'WEAPONS') - - -def get_species_troops_grade(species_name): - """ - Return troop grade string. - - :rtype str - """ - return _get_species_grades(species_name, 'ATTACKTROOPS') - - -def get_species_shield_grade(species_name): - """ - Return shield grade string. - - :rtype str - """ - return _get_species_grades(species_name, 'SHIELDS') +def get_ship_rating(ship_id, enemy_stats=None): + return ShipCombatStats(ship_id, consider_refuel=False).get_rating(enemy_stats) def weight_attack_troops(troops, grade): @@ -370,8 +428,8 @@ def combine_ratings(rating1, rating2): A natural extension for the combined rating of two ships is r_tot = (a_1+a_2)*(s_1+s_2) (2) - Assuming a_i \approx s_i (3) - It follows that a_i \approx \sqrt(r_i) \approx s_i (4) + Assuming a_i approx s_i (3) + It follows that a_i approx sqrt(r_i) approx s_i (4) And thus r_tot = (sqrt(r_1)+sqrt(r_2))^2 = r1 + r2 + 2*sqrt(r1*r2) (5) Note that this function has commutative and associative properties. @@ -396,7 +454,7 @@ def combine_ratings_list(ratings_list): :return: combined rating :rtype: float """ - return reduce(combine_ratings, ratings_list) if ratings_list else 0 + return reduce(combine_ratings, ratings_list, 0) def rating_needed(target, current=0): @@ -413,3 +471,18 @@ def rating_needed(target, current=0): return 0 else: return target + current - 2 * (target * current)**0.5 + + +def rating_difference(first_rating, second_rating): + + """Return the absolute nonlinear difference between ratings. + + :param first_rating: rating of a first force + :type first_rating: float + :param second_rating: rating of a second force + :type second_rating: float + :return: Estimated rating by which the greater force (nonlinearly) exceeds the lesser + :rtype: float + """ + + return rating_needed(max(first_rating, second_rating), min(first_rating, second_rating)) diff --git a/default/python/AI/DiplomaticCorp.py b/default/python/AI/DiplomaticCorp.py index c2d8901884f..593a26d3016 100644 --- a/default/python/AI/DiplomaticCorp.py +++ b/default/python/AI/DiplomaticCorp.py @@ -1,14 +1,12 @@ """Handle diplomatic messages and response determination.""" import random +from logging import debug import freeOrionAIInterface as fo -import FreeOrionAI as foAI from character.character_module import Aggression from character.character_strings_module import possible_greetings -from freeorion_tools import UserStringList, chat_on_error - -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) +from freeorion_tools import UserStringList +from aistate_interface import get_aistate def handle_pregame_chat(sender_player_id, message_txt): @@ -16,34 +14,73 @@ def handle_pregame_chat(sender_player_id, message_txt): return possible_acknowledgments = UserStringList("AI_PREGAME_ACKNOWLEDGEMENTS__LIST") acknowledgement = random.choice(possible_acknowledgments) - print "Acknowledging pregame chat with initial message (from %d choices): '%s'" % ( - len(possible_acknowledgments), acknowledgement) + debug("Acknowledging pregame chat with initial message (from %d choices): '%s'" % ( + len(possible_acknowledgments), acknowledgement)) fo.sendChatMessage(sender_player_id, acknowledgement) -class DiplomaticCorp(object): +class DiplomaticCorp: def __init__(self): self.be_chatty = True - @chat_on_error def handle_diplomatic_message(self, message): """Handle a diplomatic message update from the server, such as if another player declares war, accepts peace, or cancels a proposed peace treaty. :param message: message.recipient and message.sender are respective empire IDs :return: """ - print "Received diplomatic %s message from %s to %s." % ( + debug("Received diplomatic %s message from %s to %s." % ( message.type, fo.getEmpire(message.sender), - 'me' if message.recipient == fo.empireID() else fo.getEmpire(message.recipient)) + 'me' if message.recipient == fo.empireID() else fo.getEmpire(message.recipient))) # TODO: remove the following early return once proper support for third party diplomatic history is added if message.recipient != fo.empireID(): return + aistate = get_aistate() + if message.type == fo.diplomaticMessageType.alliesProposal: + aistate.log_alliance_request(message.sender, message.recipient) + proposal_sender_player = fo.empirePlayerID(message.sender) + attitude = aistate.character.attitude_to_empire(message.sender, aistate.diplomatic_logs) + possible_acknowledgments = [] + aggression = aistate.character.get_trait(Aggression) + if aggression.key == fo.aggression.beginner: + accept_proposal = True + elif aggression.key == fo.aggression.turtle: + accept_proposal = attitude > 0 + elif aggression.key == fo.aggression.cautious: + accept_proposal = attitude > 5 + else: # aggression typical or greater + accept_proposal = False + if aggression.key <= fo.aggression.typical: + possible_acknowledgments = UserStringList("AI_ALLIANCE_PROPOSAL_ACKNOWLEDGEMENTS_MILD_LIST") + if accept_proposal: + possible_replies = UserStringList("AI_ALLIANCE_PROPOSAL_RESPONSES_YES_MILD_LIST") + else: + possible_replies = UserStringList("AI_ALLIANCE_PROPOSAL_RESPONSES_NO_MILD_LIST") + else: + possible_acknowledgments = UserStringList("AI_ALLIANCE_PROPOSAL_ACKNOWLEDGEMENTS_HARSH_LIST") + if accept_proposal: + possible_replies = UserStringList("AI_ALLIANCE_PROPOSAL_RESPONSES_YES_HARSH_LIST") + else: + possible_replies = UserStringList("AI_ALLIANCE_PROPOSAL_RESPONSES_NO_HARSH_LIST") + acknowledgement = random.choice(possible_acknowledgments) + reply_text = random.choice(possible_replies) + debug("Acknowledging proposal with initial message (from %d choices): '%s'" % ( + len(possible_acknowledgments), acknowledgement)) + fo.sendChatMessage(proposal_sender_player, acknowledgement) + if accept_proposal: + diplo_reply = fo.diplomaticMessage(message.recipient, message.sender, + fo.diplomaticMessageType.acceptAlliesProposal) + debug("Sending diplomatic message to empire %s of type %s" % (message.sender, diplo_reply.type)) + fo.sendDiplomaticMessage(diplo_reply) + debug("sending chat to player %d of empire %d, message body: '%s'" % ( + proposal_sender_player, message.sender, reply_text)) + fo.sendChatMessage(proposal_sender_player, reply_text) if message.type == fo.diplomaticMessageType.peaceProposal: - foAI.foAIstate.log_peace_request(message.sender, message.recipient) + aistate.log_peace_request(message.sender, message.recipient) proposal_sender_player = fo.empirePlayerID(message.sender) - attitude = foAI.foAIstate.character.attitude_to_empire(message.sender, foAI.foAIstate.diplomatic_logs) + attitude = aistate.character.attitude_to_empire(message.sender, aistate.diplomatic_logs) possible_acknowledgments = [] - aggression = foAI.foAIstate.character.get_trait(Aggression) + aggression = aistate.character.get_trait(Aggression) if aggression.key <= fo.aggression.typical: possible_acknowledgments = UserStringList("AI_PEACE_PROPOSAL_ACKNOWLEDGEMENTS_MILD_LIST") if attitude > 0: @@ -58,48 +95,47 @@ def handle_diplomatic_message(self, message): possible_replies = UserStringList("AI_PEACE_PROPOSAL_RESPONSES_NO_HARSH_LIST") acknowledgement = random.choice(possible_acknowledgments) reply_text = random.choice(possible_replies) - print "Acknowledging proposal with initial message (from %d choices): '%s'" % ( - len(possible_acknowledgments), acknowledgement) + debug("Acknowledging proposal with initial message (from %d choices): '%s'" % ( + len(possible_acknowledgments), acknowledgement)) fo.sendChatMessage(proposal_sender_player, acknowledgement) if attitude > 0: diplo_reply = fo.diplomaticMessage(message.recipient, message.sender, fo.diplomaticMessageType.acceptPeaceProposal) - print "Sending diplomatic message to empire %s of type %s" % (message.sender, diplo_reply.type) + debug("Sending diplomatic message to empire %s of type %s" % (message.sender, diplo_reply.type)) fo.sendDiplomaticMessage(diplo_reply) - print "sending chat to player %d of empire %d, message body: '%s'" % ( - proposal_sender_player, message.sender, reply_text) + debug("sending chat to player %d of empire %d, message body: '%s'" % ( + proposal_sender_player, message.sender, reply_text)) fo.sendChatMessage(proposal_sender_player, reply_text) elif message.type == fo.diplomaticMessageType.warDeclaration: # note: apparently this is currently (normally?) sent not as a warDeclaration, # but as a simple diplomatic_status_update to war - foAI.foAIstate.log_war_declaration(message.sender, message.recipient) + aistate.log_war_declaration(message.sender, message.recipient) @staticmethod def get_first_turn_greet_message(): - greets = possible_greetings(foAI.foAIstate.character) + greets = possible_greetings(get_aistate().character) # no such entry if len(greets) == 1 and greets[0] == '?': greets = UserStringList("AI_FIRST_TURN_GREETING_BEGINNER") return random.choice(greets) - @chat_on_error def handle_diplomatic_status_update(self, status_update): """Handle an update about the diplomatic status between players, which may or may not include this player.""" - print "Received diplomatic status update to %s about empire %s and empire %s" % ( - status_update.status, status_update.empire1, status_update.empire2) + debug("Received diplomatic status update to %s about empire %s and empire %s" % ( + status_update.status, status_update.empire1, status_update.empire2)) if status_update.empire2 == fo.empireID() and status_update.status == fo.diplomaticStatus.war: - foAI.foAIstate.log_war_declaration(status_update.empire1, status_update.empire2) + get_aistate().log_war_declaration(status_update.empire1, status_update.empire2) def handle_midgame_chat(self, sender_player_id, message_txt): - print "Midgame chat received from player %d, message: %s" % (sender_player_id, message_txt) + debug("Midgame chat received from player %d, message: %s" % (sender_player_id, message_txt)) if fo.playerIsAI(sender_player_id) or not self.be_chatty: return if "BE QUIET" in message_txt.upper(): possible_acknowledgments = UserStringList("AI_BE_QUIET_ACKNOWLEDGEMENTS__LIST") acknowledgement = random.choice(possible_acknowledgments) - print "Acknowledging 'Be Quiet' chat request with initial message (from %d choices): '%s'" % ( - len(possible_acknowledgments), acknowledgement) + debug("Acknowledging 'Be Quiet' chat request with initial message (from %d choices): '%s'" % ( + len(possible_acknowledgments), acknowledgement)) fo.sendChatMessage(sender_player_id, acknowledgement) self.be_chatty = False return @@ -107,8 +143,8 @@ def handle_midgame_chat(self, sender_player_id, message_txt): return possible_acknowledgments = UserStringList("AI_MIDGAME_ACKNOWLEDGEMENTS__LIST") acknowledgement = random.choice(possible_acknowledgments) - print "Acknowledging midgame chat with initial message (from %d choices): '%s'" % ( - len(possible_acknowledgments), acknowledgement) + debug("Acknowledging midgame chat with initial message (from %d choices): '%s'" % ( + len(possible_acknowledgments), acknowledgement)) fo.sendChatMessage(sender_player_id, acknowledgement) self.be_chatty = False diff --git a/default/python/AI/DynamicResearchAI.py b/default/python/AI/DynamicResearchAI.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/default/python/AI/EnumsAI.py b/default/python/AI/EnumsAI.py index 76b3fc1f859..3afb94565b9 100644 --- a/default/python/AI/EnumsAI.py +++ b/default/python/AI/EnumsAI.py @@ -4,16 +4,14 @@ def check_validity(value): class EnumItem(int): - @staticmethod - def __new__(cls, *more): - return super(EnumItem, cls).__new__(cls, more[0]) - - def __init__(self, number, name): - super(EnumItem, self).__init__(number) - self.name = name + def __new__(self, number, name, enum_name): + obj = int.__new__(EnumItem, number) + obj.name = name + obj.enum_name = enum_name + return obj def __str__(self): - return self.name + return "%s.%s" % (self.enum_name, self.name) class EnumMeta(type): @@ -25,14 +23,12 @@ def __new__(cls, name, bases, attrs): return super_new(cls, name, bases, attrs) for k, v in attrs.items(): - if not k.startswith('_') and isinstance(v, (int, long)): - attrs[k] = EnumItem(v, k) + if not k.startswith('_') and isinstance(v, int): + attrs[k] = EnumItem(v, k, name) return super_new(cls, name, bases, attrs) -class Enum(object): - __metaclass__ = EnumMeta - +class Enum(metaclass=EnumMeta): @classmethod def range(cls, start, end): result = [] @@ -44,6 +40,15 @@ def range(cls, start, end): result.append(value) return sorted(result) + @classmethod + def has_item(cls, item): + """Return True if specified item is a member of the enum + + :type item: EnumItem + :rtype: bool + """ + return hasattr(cls, item.name) and isinstance(getattr(cls, item.name), EnumItem) + class PriorityType(Enum): RESOURCE_GROWTH = 1 @@ -86,11 +91,14 @@ class MissionType(Enum): EXPLORATION = 5 INVASION = 9 MILITARY = 10 - SECURE = 11 # mostly same as MILITARY, but waits for system removal from all targeted system lists (invasion, colonization, outpost, blockade) before clearing + # mostly same as MILITARY, but waits for system removal from all targeted system lists + # (invasion, colonization, outpost, blockade) before clearing + SECURE = 11 ORBITAL_DEFENSE = 12 ORBITAL_INVASION = 13 ORBITAL_OUTPOST = 14 # ORBITAL_COLONISATION = 15 Not implemented yet + PROTECT_REGION = 16 class ShipRoleType(Enum): # this is also used in determining fleetRoles @@ -115,10 +123,14 @@ class EmpireProductionTypes(Enum): BT_SHIP = 2 # ///< a Ship object is being built -class FocusType(object): +class FocusType: FOCUS_PROTECTION = "FOCUS_PROTECTION" FOCUS_GROWTH = "FOCUS_GROWTH" FOCUS_INDUSTRY = "FOCUS_INDUSTRY" FOCUS_RESEARCH = "FOCUS_RESEARCH" FOCUS_TRADE = "FOCUS_TRADE" FOCUS_CONSTRUCTION = "FOCUS_CONSTRUCTION" + + +class EmpireMeters: + DETECTION_STRENGTH = "METER_DETECTION_STRENGTH" diff --git a/default/python/AI/EspionageAI.py b/default/python/AI/EspionageAI.py new file mode 100644 index 00000000000..2d9fb903628 --- /dev/null +++ b/default/python/AI/EspionageAI.py @@ -0,0 +1,111 @@ +from logging import error, warning + +import freeOrionAIInterface as fo # pylint: disable=import-error +import AIDependencies +from AIDependencies import ALL_EMPIRES, Tags +from EnumsAI import EmpireMeters +from freeorion_tools import cache_for_current_turn, get_species_tag_grade + + +def default_empire_detection_strength(): + # TODO doublecheck typical AI research times for Radar, for below default value + return (AIDependencies.DETECTION_TECH_STRENGTHS["SPY_DETECT_1"] if fo.currentTurn() < 40 + else AIDependencies.DETECTION_TECH_STRENGTHS["SPY_DETECT_2"]) + + +@cache_for_current_turn +def get_empire_detection(empire_id): + """ + Returns the detection strength for the provided empire ID. + + If passed the ALL_EMPIRES ID, then it returns the max detection strength across + all empires except for the current AI's empire. + + :rtype: float + """ + if empire_id == ALL_EMPIRES: + return get_max_empire_detection(fo.allEmpireIDs()) + + empire = fo.getEmpire(empire_id) + if empire: + return empire.getMeter(EmpireMeters.DETECTION_STRENGTH).initial + else: + warning("AI failed to retrieve empire ID %d, in game with %d empires." % (empire_id, len(fo.allEmpireIDs()))) + return default_empire_detection_strength() + + +def get_max_empire_detection(empire_list): + """ + Returns the max detection strength across all empires except for the current AI's empire. + + :param empire_list: list of empire IDs + :type empire_list: list[int] | fo.IntVec + :return: max detection strength of provided empires, excluding the current self empire + :rtype: float + """ + max_detection = 0 + for this_empire_id in empire_list: + if this_empire_id != fo.empireID(): + max_detection = max(max_detection, get_empire_detection(this_empire_id)) + return max_detection + + +def colony_detectable_by_empire(planet_id, species_name=None, empire=ALL_EMPIRES, future_stealth_bonus=0, + default_result=True): + """ + Predicts if a planet/colony is/will-be detectable by an empire. + + The passed empire value can be a single empire ID or a list of empire IDs. If passed ALL_empires, will use the list + of all empires. When using a list of empires (or when passed ALL_EMPIRES), the present empire is excluded. To + check for the present empire the current empire ID must be passed as a simple int. Ignores current ownership of the + planet unless the passed empire value is a simple int matching fo.empireID(), because in most cases we are concerned + about (i) visibility to us of a planet we do not own, or (ii) visibility by enemies of a planet we own or expect to + own at the time we are making the visibility projection for (even if they may own it right now). The only case + where current ownership matters is when we are considering whether to abort a colonization mission, which might be + for a planet we already own. + + :param planet_id: required, the planet of concern + :type planet_id: int + :param species_name: will override the existing planet species if provided + :type species_name: str + :param empire: empire ID (or list of empire IDs) whose detection ability is of concern + :type empire: int | list[int] + :param future_stealth_bonus: can specify a projected future stealth bonus, such as from a stealth tech + :type future_stealth_bonus: int + :param default_result: generally for offensive assessments should be False, for defensive should be True + :type default_result: bool + :return: whether the planet is predicted to be detectable + :rtype: bool + """ + # The future_stealth_bonus can be used if the AI knows it has researched techs that would grant a stealth bonus to + # the planet once it was colonized/captured + + planet = fo.getUniverse().getPlanet(planet_id) + if not planet: + error("Couldn't retrieve planet ID %d." % planet_id) + return default_result + if species_name is None: + species_name = planet.speciesName + + # in case we are checking about aborting a colonization mission + if empire == fo.empireID() and planet.ownedBy(empire): + return True + + # could just check stealth meter, but this approach might allow us to plan ahead a bit even if the planet + # is temporarily stealth boosted by temporary effects like ion storm + planet_stealth = AIDependencies.BASE_PLANET_STEALTH + if planet.specials: + planet_stealth += max([AIDependencies.STEALTH_SPECIAL_STRENGTHS.get(_spec, 0) for _spec in planet.specials]) + # TODO: check planet buildings for stealth bonuses + + # if the planet already has an existing stealth special, then the most common situation is that it would be + # overlapping with or superseded by the future_stealth_bonus, not additive with it. + planet_stealth = max(planet_stealth, AIDependencies.BASE_PLANET_STEALTH + future_stealth_bonus) + species_stealth_mod = AIDependencies.STEALTH_STRENGTHS_BY_SPECIES_TAG.get( + get_species_tag_grade(species_name, Tags.STEALTH), 0) + total_stealth = planet_stealth + species_stealth_mod + if isinstance(empire, int): + empire_detection = get_empire_detection(empire) + else: + empire_detection = get_max_empire_detection(empire) + return total_stealth < empire_detection diff --git a/default/python/AI/ExplorationAI.py b/default/python/AI/ExplorationAI.py index 6f3a130dc9c..ac2ff91b340 100644 --- a/default/python/AI/ExplorationAI.py +++ b/default/python/AI/ExplorationAI.py @@ -1,19 +1,14 @@ +from logging import debug, error, info + import freeOrionAIInterface as fo # interface used to interact with FreeOrion AI client # pylint: disable=import-error -import FreeOrionAI as foAI import FleetUtilsAI from EnumsAI import MissionType -import universe_object import MoveUtilsAI import PlanetUtilsAI -from freeorion_tools import dict_from_map from AIDependencies import INVALID_ID - -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) - - -TARGET_POP = 'targetPop' -TROOPS = 'troops' +from freeorion_tools import get_partial_visibility_turn +from aistate_interface import get_aistate +from target import TargetSystem graph_flags = set() border_unexplored_system_ids = set() @@ -24,17 +19,20 @@ def get_current_exploration_info(): fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.EXPLORATION) available_scouts = [] already_covered = set() + aistate = get_aistate() for fleet_id in fleet_ids: - fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) + fleet_mission = aistate.get_fleet_mission(fleet_id) if not fleet_mission.type: available_scouts.append(fleet_id) else: if fleet_mission.type == MissionType.EXPLORATION: - already_covered.add(fleet_mission.target) + already_covered.add(fleet_mission.target.id) if not fleet_mission.target: - print "problem determining existing exploration target systems" + debug("problem determining existing exploration target systems") else: - print "found existing exploration target: %s" % fleet_mission.target + debug("found existing exploration target: %s" % fleet_mission.target) + debug("Current exploration targets: %s" % already_covered) + debug("Available scout fleets: %s" % available_scouts) return list(already_covered), available_scouts @@ -46,103 +44,105 @@ def assign_scouts_to_explore_systems(): if not border_unexplored_system_ids or (capital_sys_id == INVALID_ID): return exp_systems_by_dist = sorted((universe.linearDistance(capital_sys_id, x), x) for x in border_unexplored_system_ids) - print "Exploration system considering following system-distance pairs:\n %s" % ("\n ".join("%3d: %5.1f" % info for info in exp_systems_by_dist)) + debug("Exploration system considering following system-distance pairs:\n %s" % ( + "\n ".join("%3d: %5.1f" % (sys_id, dist) for (dist, sys_id) in exp_systems_by_dist))) explore_list = [sys_id for dist, sys_id in exp_systems_by_dist] already_covered, available_scouts = get_current_exploration_info() - print "Explorable system IDs: %s" % explore_list - print "Already targeted: %s" % already_covered - needs_vis = foAI.foAIstate.misc.setdefault('needs_vis', []) - check_list = foAI.foAIstate.needsEmergencyExploration + needs_vis + explore_list + debug("Explorable system IDs: %s" % explore_list) + debug("Already targeted: %s" % already_covered) + aistate = get_aistate() + needs_vis = aistate.misc.setdefault('needs_vis', []) + check_list = aistate.needsEmergencyExploration + needs_vis + explore_list if INVALID_ID in check_list: # shouldn't normally happen, unless due to bug elsewhere - for sys_list, name in [(foAI.foAIstate.needsEmergencyExploration, "foAI.foAIstate.needsEmergencyExploration"), (needs_vis, "needs_vis"), (explore_list, "explore_list")]: + for sys_list, name in [(aistate.needsEmergencyExploration, "aistate.needsEmergencyExploration"), + (needs_vis, "needs_vis"), (explore_list, "explore_list")]: if INVALID_ID in sys_list: error("INVALID_ID found in " + name, exc_info=True) - needs_coverage = [sys_id for sys_id in check_list if sys_id not in already_covered and sys_id != INVALID_ID] # emergency coverage can be due to invasion detection trouble, etc. - print "Needs coverage: %s" % needs_coverage - - print "Available scouts & AIstate locs: %s" % [(x, foAI.foAIstate.fleetStatus.get(x, {}).get('sysID', INVALID_ID)) for x in available_scouts] - print "Available scouts & universe locs: %s" % [(x, universe.getFleet(x).systemID) for x in available_scouts] + # emergency coverage can be due to invasion detection trouble, etc. + debug("Check list: %s" % check_list) + needs_coverage = [sys_id for sys_id in check_list if sys_id not in already_covered and sys_id != INVALID_ID] + debug("Needs coverage: %s" % needs_coverage) + + debug("Available scouts & AIstate locs: %s" % [(x, aistate.fleetStatus.get(x, {}).get('sysID', INVALID_ID)) + for x in available_scouts]) + debug("Available scouts & universe locs: %s" % [(x, universe.getFleet(x).systemID) for x in available_scouts]) if not needs_coverage or not available_scouts: return - available_scouts = set(available_scouts) - sent_list = [] - while available_scouts and needs_coverage: - this_sys_id = needs_coverage.pop(0) - sys_status = foAI.foAIstate.systemStatus.setdefault(this_sys_id, {}) - if this_sys_id not in explore_list: # doesn't necessarily need direct visit - if universe.getVisibility(this_sys_id, fo.empireID()) >= fo.visibility.partial: + # clean up targets which can not or don't need to be scouted + for sys_id in list(needs_coverage): + if sys_id not in explore_list: # doesn't necessarily need direct visit + if universe.getVisibility(sys_id, fo.empireID()) >= fo.visibility.partial: # already got visibility; remove from visit lists and skip - if this_sys_id in needs_vis: - del needs_vis[needs_vis.index(this_sys_id)] - if this_sys_id in foAI.foAIstate.needsEmergencyExploration: - del foAI.foAIstate.needsEmergencyExploration[ - foAI.foAIstate.needsEmergencyExploration.index(this_sys_id)] - print "system id %d already currently visible; skipping exploration" % this_sys_id + if sys_id in needs_vis: + del needs_vis[needs_vis.index(sys_id)] + if sys_id in aistate.needsEmergencyExploration: + del aistate.needsEmergencyExploration[ + aistate.needsEmergencyExploration.index(sys_id)] + debug("system id %d already currently visible; skipping exploration" % sys_id) + needs_coverage.remove(sys_id) continue - # TODO: if blocked byu monster, try to find nearby system from which to see this system - if not foAI.foAIstate.character.may_explore_system(sys_status.setdefault('monsterThreat', 0)) or (fo.currentTurn() < 20 and foAI.foAIstate.systemStatus[this_sys_id]['monsterThreat'] > 200): - print "Skipping exploration of system %d due to Big Monster, threat %d" % (this_sys_id, foAI.foAIstate.systemStatus[this_sys_id]['monsterThreat']) + + # skip systems threatened by monsters + sys_status = aistate.systemStatus.setdefault(sys_id, {}) + if (not aistate.character.may_explore_system(sys_status.setdefault('monsterThreat', 0)) or ( + fo.currentTurn() < 20 and aistate.systemStatus[sys_id]['monsterThreat'] > 0)): + debug("Skipping exploration of system %d due to Big Monster, threat %d" % ( + sys_id, aistate.systemStatus[sys_id]['monsterThreat'])) + needs_coverage.remove(sys_id) continue - this_fleet_list = FleetUtilsAI.get_fleets_for_mission(target_stats={}, min_stats={}, cur_stats={}, - starting_system=this_sys_id, fleet_pool_set=available_scouts, - fleet_list=[]) - if not this_fleet_list: - print "Seem to have run out of scouts while trying to cover sys_id %d" % this_sys_id - break # must have ran out of scouts - fleet_id = this_fleet_list[0] - fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) - target = universe_object.System(this_sys_id) - if len(MoveUtilsAI.can_travel_to_system_and_return_to_resupply(fleet_id, fleet_mission.get_location_target(), target)) > 0: - fleet_mission.set_target(MissionType.EXPLORATION, target) - sent_list.append(this_sys_id) - else: # system too far out, skip it, but can add scout back to available pool - print "sys_id %d too far out for fleet ( ID %d ) to reach" % (this_sys_id, fleet_id) - available_scouts.update(this_fleet_list) - print "Sent scouting fleets to sysIDs : %s" % sent_list - return - # pylint: disable=pointless-string-statement - """ - #TODO: consider matching system to closest scout, also consider rejecting scouts that would travel a blockaded path - sent_list=[] - sysList= list(explorable_system_ids) - shuffle( sysList ) #so that a monster defended system wont always be selected early - fleetList = list(available_scouts) - isys= -1 - jfleet= -1 - while ( jfleet < len(fleetList) -1) : - jfleet += 1 - fleet_id = fleetList[ jfleet ] - while ( isys < len(sysList) -1) : - isys += 1 - sys_id = sysList[ isys] - fleet_mission = foAI.foAIstate.get_fleet_mission( fleet_id ) - target = AITarget.AITarget(AITargetType.TARGET_SYSTEM, sys_id ) - # add exploration mission to fleet with target unexplored system and this system is in range - #print "try to assign scout to system %d"%systemID - if len(MoveUtilsAI.can_travel_to_system_and_return_to_resupply(fleet_id, fleet_mission.get_location_target(), target, fo.empireID())) > 0: - fleet_mission.addAITarget(MissionType.EXPLORATION, target) - sent_list.append(sys_id) - break - print "sent scouting fleets to sysIDs : %s"%sent_list - """ + + # find the jump distance for all possible scout-system pairings + options = [] + available_scouts = set(available_scouts) + for fleet_id in available_scouts: + fleet_mission = aistate.get_fleet_mission(fleet_id) + start = fleet_mission.get_location_target() + for sys_id in needs_coverage: + target = TargetSystem(sys_id) + path = MoveUtilsAI.can_travel_to_system(fleet_id, start, target, ensure_return=True) + if not path: + continue + num_jumps = len(path) - 1 # -1 as path contains the original system + options.append((num_jumps, fleet_id, sys_id)) + + # Apply a simple, greedy heuristic to match scouts to nearby systems: + # Always choose the shortest possible path from the remaining scout-system pairing. + # This is clearly not optimal in the general case but it works well enough for now. + # TODO: Consider using a more sophisticated assignment algorithm + options.sort() + while options: + debug("Remaining options: %s" % options) + _, fleet_id, sys_id = options[0] + fleet_mission = aistate.get_fleet_mission(fleet_id) + target = TargetSystem(sys_id) + info("Sending fleet %d to explore %s" % (fleet_id, target)) + fleet_mission.set_target(MissionType.EXPLORATION, target) + options = [option for option in options if option[1] != fleet_id and option[2] != sys_id] + available_scouts.remove(fleet_id) + needs_coverage.remove(sys_id) + + debug("Exploration assignment finished.") + debug("Unassigned scouts: %s" % available_scouts) + debug("Unassigned exploration targets: %s" % needs_coverage) def follow_vis_system_connections(start_system_id, home_system_id): universe = fo.getUniverse() empire_id = fo.empireID() exploration_list = [start_system_id] + aistate = get_aistate() while exploration_list: cur_system_id = exploration_list.pop() if cur_system_id in graph_flags: continue graph_flags.add(cur_system_id) system = universe.getSystem(cur_system_id) - if cur_system_id in foAI.foAIstate.visBorderSystemIDs: + if cur_system_id in aistate.visBorderSystemIDs: pre_vis = "a border system" - elif cur_system_id in foAI.foAIstate.visInteriorSystemIDs: + elif cur_system_id in aistate.visInteriorSystemIDs: pre_vis = "an interior system" else: pre_vis = "an unknown system" @@ -151,32 +151,32 @@ def follow_vis_system_connections(start_system_id, home_system_id): visibility_turn_list = sorted(universe.getVisibilityTurnsMap(cur_system_id, empire_id).items(), key=lambda x: x[0].numerator) visibility_info = ', '.join('%s: %s' % (vis.name, turn) for vis, turn in visibility_turn_list) - print "%s previously %s. Visibility per turn: %s " % (system_header, pre_vis, visibility_info) + debug("%s previously %s. Visibility per turn: %s " % (system_header, pre_vis, visibility_info)) status_info = [] else: status_info = [system_header] - has_been_visible = universe.getVisibilityTurnsMap(cur_system_id, empire_id).get(fo.visibility.partial, 0) > 0 + has_been_visible = get_partial_visibility_turn(cur_system_id) > 0 is_connected = universe.systemsConnected(cur_system_id, home_system_id, -1) # self.empire_id) - status_info.append(" -- is%s partially visible" % ([" not", ""][has_been_visible])) - status_info.append(" -- is%s visibly connected to homesystem" % ([" not", ""][is_connected])) + status_info.append(" -- is%s partially visible" % ("" if has_been_visible else " not")) + status_info.append(" -- is%s visibly connected to homesystem" % ("" if is_connected else " not")) if has_been_visible: - sys_status = foAI.foAIstate.systemStatus.setdefault(cur_system_id, {}) - foAI.foAIstate.visInteriorSystemIDs.add(cur_system_id) - foAI.foAIstate.visBorderSystemIDs.discard(cur_system_id) - neighbors = set(dict_from_map(universe.getSystemNeighborsMap(cur_system_id, empire_id)).keys()) + sys_status = aistate.systemStatus.setdefault(cur_system_id, {}) + aistate.visInteriorSystemIDs.add(cur_system_id) + aistate.visBorderSystemIDs.discard(cur_system_id) + neighbors = set(universe.getImmediateNeighbors(cur_system_id, empire_id)) sys_status.setdefault('neighbors', set()).update(neighbors) if neighbors: status_info.append(" -- has neighbors %s" % sorted(neighbors)) for sys_id in neighbors: - if sys_id not in foAI.foAIstate.exploredSystemIDs: - foAI.foAIstate.unexploredSystemIDs.add(sys_id) - if (sys_id not in graph_flags) and (sys_id not in foAI.foAIstate.visInteriorSystemIDs): - foAI.foAIstate.visBorderSystemIDs.add(sys_id) + if sys_id not in aistate.exploredSystemIDs: + aistate.unexploredSystemIDs.add(sys_id) + if (sys_id not in graph_flags) and (sys_id not in aistate.visInteriorSystemIDs): + aistate.visBorderSystemIDs.add(sys_id) exploration_list.append(sys_id) if fo.currentTurn() < 50: - print '\n'.join(status_info) - print "----------------------------------------------------------" + debug('\n'.join(status_info)) + debug("----------------------------------------------------------") def update_explored_systems(): @@ -185,18 +185,21 @@ def update_explored_systems(): obs_lanes = empire.obstructedStarlanes() obs_lanes_list = [el for el in obs_lanes] # should result in list of tuples (sys_id1, sys_id2) if obs_lanes_list: - print "Obstructed starlanes are: %s" % ', '.join('%s-%s' % item for item in obs_lanes_list) + debug("Obstructed starlanes are: %s" % ', '.join('%s-%s' % item for item in obs_lanes_list)) else: - print "No obstructed Starlanes" + debug("No obstructed Starlanes") empire_id = fo.empireID() newly_explored = [] still_unexplored = [] - for sys_id in list(foAI.foAIstate.unexploredSystemIDs): - if empire.hasExploredSystem(sys_id): # consider making determination according to visibility rather than actual visit, which I think is what empire.hasExploredSystem covers - foAI.foAIstate.unexploredSystemIDs.discard(sys_id) - foAI.foAIstate.exploredSystemIDs.add(sys_id) + aistate = get_aistate() + for sys_id in list(aistate.unexploredSystemIDs): + # consider making determination according to visibility rather than actual visit, + # which I think is what empire.hasExploredSystem covers (Dilvish-fo) + if empire.hasExploredSystem(sys_id): + aistate.unexploredSystemIDs.discard(sys_id) + aistate.exploredSystemIDs.add(sys_id) system = universe.getSystem(sys_id) - print "Moved system %s from unexplored list to explored list" % system + debug("Moved system %s from unexplored list to explored list" % system) border_unexplored_system_ids.discard(sys_id) newly_explored.append(sys_id) else: @@ -208,11 +211,12 @@ def update_explored_systems(): for sys_id in id_list: neighbors = list(universe.getImmediateNeighbors(sys_id, empire_id)) for neighbor_id in neighbors: - if neighbor_id not in foAI.foAIstate.unexploredSystemIDs: # when it matters, unexplored will be smaller than explored + # when it matters, unexplored will be smaller than explored + if neighbor_id not in aistate.unexploredSystemIDs: next_list.append(neighbor_id) for sys_id in still_unexplored: neighbors = list(universe.getImmediateNeighbors(sys_id, empire_id)) - if any(nid in foAI.foAIstate.exploredSystemIDs for nid in neighbors): + if any(nid in aistate.exploredSystemIDs for nid in neighbors): border_unexplored_system_ids.add(sys_id) return newly_explored diff --git a/default/python/AI/FleetUtilsAI.py b/default/python/AI/FleetUtilsAI.py index e0f34024655..9e96d8f3aa9 100644 --- a/default/python/AI/FleetUtilsAI.py +++ b/default/python/AI/FleetUtilsAI.py @@ -1,36 +1,37 @@ import math -import sys +from logging import error, warning, debug import freeOrionAIInterface as fo # pylint: disable=import-error -import FreeOrionAI as foAI -from EnumsAI import MissionType, ShipRoleType + +import AIDependencies import CombatRatingsAI -from universe_object import Planet -from ShipDesignAI import get_part_type +import MoveUtilsAI from AIDependencies import INVALID_ID -import AIDependencies -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) -__designStats = {} +from aistate_interface import get_aistate +from freeorion_tools import assertion_fails +from EnumsAI import MissionType, ShipRoleType +from ShipDesignAI import get_ship_part +from target import TargetPlanet, TargetFleet, TargetSystem -def stats_meet_reqs(stats, reqs): +def stats_meet_reqs(stats, requirements): """Check if (fleet) stats meet requirements. :param stats: Stats (of fleet) :type stats: dict - :param reqs: Requirements - :type reqs: dict + :param requirements: Requirements + :type requirements: dict :return: True if requirements are met. :rtype: bool """ - try: - for key in reqs: - if stats.get(key, 0) < reqs[key]: - return False - return True - except: - return False + for key in requirements: + if key not in stats: # skip requirements not related to stats + if key != "target_system": # expected not to be in stats + warning("Requirement %s not in stats", key) + continue + if stats.get(key, 0) < requirements[key]: + return False + return True def count_troops_in_fleet(fleet_id): @@ -62,12 +63,12 @@ def get_targeted_planet_ids(planet_ids, mission_type): :return: Subset of *planet_ids* targeted by *mission_type* :rtype: list[int] """ - selected_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([mission_type]) + selected_fleet_missions = get_aistate().get_fleet_missions_with_any_mission_types([mission_type]) targeted_planets = [] for planet_id in planet_ids: # add planets that are target of a mission for fleet_mission in selected_fleet_missions: - ai_target = Planet(planet_id) + ai_target = TargetPlanet(planet_id) if fleet_mission.has_target(mission_type, ai_target): targeted_planets.append(planet_id) return targeted_planets @@ -76,7 +77,7 @@ def get_targeted_planet_ids(planet_ids, mission_type): # TODO: Avoid mutable arguments and use return values instead # TODO: Use Dijkstra's algorithm instead of BFS to consider starlane length def get_fleets_for_mission(target_stats, min_stats, cur_stats, starting_system, - fleet_pool_set, fleet_list, species=""): + fleet_pool_set, fleet_list, species="", ensure_return=False): """Get fleets for a mission. Implements breadth-first search through systems starting at the **starting_sytem**. @@ -101,6 +102,7 @@ def get_fleets_for_mission(target_stats, min_stats, cur_stats, starting_system, :type fleet_list: list[int] :param species: species for colonization mission :type species: str + :param bool ensure_return: If true, fleet must have sufficient fuel to return into supply after mission :return: List of selected fleet_ids or empty list if couldn't meet minimum requirements. :rtype: list[int] """ @@ -109,10 +111,12 @@ def get_fleets_for_mission(target_stats, min_stats, cur_stats, starting_system, systems_enqueued = [starting_system] systems_visited = [] # loop over systems in a breadth-first-search trying to find nearby suitable ships in fleet_pool_set + aistate = get_aistate() while systems_enqueued and fleet_pool_set: this_system_id = systems_enqueued.pop(0) + this_system_obj = TargetSystem(this_system_id) systems_visited.append(this_system_id) - accessible_fleets = foAI.foAIstate.systemStatus.get(this_system_id, {}).get('myFleetsAccessible', []) + accessible_fleets = aistate.systemStatus.get(this_system_id, {}).get('myFleetsAccessible', []) fleets_here = [fid for fid in accessible_fleets if fid in fleet_pool_set] # loop over all fleets in the system, split them if possible and select suitable ships while fleets_here: @@ -122,15 +126,24 @@ def get_fleets_for_mission(target_stats, min_stats, cur_stats, starting_system, fleet_pool_set.remove(fleet_id) continue # try splitting fleet - if len(list(fleet.shipIDs)) > 1: + if fleet.numShips > 1: + debug("Splitting candidate fleet to get ships for mission.") new_fleets = split_fleet(fleet_id) fleet_pool_set.update(new_fleets) fleets_here.extend(new_fleets) + + if ('target_system' in target_stats and + not MoveUtilsAI.can_travel_to_system(fleet_id, this_system_obj, + target_stats['target_system'], + ensure_return=ensure_return)): + continue + # check species for colonization missions if species: for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) - if ship and foAI.foAIstate.get_ship_role(ship.design.id) in colonization_roles and species == ship.speciesName: + if (ship and aistate.get_ship_role(ship.design.id) in colonization_roles and + species == ship.speciesName): break else: # no suitable species found continue @@ -144,15 +157,20 @@ def get_fleets_for_mission(target_stats, min_stats, cur_stats, starting_system, # check if we need additional rating vs planets this_rating_vs_planets = 0 if 'ratingVsPlanets' in target_stats: - this_rating_vs_planets = foAI.foAIstate.get_rating(fleet_id, against_planets=True) + this_rating_vs_planets = aistate.get_rating(fleet_id, against_planets=True) if this_rating_vs_planets <= 0 and cur_stats.get('rating', 0) >= target_stats.get('rating', 0): # we already have enough general rating, so do not add any more warships useless against planets continue # all checks passed, add ship to selected fleets and update the stats + try: + fleet_pool_set.remove(fleet_id) + except KeyError: + error("After having split a fleet, the original fleet apparently no longer exists.", exc_info=True) + continue fleet_list.append(fleet_id) - fleet_pool_set.remove(fleet_id) - this_rating = foAI.foAIstate.get_rating(fleet_id) + + this_rating = aistate.get_rating(fleet_id) cur_stats['rating'] = CombatRatingsAI.combine_ratings(cur_stats.get('rating', 0), this_rating) if 'ratingVsPlanets' in target_stats: cur_stats['ratingVsPlanets'] = CombatRatingsAI.combine_ratings(cur_stats.get('ratingVsPlanets', 0), @@ -165,12 +183,11 @@ def get_fleets_for_mission(target_stats, min_stats, cur_stats, starting_system, return fleet_list # finished system without meeting requirements. Add neighboring systems to search queue. - for neighbor_id in [el.key() for el in - universe.getSystemNeighborsMap(this_system_id, fo.empireID())]: + for neighbor_id in universe.getImmediateNeighbors(this_system_id, fo.empireID()): if all(( neighbor_id not in systems_visited, neighbor_id not in systems_enqueued, - neighbor_id in foAI.foAIstate.exploredSystemIDs + neighbor_id in aistate.exploredSystemIDs )): systems_enqueued.append(neighbor_id) # we ran out of systems or fleets to check but did not meet requirements yet. @@ -191,7 +208,7 @@ def split_fleet(fleet_id): universe = fo.getUniverse() empire_id = fo.empireID() fleet = universe.getFleet(fleet_id) - newfleets = [] + new_fleets = [] if fleet is None: return [] @@ -201,48 +218,74 @@ def split_fleet(fleet_id): if len(list(fleet.shipIDs)) <= 1: # fleet with only one ship cannot be split return [] ship_ids = list(fleet.shipIDs) + aistate = get_aistate() for ship_id in ship_ids[1:]: - new_fleet_id = fo.issueNewFleetOrder("Fleet %4d" % ship_id, ship_id) - if new_fleet_id: - new_fleet = universe.getFleet(new_fleet_id) - if not new_fleet: - print >> sys.stderr, "Newly split fleet %d not available from universe" % new_fleet_id - fo.issueRenameOrder(new_fleet_id, "Fleet %4d" % new_fleet_id) # to ease review of debugging logs - fo.issueAggressionOrder(new_fleet_id, True) - foAI.foAIstate.update_fleet_rating(new_fleet_id) - newfleets.append(new_fleet_id) - foAI.foAIstate.newlySplitFleets[new_fleet_id] = True + new_fleet_id = split_ship_from_fleet(fleet_id, ship_id) + new_fleets.append(new_fleet_id) + + aistate.get_fleet_role(fleet_id, force_new=True) + aistate.update_fleet_rating(fleet_id) + if new_fleets: + aistate.ensure_have_fleet_missions(new_fleets) + + return new_fleets + + +def split_ship_from_fleet(fleet_id, ship_id): + universe = fo.getUniverse() + fleet = universe.getFleet(fleet_id) + if assertion_fails(fleet is not None): + return + + if assertion_fails(ship_id in fleet.shipIDs): + return + + if assertion_fails(fleet.numShips > 1, "Can't split last ship from fleet"): + return + + new_fleet_id = fo.issueNewFleetOrder("Fleet %4d" % ship_id, ship_id) + if new_fleet_id: + aistate = get_aistate() + new_fleet = universe.getFleet(new_fleet_id) + if not new_fleet: + warning("Newly split fleet %d not available from universe" % new_fleet_id) + debug("Successfully split ship %d from fleet %d into new fleet %d", + ship_id, fleet_id, new_fleet_id) + fo.issueRenameOrder(new_fleet_id, "Fleet %4d" % new_fleet_id) # to ease review of debugging logs + fo.issueAggressionOrder(new_fleet_id, True) + aistate.update_fleet_rating(new_fleet_id) + aistate.newlySplitFleets[new_fleet_id] = True + # register the new fleets so AI logic is aware of them + sys_status = aistate.systemStatus.setdefault(fleet.systemID, {}) + sys_status['myfleets'].append(new_fleet_id) + sys_status['myFleetsAccessible'].append(new_fleet_id) + else: + if fleet.systemID == INVALID_ID: + warning("Tried to split ship id (%d) from fleet %d when fleet is in starlane" % ( + ship_id, fleet_id)) else: - if fleet.systemID == INVALID_ID: - print >> sys.stderr, "Tried to split ship id (%d) from fleet %d when fleet is in starlane" % (ship_id, fleet_id) - else: - print >> sys.stderr, "Got no fleet ID back after trying to split ship id (%d) from fleet %d" % (ship_id, fleet_id) - foAI.foAIstate.get_fleet_role(fleet_id, force_new=True) - foAI.foAIstate.update_fleet_rating(fleet_id) - if newfleets: - foAI.foAIstate.ensure_have_fleet_missions(newfleets) - return newfleets + warning("Got no fleet ID back after trying to split ship id (%d) from fleet %d" % ( + ship_id, fleet_id)) + return new_fleet_id def merge_fleet_a_into_b(fleet_a_id, fleet_b_id, leave_rating=0, need_rating=0, context=""): + debug("Merging fleet %s into %s", TargetFleet(fleet_a_id), TargetFleet(fleet_b_id)) universe = fo.getUniverse() fleet_a = universe.getFleet(fleet_a_id) fleet_b = universe.getFleet(fleet_b_id) if not fleet_a or not fleet_b: return 0 + system_id = fleet_a.systemID + if fleet_b.systemID != system_id: + return 0 + + # TODO: Should this rate against specific enemy? remaining_rating = CombatRatingsAI.get_fleet_rating(fleet_a_id) transferred_rating = 0 - b_has_monster = False - for ship_id in fleet_b.shipIDs: - this_ship = universe.getShip(ship_id) - if not this_ship: - continue - if this_ship.isMonster: - b_has_monster = True - break for ship_id in fleet_a.shipIDs: this_ship = universe.getShip(ship_id) - if not this_ship or this_ship.isMonster != b_has_monster: # TODO Is there any reason for the monster check? + if not this_ship: continue this_rating = CombatRatingsAI.ShipCombatStats(ship_id).get_rating() remaining_rating = CombatRatingsAI.rating_needed(remaining_rating, this_rating) @@ -252,14 +295,15 @@ def merge_fleet_a_into_b(fleet_a_id, fleet_b_id, leave_rating=0, need_rating=0, if transferred: transferred_rating = CombatRatingsAI.combine_ratings(transferred_rating, this_rating) else: - print " *** transfer of ship %4d, formerly of fleet %4d, into fleet %4d failed; %s" % ( - ship_id, fleet_a_id, fleet_b_id, [" context is %s" % context, ""][context == ""]) + debug(" *** transfer of ship %4d, formerly of fleet %4d, into fleet %4d failed; %s" % ( + ship_id, fleet_a_id, fleet_b_id, (" context is %s" % context) if context else "")) if need_rating != 0 and need_rating <= transferred_rating: break fleet_a = universe.getFleet(fleet_a_id) + aistate = get_aistate() if not fleet_a or fleet_a.empty or fleet_a_id in universe.destroyedObjectIDs(fo.empireID()): - foAI.foAIstate.delete_fleet_info(fleet_a_id) - foAI.foAIstate.update_fleet_rating(fleet_b_id) + aistate.delete_fleet_info(fleet_a_id) + aistate.update_fleet_rating(fleet_b_id) def fleet_has_ship_with_role(fleet_id, ship_role): @@ -269,9 +313,10 @@ def fleet_has_ship_with_role(fleet_id, ship_role): if fleet is None: return False + aistate = get_aistate() for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) - if foAI.foAIstate.get_ship_role(ship.design.id) == ship_role: + if aistate.get_ship_role(ship.design.id) == ship_role: return True return False @@ -281,15 +326,16 @@ def get_ship_id_with_role(fleet_id, ship_role, verbose=True): if not fleet_has_ship_with_role(fleet_id, ship_role): if verbose: - print "No ship with role %s found." % ship_role + debug("No ship with role %s found." % ship_role) return None universe = fo.getUniverse() fleet = universe.getFleet(fleet_id) + aistate = get_aistate() for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) - if foAI.foAIstate.get_ship_role(ship.design.id) == ship_role: + if aistate.get_ship_role(ship.design.id) == ship_role: return ship_id @@ -299,7 +345,7 @@ def get_empire_fleet_ids(): universe = fo.getUniverse() empire_fleet_ids = [] destroyed_object_ids = universe.destroyedObjectIDs(empire_id) - for fleet_id in set(list(universe.fleetIDs) + list(foAI.foAIstate.newlySplitFleets)): + for fleet_id in set(list(universe.fleetIDs) + list(get_aistate().newlySplitFleets)): fleet = universe.getFleet(fleet_id) if fleet is None: continue @@ -312,8 +358,9 @@ def get_empire_fleet_ids_by_role(fleet_role): """Returns a list with fleet_ids that have the specified role.""" fleet_ids = get_empire_fleet_ids() fleet_ids_with_role = [] + aistate = get_aistate() for fleet_id in fleet_ids: - if foAI.foAIstate.get_fleet_role(fleet_id) != fleet_role: + if aistate.get_fleet_role(fleet_id) != fleet_role: continue fleet_ids_with_role.append(fleet_id) return fleet_ids_with_role @@ -321,7 +368,8 @@ def get_empire_fleet_ids_by_role(fleet_role): def extract_fleet_ids_without_mission_types(fleets_ids): """Extracts a list with fleetIDs that have no mission.""" - return [fleet_id for fleet_id in fleets_ids if not foAI.foAIstate.get_fleet_mission(fleet_id).type] + aistate = get_aistate() + return [fleet_id for fleet_id in fleets_ids if not aistate.get_fleet_mission(fleet_id).type] def assess_fleet_role(fleet_id): @@ -333,14 +381,15 @@ def assess_fleet_role(fleet_id): ship_roles = {} fleet = universe.getFleet(fleet_id) if not fleet: - print "couldn't get fleet with id " + str(fleet_id) + debug("couldn't get fleet with id " + str(fleet_id)) return ShipRoleType.INVALID # count ship_roles + aistate = get_aistate() for ship_id in fleet.shipIDs: ship = universe.getShip(ship_id) if ship.design: - role = foAI.foAIstate.get_ship_role(ship.design.id) + role = aistate.get_ship_role(ship.design.id) else: role = ShipRoleType.INVALID @@ -380,7 +429,7 @@ def assess_fleet_role(fleet_id): def assess_ship_design_role(design): - parts = [get_part_type(partname) for partname in design.parts if partname and get_part_type(partname)] + parts = [get_ship_part(partname) for partname in design.parts if partname and get_ship_part(partname)] if any(p.partClass == fo.shipPartClass.colony and p.capacity == 0 for p in parts): if design.speed > 0: @@ -411,121 +460,137 @@ def assess_ship_design_role(design): if any(p.partClass == fo.shipPartClass.detection for p in parts): return ShipRoleType.CIVILIAN_EXPLORATION else: # if no suitable role found, use as (bad) scout as it still has inherent detection - print "AI Warning: defaulting ship role to 'exploration' for ship with parts: ", design.parts + warning("Defaulting ship role to 'exploration' for ship with parts: %s", design.parts) return ShipRoleType.CIVILIAN_EXPLORATION def generate_fleet_orders_for_fleet_missions(): """Generates fleet orders from targets.""" - print("Generating fleet orders") + debug("Generating fleet orders") # The following fleet lists are based on *Roles* -- Secure type missions are done by fleets with Military Roles - print "Fleets by Role\n" - print "Exploration Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.EXPLORATION) - print "Colonization Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.COLONISATION) - print "Outpost Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.OUTPOST) - print "Invasion Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.INVASION) - print "Military Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.MILITARY) - print "Orbital Defense Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.ORBITAL_DEFENSE) - print "Outpost Base Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.ORBITAL_OUTPOST) - print "Invasion Base Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.ORBITAL_INVASION) - print "Securing Fleets: %s (currently FLEET_MISSION_MILITARY should be used instead of this Role)" % get_empire_fleet_ids_by_role(MissionType.SECURE) - + debug("Fleets by Role\n") + debug("Exploration Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.EXPLORATION)) + debug("Colonization Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.COLONISATION)) + debug("Outpost Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.OUTPOST)) + debug("Invasion Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.INVASION)) + debug("Military Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.MILITARY)) + debug("Orbital Defense Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.ORBITAL_DEFENSE)) + debug("Outpost Base Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.ORBITAL_OUTPOST)) + debug("Invasion Base Fleets: %s" % get_empire_fleet_ids_by_role(MissionType.ORBITAL_INVASION)) + debug("Securing Fleets: %s (currently FLEET_MISSION_MILITARY should be used instead of this Role)" % ( + get_empire_fleet_ids_by_role(MissionType.SECURE))) + + aistate = get_aistate() if fo.currentTurn() < 50: - print - print "Explored systems:" - _print_systems_and_supply(foAI.foAIstate.get_explored_system_ids()) - print "Unexplored systems:" - _print_systems_and_supply(foAI.foAIstate.get_unexplored_system_ids()) - print - - exploration_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.EXPLORATION]) + debug('') + debug("Explored systems:") + _print_systems_and_supply(aistate.get_explored_system_ids()) + debug("Unexplored systems:") + _print_systems_and_supply(aistate.get_unexplored_system_ids()) + debug('') + + exploration_fleet_missions = aistate.get_fleet_missions_with_any_mission_types([MissionType.EXPLORATION]) if exploration_fleet_missions: - print "Exploration targets:" + debug("Exploration targets:") for explorationAIFleetMission in exploration_fleet_missions: - print " - %s" % explorationAIFleetMission + debug(" - %s" % explorationAIFleetMission) else: - print "Exploration targets: None" + debug("Exploration targets: None") - colonisation_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.COLONISATION]) + colonisation_fleet_missions = aistate.get_fleet_missions_with_any_mission_types([MissionType.COLONISATION]) if colonisation_fleet_missions: - print "Colonization targets: " + debug("Colonization targets: ") else: - print "Colonization targets: None" + debug("Colonization targets: None") for colonisation_fleet_mission in colonisation_fleet_missions: - print " %s" % colonisation_fleet_mission + debug(" %s" % colonisation_fleet_mission) - outpost_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.OUTPOST]) + outpost_fleet_missions = aistate.get_fleet_missions_with_any_mission_types([MissionType.OUTPOST]) if outpost_fleet_missions: - print "Outpost targets: " + debug("Outpost targets: ") else: - print "Outpost targets: None" + debug("Outpost targets: None") for outpost_fleet_mission in outpost_fleet_missions: - print " %s" % outpost_fleet_mission + debug(" %s" % outpost_fleet_mission) - outpost_base_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.ORBITAL_OUTPOST]) + outpost_base_fleet_missions = aistate.get_fleet_missions_with_any_mission_types( + [MissionType.ORBITAL_OUTPOST]) if outpost_base_fleet_missions: - print "Outpost Base targets (must have been interrupted by combat): " + debug("Outpost Base targets (must have been interrupted by combat): ") else: - print "Outpost targets: None (as expected, due to expected timing of order submission and execution)" + debug("Outpost Base targets: None (as expected, due to expected timing of order submission and execution)") for outpost_fleet_mission in outpost_base_fleet_missions: - print " %s" % outpost_fleet_mission + debug(" %s" % outpost_fleet_mission) - invasion_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.INVASION]) + invasion_fleet_missions = aistate.get_fleet_missions_with_any_mission_types([MissionType.INVASION]) if invasion_fleet_missions: - print "Invasion targets: " + debug("Invasion targets: ") else: - print "Invasion targets: None" + debug("Invasion targets: None") for invasion_fleet_mission in invasion_fleet_missions: - print " %s" % invasion_fleet_mission + debug(" %s" % invasion_fleet_mission) - troop_base_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.ORBITAL_INVASION]) + troop_base_fleet_missions = aistate.get_fleet_missions_with_any_mission_types([MissionType.ORBITAL_INVASION]) if troop_base_fleet_missions: - print "Invasion Base targets (must have been interrupted by combat): " + debug("Invasion Base targets (must have been interrupted by combat): ") else: - print "Invasion Base targets: None (as expected, due to expected timing of order submission and execution)" + debug("Invasion Base targets: None (as expected, due to expected timing of order submission and execution)") for invasion_fleet_mission in troop_base_fleet_missions: - print " %s" % invasion_fleet_mission + debug(" %s" % invasion_fleet_mission) - military_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.MILITARY]) + military_fleet_missions = aistate.get_fleet_missions_with_any_mission_types([MissionType.MILITARY]) if military_fleet_missions: - print "General Military targets: " + debug("General Military targets: ") else: - print "General Military targets: None" + debug("General Military targets: None") for military_fleet_mission in military_fleet_missions: - print " %s" % military_fleet_mission + debug(" %s" % military_fleet_mission) - secure_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.SECURE]) + secure_fleet_missions = aistate.get_fleet_missions_with_any_mission_types([MissionType.SECURE]) if secure_fleet_missions: - print "Secure targets: " + debug("Secure targets: ") else: - print "Secure targets: None" + debug("Secure targets: None") for secure_fleet_mission in secure_fleet_missions: - print " %s" % secure_fleet_mission + debug(" %s" % secure_fleet_mission) - orb_defense_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.ORBITAL_DEFENSE]) + orb_defense_fleet_missions = aistate.get_fleet_missions_with_any_mission_types([MissionType.ORBITAL_DEFENSE]) if orb_defense_fleet_missions: - print "Orbital Defense targets: " + debug("Orbital Defense targets: ") else: - print "Orbital Defense targets: None" + debug("Orbital Defense targets: None") for orb_defence_fleet_mission in orb_defense_fleet_missions: - print " %s" % orb_defence_fleet_mission + debug(" %s" % orb_defence_fleet_mission) - fleet_missions = foAI.foAIstate.get_all_fleet_missions() + fleet_missions = list(aistate.get_all_fleet_missions()) + destroyed_objects = fo.getUniverse().destroyedObjectIDs(fo.empireID()) + # merge fleets where appropriate before generating fleet orders. + # This allows us to consider the full fleet strength when determining + # e.g. whether to engage or avoid an enemy. + for mission in fleet_missions: + fleet_id = mission.fleet.id + fleet = mission.fleet.get_object() + if not fleet or not fleet.shipIDs or fleet_id in destroyed_objects: + continue + mission.check_mergers() + # get new set of fleet missions without fleets that are empty after merge + fleet_missions = aistate.get_all_fleet_missions() for mission in fleet_missions: mission.generate_fleet_orders() def issue_fleet_orders_for_fleet_missions(): """Issues fleet orders.""" - print + debug('') universe = fo.getUniverse() - fleet_missions = foAI.foAIstate.get_all_fleet_missions() + aistate = get_aistate() + fleet_missions = list(aistate.get_all_fleet_missions()) thisround = 0 while thisround < 3: thisround += 1 - print "Issuing fleet orders round %d:" % thisround + debug("Issuing fleet orders round %d:" % thisround) for mission in fleet_missions: fleet_id = mission.fleet.id fleet = mission.fleet.get_object() @@ -533,9 +598,9 @@ def issue_fleet_orders_for_fleet_missions(): if not fleet or not fleet.shipIDs or fleet_id in universe.destroyedObjectIDs(fo.empireID()): continue mission.issue_fleet_orders() - fleet_missions = foAI.foAIstate.misc.get('ReassignedFleetMissions', []) - foAI.foAIstate.misc['ReassignedFleetMissions'] = [] - print + fleet_missions = aistate.misc.get('ReassignedFleetMissions', []) + aistate.misc['ReassignedFleetMissions'] = [] + debug('') def _print_systems_and_supply(system_ids): @@ -544,11 +609,9 @@ def _print_systems_and_supply(system_ids): fleet_supplyable_system_ids = empire.fleetSupplyableSystemIDs for system_id in system_ids: system = universe.getSystem(system_id) - if system: - print " %s" % system, - else: - print " S_%s<>" % system_id, - print 'supplied' if system_id in fleet_supplyable_system_ids else '' + debug(' %s%s' % ( + system if system else " S_%s<>" % system_id, + 'supplied' if system_id in fleet_supplyable_system_ids else '')) def get_fighter_capacity_of_fleet(fleet_id): @@ -567,7 +630,7 @@ def get_fighter_capacity_of_fleet(fleet_id): design = ship and ship.design design_parts = design.parts if design and design.hasFighters else [] for partname in design_parts: - part = get_part_type(partname) + part = get_ship_part(partname) if part and part.partClass == fo.shipPartClass.fighterHangar: cur_capacity += ship.currentPartMeterValue(fo.meterType.capacity, partname) max_capacity += ship.currentPartMeterValue(fo.meterType.maxCapacity, partname) @@ -600,7 +663,7 @@ def get_max_fuel(fleet_id): def get_fleet_upkeep(): # TODO: Use new upkeep calculation - return 1 + AIDependencies.SHIP_UPKEEP * foAI.foAIstate.shipCount + return 1 + AIDependencies.SHIP_UPKEEP * get_aistate().shipCount def calculate_estimated_time_of_arrival(fleet_id, target_system_id): @@ -610,3 +673,51 @@ def calculate_estimated_time_of_arrival(fleet_id, target_system_id): return 99999 distance = universe.shortestPathDistance(fleet_id, target_system_id) return math.ceil(float(distance) / fleet.speed) + + +def get_fleet_system(fleet): + """Return the current fleet location or the target system if currently on starlane. + + :param fleet: + :type fleet: UniverseObject.TargetFleet | int + :return: current system_id or target system_id if currently on starlane + :rtype: int + """ + if isinstance(fleet, int): + fleet = fo.getUniverse().getFleet(fleet) + return fleet.systemID if fleet.systemID != INVALID_ID else fleet.nextSystemID + + +def get_current_and_max_structure(fleet): + """Return a 2-tuple of the sums of structure and maxStructure meters of all ships in the fleet + + :param fleet: + :type fleet: int | target.TargetFleet | fo.Fleet + :return: tuple of sums of structure and maxStructure meters of all ships in the fleet + :rtype: (float, float) + """ + + universe = fo.getUniverse() + destroyed_ids = universe.destroyedObjectIDs(fo.empireID()) + if isinstance(fleet, int): + fleet = universe.getFleet(fleet) + elif isinstance(fleet, TargetFleet): + fleet = fleet.get_object() + if not fleet: + return (0.0, 0.0) + ships_cur_health = 0 + ships_max_health = 0 + for ship_id in fleet.shipIDs: + # Check if we have see this ship get destroyed in a different fleet since the last time we saw the subject fleet + # this may be redundant with the new fleet assignment check made below, but for its limited scope it may be more + # reliable, in that it does not rely on any particular handling of post-destruction stats + if ship_id in destroyed_ids: + continue + this_ship = universe.getShip(ship_id) + # check that the ship has not been seen in a new fleet since this current fleet was last observed + if not (this_ship and this_ship.fleetID == fleet.id): + continue + ships_cur_health += this_ship.initialMeterValue(fo.meterType.structure) + ships_max_health += this_ship.initialMeterValue(fo.meterType.maxStructure) + + return ships_cur_health, ships_max_health diff --git a/default/python/AI/FreeOrionAI.py b/default/python/AI/FreeOrionAI.py index fe45735629b..d3dc017b8ea 100644 --- a/default/python/AI/FreeOrionAI.py +++ b/default/python/AI/FreeOrionAI.py @@ -1,25 +1,25 @@ -"""The FreeOrionAI module contains the methods which can be made by the C AIInterface; +"""The FreeOrionAI module contains the methods which can be made by the C game client; these methods in turn activate other portions of the python AI code.""" -from common.configure_logging import redirect_logging_to_freeorion_logger, convenience_function_references_for_logger +from logging import debug, info, error, fatal +from functools import wraps + +from common.configure_logging import redirect_logging_to_freeorion_logger # Logging is redirected before other imports so that import errors appear in log files. redirect_logging_to_freeorion_logger() -(debug, info, warn, error, fatal) = convenience_function_references_for_logger() -import pickle # Python object serialization library import sys import random import freeOrionAIInterface as fo # interface used to interact with FreeOrion AI client # pylint: disable=import-error -from common.option_tools import parse_config +from common.option_tools import parse_config, get_option_dict, check_bool parse_config(fo.getOptionsDBOptionStr("ai-config"), fo.getUserConfigDir()) from freeorion_tools import patch_interface patch_interface() -import AIstate import ColonisationAI import ExplorationAI import DiplomaticCorp @@ -32,8 +32,11 @@ import ResearchAI import ResourcesAI import TechsListsAI +import turn_state +from aistate_interface import create_new_aistate, load_aistate, get_aistate from AIDependencies import INVALID_ID -from freeorion_tools import chat_on_error, handle_debug_chat, AITimer, init_handlers +from freeorion_tools import handle_debug_chat, AITimer +from common.handlers import init_handlers from common.listeners import listener from character.character_module import Aggression from character.character_strings_module import get_trait_name_aggression, possible_capitals @@ -55,24 +58,30 @@ debug('Python paths %s' % sys.path) -# Mock to have proper inspection and autocomplete for this variable -class AIStateMock(AIstate.AIstate): - def __init__(self): - pass +diplomatic_corp = None -# AIstate -foAIstate = AIStateMock() -diplomatic_corp = None +def error_handler(func): + """Decorator that logs any exception in decorated function, then re-raises""" + + @wraps(func) + def _error_handler(*args, **kwargs): + try: + res = func(*args, **kwargs) + return res + except Exception as e: + error("Exception %s occurred during %s", e, func.__name__, exc_info=True) + raise + return _error_handler -@chat_on_error +@error_handler def startNewGame(aggression_input=fo.aggression.aggressive): # pylint: disable=invalid-name """Called by client when a new game is started (but not when a game is loaded). Should clear any pre-existing state and set up whatever is needed for AI to generate orders.""" empire = fo.getEmpire() if empire is None: - print "This client has no empire. Ignoring new game start message." + fatal("This client has no empire. Ignoring new game start message.") return if empire.eliminated: @@ -82,20 +91,20 @@ def startNewGame(aggression_input=fo.aggression.aggressive): # pylint: disable= turn_timer.start("Server Processing") # initialize AIstate - global foAIstate - debug("Initializing foAIstate...") - foAIstate = AIstate.AIstate(aggression_input) - aggression_trait = foAIstate.character.get_trait(Aggression) + debug("Initializing AI state...") + create_new_aistate(aggression_input) + aistate = get_aistate() + aggression_trait = aistate.character.get_trait(Aggression) debug("New game started, AI Aggression level %d (%s)" % ( - aggression_trait.key, get_trait_name_aggression(foAIstate.character))) - foAIstate.session_start_cleanup() - debug("Initialization of foAIstate complete!") + aggression_trait.key, get_trait_name_aggression(aistate.character))) + aistate.session_start_cleanup() + debug("Initialization of AI state complete!") debug("Trying to rename our homeworld...") planet_id = PlanetUtilsAI.get_capital() universe = fo.getUniverse() if planet_id is not None and planet_id != INVALID_ID: planet = universe.getPlanet(planet_id) - new_name = " ".join([random.choice(possible_capitals(foAIstate.character)).strip(), planet.name]) + new_name = " ".join([random.choice(possible_capitals(aistate.character)).strip(), planet.name]) debug(" Renaming to %s..." % new_name) res = fo.issueRenameOrder(planet_id, new_name) debug(" Result: %d; Planet is now named %s" % (res, planet.name)) @@ -107,52 +116,56 @@ def startNewGame(aggression_input=fo.aggression.aggressive): # pylint: disable= TechsListsAI.test_tech_integrity() -@chat_on_error +@error_handler def resumeLoadedGame(saved_state_string): # pylint: disable=invalid-name """Called by client to when resume a loaded game.""" if fo.getEmpire() is None: - print "This client has no empire. Doing nothing to resume loaded game." + fatal("This client has no empire. Doing nothing to resume loaded game.") return if fo.getEmpire().eliminated: - print "This empire has been eliminated. Ignoring resume loaded game." + info("This empire has been eliminated. Ignoring resume loaded game.") return turn_timer.start("Server Processing") - global foAIstate - print "Resuming loaded game" + debug("Resuming loaded game") if not saved_state_string: error("AI given empty state-string to resume from; this is expected if the AI is assigned to an empire " "previously run by a human, but is otherwise an error. AI will be set to Aggressive.") - foAIstate = AIstate.AIstate(fo.aggression.aggressive) - foAIstate.session_start_cleanup() + aistate = create_new_aistate(fo.aggression.aggressive) + aistate.session_start_cleanup() else: try: # loading saved state - # pre load code - foAIstate = pickle.loads(saved_state_string) + aistate = load_aistate(saved_state_string) except Exception as e: # assigning new state - foAIstate = AIstate.AIstate(fo.aggression.aggressive) - foAIstate.session_start_cleanup() - error("Fail to load aiState from saved game: %s", e, exc_info=True) - - aggression_trait = foAIstate.character.get_trait(Aggression) + aistate = create_new_aistate(fo.aggression.aggressive) + aistate.session_start_cleanup() + error("Failed to load the AIstate from the savegame. The AI will" + " play with a fresh AIstate instance with aggression level set" + " to 'aggressive'. The behaviour of the AI may be different" + " than in the original session. The error raised was: %s" + % e, exc_info=True) + + aggression_trait = aistate.character.get_trait(Aggression) diplomatic_corp_configs = {fo.aggression.beginner: DiplomaticCorp.BeginnerDiplomaticCorp, fo.aggression.maniacal: DiplomaticCorp.ManiacalDiplomaticCorp} global diplomatic_corp diplomatic_corp = diplomatic_corp_configs.get(aggression_trait.key, DiplomaticCorp.DiplomaticCorp)() TechsListsAI.test_tech_integrity() + debug('Size of already issued orders: ' + str(fo.getOrders().size)) + -@chat_on_error +@error_handler def prepareForSave(): # pylint: disable=invalid-name """Called by client when the game is about to be saved, to let the Python AI know it should save any AI state information, such as plans or knowledge about the game from previous turns, in the state string so that they can be restored if the game is loaded.""" empire = fo.getEmpire() if empire is None: - print "This client has no empire. Doing nothing to prepare for save." + fatal("This client has no empire. Doing nothing to prepare for save.") return if empire.eliminated: @@ -162,21 +175,24 @@ def prepareForSave(): # pylint: disable=invalid-name info("Preparing for game save by serializing state") # serialize (convert to string) global state dictionary and send to AI client to be stored in save file + import savegame_codec try: - dump_string = pickle.dumps(foAIstate) - print "foAIstate pickled to string, about to send to server" + dump_string = savegame_codec.build_savegame_string() fo.setSaveStateString(dump_string) - except: - error("foAIstate unable to pickle save-state string; the save file should be playable but the AI may have a different aggression.", exc_info=True) + except Exception as e: + error("Failed to encode the AIstate as save-state string. " + "The resulting save file should be playable but the AI " + "may have a different aggression. The error raised was: %s" + % e, exc_info=True) -@chat_on_error +@error_handler def handleChatMessage(sender_id, message_text): # pylint: disable=invalid-name """Called when this player receives a chat message. sender_id is the player who sent the message, and message_text is the text of the sent message.""" empire = fo.getEmpire() if empire is None: - print "This client has no empire. Doing nothing to handle chat message." + fatal("This client has no empire. Doing nothing to handle chat message.") return if empire.eliminated: @@ -185,7 +201,7 @@ def handleChatMessage(sender_id, message_text): # pylint: disable=invalid-name # debug("Received chat message from " + str(senderID) + " that says: " + messageText + " - ignoring it") # perhaps it is a debugging interaction - if handle_debug_chat(sender_id, message_text): + if get_option_dict().get('allow_debug_chat', False) and handle_debug_chat(sender_id, message_text): return if not diplomatic_corp: DiplomaticCorp.handle_pregame_chat(sender_id, message_text) @@ -193,13 +209,13 @@ def handleChatMessage(sender_id, message_text): # pylint: disable=invalid-name diplomatic_corp.handle_midgame_chat(sender_id, message_text) -@chat_on_error +@error_handler def handleDiplomaticMessage(message): # pylint: disable=invalid-name """Called when this player receives a diplomatic message update from the server, such as if another player declares war, accepts peace, or cancels a proposed peace treaty.""" empire = fo.getEmpire() if empire is None: - print "This client has no empire. Doing nothing to handle diplomatic message." + fatal("This client has no empire. Doing nothing to handle diplomatic message.") return if empire.eliminated: @@ -209,13 +225,13 @@ def handleDiplomaticMessage(message): # pylint: disable=invalid-name diplomatic_corp.handle_diplomatic_message(message) -@chat_on_error +@error_handler def handleDiplomaticStatusUpdate(status_update): # pylint: disable=invalid-name """Called when this player receives an update about the diplomatic status between players, which may or may not include this player.""" empire = fo.getEmpire() if empire is None: - print "This client has no empire. Doing nothing to handle diplomatic status message." + fatal("This client has no empire. Doing nothing to handle diplomatic status message.") return if empire.eliminated: @@ -225,42 +241,34 @@ def handleDiplomaticStatusUpdate(status_update): # pylint: disable=invalid-name diplomatic_corp.handle_diplomatic_status_update(status_update) -@chat_on_error +@error_handler @listener def generateOrders(): # pylint: disable=invalid-name - """Called once per turn to tell the Python AI to generate and issue orders to control its empire. - at end of this function, fo.doneTurn() should be called to indicate to the client that orders are finished - and can be sent to the server for processing.""" + """ + Called once per turn to tell the Python AI to generate + and issue orders, i.e. to control its empire. - rules = fo.getGameRules() - print "Defined game rules:" - for rule in rules.getRulesAsStrings: - print "Name: " + rule.name + " value: " + str(rule.value) - print "Rule RULE_NUM_COMBAT_ROUNDS value: " + str(rules.getInt("RULE_NUM_COMBAT_ROUNDS")) + After leaving this function, the AI's turn will be finished + and its orders will be sent to the server. + """ + try: + rules = fo.getGameRules() + debug("Defined game rules:") + for rule_name, rule_value in rules.getRulesAsStrings().items(): + debug("%s: %s", rule_name, rule_value) + debug("Rule RULE_NUM_COMBAT_ROUNDS value: " + str(rules.getInt("RULE_NUM_COMBAT_ROUNDS"))) + except Exception as e: + error("Exception %s when trying to get game rules" % e, exc_info=True) + # If nothing can be ordered anyway, exit early. + # Note that there is no need to update meters etc. in this case. empire = fo.getEmpire() if empire is None: - print "This client has no empire. Doing nothing to generate orders." - try: - # early abort if no empire. no need to do meter calculations - # on last-seen gamestate if nothing can be ordered anyway... - # - # note that doneTurn() is issued on behalf of the client network - # id, not the empire id, so not having a correct empire id does - # not invalidate doneTurn() - fo.doneTurn() - except Exception as e: - error("Exception in doneTurn() on non-existent empire", e, exc_info=True) + fatal("This client has no empire. Aborting order generation.") return if empire.eliminated: - debug("This empire has been eliminated. Aborting order generation") - try: - # early abort if already eliminated. no need to do meter calculations - # on last-seen gamestate if nothing can be ordered anyway... - fo.doneTurn() - except Exception as e: - error("Exception while trying doneTurn() on eliminated empire", e, exc_info=True) + info("This empire has been eliminated. Aborting order generation.") return # This code block is required for correct AI work. @@ -270,9 +278,10 @@ def generateOrders(): # pylint: disable=invalid-name fo.updateResourcePools() turn = fo.currentTurn() - turn_uid = foAIstate.set_turn_uid() + aistate = get_aistate() + turn_uid = aistate.set_turn_uid() debug("\n\n\n" + "=" * 20) - debug("Starting turn %s (%s) of game: %s" % (turn, turn_uid, foAIstate.uid)) + debug("Starting turn %s (%s) of game: %s" % (turn, turn_uid, aistate.uid)) debug("=" * 20 + "\n") turn_timer.start("AI planning") @@ -287,34 +296,45 @@ def generateOrders(): # pylint: disable=invalid-name planet = None if planet_id is not None: planet = universe.getPlanet(planet_id) - aggression_name = get_trait_name_aggression(foAIstate.character) - print "***************************************************************************" - print "******* Log info for AI progress chart script. Do not modify. **********" - print ("Generating Orders") - print ("EmpireID: {empire.empireID}" - " Name: {empire.name}_{empire.empireID}_pid:{p_id}_{p_name}RIdx_{res_idx}_{aggression}" - " Turn: {turn}").format(empire=empire, p_id=fo.playerID(), p_name=fo.playerName(), - res_idx=ResearchAI.get_research_index(), turn=turn, - aggression=aggression_name.capitalize()) - print "EmpireColors: {0.colour.r} {0.colour.g} {0.colour.b} {0.colour.a}".format(empire) + aggression_name = get_trait_name_aggression(aistate.character) + debug("***************************************************************************") + debug("******* Log info for AI progress chart script. Do not modify. **********") + debug("Generating Orders") + debug("EmpireID: {empire.empireID}" + " Name: {empire.name}_{empire.empireID}_pid:{p_id}_{p_name}RIdx_{res_idx}_{aggression}" + " Turn: {turn}".format(empire=empire, p_id=fo.playerID(), p_name=fo.playerName(), + res_idx=ResearchAI.get_research_index(), turn=turn, + aggression=aggression_name.capitalize())) + debug("EmpireColors: {0.colour.r} {0.colour.g} {0.colour.b} {0.colour.a}".format(empire)) if planet: - print "CapitalID: " + str(planet_id) + " Name: " + planet.name + " Species: " + planet.speciesName + debug("CapitalID: " + str(planet_id) + " Name: " + planet.name + " Species: " + planet.speciesName) else: - print "CapitalID: None Currently Name: None Species: None " - print "***************************************************************************" - print "***************************************************************************" + debug("CapitalID: None Currently Name: None Species: None ") + debug("***************************************************************************") + debug("***************************************************************************") + + # When loading a savegame, the AI will already have issued orders for this turn. + # To avoid duplicate orders, generally try not to replay turns. However, for debugging + # purposes it is often useful to replay the turn and observe varying results after + # code changes. Set the replay_after_load flag in the AI config to let the AI issue + # new orders after a game load. Note that the orders from the original savegame are + # still being issued and the AIstate was saved after those orders were issued. + # TODO: Consider adding an option to clear AI orders after load (must save AIstate at turn start then) + if fo.currentTurn() == aistate.last_turn_played: + info("The AIstate indicates that this turn was already played.") + if not check_bool(get_option_dict().get('replay_turn_after_load', 'False')): + info("Aborting new order generation. Orders from savegame will still be issued.") + return + info("Issuing new orders anyway.") if turn == 1: - declare_war_on_all() human_player = fo.empirePlayerID(1) greet = diplomatic_corp.get_first_turn_greet_message() - fo.sendChatMessage(human_player, '%s (%s): [[%s]]' % (empire.name, get_trait_name_aggression(foAIstate.character), greet)) - - # turn cleanup !!! this was formerly done at start of every turn -- not sure why - foAIstate.split_new_fleets() + fo.sendChatMessage(human_player, + '%s (%s): [[%s]]' % (empire.name, get_trait_name_aggression(aistate.character), greet)) - foAIstate.refresh() # checks exploration border & clears roles/missions of missing fleets & updates fleet locs & threats - foAIstate.report_system_threats() + aistate.prepare_for_new_turn() + turn_state.state.update() debug("Calling AI Modules") # call AI modules action_list = [ColonisationAI.survey_universe, @@ -337,33 +357,23 @@ def generateOrders(): # pylint: disable=invalid-name action() main_timer.stop() except Exception as e: - error("Exception while trying to %s" % action.__name__, e, exc_info=True) + error("Exception %s while trying to %s" % (e, action.__name__), exc_info=True) main_timer.stop_print_and_clear() turn_timer.stop_print_and_clear() + + debug('Size of issued orders: ' + str(fo.getOrders().size)) + turn_timer.start("Server_Processing") - try: - fo.doneTurn() - except Exception as e: - error("Exception while trying doneTurn()", e, exc_info=True) # TODO move it to cycle above + aistate.last_turn_played = fo.currentTurn() if using_statprof: try: statprof.stop() statprof.display() statprof.start() - except: + except: # noqa: E722 pass -# The following methods should probably be moved to the AIstate module, to keep this module more focused on implementing required interface -def declare_war_on_all(): # pylint: disable=invalid-name - """Used to declare war on all other empires (at start of game)""" - my_emp_id = fo.empireID() - for emp_id in fo.allEmpireIDs(): - if emp_id != my_emp_id: - msg = fo.diplomaticMessage(my_emp_id, emp_id, fo.diplomaticMessageType.warDeclaration) - fo.sendDiplomaticMessage(msg) - - init_handlers(fo.getOptionsDBOptionStr("ai-config"), fo.getAIDir()) diff --git a/default/python/AI/InvasionAI.py b/default/python/AI/InvasionAI.py index 0c4d07aaacd..a3551545cdf 100644 --- a/default/python/AI/InvasionAI.py +++ b/default/python/AI/InvasionAI.py @@ -1,30 +1,29 @@ import math -import sys +from logging import debug, info, warning -from turn_state import state import freeOrionAIInterface as fo -from common.print_utils import Table, Text, Float -import FreeOrionAI as foAI -import AIstate +from aistate_interface import get_aistate import AIDependencies +import AIstate +import ColonisationAI +import CombatRatingsAI +import EspionageAI import FleetUtilsAI +import MilitaryAI import PlanetUtilsAI -import universe_object import ProductionAI -import ColonisationAI -import MilitaryAI +from AIDependencies import INVALID_ID, Tags from EnumsAI import MissionType, PriorityType -import CombatRatingsAI -from freeorion_tools import tech_is_complete, AITimer -from AIDependencies import INVALID_ID - -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) +from common.print_utils import Table, Text, Float +from freeorion_tools import tech_is_complete, AITimer, get_partial_visibility_turn, get_species_tag_grade +from target import TargetPlanet, TargetSystem +from turn_state import state MAX_BASE_TROOPERS_GOOD_INVADERS = 20 MAX_BASE_TROOPERS_POOR_INVADERS = 10 _TROOPS_SAFETY_MARGIN = 1 # try to send this amount of additional troops to account for uncertainties in calculation +MIN_INVASION_SCORE = 20 invasion_timer = AITimer('get_invasion_fleets()', write_log=False) @@ -35,18 +34,18 @@ def get_invasion_fleets(): empire = fo.getEmpire() empire_id = fo.empireID() - all_invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.INVASION) - AIstate.invasionFleetIDs = FleetUtilsAI.extract_fleet_ids_without_mission_types(all_invasion_fleet_ids) - home_system_id = PlanetUtilsAI.get_capital_sys_id() - visible_system_ids = list(foAI.foAIstate.visInteriorSystemIDs) + list(foAI.foAIstate.visBorderSystemIDs) + aistate = get_aistate() + visible_system_ids = list(aistate.visInteriorSystemIDs) + list(aistate.visBorderSystemIDs) if home_system_id != INVALID_ID: - accessible_system_ids = [sys_id for sys_id in visible_system_ids - if (sys_id != INVALID_ID) and universe.systemsConnected(sys_id, home_system_id, empire_id)] + accessible_system_ids = [sys_id for sys_id in visible_system_ids if + (sys_id != INVALID_ID) and universe.systemsConnected(sys_id, home_system_id, + empire_id)] else: - print "Warning: Empire has no identifiable homeworld; will treat all visible planets as accessible." - accessible_system_ids = visible_system_ids # TODO: check if any troop ships owned, use their system as home system + debug("Empire has no identifiable homeworld; will treat all visible planets as accessible.") + # TODO: check if any troop ships owned, use their system as home system + accessible_system_ids = visible_system_ids acessible_planet_ids = PlanetUtilsAI.get_planets_in__systems_ids(accessible_system_ids) all_owned_planet_ids = PlanetUtilsAI.get_all_owned_planet_ids(acessible_planet_ids) # includes unpopulated outposts @@ -55,20 +54,21 @@ def get_invasion_fleets(): invadable_planet_ids = set(all_owned_planet_ids).union(all_populated_planets) - set(empire_owned_planet_ids) invasion_targeted_planet_ids = get_invasion_targeted_planet_ids(universe.planetIDs, MissionType.INVASION) - invasion_targeted_planet_ids.extend(get_invasion_targeted_planet_ids(universe.planetIDs, MissionType.ORBITAL_INVASION)) + invasion_targeted_planet_ids.extend( + get_invasion_targeted_planet_ids(universe.planetIDs, MissionType.ORBITAL_INVASION)) all_invasion_targeted_system_ids = set(PlanetUtilsAI.get_systems(invasion_targeted_planet_ids)) invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.INVASION) num_invasion_fleets = len(FleetUtilsAI.extract_fleet_ids_without_mission_types(invasion_fleet_ids)) - print "Current Invasion Targeted SystemIDs: ", PlanetUtilsAI.sys_name_ids(AIstate.invasionTargetedSystemIDs) - print "Current Invasion Targeted PlanetIDs: ", PlanetUtilsAI.planet_name_ids(invasion_targeted_planet_ids) - print invasion_fleet_ids and "Invasion Fleet IDs: %s" % invasion_fleet_ids or "Available Invasion Fleets: 0" - print "Invasion Fleets Without Missions: %s" % num_invasion_fleets + debug("Current Invasion Targeted SystemIDs: %s" % PlanetUtilsAI.sys_name_ids(AIstate.invasionTargetedSystemIDs)) + debug("Current Invasion Targeted PlanetIDs: %s" % PlanetUtilsAI.planet_string(invasion_targeted_planet_ids)) + debug(invasion_fleet_ids and "Invasion Fleet IDs: %s" % invasion_fleet_ids or "Available Invasion Fleets: 0") + debug("Invasion Fleets Without Missions: %s" % num_invasion_fleets) invasion_timer.start("planning troop base production") reserved_troop_base_targets = [] - if foAI.foAIstate.character.may_invade_with_bases(): + if aistate.character.may_invade_with_bases(): available_pp = {} for el in empire.planetsWithAvailablePP: # keys are sets of ints; data is doubles avail_pp = el.data() @@ -85,7 +85,7 @@ def get_invasion_fleets(): # we lost our base trooper source planet since it was first added to list). # # For planning and tracking base troopers under construction, we use a dictionary store in - # foAI.foAIstate.qualifyingTroopBaseTargets, keyed by the invasion target planet ID. We only store values + # get_aistate().qualifyingTroopBaseTargets, keyed by the invasion target planet ID. We only store values # for invasion targets that appear likely to be suitable for base trooper use, and store a 2-item list. # The first item in this list is the ID of the planet where we expect to build the base troopers, and the second # entry initially is set to INVALID_ID (-1). The presence of this entry in qualifyingTroopBaseTargets @@ -95,24 +95,24 @@ def get_invasion_fleets(): # same as the source planet originally identified, but we could consider reevaluating that, or use that second # value to instead record how many base troopers have been queued, so that on later turns we can assess if the # process got delayed & perhaps more troopers need to be queued). - - secure_ai_fleet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.SECURE]) + secure_ai_fleet_missions = aistate.get_fleet_missions_with_any_mission_types([MissionType.SECURE, + MissionType.MILITARY]) # Pass 1: identify qualifying base troop invasion targets for pid in invadable_planet_ids: # TODO: reorganize - if pid in foAI.foAIstate.qualifyingTroopBaseTargets: + if pid in aistate.qualifyingTroopBaseTargets: continue planet = universe.getPlanet(pid) if not planet: continue sys_id = planet.systemID - sys_partial_vis_turn = universe.getVisibilityTurnsMap(planet.systemID, empire_id).get(fo.visibility.partial, -9999) - planet_partial_vis_turn = universe.getVisibilityTurnsMap(pid, empire_id).get(fo.visibility.partial, -9999) + sys_partial_vis_turn = get_partial_visibility_turn(sys_id) + planet_partial_vis_turn = get_partial_visibility_turn(pid) if planet_partial_vis_turn < sys_partial_vis_turn: continue best_base_planet = INVALID_ID best_trooper_count = 0 - for pid2 in state.get_empire_inhabited_planets_by_system().get(sys_id, []): + for pid2 in state.get_empire_planets_by_system(sys_id, include_outposts=False): if available_pp.get(pid2, 0) < 2: # TODO: improve troop base PP sufficiency determination break planet2 = universe.getPlanet(pid2) @@ -125,38 +125,38 @@ def get_invasion_fleets(): troops_per_ship = best_base_trooper_here.troopCapacity if not troops_per_ship: continue - species_troop_grade = CombatRatingsAI.get_species_troops_grade(planet2.speciesName) + species_troop_grade = get_species_tag_grade(planet2.speciesName, Tags.ATTACKTROOPS) troops_per_ship = CombatRatingsAI.weight_attack_troops(troops_per_ship, species_troop_grade) if troops_per_ship > best_trooper_count: best_base_planet = pid2 best_trooper_count = troops_per_ship if best_base_planet != INVALID_ID: - foAI.foAIstate.qualifyingTroopBaseTargets.setdefault(pid, [best_base_planet, INVALID_ID]) + aistate.qualifyingTroopBaseTargets.setdefault(pid, [best_base_planet, INVALID_ID]) # Pass 2: for each target previously identified for base troopers, check that still qualifies and # check how many base troopers would be needed; if reasonable then queue up the troops and record this in - # foAI.foAIstate.qualifyingTroopBaseTargets - for pid in foAI.foAIstate.qualifyingTroopBaseTargets.keys(): + # get_aistate().qualifyingTroopBaseTargets + for pid in list(aistate.qualifyingTroopBaseTargets.keys()): planet = universe.getPlanet(pid) if planet and planet.owner == empire_id: - del foAI.foAIstate.qualifyingTroopBaseTargets[pid] + del aistate.qualifyingTroopBaseTargets[pid] continue if pid in invasion_targeted_planet_ids: # TODO: consider overriding standard invasion mission continue - if foAI.foAIstate.qualifyingTroopBaseTargets[pid][1] != -1: + if aistate.qualifyingTroopBaseTargets[pid][1] != -1: reserved_troop_base_targets.append(pid) if planet: all_invasion_targeted_system_ids.add(planet.systemID) # TODO: evaluate changes to situation, any more troops needed, etc. continue # already building for here - _, planet_troops = evaluate_invasion_planet(pid, secure_ai_fleet_missions, False) + _, planet_troops = evaluate_invasion_planet(pid, secure_ai_fleet_missions, True) sys_id = planet.systemID - this_sys_status = foAI.foAIstate.systemStatus.get(sys_id, {}) + this_sys_status = aistate.systemStatus.get(sys_id, {}) troop_tally = 0 for _fid in this_sys_status.get('myfleets', []): troop_tally += FleetUtilsAI.count_troops_in_fleet(_fid) if troop_tally > planet_troops: # base troopers appear unneeded - del foAI.foAIstate.qualifyingTroopBaseTargets[pid] + del aistate.qualifyingTroopBaseTargets[pid] continue if (planet.currentMeterValue(fo.meterType.shield) > 0 and (this_sys_status.get('myFleetRating', 0) < 0.8 * this_sys_status.get('totalThreat', 0) or @@ -164,51 +164,53 @@ def get_invasion_fleets(): # this system not secured, so ruling out invasion base troops for now # don't immediately delete from qualifyingTroopBaseTargets or it will be opened up for regular troops continue - loc = foAI.foAIstate.qualifyingTroopBaseTargets[pid][0] + loc = aistate.qualifyingTroopBaseTargets[pid][0] best_base_trooper_here = ProductionAI.get_best_ship_info(PriorityType.PRODUCTION_ORBITAL_INVASION, loc)[1] loc_planet = universe.getPlanet(loc) if best_base_trooper_here is None: # shouldn't be possible at this point, but just to be safe - print >> sys.stderr, "Could not find a suitable orbital invasion design at %s" % loc_planet + warning("Could not find a suitable orbital invasion design at %s" % loc_planet) continue # TODO: have TroopShipDesigner give the expected number of troops including species effects directly troops_per_ship = best_base_trooper_here.troopCapacity - species_troop_grade = CombatRatingsAI.get_species_troops_grade(loc_planet.speciesName) + species_troop_grade = get_species_tag_grade(loc_planet.speciesName, Tags.ATTACKTROOPS) troops_per_ship = CombatRatingsAI.weight_attack_troops(troops_per_ship, species_troop_grade) if not troops_per_ship: - print >> sys.stderr, "The best orbital invasion design at %s seems not to have any troop capacity." % loc_planet + warning("The best orbital invasion design at %s seems not to have any troop capacity." % loc_planet) continue - _, col_design, build_choices = ProductionAI.get_best_ship_info(PriorityType.PRODUCTION_ORBITAL_INVASION, loc) + _, col_design, build_choices = ProductionAI.get_best_ship_info(PriorityType.PRODUCTION_ORBITAL_INVASION, + loc) if not col_design: continue if loc not in build_choices: - sys.stderr.write( - 'Best troop design %s can not be produces in at planet with id: %s\d' % (col_design, build_choices) - ) + warning('Best troop design %s can not be produced at planet with id: %s' % (col_design, build_choices)) + continue n_bases = math.ceil((planet_troops + 1) / troops_per_ship) # TODO: reconsider this +1 safety factor # TODO: evaluate cost and time-to-build of best base trooper here versus cost and time-to-build-and-travel # for best regular trooper elsewhere # For now, we assume what building base troopers is best so long as either (1) we would need no more than # MAX_BASE_TROOPERS_POOR_INVADERS base troop ships, or (2) our base troopers have more than 1 trooper per # ship and we would need no more than MAX_BASE_TROOPERS_GOOD_INVADERS base troop ships - if (n_bases <= MAX_BASE_TROOPERS_POOR_INVADERS or - (troops_per_ship > 1 and n_bases <= MAX_BASE_TROOPERS_GOOD_INVADERS)): - print "ruling out base invasion troopers for %s due to high number (%d) required." % (planet, n_bases) - del foAI.foAIstate.qualifyingTroopBaseTargets[pid] + if (n_bases > MAX_BASE_TROOPERS_POOR_INVADERS or + (troops_per_ship > 1 and n_bases > MAX_BASE_TROOPERS_GOOD_INVADERS)): + debug("ruling out base invasion troopers for %s due to high number (%d) required." % (planet, n_bases)) + del aistate.qualifyingTroopBaseTargets[pid] continue - print "Invasion base planning, need %d troops at %d pership, will build %d ships." % ( - (planet_troops + 1), troops_per_ship, n_bases) + debug("Invasion base planning, need %d troops at %d per ship, will build %d ships." % ( + (planet_troops + 1), troops_per_ship, n_bases)) retval = fo.issueEnqueueShipProductionOrder(col_design.id, loc) - print "Enqueueing %d Troop Bases at %s for %s" % (n_bases, PlanetUtilsAI.planet_name_ids([loc]), PlanetUtilsAI.planet_name_ids([pid])) + debug("Enqueueing %d Troop Bases at %s for %s" % (n_bases, PlanetUtilsAI.planet_string(loc), + PlanetUtilsAI.planet_string(pid))) if retval != 0: all_invasion_targeted_system_ids.add(planet.systemID) reserved_troop_base_targets.append(pid) - foAI.foAIstate.qualifyingTroopBaseTargets[pid][1] = loc + aistate.qualifyingTroopBaseTargets[pid][1] = loc fo.issueChangeProductionQuantityOrder(empire.productionQueue.size - 1, 1, int(n_bases)) fo.issueRequeueProductionOrder(empire.productionQueue.size - 1, 0) invasion_timer.start("evaluating target planets") # TODO: check if any invasion_targeted_planet_ids need more troops assigned - evaluated_planet_ids = list(set(invadable_planet_ids) - set(invasion_targeted_planet_ids) - set(reserved_troop_base_targets)) + evaluated_planet_ids = list( + set(invadable_planet_ids) - set(invasion_targeted_planet_ids) - set(reserved_troop_base_targets)) evaluated_planets = assign_invasion_values(evaluated_planet_ids) sorted_planets = [(pid, pscore % 10000, ptroops) for pid, (pscore, ptroops) in evaluated_planets.items()] @@ -226,12 +228,11 @@ def get_invasion_fleets(): planet and planet.speciesName or "unknown", ptroops ]) - print - invasion_table.print_table() + info(invasion_table) - sorted_planets = filter(lambda x: x[1] > 0, sorted_planets) + sorted_planets = [x for x in sorted_planets if x[1] > 0] # export opponent planets for other AI modules - AIstate.opponentPlanetIDs = [pid for pid, _, _ in sorted_planets] + AIstate.opponentPlanetIDs = [pid for pid, __, __ in sorted_planets] AIstate.invasionTargets = sorted_planets # export invasion targeted systems for other AI modules @@ -241,12 +242,12 @@ def get_invasion_fleets(): def get_invasion_targeted_planet_ids(planet_ids, mission_type): - invasion_feet_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([mission_type]) + invasion_feet_missions = get_aistate().get_fleet_missions_with_any_mission_types([mission_type]) targeted_planets = [] for pid in planet_ids: # add planets that are target of a mission for mission in invasion_feet_missions: - target = universe_object.Planet(pid) + target = TargetPlanet(pid) if mission.has_target(mission_type, target): targeted_planets.append(pid) return targeted_planets @@ -268,10 +269,11 @@ def assign_invasion_values(planet_ids): neighbor_values = {} neighbor_val_ratio = .95 universe = fo.getUniverse() - secure_missions = foAI.foAIstate.get_fleet_missions_with_any_mission_types([MissionType.SECURE]) + secure_missions = get_aistate().get_fleet_missions_with_any_mission_types([MissionType.SECURE, + MissionType.MILITARY]) for pid in planet_ids: planet_values[pid] = neighbor_values.setdefault(pid, evaluate_invasion_planet(pid, secure_missions)) - print "planet %d, values %s" % (pid, planet_values[pid]) + debug("planet %d, values %s", pid, planet_values[pid]) planet = universe.getPlanet(pid) species_name = (planet and planet.speciesName) or "" species = fo.getSpecies(species_name) @@ -285,20 +287,72 @@ def assign_invasion_values(planet_ids): species_name2 = (planet2 and planet2.speciesName) or "" species2 = fo.getSpecies(species_name2) if species2 and species2.canProduceShips: - planet_industries[pid2] = planet2.currentMeterValue(fo.meterType.industry) + 0.1 # to prevent divide-by-zero + # to prevent divide-by-zero + planet_industries[pid2] = planet2.initialMeterValue(fo.meterType.industry) + 0.1 industry_ratio = planet_industries[pid] / max(planet_industries.values()) for pid2 in system.planetIDs: if pid2 == pid: continue planet2 = universe.getPlanet(pid2) - if planet2 and (planet2.owner != empire_id) and ((planet2.owner != -1) or (planet.currentMeterValue(fo.meterType.population) > 0)): # TODO check for allies - planet_values[pid][0] += industry_ratio * neighbor_val_ratio * (neighbor_values.setdefault(pid2, evaluate_invasion_planet(pid2, secure_missions))[0]) + # TODO check for allies + if (planet2 and (planet2.owner != empire_id) and + ((planet2.owner != -1) or (planet2.initialMeterValue(fo.meterType.population) > 0))): + planet_values[pid][0] += ( + industry_ratio * + neighbor_val_ratio * + (neighbor_values.setdefault(pid2, evaluate_invasion_planet(pid2, secure_missions))[0]) + ) return planet_values def evaluate_invasion_planet(planet_id, secure_fleet_missions, verbose=True): """Return the invasion value (score, troops) of a planet.""" + universe = fo.getUniverse() + empire_id = fo.empireID() detail = [] + + planet = universe.getPlanet(planet_id) + if planet is None: + debug("Invasion AI couldn't access any info for planet id %d" % planet_id) + return [0, 0] + + system_id = planet.systemID + + # by using the following instead of simply relying on stealth meter reading, can (sometimes) plan ahead even if + # planet is temporarily shrouded by an ion storm + predicted_detectable = EspionageAI.colony_detectable_by_empire(planet_id, empire=fo.empireID(), + default_result=False) + if not predicted_detectable: + if get_partial_visibility_turn(planet_id) < fo.currentTurn(): + debug("InvasionAI predicts planet id %d to be stealthed" % planet_id) + return [0, 0] + else: + debug("InvasionAI predicts planet id %d to be stealthed" % planet_id + + ", but somehow have current visibity anyway, will still consider as target") + + # Check if the target planet was extra-stealthed somehow its system was last viewed + # this test below may augment the tests above, but can be thrown off by temporary combat-related sighting + system_last_seen = get_partial_visibility_turn(planet_id) + planet_last_seen = get_partial_visibility_turn(system_id) + if planet_last_seen < system_last_seen: + # TODO: track detection strength, order new scouting when it goes up + debug("Invasion AI considering planet id %d (stealthed at last view), still proceeding." % planet_id) + + # get a baseline evaluation of the planet as determined by ColonisationAI + species_name = planet.speciesName + species = fo.getSpecies(species_name) + empire_research_list = tuple(element.tech for element in fo.getEmpire().researchQueue) + if not species or AIDependencies.TAG_DESTROYED_ON_CONQUEST in species.tags: + # this call iterates over this Empire's available species with which it could colonize after an invasion + planet_eval = ColonisationAI.assign_colonisation_values([planet_id], MissionType.INVASION, None, detail) + colony_base_value = max(0.75 * planet_eval.get(planet_id, [0])[0], + ColonisationAI.evaluate_planet( + planet_id, MissionType.OUTPOST, None, detail, empire_research_list)) + else: + colony_base_value = ColonisationAI.evaluate_planet( + planet_id, MissionType.INVASION, species_name, detail, empire_research_list) + + # Add extra score for all buildings on the planet building_values = {"BLD_IMPERIAL_PALACE": 1000, "BLD_CULTURE_ARCHIVES": 1000, "BLD_AUTO_HISTORY_ANALYSER": 100, @@ -324,66 +378,40 @@ def evaluate_invasion_planet(planet_id, secure_fleet_missions, verbose=True): "BLD_BIOTERROR_PROJECTOR": 1000, "BLD_SHIPYARD_ENRG_COMP": 3000, } - # TODO: add more factors, as used for colonization - universe = fo.getUniverse() - empire_id = fo.empireID() - max_jumps = 8 - planet = universe.getPlanet(planet_id) - if planet is None: # TODO: exclude planets with stealth higher than empireDetection - print "invasion AI couldn't access any info for planet id %d" % planet_id - return [0, 0] - - sys_partial_vis_turn = universe.getVisibilityTurnsMap(planet.systemID, empire_id).get(fo.visibility.partial, -9999) - planet_partial_vis_turn = universe.getVisibilityTurnsMap(planet_id, empire_id).get(fo.visibility.partial, -9999) - - if planet_partial_vis_turn < sys_partial_vis_turn: - print "invasion AI couldn't get current info on planet id %d (was stealthed at last sighting)" % planet_id - # TODO: track detection strength, order new scouting when it goes up - return [0, 0] # last time we had partial vis of the system, the planet was stealthed to us - - species_name = planet.speciesName - species = fo.getSpecies(species_name) - if not species or AIDependencies.TAG_DESTROYED_ON_CONQUEST in species.tags: - # this call iterates over this Empire's available species with which it could colonize after an invasion - planet_eval = ColonisationAI.assign_colonisation_values([planet_id], MissionType.INVASION, None, detail) - pop_val = max(0.75 * planet_eval.get(planet_id, [0])[0], - ColonisationAI.evaluate_planet(planet_id, MissionType.OUTPOST, None, detail)) - else: - pop_val = ColonisationAI.evaluate_planet(planet_id, MissionType.INVASION, species_name, detail) - bld_tally = 0 for bldType in [universe.getBuilding(bldg).buildingTypeName for bldg in planet.buildingIDs]: bval = building_values.get(bldType, 50) bld_tally += bval detail.append("%s: %d" % (bldType, bval)) + # Add extra score for unlocked techs when we conquer the species tech_tally = 0 + value_per_pp = 4 for unlocked_tech in AIDependencies.SPECIES_TECH_UNLOCKS.get(species_name, []): if not tech_is_complete(unlocked_tech): rp_cost = fo.getTech(unlocked_tech).researchCost(empire_id) - tech_tally += rp_cost * 4 - detail.append("%s: %d" % (unlocked_tech, rp_cost * 4)) + tech_value = value_per_pp * rp_cost + tech_tally += tech_value + detail.append("%s: %d" % (unlocked_tech, tech_value)) - p_sys_id = planet.systemID + max_jumps = 8 capitol_id = PlanetUtilsAI.get_capital() least_jumps_path = [] clear_path = True if capitol_id: homeworld = universe.getPlanet(capitol_id) - if homeworld: - home_system_id = homeworld.systemID - eval_system_id = planet.systemID - if (home_system_id != INVALID_ID) and (eval_system_id != INVALID_ID): - least_jumps_path = list(universe.leastJumpsPath(home_system_id, eval_system_id, empire_id)) - max_jumps = len(least_jumps_path) - system_status = foAI.foAIstate.systemStatus.get(p_sys_id, {}) + if homeworld and homeworld.systemID != INVALID_ID and system_id != INVALID_ID: + least_jumps_path = list(universe.leastJumpsPath(homeworld.systemID, system_id, empire_id)) + max_jumps = len(least_jumps_path) + aistate = get_aistate() + system_status = aistate.systemStatus.get(system_id, {}) system_fleet_treat = system_status.get('fleetThreat', 1000) system_monster_threat = system_status.get('monsterThreat', 0) sys_total_threat = system_fleet_treat + system_monster_threat + system_status.get('planetThreat', 0) max_path_threat = system_fleet_treat mil_ship_rating = MilitaryAI.cur_best_mil_ship_rating() for path_sys_id in least_jumps_path: - path_leg_status = foAI.foAIstate.systemStatus.get(path_sys_id, {}) + path_leg_status = aistate.systemStatus.get(path_sys_id, {}) path_leg_threat = path_leg_status.get('fleetThreat', 1000) + path_leg_status.get('monsterThreat', 0) if path_leg_threat > 0.5 * mil_ship_rating: clear_path = False @@ -393,21 +421,22 @@ def evaluate_invasion_planet(planet_id, secure_fleet_missions, verbose=True): pop = planet.currentMeterValue(fo.meterType.population) target_pop = planet.currentMeterValue(fo.meterType.targetPopulation) troops = planet.currentMeterValue(fo.meterType.troops) + troop_regen = planet.currentMeterValue(fo.meterType.troops) - planet.initialMeterValue(fo.meterType.troops) max_troops = planet.currentMeterValue(fo.meterType.maxTroops) # TODO: refactor troop determination into function for use in mid-mission updates and also consider defender techs max_troops += AIDependencies.TROOPS_PER_POP * (target_pop - pop) - this_system = universe.getSystem(p_sys_id) - secure_targets = [p_sys_id] + list(this_system.planetIDs) + this_system = universe.getSystem(system_id) + secure_targets = [system_id] + list(this_system.planetIDs) system_secured = False for mission in secure_fleet_missions: if system_secured: break secure_fleet_id = mission.fleet.id s_fleet = universe.getFleet(secure_fleet_id) - if not s_fleet or s_fleet.systemID != p_sys_id: + if not s_fleet or s_fleet.systemID != system_id: continue - if mission.type == MissionType.SECURE: + if mission.type in [MissionType.SECURE, MissionType.MILITARY]: target_obj = mission.target.get_object() if target_obj is not None and target_obj.id in secure_targets: system_secured = True @@ -415,37 +444,27 @@ def evaluate_invasion_planet(planet_id, secure_fleet_missions, verbose=True): system_secured = system_secured and system_status.get('myFleetRating', 0) if verbose: - print ("Invasion eval of %s\n" - " - maxShields: %.1f\n" - " - sysFleetThreat: %.1f\n" - " - sysMonsterThreat: %.1f") % ( - planet, planet.currentMeterValue(fo.meterType.maxShield), system_fleet_treat, - system_monster_threat) - supply_val = 0 + debug("Invasion eval of %s\n" + " - maxShields: %.1f\n" + " - sysFleetThreat: %.1f\n" + " - sysMonsterThreat: %.1f", + planet, planet.currentMeterValue(fo.meterType.maxShield), system_fleet_treat, system_monster_threat) enemy_val = 0 if planet.owner != -1: # value in taking this away from an enemy - enemy_val = 20 * (planet.currentMeterValue(fo.meterType.targetIndustry) + 2*planet.currentMeterValue(fo.meterType.targetResearch)) - if p_sys_id in ColonisationAI.annexable_system_ids: # TODO: extend to rings - supply_val = 100 - elif p_sys_id in ColonisationAI.annexable_ring1: - supply_val = 200 - elif p_sys_id in ColonisationAI.annexable_ring2: - supply_val = 300 - elif p_sys_id in ColonisationAI.annexable_ring3: - supply_val = 400 - if max_path_threat > 0.5 * mil_ship_rating: - if max_path_threat < 3 * mil_ship_rating: - supply_val *= 0.5 - else: - supply_val *= 0.2 + enemy_val = 20 * (planet.currentMeterValue(fo.meterType.targetIndustry) + + 2*planet.currentMeterValue(fo.meterType.targetResearch)) - threat_factor = min(1, 0.2*MilitaryAI.get_tot_mil_rating()/(sys_total_threat+0.001))**2 # devalue invasions that would require too much military force + # devalue invasions that would require too much military force + preferred_max_portion = MilitaryAI.get_preferred_max_military_portion_for_single_battle() + total_max_mil_rating = MilitaryAI.get_concentrated_tot_mil_rating() + threat_exponent = 2 # TODO: make this a character trait; higher aggression with a lower exponent + threat_factor = min(1, preferred_max_portion * total_max_mil_rating/(sys_total_threat+0.001))**threat_exponent design_id, _, locs = ProductionAI.get_best_ship_info(PriorityType.PRODUCTION_INVASION) if not locs or not universe.getPlanet(locs[0]): # We are in trouble anyway, so just calculate whatever approximation... build_time = 4 - planned_troops = troops if system_secured else min(troops + max_jumps + build_time, max_troops) + planned_troops = troops if system_secured else min(troops + troop_regen*(max_jumps + build_time), max_troops) planned_troops += .01 # we must attack with more troops than there are defenders troop_cost = math.ceil((planned_troops+_TROOPS_SAFETY_MARGIN) / 6.0) * 20 * FleetUtilsAI.get_fleet_upkeep() else: @@ -454,33 +473,40 @@ def evaluate_invasion_planet(planet_id, secure_fleet_missions, verbose=True): design = fo.getShipDesign(design_id) cost_per_ship = design.productionCost(empire_id, loc) build_time = design.productionTime(empire_id, loc) - troops_per_ship = CombatRatingsAI.weight_attack_troops(design.troopCapacity, - CombatRatingsAI.get_species_troops_grade(species_here)) - planned_troops = troops if system_secured else min(troops + max_jumps + build_time, max_troops) + troops_per_ship = CombatRatingsAI.weight_attack_troops( + design.troopCapacity, get_species_tag_grade(species_here, Tags.ATTACKTROOPS)) + planned_troops = troops if system_secured else min(troops + troop_regen*(max_jumps + build_time), max_troops) planned_troops += .01 # we must attack with more troops than there are defenders ships_needed = math.ceil((planned_troops+_TROOPS_SAFETY_MARGIN) / float(troops_per_ship)) troop_cost = ships_needed * cost_per_ship # fleet upkeep is already included in query from server # apply some bias to expensive operations normalized_cost = float(troop_cost) / max(fo.getEmpire().productionPoints, 1) - normalized_cost = max(1, normalized_cost) + normalized_cost = max(1., normalized_cost) cost_score = (normalized_cost**2 / 50.0) * troop_cost - base_score = pop_val + supply_val + bld_tally + tech_tally + enemy_val - cost_score + base_score = colony_base_value + bld_tally + tech_tally + enemy_val - cost_score + # If the AI does have enough total military to attack this target, and the target is more than minimally valuable, + # don't let the threat_factor discount the adjusted value below MIN_INVASION_SCORE +1, so that if there are no + # other targets the AI could still pursue this one. Otherwise, scoring pressure from + # MilitaryAI.get_preferred_max_military_portion_for_single_battle might prevent the AI from attacking heavily + # defended but still defeatable targets even if it has no softer targets available. + if total_max_mil_rating > sys_total_threat and base_score > 2 * MIN_INVASION_SCORE: + threat_factor = max(threat_factor, (MIN_INVASION_SCORE + 1)/base_score) planet_score = retaliation_risk_factor(planet.owner) * threat_factor * max(0, base_score) if clear_path: planet_score *= 1.5 if verbose: - print (' - planet score: %.2f\n' - ' - troop score: %.2f\n' - ' - projected troop cost: %.1f\n' - ' - threat factor: %s\n' - ' - planet detail: %s\n' - ' - popval: %.1f\n' - ' - supplyval: %.1f\n' - ' - bldval: %s\n' - ' - enemyval: %s') % (planet_score, planned_troops, troop_cost, - threat_factor, detail, pop_val, supply_val, bld_tally, enemy_val) + debug(' - planet score: %.2f\n' + ' - planned troops: %.2f\n' + ' - projected troop cost: %.1f\n' + ' - threat factor: %s\n' + ' - planet detail: %s\n' + ' - popval: %.1f\n' + ' - bldval: %s\n' + ' - enemyval: %s', + planet_score, planned_troops, troop_cost, threat_factor, detail, colony_base_value, bld_tally, enemy_val) + debug(' - system secured: %s' % system_secured) return [planet_score, planned_troops] @@ -493,6 +519,8 @@ def send_invasion_fleets(fleet_ids, evaluated_planets, mission_type): invasion_fleet_pool = set(fleet_ids) for planet_id, pscore, ptroops in evaluated_planets: + if pscore < MIN_INVASION_SCORE: + continue planet = universe.getPlanet(planet_id) if not planet: continue @@ -500,22 +528,25 @@ def send_invasion_fleets(fleet_ids, evaluated_planets, mission_type): found_fleets = [] found_stats = {} min_stats = {'rating': 0, 'troopCapacity': ptroops} - target_stats = {'rating': 10, 'troopCapacity': ptroops + _TROOPS_SAFETY_MARGIN} + target_stats = {'rating': 10, + 'troopCapacity': ptroops + _TROOPS_SAFETY_MARGIN, + 'target_system': TargetSystem(sys_id)} these_fleets = FleetUtilsAI.get_fleets_for_mission(target_stats, min_stats, found_stats, starting_system=sys_id, fleet_pool_set=invasion_fleet_pool, fleet_list=found_fleets) if not these_fleets: if not FleetUtilsAI.stats_meet_reqs(found_stats, min_stats): - print "Insufficient invasion troop allocation for system %d ( %s ) -- requested %s , found %s" % ( - sys_id, universe.getSystem(sys_id).name, min_stats, found_stats) + debug("Insufficient invasion troop allocation for system %d ( %s ) -- requested %s , found %s" % ( + sys_id, universe.getSystem(sys_id).name, min_stats, found_stats)) invasion_fleet_pool.update(found_fleets) continue else: these_fleets = found_fleets - target = universe_object.Planet(planet_id) - print "assigning invasion fleets %s to target %s" % (these_fleets, target) + target = TargetPlanet(planet_id) + debug("assigning invasion fleets %s to target %s" % (these_fleets, target)) + aistate = get_aistate() for fleetID in these_fleets: - fleet_mission = foAI.foAIstate.get_fleet_mission(fleetID) + fleet_mission = aistate.get_fleet_mission(fleetID) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() fleet_mission.set_target(mission_type, target) @@ -527,6 +558,7 @@ def assign_invasion_bases(): all_troopbase_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.ORBITAL_INVASION) available_troopbase_fleet_ids = set(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_troopbase_fleet_ids)) + aistate = get_aistate() for fid in list(available_troopbase_fleet_ids): if fid not in available_troopbase_fleet_ids: # entry may have been discarded in previous loop iterations continue @@ -535,15 +567,15 @@ def assign_invasion_bases(): continue sys_id = fleet.systemID system = universe.getSystem(sys_id) - available_planets = set(system.planetIDs).intersection(set(foAI.foAIstate.qualifyingTroopBaseTargets.keys())) - print "Considering Base Troopers in %s, found planets %s and registered targets %s with status %s" % ( + available_planets = set(system.planetIDs).intersection(set(aistate.qualifyingTroopBaseTargets.keys())) + debug("Considering Base Troopers in %s, found planets %s and registered targets %s with status %s" % ( system.name, list(system.planetIDs), available_planets, - [(pid, foAI.foAIstate.qualifyingTroopBaseTargets[pid]) for pid in available_planets]) - targets = [pid for pid in available_planets if foAI.foAIstate.qualifyingTroopBaseTargets[pid][1] != -1] + [(pid, aistate.qualifyingTroopBaseTargets[pid]) for pid in available_planets])) + targets = [pid for pid in available_planets if aistate.qualifyingTroopBaseTargets[pid][1] != -1] if not targets: - print "Failure: found no valid target for troop base in system %s" % system + debug("Failure: found no valid target for troop base in system %s" % system) continue - status = foAI.foAIstate.systemStatus.get(sys_id, {}) + status = aistate.systemStatus.get(sys_id, {}) local_base_troops = set(status.get('myfleets', [])).intersection(available_troopbase_fleet_ids) target_id = INVALID_ID @@ -570,20 +602,22 @@ def assign_invasion_bases(): FleetUtilsAI.merge_fleet_a_into_b(fid2, fid) available_troopbase_fleet_ids.discard(fid2) available_troopbase_fleet_ids.discard(fid) - foAI.foAIstate.qualifyingTroopBaseTargets[target_id][1] = -1 # TODO: should probably delete - target = universe_object.Planet(target_id) - fleet_mission = foAI.foAIstate.get_fleet_mission(fid) + aistate.qualifyingTroopBaseTargets[target_id][1] = -1 # TODO: should probably delete + target = TargetPlanet(target_id) + fleet_mission = aistate.get_fleet_mission(fid) fleet_mission.set_target(MissionType.ORBITAL_INVASION, target) def assign_invasion_fleets_to_invade(): """Assign fleet targets to invadable planets.""" + aistate = get_aistate() assign_invasion_bases() - invasion_fleet_ids = AIstate.invasionFleetIDs + all_invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.INVASION) + invasion_fleet_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(all_invasion_fleet_ids) send_invasion_fleets(invasion_fleet_ids, AIstate.invasionTargets, MissionType.INVASION) all_invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.INVASION) for fid in FleetUtilsAI.extract_fleet_ids_without_mission_types(all_invasion_fleet_ids): - this_mission = foAI.foAIstate.get_fleet_mission(fid) + this_mission = aistate.get_fleet_mission(fid) this_mission.check_mergers(context="Post-send consolidation of unassigned troops") diff --git a/default/python/AI/MilitaryAI.py b/default/python/AI/MilitaryAI.py index 7c6194a5ac3..5852912b9a5 100644 --- a/default/python/AI/MilitaryAI.py +++ b/default/python/AI/MilitaryAI.py @@ -1,19 +1,21 @@ +from logging import debug + import freeOrionAIInterface as fo # pylint: disable=import-error -import FreeOrionAI as foAI import AIstate -import universe_object -from EnumsAI import MissionType +import CombatRatingsAI +import EspionageAI import FleetUtilsAI -from CombatRatingsAI import combine_ratings +import InvasionAI import PlanetUtilsAI import PriorityAI -import ColonisationAI import ProductionAI -import CombatRatingsAI -from freeorion_tools import ppstring, cache_by_turn from AIDependencies import INVALID_ID -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) +from aistate_interface import get_aistate +from CombatRatingsAI import combine_ratings, combine_ratings_list, rating_difference +from EnumsAI import MissionType +from freeorion_tools import cache_by_turn_persistent +from target import TargetSystem +from turn_state import state MinThreat = 10 # the minimum threat level that will be ascribed to an unknown threat capable of killing scouts _military_allocations = [] @@ -36,10 +38,11 @@ def cur_best_mil_ship_rating(include_designs=False): return best_rating best_rating = 0.001 universe = fo.getUniverse() + aistate = get_aistate() for fleet_id in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY): fleet = universe.getFleet(fleet_id) for ship_id in fleet.shipIDs: - ship_rating = CombatRatingsAI.ShipCombatStats(ship_id).get_rating(enemy_stats=foAI.foAIstate.get_standard_enemy()) + ship_rating = CombatRatingsAI.ShipCombatStats(ship_id).get_rating(enemy_stats=aistate.get_standard_enemy()) best_rating = max(best_rating, ship_rating) _best_ship_rating_cache[current_turn] = best_rating if include_designs: @@ -48,10 +51,50 @@ def cur_best_mil_ship_rating(include_designs=False): return max(best_rating, 0.001) +def get_preferred_max_military_portion_for_single_battle(): + """ + Determine and return the preferred max portion of military to be allocated to a single battle. + + May be used to downgrade various possible actions requiring military support if they would require an excessive + allocation of military forces. At the beginning of the game this max portion starts as 1.0, then is slightly + reduced to account for desire to reserve some defenses for other locations, and then in mid to late game, as the + size of the the military grows, this portion is further reduced to promote pursuit of multiple battlefronts in + parallel as opposed to single battlefronts against heavily defended positions. + + :return: a number in range (0:1] for preferred max portion of military to be allocated to a single battle + :rtype: float + """ + # TODO: this is a roughcut first pass, needs plenty of refinement + if fo.currentTurn() < 40: + return 1.0 + best_ship_equivalents = (get_concentrated_tot_mil_rating() / cur_best_mil_ship_rating())**0.5 + _MAX_SHIPS_BEFORE_PREFERRING_LESS_THAN_FULL_ENGAGEMENT = 3 + if best_ship_equivalents <= _MAX_SHIPS_BEFORE_PREFERRING_LESS_THAN_FULL_ENGAGEMENT: + return 1.0 + # the below ratio_exponent is still very much a work in progress. It should probably be somewhere in the range of + # 0.2 to 0.5. Values at the larger end will create a smaller expected battle size threshold that would + # cause the respective opportunity (invasion, colonization) scores to be discounted, thereby more quickly creating + # pressure for the AI to pursue multiple small/medium resistance fronts rather than pursuing a smaller number fronts + # facing larger resistance. The AI will start facing some scoring pressure to not need to throw 100% of its + # military at a target as soon as its max military rating surpasses the equvalent of + # _MAX_SHIPS_BEFORE_PREFERRING_LESS_THAN_FULL_ENGAGEMENT of its best ships. That starts simply as some scoring + # pressure to be able to hold back some small portion of its ships from the engagement, in order to be able to use + # them for defense or for other targets. With an exponent value of 0.25, this would start creating substantial + # pressure against devoting more than half the military to a single target once the total military is somewhere + # above 18 best-ship equivalents, and pressure against deovting more than a third once the total is above about 80 + # best-ship equivalents. With an exponent value of 0.5, those thresholds would be 6 ships and 11 ships. With the + # initial value of 0.35, those thresholds are about 10 ships and 25 ships. Depending on how this return value is + # used, it should not prevent the more heavily fortified targets (and therefore discounted) from being taken + # if there are no more remaining easier targets available. + ratio_exponent = 0.35 + return 1.0 / (best_ship_equivalents + 1 - _MAX_SHIPS_BEFORE_PREFERRING_LESS_THAN_FULL_ENGAGEMENT)**ratio_exponent + + def try_again(mil_fleet_ids, try_reset=False, thisround=""): """Clear targets and orders for all specified fleets then call get_military_fleets again.""" + aistate = get_aistate() for fid in mil_fleet_ids: - mission = foAI.foAIstate.get_fleet_mission(fid) + mission = aistate.get_fleet_mission(fid) mission.clear_fleet_orders() mission.clear_target() get_military_fleets(try_reset=try_reset, thisround=thisround) @@ -62,6 +105,7 @@ def avail_mil_needing_repair(mil_fleet_ids, split_ships=False, on_mission=False, fleet_buckets = [[], []] universe = fo.getUniverse() cutoff = [repair_limit, 0.25][on_mission] + aistate = get_aistate() for fleet_id in mil_fleet_ids: fleet = universe.getFleet(fleet_id) ship_buckets = [[], []] @@ -69,33 +113,47 @@ def avail_mil_needing_repair(mil_fleet_ids, split_ships=False, on_mission=False, ships_max_health = [0, 0] for ship_id in fleet.shipIDs: this_ship = universe.getShip(ship_id) - cur_struc = this_ship.currentMeterValue(fo.meterType.structure) - max_struc = this_ship.currentMeterValue(fo.meterType.maxStructure) + cur_struc = this_ship.initialMeterValue(fo.meterType.structure) + max_struc = this_ship.initialMeterValue(fo.meterType.maxStructure) ship_ok = cur_struc >= cutoff * max_struc ship_buckets[ship_ok].append(ship_id) ships_cur_health[ship_ok] += cur_struc ships_max_health[ship_ok] += max_struc - this_sys_id = (fleet.nextSystemID != INVALID_ID and fleet.nextSystemID) or fleet.systemID + this_sys_id = fleet.systemID if fleet.nextSystemID == INVALID_ID else fleet.nextSystemID fleet_ok = (sum(ships_cur_health) >= cutoff * sum(ships_max_health)) - local_status = foAI.foAIstate.systemStatus.get(this_sys_id, {}) + local_status = aistate.systemStatus.get(this_sys_id, {}) my_local_rating = combine_ratings(local_status.get('mydefenses', {}).get('overall', 0), local_status.get('myFleetRating', 0)) my_local_rating_vs_planets = local_status.get('myFleetRatingVsPlanets', 0) - needed_here = local_status.get('totalThreat', 0) > 0 # TODO: assess if remaining other forces are sufficient - safely_needed = needed_here and my_local_rating > local_status.get('totalThreat', 0) and my_local_rating_vs_planets > local_status.get('planetThreat', 0)# TODO: improve both assessment prongs + combat_trigger = bool(local_status.get('fleetThreat', 0) or local_status.get('monsterThreat', 0)) + if not combat_trigger and local_status.get('planetThreat', 0): + universe = fo.getUniverse() + system = universe.getSystem(this_sys_id) + for planet_id in system.planetIDs: + planet = universe.getPlanet(planet_id) + if planet.ownedBy(fo.empireID()): # TODO: also exclude at-peace planets + continue + if planet.unowned and not EspionageAI.colony_detectable_by_empire(planet_id, empire=fo.empireID()): + continue + if sum([planet.currentMeterValue(meter_type) for meter_type in + [fo.meterType.defense, fo.meterType.shield, fo.meterType.construction]]): + combat_trigger = True + break + needed_here = combat_trigger and local_status.get('totalThreat', 0) > 0 # TODO: assess if remaining other forces are sufficient + safely_needed = needed_here and my_local_rating > local_status.get('totalThreat', 0) and my_local_rating_vs_planets > local_status.get('planetThreat', 0) # TODO: improve both assessment prongs if not fleet_ok: if safely_needed: - print "Fleet %d at %s needs repair but deemed safely needed to remain for defense" % (fleet_id, ppstring(PlanetUtilsAI.sys_name_ids([fleet.systemID]))) + debug("Fleet %d at %s needs repair but deemed safely needed to remain for defense" % (fleet_id, universe.getSystem(fleet.systemID))) else: if needed_here: - print "Fleet %d at %s needed present for combat, but is damaged and deemed unsafe to remain." % (fleet_id, ppstring(PlanetUtilsAI.sys_name_ids([fleet.systemID]))) - print "\t my_local_rating: %.1f ; threat: %.1f" % (my_local_rating, local_status.get('totalThreat', 0)) - print "Selecting fleet %d at %s for repair" % (fleet_id, ppstring(PlanetUtilsAI.sys_name_ids([fleet.systemID]))) - fleet_buckets[fleet_ok or safely_needed].append(fleet_id) + debug("Fleet %d at %s needed present for combat, but is damaged and deemed unsafe to remain." % (fleet_id, universe.getSystem(fleet.systemID))) + debug("\t my_local_rating: %.1f ; threat: %.1f" % (my_local_rating, local_status.get('totalThreat', 0))) + debug("Selecting fleet %d at %s for repair" % (fleet_id, universe.getSystem(fleet.systemID))) + fleet_buckets[fleet_ok or bool(safely_needed)].append(fleet_id) return fleet_buckets # TODO Move relevant initialization code from get_military_fleets into this class -class AllocationHelper(object): +class AllocationHelper: def __init__(self, already_assigned_rating, already_assigned_rating_vs_planets, available_rating, try_reset): """ @@ -110,7 +168,7 @@ def __init__(self, already_assigned_rating, already_assigned_rating_vs_planets, self._remaining_rating = available_rating self.threat_bias = 0. - self.safety_factor = foAI.foAIstate.character.military_safety_factor() + self.safety_factor = get_aistate().character.military_safety_factor() self.already_assigned_rating = dict(already_assigned_rating) self.already_assigned_rating_vs_planets = dict(already_assigned_rating_vs_planets) @@ -130,10 +188,13 @@ def allocate(self, group, sys_id, min_rating, min_rating_vs_planets, take_any, m tup = (sys_id, min_rating, min_rating_vs_planets, take_any, max_rating) self.allocations.append(tup) self.allocation_by_groups.setdefault(group, []).append(tup) - self._remaining_rating -= min_rating + if self._remaining_rating <= min_rating: + self._remaining_rating = 0 + else: + self._remaining_rating = rating_difference(self._remaining_rating, min_rating) -class Allocator(object): +class Allocator: """ Base class for Military allocation for a single system. @@ -342,7 +403,7 @@ def _planet_threat(self): return get_system_planetary_threat(self.sys_id) def _enemy_ship_count(self): - return foAI.foAIstate.systemStatus.get(self.sys_id, {}).get('enemy_ship_count', 0.) + return get_aistate().systemStatus.get(self.sys_id, {}).get('enemy_ship_count', 0.) class CapitalDefenseAllocator(Allocator): @@ -376,8 +437,8 @@ def _take_any(self): class PlanetDefenseAllocator(Allocator): _allocation_group = 'occupied' - _min_alloc_factor = 1.3 - _max_alloc_factor = 2 + _min_alloc_factor = 1.1 + _max_alloc_factor = 1.5 _potential_threat_factor = 0.5 _military_reset_ratio = 0.8 @@ -410,8 +471,8 @@ def _take_any(self): class TargetAllocator(Allocator): _allocation_group = 'otherTargets' - _min_alloc_factor = 1.4 - _max_alloc_factor = 2 + _min_alloc_factor = 1.3 + _max_alloc_factor = 2.5 _potential_threat_factor = 0.5 def _calculate_threat(self): @@ -434,8 +495,8 @@ def _planet_threat_multiplier(self): # present, the smaller proportion of our attacks would be directed against the enemy planet. The following is # just one of many forms of calculation that might work reasonably. # TODO: assess and revamp the planet_threat_multiplier calculation - return ((self._enemy_ship_count() + self._local_threat()/self._planet_threat())**0.5 - if self._planet_threat() > 0 else 1.0) + return ((self._enemy_ship_count() + self._local_threat()/self._planet_threat())**0.5 + if self._planet_threat() > 0 else 1.0) def _allocation_vs_planets(self): return CombatRatingsAI.rating_needed( @@ -462,13 +523,13 @@ def _maximum_allocation(self, threat): class LocalThreatAllocator(Allocator): _potential_threat_factor = 0 - _min_alloc_factor = 1.4 + _min_alloc_factor = 1.3 _max_alloc_factor = 2 _allocation_group = 'otherTargets' def _calculate_threat(self): - systems_status = foAI.foAIstate.systemStatus.get(self.sys_id, {}) + systems_status = get_aistate().systemStatus.get(self.sys_id, {}) threat = self.safety_factor * CombatRatingsAI.combine_ratings(systems_status.get('fleetThreat', 0), systems_status.get('monsterThreat', 0) + + systems_status.get('planetThreat', 0)) @@ -480,8 +541,8 @@ def _take_any(self): class InteriorTargetsAllocator(LocalThreatAllocator): - _max_alloc_factor = 3 - _min_alloc_factor = 1.5 + _max_alloc_factor = 2.5 + _min_alloc_factor = 1.3 def _calculate_threat(self): return self.threat_bias + self.safety_factor * self._local_threat() @@ -495,7 +556,7 @@ def _take_any(self): class ExplorationTargetAllocator(LocalThreatAllocator): _potential_threat_factor = 0.25 - _max_alloc_factor = 2 * 1.4 + _max_alloc_factor = 2.0 _allocation_group = 'exploreTargets' def _calculate_threat(self): @@ -523,40 +584,40 @@ class ReleaseMilitaryException(Exception): # TODO: May want to move these functions into AIstate class def get_system_local_threat(sys_id): - return foAI.foAIstate.systemStatus.get(sys_id, {}).get('totalThreat', 0.) + return get_aistate().systemStatus.get(sys_id, {}).get('totalThreat', 0.) def get_system_jump2_threat(sys_id): - return foAI.foAIstate.systemStatus.get(sys_id, {}).get('jump2_threat', 0.) + return get_aistate().systemStatus.get(sys_id, {}).get('jump2_threat', 0.) def get_system_neighbor_support(sys_id): - return foAI.foAIstate.systemStatus.get(sys_id, {}).get('my_neighbor_rating', 0.) + return get_aistate().systemStatus.get(sys_id, {}).get('my_neighbor_rating', 0.) def get_system_neighbor_threat(sys_id): - return foAI.foAIstate.systemStatus.get(sys_id, {}).get('neighborThreat', 0.) + return get_aistate().systemStatus.get(sys_id, {}).get('neighborThreat', 0.) def get_system_regional_threat(sys_id): - return foAI.foAIstate.systemStatus.get(sys_id, {}).get('regional_threat', 0.) + return get_aistate().systemStatus.get(sys_id, {}).get('regional_threat', 0.) def get_system_planetary_threat(sys_id): - return foAI.foAIstate.systemStatus.get(sys_id, {}).get('planetThreat', 0.) + return get_aistate().systemStatus.get(sys_id, {}).get('planetThreat', 0.) def enemy_rating(): """:rtype: float""" - return foAI.foAIstate.empire_standard_enemy_rating + return get_aistate().empire_standard_enemy_rating def get_my_defense_rating_in_system(sys_id): - return foAI.foAIstate.systemStatus.get(sys_id, {}).get('mydefenses', {}).get('overall') + return get_aistate().systemStatus.get(sys_id, {}).get('mydefenses', {}).get('overall') def enemies_nearly_supplying_system(sys_id): - return foAI.foAIstate.systemStatus.get(sys_id, {}).get('enemies_nearly_supplied', []) + return get_aistate().systemStatus.get(sys_id, {}).get('enemies_nearly_supplied', []) def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): @@ -570,13 +631,21 @@ def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): all_military_fleet_ids = (mil_fleets_ids if mil_fleets_ids is not None else FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)) - if try_reset and (fo.currentTurn() + empire_id) % 30 == 0 and thisround == "Main": + # Todo: This block had been originally added to address situations where fleet missions were not properly + # terminating, leaving fleets stuck in stale deployments. Assess if this block is still needed at all; delete + # if not, otherwise restructure the following code so that in event a reset is occurring greater priority is given + # to providing military support to locations where a necessary Secure mission might have just been released (i.e., + # at invasion and colony/outpost targets where the troopships and colony ships are on their way), or else allow + # only a partial reset which does not reset Secure missions. + enable_periodic_mission_reset = False + if enable_periodic_mission_reset and try_reset and (fo.currentTurn() + empire_id) % 30 == 0 and thisround == "Main": + debug("Resetting all Military missions as part of an automatic periodic reset to clear stale missions.") try_again(all_military_fleet_ids, try_reset=False, thisround=thisround + " Reset") return mil_fleets_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) mil_needing_repair_ids, mil_fleets_ids = avail_mil_needing_repair(mil_fleets_ids, split_ships=True) - avail_mil_rating = sum(map(CombatRatingsAI.get_fleet_rating, mil_fleets_ids)) + avail_mil_rating = combine_ratings_list(CombatRatingsAI.get_fleet_rating(x) for x in mil_fleets_ids) if not mil_fleets_ids: if "Main" in thisround: @@ -586,14 +655,15 @@ def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): # for each system, get total rating of fleets assigned to it already_assigned_rating = {} already_assigned_rating_vs_planets = {} - systems_status = foAI.foAIstate.systemStatus + aistate = get_aistate() + systems_status = aistate.systemStatus enemy_sup_factor = {} # enemy supply for sys_id in universe.systemIDs: already_assigned_rating[sys_id] = 0 already_assigned_rating_vs_planets[sys_id] = 0 enemy_sup_factor[sys_id] = min(2, len(systems_status.get(sys_id, {}).get('enemies_nearly_supplied', []))) for fleet_id in [fid for fid in all_military_fleet_ids if fid not in mil_fleets_ids]: - ai_fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) + ai_fleet_mission = aistate.get_fleet_mission(fleet_id) if not ai_fleet_mission.target: # shouldn't really be possible continue last_sys = ai_fleet_mission.target.get_system().id # will count this fleet as assigned to last system in target list # TODO last_sys or target sys? @@ -607,8 +677,8 @@ def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): my_defense_rating = systems_status.get(sys_id, {}).get('mydefenses', {}).get('overall', 0) already_assigned_rating[sys_id] = CombatRatingsAI.combine_ratings(my_defense_rating, already_assigned_rating[sys_id]) if _verbose_mil_reporting and already_assigned_rating[sys_id]: - print "\t System %s already assigned rating %.1f" % ( - universe.getSystem(sys_id), already_assigned_rating[sys_id]) + debug("\t System %s already assigned rating %.1f" % ( + universe.getSystem(sys_id), already_assigned_rating[sys_id])) # get systems to defend capital_id = PlanetUtilsAI.get_capital() @@ -623,31 +693,35 @@ def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): capital_sys_id = None # unless we can find one to use system_dict = {} for fleet_id in all_military_fleet_ids: - status = foAI.foAIstate.fleetStatus.get(fleet_id, None) + status = aistate.fleetStatus.get(fleet_id, None) if status is not None: - sys_id = status['sysID'] - if not list(universe.getSystem(sys_id).planetIDs): + system_id = status['sysID'] + if not list(universe.getSystem(system_id).planetIDs): continue - system_dict[sys_id] = system_dict.get(sys_id, 0) + status.get('rating', 0) + system_dict[system_id] = system_dict.get(system_id, 0) + status.get('rating', 0) ranked_systems = sorted([(val, sys_id) for sys_id, val in system_dict.items()]) if ranked_systems: capital_sys_id = ranked_systems[-1][-1] else: try: - capital_sys_id = foAI.foAIstate.fleetStatus.items()[0][1]['sysID'] - except: + capital_sys_id = next(iter(aistate.fleetStatus.items()))[1]['sysID'] + except: # noqa: E722 pass num_targets = max(10, PriorityAI.allotted_outpost_targets) - top_target_planets = ([pid for pid, pscore, trp in AIstate.invasionTargets[:PriorityAI.allottedInvasionTargets] if pscore > 20] + - [pid for pid, (pscore, spec) in foAI.foAIstate.colonisableOutpostIDs.items()[:num_targets] if pscore > 20] + - [pid for pid, (pscore, spec) in foAI.foAIstate.colonisablePlanetIDs.items()[:num_targets] if pscore > 20]) - top_target_planets.extend(foAI.foAIstate.qualifyingTroopBaseTargets.keys()) + top_target_planets = ([pid for pid, pscore, trp in AIstate.invasionTargets[:PriorityAI.allotted_invasion_targets()] + if pscore > InvasionAI.MIN_INVASION_SCORE] + + [pid for pid, (pscore, spec) in list(aistate.colonisableOutpostIDs.items())[:num_targets] + if pscore > InvasionAI.MIN_INVASION_SCORE] + + [pid for pid, (pscore, spec) in list(aistate.colonisablePlanetIDs.items())[:num_targets] + if pscore > InvasionAI.MIN_INVASION_SCORE]) + top_target_planets.extend(aistate.qualifyingTroopBaseTargets.keys()) + base_col_target_systems = PlanetUtilsAI.get_systems(top_target_planets) top_target_systems = [] for sys_id in AIstate.invasionTargetedSystemIDs + base_col_target_systems: if sys_id not in top_target_systems: - if foAI.foAIstate.systemStatus[sys_id]['totalThreat'] > get_tot_mil_rating(): + if aistate.systemStatus[sys_id]['totalThreat'] > get_tot_mil_rating(): continue top_target_systems.append(sys_id) # doing this rather than set, to preserve order @@ -683,8 +757,7 @@ def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): # TODO blockade enemy systems # interior systems - targetable_ids = set(ColonisationAI.systems_by_supply_tier.get(0, []) + - ColonisationAI.systems_by_supply_tier.get(1, [])) + targetable_ids = set(state.get_systems_by_supply_tier(0)) current_mil_systems = [sid for sid, _, _, _, _ in allocation_helper.allocations] interior_targets1 = targetable_ids.difference(current_mil_systems) interior_targets = [sid for sid in interior_targets1 if ( @@ -695,9 +768,10 @@ def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): # TODO Exploration targets # border protections - visible_system_ids = foAI.foAIstate.visInteriorSystemIDs | foAI.foAIstate.visBorderSystemIDs - accessible_system_ids = [sys_id for sys_id in visible_system_ids if + visible_system_ids = aistate.visInteriorSystemIDs | aistate.visBorderSystemIDs + accessible_system_ids = ([sys_id for sys_id in visible_system_ids if universe.systemsConnected(sys_id, home_system_id, empire_id)] + if home_system_id != INVALID_ID else []) current_mil_systems = [sid for sid, alloc, rvp, take_any, _ in allocation_helper.allocations if alloc > 0] border_targets1 = [sid for sid in accessible_system_ids if sid not in current_mil_systems] border_targets = [sid for sid in border_targets1 if ( @@ -718,7 +792,7 @@ def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): break this_alloc = min(remaining_mil_rating, max_alloc) new_allocations.append((sid, this_alloc, alloc, rvp, take_any)) - remaining_mil_rating -= this_alloc + remaining_mil_rating = rating_difference(remaining_mil_rating, this_alloc) base_allocs = set() # for lower priority categories, first assign base_alloc around to all, then top up as available @@ -726,8 +800,9 @@ def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if remaining_mil_rating <= 0: break + alloc = min(remaining_mil_rating, alloc) base_allocs.add(sid) - remaining_mil_rating -= alloc + remaining_mil_rating = rating_difference(remaining_mil_rating, alloc) for cat in ['otherTargets', 'accessibleTargets', 'exploreTargets']: for sid, alloc, rvp, take_any, max_alloc in allocation_helper.allocation_by_groups.get(cat, []): if sid not in base_allocs: @@ -735,23 +810,17 @@ def get_military_fleets(mil_fleets_ids=None, try_reset=True, thisround="Main"): if remaining_mil_rating <= 0: new_allocations.append((sid, alloc, alloc, rvp, take_any)) else: - new_rating = min(remaining_mil_rating + alloc, max_alloc) + local_max_avail = combine_ratings(remaining_mil_rating, alloc) + new_rating = min(local_max_avail, max_alloc) new_allocations.append((sid, new_rating, alloc, rvp, take_any)) - remaining_mil_rating -= (new_rating - alloc) + remaining_mil_rating = rating_difference(local_max_avail, new_rating) if "Main" in thisround: _military_allocations = new_allocations if _verbose_mil_reporting or "Main" in thisround: - print "------------------------------\nFinal %s Round Military Allocations: %s \n-----------------------" % (thisround, dict([(sid, alloc) for sid, alloc, _, _, _ in new_allocations])) - print "(Apparently) remaining military rating: %.1f" % remaining_mil_rating + debug("------------------------------\nFinal %s Round Military Allocations: %s \n-----------------------" % (thisround, dict([(sid, alloc) for sid, alloc, _, _, _ in new_allocations]))) + debug("(Apparently) remaining military rating: %.1f" % remaining_mil_rating) - # export military systems for other AI modules - if "Main" in thisround: - AIstate.militarySystemIDs = list(set([sid for sid, _, _, _, _ in new_allocations]).union( - [sid for sid in allocation_helper.already_assigned_rating - if allocation_helper.already_assigned_rating[sid] > 0])) - else: - AIstate.militarySystemIDs = list(set([sid for sid, _, _, _, _ in new_allocations]).union(AIstate.militarySystemIDs)) return new_allocations @@ -763,8 +832,9 @@ def assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, allocations = [] doing_main = (use_fleet_id_list is None) + aistate = get_aistate() if doing_main: - foAI.foAIstate.misc['ReassignedFleetMissions'] = [] + aistate.misc['ReassignedFleetMissions'] = [] base_defense_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.ORBITAL_DEFENSE) unassigned_base_defense_ids = FleetUtilsAI.extract_fleet_ids_without_mission_types(base_defense_ids) for fleet_id in unassigned_base_defense_ids: @@ -772,8 +842,8 @@ def assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, if not fleet: continue sys_id = fleet.systemID - target = universe_object.System(sys_id) - fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) + target = TargetSystem(sys_id) + fleet_mission = aistate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() mission_type = MissionType.ORBITAL_DEFENSE @@ -786,9 +856,9 @@ def assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids) these_allocations = _military_allocations - print "==================================================" - print "Assigning military fleets" - print "---------------------------------" + debug("==================================================") + debug("Assigning military fleets") + debug("---------------------------------") else: avail_mil_fleet_ids = list(use_fleet_id_list) mil_needing_repair_ids, avail_mil_fleet_ids = avail_mil_needing_repair(avail_mil_fleet_ids) @@ -802,40 +872,55 @@ def assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, for sys_id, alloc, minalloc, rvp, takeAny in these_allocations: if not doing_main and not avail_mil_fleet_ids: break + debug("Allocating for: %s", TargetSystem(sys_id)) found_fleets = [] found_stats = {} - these_fleets = FleetUtilsAI.get_fleets_for_mission({'rating': alloc, 'ratingVsPlanets': rvp}, - {'rating': minalloc, 'ratingVsPlanets': rvp}, found_stats, - starting_system=sys_id, fleet_pool_set=avail_mil_fleet_ids, - fleet_list=found_fleets) + ensure_return = sys_id not in set(AIstate.colonyTargetedSystemIDs + + AIstate.outpostTargetedSystemIDs + + AIstate.invasionTargetedSystemIDs) + these_fleets = FleetUtilsAI.get_fleets_for_mission( + target_stats={'rating': alloc, 'ratingVsPlanets': rvp, 'target_system': TargetSystem(sys_id)}, + min_stats={'rating': minalloc, 'ratingVsPlanets': rvp, 'target_system': TargetSystem(sys_id)}, + cur_stats=found_stats, starting_system=sys_id, fleet_pool_set=avail_mil_fleet_ids, + fleet_list=found_fleets, ensure_return=ensure_return) if not these_fleets: + debug("Could not allocate any fleets.") if not found_fleets or not (FleetUtilsAI.stats_meet_reqs(found_stats, {'rating': minalloc}) or takeAny): if doing_main: if _verbose_mil_reporting: - print "NO available/suitable military allocation for system %d ( %s ) -- requested allocation %8d, found available rating %8d in fleets %s" % (sys_id, universe.getSystem(sys_id).name, minalloc, found_stats.get('rating', 0), found_fleets) + debug("NO available/suitable military allocation for system %d ( %s ) " + "-- requested allocation %8d, found available rating %8d in fleets %s" + % (sys_id, universe.getSystem(sys_id).name, minalloc, + found_stats.get('rating', 0), found_fleets)) avail_mil_fleet_ids.update(found_fleets) continue else: these_fleets = found_fleets - elif doing_main and _verbose_mil_reporting: - print "FULL+ military allocation for system %d ( %s ) -- requested allocation %8d, got %8d with fleets %s" % (sys_id, universe.getSystem(sys_id).name, alloc, found_stats.get('rating', 0), these_fleets) - target = universe_object.System(sys_id) + else: + debug("Assigning fleets %s to target %s", these_fleets, TargetSystem(sys_id)) + if doing_main and _verbose_mil_reporting: + debug("FULL+ military allocation for system %d ( %s )" + " -- requested allocation %8d, got %8d with fleets %s" + % (sys_id, universe.getSystem(sys_id).name, alloc, found_stats.get('rating', 0), these_fleets)) + target = TargetSystem(sys_id) for fleet_id in these_fleets: fo.issueAggressionOrder(fleet_id, True) - fleet_mission = foAI.foAIstate.get_fleet_mission(fleet_id) + fleet_mission = aistate.get_fleet_mission(fleet_id) fleet_mission.clear_fleet_orders() fleet_mission.clear_target() - if sys_id in list(set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs + AIstate.blockadeTargetedSystemIDs)): + if sys_id in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs + AIstate.invasionTargetedSystemIDs): mission_type = MissionType.SECURE + elif state.get_empire_planets_by_system(sys_id): + mission_type = MissionType.PROTECT_REGION else: mission_type = MissionType.MILITARY fleet_mission.set_target(mission_type, target) fleet_mission.generate_fleet_orders() if not doing_main: - foAI.foAIstate.misc.setdefault('ReassignedFleetMissions', []).append(fleet_mission) + aistate.misc.setdefault('ReassignedFleetMissions', []).append(fleet_mission) if doing_main: - print "---------------------------------" + debug("---------------------------------") last_round = 3 last_round_name = "LastRound" if round <= last_round: @@ -846,28 +931,81 @@ def assign_military_fleets_to_systems(use_fleet_id_list=None, allocations=None, round += 1 thisround = "Extras Remaining Round %d" % round if round < last_round else last_round_name if avail_mil_fleet_ids: - print "Still have available military fleets: %s" % avail_mil_fleet_ids + debug("Round %s - still have available military fleets: %s", thisround, avail_mil_fleet_ids) allocations = get_military_fleets(mil_fleets_ids=avail_mil_fleet_ids, try_reset=False, thisround=thisround) if allocations: assign_military_fleets_to_systems(use_fleet_id_list=avail_mil_fleet_ids, allocations=allocations, round=round) + else: + # assign remaining fleets to nearest systems to protect. + all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) + avail_mil_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_military_fleet_ids)) + + def system_score(_fid, _sys_id): + """Helper function to rank systems by priority""" + jump_distance = universe.jumpDistance(_fid, _sys_id) + if get_system_local_threat(_sys_id): + weight = 10 + elif get_system_neighbor_threat(_sys_id): + weight = 3 + elif get_system_jump2_threat(_sys_id): + weight = 1 + else: + weight = 1 / max(.5, float(state.get_distance_to_enemy_supply(_sys_id)))**1.25 + return float(weight) / (jump_distance+1) + + for fid in avail_mil_fleet_ids: + fleet = universe.getFleet(fid) + FleetUtilsAI.get_fleet_system(fleet) + systems = state.get_empire_planets_by_system().keys() + if not systems: + continue + sys_id = max(systems, key=lambda x: system_score(fid, x)) + + debug("Assigning leftover %s to system %d " + "- nothing better to do.", fleet, sys_id) + fleet_mission = aistate.get_fleet_mission(fid) + fleet_mission.clear_fleet_orders() + target_system = TargetSystem(sys_id) + fleet_mission.set_target(MissionType.PROTECT_REGION, target_system) + fleet_mission.generate_fleet_orders() -@cache_by_turn + +@cache_by_turn_persistent def get_tot_mil_rating(): + """ + Give an assessment of total military rating considering all fleets as if distributed to separate systems. + + :return: a military rating value + :rtype: float + """ return sum(CombatRatingsAI.get_fleet_rating(fleet_id) for fleet_id in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)) -@cache_by_turn +@cache_by_turn_persistent +def get_concentrated_tot_mil_rating(): + """ + Give an assessment of total military rating as if all fleets were merged into a single mega-fleet. + + :return: a military rating value + :rtype: float + """ + return CombatRatingsAI.combine_ratings_list([CombatRatingsAI.get_fleet_rating(fleet_id) for fleet_id in + FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)]) + + +@cache_by_turn_persistent def get_num_military_ships(): - return sum(foAI.foAIstate.fleetStatus.get(fid, {}).get('nships', 0) + fleet_status = get_aistate().fleetStatus + return sum(fleet_status.get(fid, {}).get('nships', 0) for fid in FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY)) def get_military_fleets_with_target_system(target_system_id): military_mission_types = [MissionType.MILITARY, MissionType.SECURE] found_fleets = [] - for fleet_mission in foAI.foAIstate.get_fleet_missions_with_any_mission_types(military_mission_types): + for fleet_mission in get_aistate().get_fleet_missions_with_any_mission_types(military_mission_types): if fleet_mission.target and fleet_mission.target.id == target_system_id: found_fleets.append(fleet_mission.fleet.id) return found_fleets diff --git a/default/python/AI/MoveUtilsAI.py b/default/python/AI/MoveUtilsAI.py index 78cd8ae6d98..3605546d5af 100644 --- a/default/python/AI/MoveUtilsAI.py +++ b/default/python/AI/MoveUtilsAI.py @@ -1,131 +1,78 @@ -import sys +from logging import warning, debug import freeOrionAIInterface as fo # pylint: disable=import-error -import FreeOrionAI as foAI +from aistate_interface import get_aistate import AIstate -import universe_object import fleet_orders -import ColonisationAI -import FleetUtilsAI import PlanetUtilsAI -from freeorion_tools import ppstring +import pathfinding from AIDependencies import INVALID_ID, DRYDOCK_HAPPINESS_THRESHOLD +from target import TargetSystem +from turn_state import state -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) def create_move_orders_to_system(fleet, target): """ Create a list of move orders from the fleet's current system to the target system. :param fleet: Fleet to be moved - :type fleet: universe_object.Fleet + :type fleet: target.TargetFleet :param target: target system - :type target: universe_object.System + :type target: target.TargetSystem :return: list of move orders :rtype: list[fleet_orders.OrdersMove] """ # TODO: use Graph Theory to construct move orders # TODO: add priority starting_system = fleet.get_system() # current fleet location or current target system if on starlane + if starting_system == target: + # nothing to do here + return [] # if the mission does not end at the targeted system, make sure we can actually return to supply after moving. ensure_return = target.id not in set(AIstate.colonyTargetedSystemIDs + AIstate.outpostTargetedSystemIDs - + AIstate.invasionTargetedSystemIDs + AIstate.blockadeTargetedSystemIDs) + + AIstate.invasionTargetedSystemIDs) system_targets = can_travel_to_system(fleet.id, starting_system, target, ensure_return=ensure_return) result = [fleet_orders.OrderMove(fleet, system) for system in system_targets] if not result and starting_system.id != target.id: - print >> sys.stderr, "fleet %s can't travel to system %s" % (fleet.id, target) + warning("fleet %s can't travel to system %s" % (fleet.id, target)) return result -def can_travel_to_system(fleet_id, from_system_target, to_system_target, ensure_return=False): +def can_travel_to_system(fleet_id, start, target, ensure_return=False): """ Return list systems to be visited. :param fleet_id: :type fleet_id: int - :param from_system_target: - :type from_system_target: universe_object.System - :param to_system_target: - :type to_system_target: universe_object.System + :param start: + :type start: target.TargetSystem + :param target: + :type target: target.TargetSystem :param ensure_return: :type ensure_return: bool :return: :rtype: list """ - empire = fo.getEmpire() - empire_id = empire.empireID - fleet_supplyable_system_ids = set(empire.fleetSupplyableSystemIDs) - # get current fuel and max fuel - universe = fo.getUniverse() - fuel = int(FleetUtilsAI.get_fuel(fleet_id)) # round down to get actually number of jumps - if fuel < 1.0 or from_system_target.id == to_system_target.id: - return [] - if True: # TODO: sort out if shortestPath leaves off some intermediate destinations - path_func = universe.leastJumpsPath - else: - path_func = universe.shortestPath - start_sys_id = from_system_target.id - target_sys_id = to_system_target.id - if start_sys_id != INVALID_ID and target_sys_id != INVALID_ID: - short_path = list(path_func(start_sys_id, target_sys_id, empire_id)) - else: - short_path = [] - legs = zip(short_path[:-1], short_path[1:]) - # suppliedStops = [ sid for sid in short_path if sid in fleet_supplyable_system_ids ] - # unsupplied_stops = [sid for sid in short_path if sid not in suppliedStops ] - unsupplied_stops = [sys_b for sys_a, sys_b in legs if ((sys_a not in fleet_supplyable_system_ids) and (sys_b not in fleet_supplyable_system_ids))] - # print "getting path from %s to %s "%(ppstring(PlanetUtilsAI.sys_name_ids([ start_sys_id ])), ppstring(PlanetUtilsAI.sys_name_ids([ target_sys_id ])) ), - # print " ::: found initial path %s having suppliedStops %s and unsupplied_stops %s ; tot fuel available is %.1f"%( ppstring(PlanetUtilsAI.sys_name_ids( short_path[:])), suppliedStops, unsupplied_stops, fuel) - if False: - if target_sys_id in fleet_supplyable_system_ids: - print "target has FleetSupply" - elif target_sys_id in ColonisationAI.annexable_ring1: - print "target in Ring 1" - elif target_sys_id in ColonisationAI.annexable_ring2 and foAI.foAIstate.character.may_travel_beyond_supply(2): - print "target in Ring 2, has enough aggression" - elif target_sys_id in ColonisationAI.annexable_ring3 and foAI.foAIstate.character.may_travel_beyond_supply(3): - print "target in Ring 2, has enough aggression" - if (not unsupplied_stops or not ensure_return or - target_sys_id in fleet_supplyable_system_ids and len(unsupplied_stops) <= fuel - or target_sys_id in ColonisationAI.annexable_ring1 and len(unsupplied_stops) < fuel - or target_sys_id in ColonisationAI.annexable_ring2 and foAI.foAIstate.character.may_travel_beyond_supply(2) and len(unsupplied_stops) < fuel - 1 - or target_sys_id in ColonisationAI.annexable_ring3 and foAI.foAIstate.character.may_travel_beyond_supply(3) and len(unsupplied_stops) < fuel - 2): - return [universe_object.System(sid) for sid in short_path] - else: - # print " getting path from 'can_travel_to_system_and_return_to_resupply' ", - return can_travel_to_system_and_return_to_resupply(fleet_id, from_system_target, to_system_target) + if start == target: + return [TargetSystem(start.id)] + debug("Requesting path for fleet %s from %s to %s" % (fo.getUniverse().getFleet(fleet_id), start, target)) + target_distance_from_supply = -min(state.get_system_supply(target.id), 0) -def can_travel_to_system_and_return_to_resupply(fleet_id, from_system_target, to_system_target): - """ - Filter systems where fleet can travel from starting system. # TODO rename function + # low-aggression AIs may not travel far from supply + if not get_aistate().character.may_travel_beyond_supply(target_distance_from_supply): + debug("May not move %d out of supply" % target_distance_from_supply) + return [] - :param fleet_id: - :type fleet_id: int - :param from_system_target: - :type from_system_target: universe_object.System - :param to_system_target: - :type to_system_target: universe_object.System - :return: - :rtype: list - """ - system_targets = [] - if not from_system_target.id == to_system_target.id: - fleet_supplyable_system_ids = fo.getEmpire().fleetSupplyableSystemIDs - fuel = int(FleetUtilsAI.get_fuel(fleet_id)) # int to get actual number of jumps - max_fuel = int(FleetUtilsAI.get_max_fuel(fleet_id)) - # try to find path without going resupply first - supply_system_target = get_nearest_supplied_system(to_system_target.id) - system_targets = __find_path_with_fuel_to_system_with_possible_return(from_system_target, to_system_target, system_targets, fleet_supplyable_system_ids, max_fuel, fuel, supply_system_target) - # resupply in system first is required to find path - if from_system_target.id not in fleet_supplyable_system_ids and not system_targets: - # add supply system to visit - from_system_target = get_nearest_supplied_system(from_system_target.id) - system_targets.append(from_system_target) - # find path from supplied system to wanted system - system_targets = __find_path_with_fuel_to_system_with_possible_return(from_system_target, to_system_target, system_targets, fleet_supplyable_system_ids, max_fuel, max_fuel, supply_system_target) - return system_targets + min_fuel_at_target = target_distance_from_supply if ensure_return else 0 + path_info = pathfinding.find_path_with_resupply(start.id, target.id, fleet_id, + minimum_fuel_at_target=min_fuel_at_target) + if path_info is None: + debug("Found no valid path.") + return [] + + debug("Found valid path: %s" % str(path_info)) + return [TargetSystem(sys_id) for sys_id in path_info.path] def get_nearest_supplied_system(start_system_id): @@ -135,7 +82,7 @@ def get_nearest_supplied_system(start_system_id): universe = fo.getUniverse() if start_system_id in fleet_supplyable_system_ids: - return universe_object.System(start_system_id) + return TargetSystem(start_system_id) else: min_jumps = 9999 # infinity supply_system_id = INVALID_ID @@ -145,162 +92,90 @@ def get_nearest_supplied_system(start_system_id): if least_jumps_len < min_jumps: min_jumps = least_jumps_len supply_system_id = system_id - return universe_object.System(supply_system_id) + return TargetSystem(supply_system_id) def get_best_drydock_system_id(start_system_id, fleet_id): """ Get system_id of best drydock capable of repair, where best is nearest drydock - that has a current and target happiness greater than the HAPPINESS _THRESHOLD + that has a current and target happiness greater than the HAPPINESS_THRESHOLD with a path that is not blockaded or that the fleet can fight through to with acceptable losses. :param start_system_id: current location of fleet - used to find closest target :type start_system_id: int - :param fleet_id: fleet that need path to drydock + :param fleet_id: fleet that needs path to drydock :type: int - :return: closest system_id capable of repairing + :return: most suitable system id where the fleet should be repaired. :rtype: int """ if start_system_id == INVALID_ID: - print >> sys.stderr, "get_best_drydock_system_id passed bad system id." + warning("get_best_drydock_system_id passed bad system id.") return None if fleet_id == INVALID_ID: - print >> sys.stderr, "get_best_drydock_system_id passed bad fleet id." + warning("get_best_drydock_system_id passed bad fleet id.") return None universe = fo.getUniverse() - start_sys = universe.getSystem(start_system_id) + start_system = TargetSystem(start_system_id) drydock_system_ids = set() - for sys_id, pids in ColonisationAI.empire_dry_docks.iteritems(): + for sys_id, pids in state.get_empire_drydocks().items(): if sys_id == INVALID_ID: - print >> sys.stderr, "get_best_drydock_system_id passed bad dry dock sys_id." + warning("get_best_drydock_system_id passed bad drydock sys_id.") continue for pid in pids: planet = universe.getPlanet(pid) - if (planet - and planet.currentMeterValue(fo.meterType.happiness) >= DRYDOCK_HAPPINESS_THRESHOLD - and planet.currentMeterValue(fo.meterType.targetHappiness) >= DRYDOCK_HAPPINESS_THRESHOLD): + if (planet and + planet.currentMeterValue(fo.meterType.happiness) >= DRYDOCK_HAPPINESS_THRESHOLD and + planet.currentMeterValue(fo.meterType.targetHappiness) >= DRYDOCK_HAPPINESS_THRESHOLD): drydock_system_ids.add(sys_id) break sys_distances = sorted([(universe.jumpDistance(start_system_id, sys_id), sys_id) for sys_id in drydock_system_ids]) - fleet_rating = foAI.foAIstate.get_rating(fleet_id) - for dock_sys in [universe.getSystem(sys_id) for (_, sys_id) in sys_distances]: - path = can_travel_to_system(fleet_id, start_sys, dock_sys) + aistate = get_aistate() + fleet_rating = aistate.get_rating(fleet_id) + for _, dock_sys_id in sys_distances: + dock_system = TargetSystem(dock_sys_id) + path = can_travel_to_system(fleet_id, start_system, dock_system) - path_rating = sum([foAI.foAIstate.systemStatus[path_sys.id]['totalThreat'] + path_rating = sum([aistate.systemStatus[path_sys.id]['totalThreat'] for path_sys in path]) SAFETY_MARGIN = 10 if SAFETY_MARGIN * path_rating <= fleet_rating: - print ("Drydock recommendation %s(%d) from %s(%d) for fleet %s(%d) with fleet rating %2f and path rating %2f." - % (dock_sys.name, dock_sys.id, - start_sys.name, start_sys.id, - universe.getFleet(fleet_id).name, fleet_id, - fleet_rating, path_rating)) - return dock_sys.id - - print ("No safe drydock recommendation from %s(%d) for fleet %s(%d) with fleet rating %2f." - % (start_sys.name, start_sys.id, - universe.getFleet(fleet_id).name, fleet_id, - fleet_rating)) + debug("Drydock recommendation %s from %s for fleet %s with fleet rating %.1f and path rating %.1f." + % (dock_system, start_system, universe.getFleet(fleet_id), fleet_rating, path_rating)) + return dock_system.id + + debug("No safe drydock recommendation from %s for fleet %s with fleet rating %.1f." + % (start_system, universe.getFleet(fleet_id), fleet_rating)) return None def get_safe_path_leg_to_dest(fleet_id, start_id, dest_id): - start_targ = universe_object.System(start_id) - dest_targ = universe_object.System(dest_id) + start_targ = TargetSystem(start_id) + dest_targ = TargetSystem(dest_id) # TODO actually get a safe path this_path = can_travel_to_system(fleet_id, start_targ, dest_targ, ensure_return=False) path_ids = [targ.id for targ in this_path if targ.id != start_id] + [start_id] - start_info = PlanetUtilsAI.sys_name_ids([start_id]) - dest_info = PlanetUtilsAI.sys_name_ids([dest_id]) - path_info = [PlanetUtilsAI.sys_name_ids([sys_id]) for sys_id in path_ids] - print "Fleet %d requested safe path leg from %s to %s, found path %s" % (fleet_id, ppstring(start_info), ppstring(dest_info), ppstring(path_info)) + universe = fo.getUniverse() + debug("Fleet %d requested safe path leg from %s to %s, found path %s" % ( + fleet_id, universe.getSystem(start_id), universe.getSystem(dest_id), PlanetUtilsAI.sys_name_ids(path_ids))) return path_ids[0] -def __find_path_with_fuel_to_system_with_possible_return(from_system_target, to_system_target, result_system_targets, fleet_supplyable_system_ids, max_fuel, fuel, supply_system_target): - """ - Return systems required to visit with fuel to nearest supplied system. - - :param from_system_target: - :type from_system_target: universe_object.System - :param to_system_target: - :type to_system_target: universe_object.System - :param result_system_targets: - :type result_system_targets: list - :param fleet_supplyable_system_ids: - :type fleet_supplyable_system_ids: list - :param max_fuel: - :type max_fuel: int - :param fuel: - :type fuel: int - :param supply_system_target: - :type supply_system_target: universe_object.System - :return: - :rtype list: - """ - empire_id = fo.empireID() - result = True - # try to find if there is possible path to wanted system from system - new_targets = result_system_targets[:] - if from_system_target and to_system_target and supply_system_target: - universe = fo.getUniverse() - if from_system_target.id != INVALID_ID and to_system_target.id != INVALID_ID: - least_jumps_path = universe.leastJumpsPath(from_system_target.id, to_system_target.id, empire_id) - else: - least_jumps_path = [] - result = False - from_system_id = from_system_target.id - for system_id in least_jumps_path: - if from_system_id != system_id: - if from_system_id in fleet_supplyable_system_ids: - # from supplied system fleet can travel without fuel consumption and also in this system refuels - fuel = max_fuel - else: - fuel -= 1 - - # leastJumpPath can differ from shortestPath - # TODO: use Graph Theory to optimize - if True or (system_id != to_system_target.id and system_id in fleet_supplyable_system_ids): # TODO: restructure - new_targets.append(universe_object.System(system_id)) - if fuel < 0: - result = False - from_system_id = system_id - else: - result = False - - # if there is path to wanted system, then also if there is path back to supplyable system - if result: - # jump from A to B means least_jumps_path=[A,B], but min_jumps=1 - min_jumps = len(universe.leastJumpsPath(to_system_target.id, supply_system_target.id, empire_id)) - 1 - - if min_jumps > fuel: - # print "fleetID:" + str(fleetID) + " fuel:" + str(fuel) + " required: " + str(min_jumps) - result = False - # else: - # resultSystemAITargets.append(toSystemAITarget) - - if not result: - return [] - return new_targets - - def get_resupply_fleet_order(fleet_target, current_system_target): - """ - Return fleet_orders.OrderResupply to nearest supplied system. + """Return fleet_orders.OrderResupply to nearest supplied system. - :param fleet_target: fleet that need to be resupplied - :type fleet_target: universe_object.Fleet + :param fleet_target: fleet that needs to be resupplied + :type fleet_target: target.TargetFleet # TODO check if we can remove this id, because fleet already have it. :param current_system_target: current system of fleet - :type current_system_target: universe_object.System + :type current_system_target: target.TargetSystem :return: order to resupply :rtype fleet_orders.OrderResupply """ @@ -310,23 +185,22 @@ def get_resupply_fleet_order(fleet_target, current_system_target): return fleet_orders.OrderResupply(fleet_target, supplied_system_target) -def get_repair_fleet_order(fleet_target, current_sys_id): - """ - Return fleet_orders.OrderRepair for fleet to proceed system with drydock. +def get_repair_fleet_order(fleet, current_system_id): + """Return fleet_orders.OrderRepair for fleet to proceed to system with drydock. - :param fleet_target: fleet that need to be repaired - :type fleet_target: universe_object.Fleet + :param fleet: fleet that need to be repaired + :type fleet: target.TargetFleet # TODO check if we can remove this id, because fleet already have it. - :param current_sys_id: current system id - :type current_sys_id: int + :param current_system_id: current location of the fleet, next system if currently on starlane. + :type current_system_id: int :return: order to repair :rtype fleet_orders.OrderRepair """ # TODO Cover new mechanics where happiness increases repair rate - don't always use nearest system! - # find nearest supplied system - drydock_sys_id = get_best_drydock_system_id(current_sys_id, fleet_target.id) + # find nearest drydock system + drydock_sys_id = get_best_drydock_system_id(current_system_id, fleet.id) if drydock_sys_id is None: return None - print "ordering fleet %d to %s for repair" % (fleet_target.id, ppstring(PlanetUtilsAI.sys_name_ids([drydock_sys_id]))) - # create resupply AIFleetOrder - return fleet_orders.OrderRepair(fleet_target, universe_object.System(drydock_sys_id)) + + debug("Ordering fleet %s to %s for repair" % (fleet, fo.getUniverse().getSystem(drydock_sys_id))) + return fleet_orders.OrderRepair(fleet, TargetSystem(drydock_sys_id)) diff --git a/default/python/AI/PlanetUtilsAI.py b/default/python/AI/PlanetUtilsAI.py index 7906b24de8f..eda9db15ef3 100644 --- a/default/python/AI/PlanetUtilsAI.py +++ b/default/python/AI/PlanetUtilsAI.py @@ -1,12 +1,10 @@ -import sys +from logging import error, debug import freeOrionAIInterface as fo # pylint: disable=import-error import ColonisationAI from AIDependencies import INVALID_ID - -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) +from freeorion_tools import ppstring def safe_name(univ_object): @@ -15,22 +13,32 @@ def safe_name(univ_object): def sys_name_ids(sys_ids): """ - Get list of text representing pairs system name and system id. - :param sys_ids: list if system ids - :return: list of string : + Get a string representation of a list with system_ids. + + The returned string is of the form "[S_id, ...]" + + :param sys_ids: list of system ids + :rtype: string + :return: string representation of the systems in the list """ universe = fo.getUniverse() - return [str(universe.getSystem(sys_id)) for sys_id in sys_ids] + return ppstring([str(universe.getSystem(sys_id)) for sys_id in sys_ids]) -def planet_name_ids(planet_ids): +def planet_string(planet_ids): """ - Get list of text representing pairs planet name and system id. - :param planet_ids: list if planet ids - :return: list of string : + Get a string representation of the passed planets + :param planet_ids: list of planet ids or single id + :rtype: str """ - universe = fo.getUniverse() - return [fo.to_str('P', planet_id, safe_name(universe.getPlanet(planet_id))) for planet_id in planet_ids] + + def _safe_planet_name(planet_id): + planet = fo.getUniverse().getPlanet(planet_id) + return fo.to_str('P', planet_id, (planet and planet.name) or "?") + + if isinstance(planet_ids, int): + return _safe_planet_name(planet_ids) + return ppstring([_safe_planet_name(pid) for pid in planet_ids]) def get_capital(): @@ -42,9 +50,6 @@ def get_capital(): """ universe = fo.getUniverse() empire = fo.getEmpire() - if empire is None: - print >> sys.stderr, "Danger Danger! FO can't find an empire for me!!!!" - return INVALID_ID empire_id = empire.empireID capital_id = empire.capitalID homeworld = universe.getPlanet(capital_id) @@ -52,8 +57,8 @@ def get_capital(): if homeworld.owner == empire_id: return capital_id else: - print "Nominal Capitol %s does not appear to be owned by empire %d %s" % ( - homeworld.name, empire_id, empire.name) + debug("Nominal Capitol %s does not appear to be owned by empire %d %s" % ( + homeworld.name, empire_id, empire.name)) empire_owned_planet_ids = get_owned_planets_by_empire(universe.planetIDs) peopled_planets = get_populated_planet_ids(empire_owned_planet_ids) if not peopled_planets: @@ -67,7 +72,7 @@ def get_capital(): for planet_id in peopled_planets: planet = universe.getPlanet(planet_id) if spec_list is None or planet.speciesName in spec_list: - population_id_pairs.append((planet.currentMeterValue(fo.meterType.population), planet_id)) + population_id_pairs.append((planet.initialMeterValue(fo.meterType.population), planet_id)) if population_id_pairs: return max(population_id_pairs)[-1] except Exception as e: @@ -134,7 +139,7 @@ def get_all_owned_planet_ids(planet_ids): for pid in planet_ids: planet = universe.getPlanet(pid) if planet: - population = planet.currentMeterValue(fo.meterType.population) + population = planet.initialMeterValue(fo.meterType.population) if not planet.unowned or population > 0: result.append(pid) return result @@ -147,7 +152,7 @@ def get_populated_planet_ids(planet_ids): :return: list of planets ids """ universe = fo.getUniverse() - return [pid for pid in planet_ids if universe.getPlanet(pid).currentMeterValue(fo.meterType.population) > 0] + return [pid for pid in planet_ids if universe.getPlanet(pid).initialMeterValue(fo.meterType.population) > 0] def get_systems(planet_ids): diff --git a/default/python/AI/PriorityAI.py b/default/python/AI/PriorityAI.py index b5a1740452a..383f2f2e318 100644 --- a/default/python/AI/PriorityAI.py +++ b/default/python/AI/PriorityAI.py @@ -1,27 +1,27 @@ import math +from logging import debug +from operator import itemgetter import freeOrionAIInterface as fo # pylint: disable=import-error + +import AIDependencies import AIstate import ColonisationAI import ExplorationAI import FleetUtilsAI -import FreeOrionAI as foAI +from aistate_interface import get_aistate import InvasionAI import MilitaryAI import PlanetUtilsAI import ProductionAI import ResearchAI -import AIDependencies -from turn_state import state -from EnumsAI import PriorityType, MissionType, EmpireProductionTypes, get_priority_production_types, ShipRoleType -from freeorion_tools import AITimer, tech_is_complete from AIDependencies import INVALID_ID -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) +from EnumsAI import EmpireProductionTypes, MissionType, PriorityType, ShipRoleType, get_priority_production_types +from freeorion_tools import AITimer, tech_is_complete +from turn_state import state prioritiees_timer = AITimer('calculate_priorities()') -allottedInvasionTargets = 0 allottedColonyTargets = 0 allotted_outpost_targets = 0 unmetThreat = 0 @@ -29,53 +29,47 @@ def calculate_priorities(): """Calculates the priorities of the AI player.""" - print "\n", 10 * "=", "Preparing to Calculate Priorities", 10 * "=" + debug("\n{0}Preparing to Calculate Priorities{0}".format(10 * "=")) prioritiees_timer.start('setting Production Priority') - foAI.foAIstate.set_priority(PriorityType.RESOURCE_PRODUCTION, 50) # let this one stay fixed & just adjust Research + aistate = get_aistate() + aistate.set_priority(PriorityType.RESOURCE_PRODUCTION, 50) # let this one stay fixed & just adjust Research - print "\n*** Calculating Research Priority ***\n" + debug("\n*** Calculating Research Priority ***\n") prioritiees_timer.start('setting Research Priority') - foAI.foAIstate.set_priority(PriorityType.RESOURCE_RESEARCH, _calculate_research_priority()) # TODO: do univ _survey before this + aistate.set_priority(PriorityType.RESOURCE_RESEARCH, _calculate_research_priority()) # TODO: do univ _survey before this - print "\n*** Updating Colonization Status ***\n" + debug("\n*** Updating Colonization Status ***\n") prioritiees_timer.start('Evaluating Colonization Status') - ColonisationAI.get_colony_fleets() # sets foAI.foAIstate.colonisablePlanetIDs and foAI.foAIstate.outpostPlanetIDs and many other values used by other modules + ColonisationAI.get_colony_fleets() # sets aistate.colonisablePlanetIDs and many other values used by other modules - print "\n*** Updating Invasion Status ***\n" + debug("\n*** Updating Invasion Status ***\n") prioritiees_timer.start('Evaluating Invasion Status') - InvasionAI.get_invasion_fleets() # sets AIstate.invasionFleetIDs, AIstate.opponentPlanetIDs, and AIstate.invasionTargetedPlanetIDs + InvasionAI.get_invasion_fleets() # sets AIstate.opponentPlanetIDs, and AIstate.invasionTargetedPlanetIDs - print "\n*** Updating Military Status ***\n" + debug("\n*** Updating Military Status ***\n") prioritiees_timer.start('Evaluating Military Status') MilitaryAI.get_military_fleets() - print("\n** Calculating Production Priorities ***\n") + debug("\n** Calculating Production Priorities ***\n") prioritiees_timer.start('reporting Production Priority') _calculate_industry_priority() # purely for reporting purposes prioritiees_timer.start('setting Exploration Priority') - foAI.foAIstate.set_priority(PriorityType.RESOURCE_TRADE, 0) - foAI.foAIstate.set_priority(PriorityType.RESOURCE_CONSTRUCTION, 0) + aistate.set_priority(PriorityType.RESOURCE_TRADE, 0) + aistate.set_priority(PriorityType.RESOURCE_CONSTRUCTION, 0) - foAI.foAIstate.set_priority(PriorityType.PRODUCTION_EXPLORATION, _calculate_exploration_priority()) + aistate.set_priority(PriorityType.PRODUCTION_EXPLORATION, _calculate_exploration_priority()) prioritiees_timer.start('setting Colony Priority') - foAI.foAIstate.set_priority(PriorityType.PRODUCTION_COLONISATION, _calculate_colonisation_priority()) + aistate.set_priority(PriorityType.PRODUCTION_COLONISATION, _calculate_colonisation_priority()) prioritiees_timer.start('setting Outpost Priority') - foAI.foAIstate.set_priority(PriorityType.PRODUCTION_OUTPOST, _calculate_outpost_priority()) + aistate.set_priority(PriorityType.PRODUCTION_OUTPOST, _calculate_outpost_priority()) prioritiees_timer.start('setting Invasion Priority') - foAI.foAIstate.set_priority(PriorityType.PRODUCTION_INVASION, _calculate_invasion_priority()) + aistate.set_priority(PriorityType.PRODUCTION_INVASION, _calculate_invasion_priority()) prioritiees_timer.start('setting Military Priority') - foAI.foAIstate.set_priority(PriorityType.PRODUCTION_MILITARY, _calculate_military_priority()) + aistate.set_priority(PriorityType.PRODUCTION_MILITARY, _calculate_military_priority()) prioritiees_timer.start('setting other priorities') - foAI.foAIstate.set_priority(PriorityType.PRODUCTION_BUILDINGS, 25) - - foAI.foAIstate.set_priority(PriorityType.RESEARCH_LEARNING, _calculate_learning_priority()) - foAI.foAIstate.set_priority(PriorityType.RESEARCH_GROWTH, _calculate_growth_priority()) - foAI.foAIstate.set_priority(PriorityType.RESEARCH_PRODUCTION, _calculate_techs_production_priority()) - foAI.foAIstate.set_priority(PriorityType.RESEARCH_CONSTRUCTION, _calculate_construction_priority()) - foAI.foAIstate.set_priority(PriorityType.RESEARCH_ECONOMICS, 0) - foAI.foAIstate.set_priority(PriorityType.RESEARCH_SHIPS, _calculate_ships_priority()) - foAI.foAIstate.set_priority(PriorityType.RESEARCH_DEFENSE, 0) + aistate.set_priority(PriorityType.PRODUCTION_BUILDINGS, 25) + prioritiees_timer.stop_print_and_clear() @@ -86,15 +80,15 @@ def _calculate_industry_priority(): # currently only used to print status # get current industry production & Target industry_production = empire.resourceProduction(fo.resourceType.industry) owned_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire(universe.planetIDs) - planets = map(universe.getPlanet, owned_planet_ids) - target_pp = sum(map(lambda x: x.currentMeterValue(fo.meterType.targetIndustry), planets)) + planets = (universe.getPlanet(x) for x in owned_planet_ids) + target_pp = sum(x.currentMeterValue(fo.meterType.targetIndustry) for x in planets) # currently, previously set to 50 in calculatePriorities(), this is just for reporting - industry_priority = foAI.foAIstate.get_priority(PriorityType.RESOURCE_PRODUCTION) + industry_priority = get_aistate().get_priority(PriorityType.RESOURCE_PRODUCTION) - print - print "Industry Production (current/target) : ( %.1f / %.1f ) at turn %s" % (industry_production, target_pp, fo.currentTurn()) - print "Priority for Industry: %s" % industry_priority + debug('') + debug("Industry Production (current/target) : ( %.1f / %.1f ) at turn %s" % (industry_production, target_pp, fo.currentTurn())) + debug("Priority for Industry: %s" % industry_priority) return industry_priority @@ -102,12 +96,12 @@ def _calculate_research_priority(): """Calculates the AI empire's demand for research.""" universe = fo.getUniverse() empire = fo.getEmpire() - empire_id = empire.empireID current_turn = fo.currentTurn() - enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) + aistate = get_aistate() + enemies_sighted = aistate.misc.get('enemies_sighted', {}) recent_enemies = [x for x in enemies_sighted if x > current_turn - 8] - industry_priority = foAI.foAIstate.get_priority(PriorityType.RESOURCE_PRODUCTION) + industry_priority = aistate.get_priority(PriorityType.RESOURCE_PRODUCTION) got_algo = tech_is_complete(AIDependencies.LRN_ALGO_ELEGANCE) got_quant = tech_is_complete(AIDependencies.LRN_QUANT_NET) @@ -120,23 +114,23 @@ def _calculate_research_priority(): milestone_techs = ["PRO_SENTIENT_AUTOMATION", "LRN_DISTRIB_THOUGHT", "LRN_QUANT_NET", "SHP_WEAPON_2_4", "SHP_WEAPON_3_2", "SHP_WEAPON_4_2"] milestones_done = [mstone for mstone in milestone_techs if tech_is_complete(mstone)] - print "Research Milestones accomplished at turn %d: %s" % (current_turn, milestones_done) + debug("Research Milestones accomplished at turn %d: %s" % (current_turn, milestones_done)) total_pp = empire.productionPoints total_rp = empire.resourceProduction(fo.resourceType.research) - industry_surge = (foAI.foAIstate.character.may_surge_industry(total_pp, total_rp) and + industry_surge = (aistate.character.may_surge_industry(total_pp, total_rp) and (((orb_gen_tech in research_queue_list[:2] or got_orb_gen) and state.have_gas_giant) or ((mgrav_prod_tech in research_queue_list[:2] or got_mgrav_prod) and state.have_asteroids)) and - (not (len(AIstate.popCtrIDs) >= 12))) + (state.get_number_of_colonies() < 12)) # get current industry production & Target owned_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire(universe.planetIDs) - planets = map(universe.getPlanet, owned_planet_ids) - target_rp = sum(map(lambda x: x.currentMeterValue(fo.meterType.targetResearch), planets)) + planets = (universe.getPlanet(x) for x in owned_planet_ids) + target_rp = sum(map(lambda _x: _x.currentMeterValue(fo.meterType.targetResearch), planets)) galaxy_is_sparse = ColonisationAI.galaxy_is_sparse() - enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) + enemies_sighted = aistate.misc.get('enemies_sighted', {}) - style_index = foAI.foAIstate.character.preferred_research_cutoff([0, 1]) - if foAI.foAIstate.character.may_maximize_research(): + style_index = aistate.character.preferred_research_cutoff([0, 1]) + if aistate.character.may_maximize_research(): style_index += 1 cutoff_sets = [[25, 45, 70, 110], [30, 45, 70, 150], [25, 40, 80, 160]] @@ -160,7 +154,7 @@ def _calculate_research_priority(): research_priority = len(research_queue) * 0.01 * industry_priority # barely not done with research elif len(research_queue) < 10 and research_queue[-1].allocation > 0: research_priority = (4 + 2 * len(research_queue)) * 0.01 * industry_priority # almost done with research - elif len(research_queue) < 20 and research_queue[int(len(research_queue) / 2)].allocation > 0: + elif len(research_queue) < 20 and research_queue[int(len(research_queue) // 2)].allocation > 0: research_priority *= 0.7 # closing in on end of research if industry_surge: if galaxy_is_sparse and not any(enemies_sighted): @@ -177,8 +171,8 @@ def _calculate_research_priority(): if got_quant: research_priority = min(research_priority + 0.1 * industry_priority, research_priority * 1.3) research_priority = int(research_priority) - print "Research Production (current/target) : ( %.1f / %.1f )" % (total_rp, target_rp) - print "Priority for Research: %d (new target ~ %d RP)" % (research_priority, total_pp * research_priority / industry_priority) + debug("Research Production (current/target) : ( %.1f / %.1f )" % (total_rp, target_rp)) + debug("Priority for Research: %d (new target ~ %d RP)" % (research_priority, total_pp * research_priority / industry_priority)) if len(enemies_sighted) < (2 + current_turn/20.0): # TODO: adjust for colonisation priority research_priority *= 1.2 @@ -191,14 +185,15 @@ def _calculate_exploration_priority(): """Calculates the demand for scouts by unexplored systems.""" empire = fo.getEmpire() num_unexplored_systems = len(ExplorationAI.border_unexplored_system_ids) - num_scouts = sum([foAI.foAIstate.fleetStatus.get(fid, {}).get('nships', 0) for fid in FleetUtilsAI.get_empire_fleet_ids_by_role( + aistate = get_aistate() + num_scouts = sum([aistate.fleetStatus.get(fid, {}).get('nships', 0) for fid in FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.EXPLORATION)]) production_queue = empire.productionQueue queued_scout_ships = 0 for queue_index in range(0, len(production_queue)): element = production_queue[queue_index] if element.buildType == EmpireProductionTypes.BT_SHIP: - if foAI.foAIstate.get_ship_role(element.designID) == ShipRoleType.CIVILIAN_EXPLORATION: + if aistate.get_ship_role(element.designID) == ShipRoleType.CIVILIAN_EXPLORATION: queued_scout_ships += element.remaining * element.blocksize mil_ships = MilitaryAI.get_num_military_ships() @@ -212,11 +207,11 @@ def _calculate_exploration_priority(): scouts_needed = max(0, desired_number_of_scouts - (num_scouts + queued_scout_ships)) exploration_priority = int(40 * scouts_needed) - print - print "Number of Scouts: %s" % num_scouts - print "Number of Unexplored systems: %s" % num_unexplored_systems - print "Military size: %s" % mil_ships - print "Priority for scouts: %s" % exploration_priority + debug('') + debug("Number of Scouts: %s" % num_scouts) + debug("Number of Unexplored systems: %s" % num_unexplored_systems) + debug("Military size: %s" % mil_ships) + debug("Priority for scouts: %s" % exploration_priority) return exploration_priority @@ -224,19 +219,22 @@ def _calculate_exploration_priority(): def _calculate_colonisation_priority(): """Calculates the demand for colony ships by colonisable planets.""" global allottedColonyTargets - enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) + aistate = get_aistate() + enemies_sighted = aistate.misc.get('enemies_sighted', {}) galaxy_is_sparse = ColonisationAI.galaxy_is_sparse() total_pp = fo.getEmpire().productionPoints - num_colonies = len(list(AIstate.popCtrIDs)) - colony_growth_barrier = foAI.foAIstate.character.max_number_colonies() + num_colonies = state.get_number_of_colonies() + colony_growth_barrier = aistate.character.max_number_colonies() + if num_colonies > colony_growth_barrier: + return 0.0 colony_cost = AIDependencies.COLONY_POD_COST * (1 + AIDependencies.COLONY_POD_UPKEEP * num_colonies) turns_to_build = 8 # TODO: check for susp anim pods, build time 10 - mil_prio = foAI.foAIstate.get_priority(PriorityType.PRODUCTION_MILITARY) + mil_prio = aistate.get_priority(PriorityType.PRODUCTION_MILITARY) allotted_portion = ([[[0.6, 0.8], [0.3, 0.4]], [[0.8, 0.9], [0.4, 0.6]]][galaxy_is_sparse] - [any(enemies_sighted)]) - allotted_portion = foAI.foAIstate.character.preferred_colonization_portion(allotted_portion) - # if ( foAI.foAIstate.get_priority(AIPriorityType.PRIORITY_PRODUCTION_COLONISATION) - # > 2 * foAI.foAIstate.get_priority(AIPriorityType.PRIORITY_PRODUCTION_MILITARY)): + [any(enemies_sighted)]) + allotted_portion = aistate.character.preferred_colonization_portion(allotted_portion) + # if ( get_aistate().get_priority(AIPriorityType.PRIORITY_PRODUCTION_COLONISATION) + # > 2 * get_aistate().get_priority(AIPriorityType.PRIORITY_PRODUCTION_MILITARY)): # allotted_portion *= 1.5 if mil_prio < 100: allotted_portion *= 2 @@ -246,15 +244,20 @@ def _calculate_colonisation_priority(): allotted_portion *= 0.75 ** (num_colonies / 10.0) # allottedColonyTargets = 1+ int(fo.currentTurn()/50) allottedColonyTargets = 1 + int(total_pp * turns_to_build * allotted_portion / colony_cost) - outpost_prio = foAI.foAIstate.get_priority(PriorityType.PRODUCTION_OUTPOST) - # if have any outposts to build, don't build colony ships TODO: make more complex assessment - if outpost_prio > 0 or num_colonies > colony_growth_barrier: - return 0.0 + outpost_prio = aistate.get_priority(PriorityType.PRODUCTION_OUTPOST) - if num_colonies > colony_growth_barrier: + # if have no SP_SLY, and have any outposts to build, don't build colony ships TODO: make more complex assessment + colonizers = list(ColonisationAI.empire_colonizers) + if "SP_SLY" not in colonizers and outpost_prio > 0: return 0.0 - num_colonisable_planet_ids = len([pid for (pid, (score, _)) in foAI.foAIstate.colonisablePlanetIDs.items() - if score > 60][:allottedColonyTargets + 2]) + min_score = ColonisationAI.MINIMUM_COLONY_SCORE + minimal_top = min_score + 2 # one more than the conditional floor set by ColonisationAI.revise_threat_factor() + minimal_opportunities = [species_name for (_, (score, species_name)) in aistate.colonisablePlanetIDs.items() + if min_score < score <= minimal_top] + decent_opportunities = [species_name for (_, (score, species_name)) in aistate.colonisablePlanetIDs.items() + if score > minimal_top] + minimal_planet_factor = 0.2 # count them for something, but not much + num_colonisable_planet_ids = len(decent_opportunities) + minimal_planet_factor * len(minimal_opportunities) if num_colonisable_planet_ids == 0: return 1 @@ -262,6 +265,12 @@ def _calculate_colonisation_priority(): num_colony_ships = len(FleetUtilsAI.extract_fleet_ids_without_mission_types(colony_ship_ids)) colonisation_priority = 60 * (1.0 + num_colonisable_planet_ids - num_colony_ships) / (num_colonisable_planet_ids + 1) + if colonizers == ["SP_SLY"]: + colonisation_priority *= 2 + elif "SP_SLY" in colonizers: + colony_opportunities = minimal_opportunities + decent_opportunities + colonisation_priority *= (1.0 + colony_opportunities.count("SP_SLY")) / len(colony_opportunities) + # print # print "Number of Colony Ships : " + str(num_colony_ships) # print "Number of Colonisable planets : " + str(num_colonisable_planet_ids) @@ -277,14 +286,14 @@ def _calculate_outpost_priority(): global allotted_outpost_targets base_outpost_cost = AIDependencies.OUTPOST_POD_COST - enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) + aistate = get_aistate() + enemies_sighted = aistate.misc.get('enemies_sighted', {}) galaxy_is_sparse = ColonisationAI.galaxy_is_sparse() total_pp = fo.getEmpire().productionPoints - num_colonies = len(list(AIstate.popCtrIDs)) - colony_growth_barrier = foAI.foAIstate.character.max_number_colonies() - if num_colonies > colony_growth_barrier: + colony_growth_barrier = aistate.character.max_number_colonies() + if state.get_number_of_colonies() > colony_growth_barrier: return 0.0 - mil_prio = foAI.foAIstate.get_priority(PriorityType.PRODUCTION_MILITARY) + mil_prio = aistate.get_priority(PriorityType.PRODUCTION_MILITARY) not_sparse, enemy_unseen = 0, 0 is_sparse, enemy_seen = 1, 1 @@ -294,15 +303,16 @@ def _calculate_outpost_priority(): (is_sparse, enemy_unseen): (0.8, 0.9), (is_sparse, enemy_seen): (0.3, 0.4), }[(galaxy_is_sparse, any(enemies_sighted))] - allotted_portion = foAI.foAIstate.character.preferred_outpost_portion(allotted_portion) + allotted_portion = aistate.character.preferred_outpost_portion(allotted_portion) if mil_prio < 100: allotted_portion *= 2 elif mil_prio < 200: allotted_portion *= 1.5 allotted_outpost_targets = 1 + int(total_pp * 3 * allotted_portion / base_outpost_cost) - num_outpost_targets = len([pid for (pid, (score, specName)) in foAI.foAIstate.colonisableOutpostIDs.items() - if score > 1.0 * base_outpost_cost / 3.0][:allotted_outpost_targets]) + num_outpost_targets = len([pid for (pid, (score, specName)) in aistate.colonisableOutpostIDs.items() + if score > max(1.0 * base_outpost_cost / 3.0, ColonisationAI.MINIMUM_COLONY_SCORE)] + [:allotted_outpost_targets]) if num_outpost_targets == 0 or not tech_is_complete(AIDependencies.OUTPOSTING_TECH): return 0 @@ -310,10 +320,15 @@ def _calculate_outpost_priority(): num_outpost_ships = len(FleetUtilsAI.extract_fleet_ids_without_mission_types(outpost_ship_ids)) outpost_priority = (50.0 * (num_outpost_targets - num_outpost_ships)) / num_outpost_targets + # discourage early outposting for SP_SLY, due to supply and stealth considerations they are best off + # using colony ships until they have other colonizers (and more established military) + if list(ColonisationAI.empire_colonizers) == ["SP_SLY"]: + outpost_priority /= 3.0 + # print # print "Number of Outpost Ships : " + str(num_outpost_ships) # print "Number of Colonisable outposts: " + str(num_outpost_planet_ids) - print "Priority for outpost ships: " + str(outpost_priority) + debug("Priority for outpost ships: %s" % outpost_priority) if outpost_priority < 1: return 0 @@ -323,27 +338,25 @@ def _calculate_outpost_priority(): def _calculate_invasion_priority(): """Calculates the demand for troop ships by opponent planets.""" - global allottedInvasionTargets - if not foAI.foAIstate.character.may_invade(): + aistate = get_aistate() + if not aistate.character.may_invade(): return 0 empire = fo.getEmpire() - enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) + enemies_sighted = aistate.misc.get('enemies_sighted', {}) multiplier = 1 - num_colonies = len(list(AIstate.popCtrIDs)) - colony_growth_barrier = foAI.foAIstate.character.max_number_colonies() - if num_colonies > colony_growth_barrier: + colony_growth_barrier = aistate.character.max_number_colonies() + if state.get_number_of_colonies() > colony_growth_barrier: return 0.0 - if len(foAI.foAIstate.colonisablePlanetIDs) > 0: - best_colony_score = max(2, foAI.foAIstate.colonisablePlanetIDs.items()[0][1][0]) + if len(aistate.colonisablePlanetIDs) > 0: + best_colony_score = max(2, next(iter(aistate.colonisablePlanetIDs.items()))[1][0]) else: best_colony_score = 2 - allottedInvasionTargets = 1 + int(fo.currentTurn() / 25) total_val = 0 troops_needed = 0 - for pid, pscore, trp in AIstate.invasionTargets[:allottedInvasionTargets]: + for pid, pscore, trp in AIstate.invasionTargets[:allotted_invasion_targets()]: if pscore > best_colony_score: multiplier += 1 total_val += 2 * pscore @@ -359,8 +372,8 @@ def _calculate_invasion_priority(): for queue_index in range(0, len(production_queue)): element = production_queue[queue_index] if element.buildType == EmpireProductionTypes.BT_SHIP: - if foAI.foAIstate.get_ship_role(element.designID) in [ShipRoleType.MILITARY_INVASION, - ShipRoleType.BASE_INVASION]: + if aistate.get_ship_role(element.designID) in [ShipRoleType.MILITARY_INVASION, + ShipRoleType.BASE_INVASION]: design = fo.getShipDesign(element.designID) queued_troop_capacity += element.remaining * element.blocksize * design.troopCapacity _, best_design, _ = ProductionAI.get_best_ship_info(PriorityType.PRODUCTION_INVASION) @@ -390,7 +403,11 @@ def _calculate_invasion_priority(): if invasion_priority < 0: return 0 - return invasion_priority * foAI.foAIstate.character.invasion_priority_scaling() + return invasion_priority * aistate.character.invasion_priority_scaling() + + +def allotted_invasion_targets(): + return 1 + int(fo.currentTurn() // 25) def _calculate_military_priority(): @@ -408,12 +425,14 @@ def _calculate_military_priority(): tech_is_complete("SHP_WEAPON_4_1")) have_l2_weaps = (tech_is_complete("SHP_WEAPON_2_3") or tech_is_complete("SHP_WEAPON_4_1")) - enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) + aistate = get_aistate() + enemies_sighted = aistate.misc.get('enemies_sighted', {}) - allotted_invasion_targets = 1 + int(fo.currentTurn()/25) - target_planet_ids = [pid for pid, pscore, trp in AIstate.invasionTargets[:allotted_invasion_targets]] + [pid for pid, pscore in foAI.foAIstate.colonisablePlanetIDs.items()[:allottedColonyTargets]] + [pid for pid, pscore in foAI.foAIstate.colonisableOutpostIDs.items()[:allottedColonyTargets]] + target_planet_ids = ([pid for pid, pscore, trp in AIstate.invasionTargets[:allotted_invasion_targets()]] + + [pid for pid, pscore in list(aistate.colonisablePlanetIDs.items())[:allottedColonyTargets]] + + [pid for pid, pscore in list(aistate.colonisableOutpostIDs.items())[:allottedColonyTargets]]) - my_systems = set(AIstate.popCtrSystemIDs).union(AIstate.outpostSystemIDs) + my_systems = set(state.get_empire_planets_by_system()) target_systems = set(PlanetUtilsAI.get_systems(target_planet_ids)) cur_ship_rating = ProductionAI.cur_best_military_design_rating() @@ -422,7 +441,7 @@ def _calculate_military_priority(): defense_ships_needed = 0 ships_needed_allocation = [] for sys_id in my_systems.union(target_systems): - status = foAI.foAIstate.systemStatus.get(sys_id, {}) + status = aistate.systemStatus.get(sys_id, {}) my_rating = status.get('myFleetRating', 0) my_defenses = status.get('mydefenses', {}).get('overall', 0) base_monster_threat = status.get('monsterThreat', 0) @@ -457,69 +476,24 @@ def _calculate_military_priority(): elif not (have_l2_weaps and enemies_sighted): military_priority /= 1.5 fmt_string = "Calculating Military Priority: min(t,40) + max(0,75 * ships_needed) \n\t Priority: %d \t ships_needed: %d \t defense_ships_needed: %d \t curShipRating: %.0f \t l1_weaps: %s \t enemies_sighted: %s" - print fmt_string % (military_priority, ships_needed, defense_ships_needed, cur_ship_rating, have_l1_weaps, enemies_sighted) - print "Source of milship demand: ", ships_needed_allocation + debug(fmt_string % (military_priority, ships_needed, defense_ships_needed, cur_ship_rating, have_l1_weaps, enemies_sighted)) + debug("Source of milship demand: %s" % ships_needed_allocation) - military_priority *= foAI.foAIstate.character.military_priority_scaling() + military_priority *= aistate.character.military_priority_scaling() return max(military_priority, 0) def _calculate_top_production_queue_priority(): """Calculates the top production queue priority.""" production_queue_priorities = {} + aistate = get_aistate() for priorityType in get_priority_production_types(): - production_queue_priorities[priorityType] = foAI.foAIstate.get_priority(priorityType) + production_queue_priorities[priorityType] = aistate.get_priority(priorityType) - sorted_priorities = production_queue_priorities.items() - sorted_priorities.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) + sorted_priorities = sorted(production_queue_priorities.items(), key=itemgetter(1), reverse=True) top_production_queue_priority = -1 for evaluationPair in sorted_priorities: if top_production_queue_priority < 0: top_production_queue_priority = evaluationPair[0] return top_production_queue_priority - - -def _calculate_learning_priority(): - """Calculates the demand for techs learning category.""" - turn = fo.currentTurn() - if turn == 1: - return 100 - elif turn > 1: - return 0 - - -def _calculate_growth_priority(): - """Calculates the demand for techs growth category.""" - production_priority = _calculate_top_production_queue_priority() - if production_priority == 8: - return 70 - elif production_priority != 8: - return 0 - - -def _calculate_techs_production_priority(): - """Calculates the demand for techs production category.""" - production_priority = _calculate_top_production_queue_priority() - if production_priority == 7 or production_priority == 9: - return 60 - elif production_priority != 7 or production_priority != 9: - return 0 - - -def _calculate_construction_priority(): - """Calculates the demand for techs construction category.""" - production_priority = _calculate_top_production_queue_priority() - if production_priority == 6 or production_priority == 11: - return 80 - elif production_priority != 6 or production_priority != 11: - return 30 - - -def _calculate_ships_priority(): - """Calculates the demand for techs ships category.""" - production_priority = _calculate_top_production_queue_priority() - if production_priority == 10: - return 90 - elif production_priority != 10: - return 0 diff --git a/default/python/AI/ProductionAI.py b/default/python/AI/ProductionAI.py index c95c12da456..f109c3db13e 100644 --- a/default/python/AI/ProductionAI.py +++ b/default/python/AI/ProductionAI.py @@ -1,30 +1,27 @@ import math import random -import sys -import traceback +from logging import debug, error, info, warning +from operator import itemgetter import freeOrionAIInterface as fo # pylint: disable=import-error + import AIDependencies import AIstate -from character.character_module import Aggression +import ColonisationAI +import CombatRatingsAI import FleetUtilsAI -import FreeOrionAI as foAI +import MilitaryAI import PlanetUtilsAI import PriorityAI -import ColonisationAI -import MilitaryAI import ShipDesignAI -import CombatRatingsAI -from turn_state import state - -from EnumsAI import (PriorityType, EmpireProductionTypes, MissionType, get_priority_production_types, - FocusType, ShipRoleType) -from freeorion_tools import dict_from_map, ppstring, chat_human, tech_is_complete, AITimer -from common.print_utils import Table, Sequence, Text from AIDependencies import INVALID_ID - -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) +from EnumsAI import (EmpireProductionTypes, FocusType, MissionType, PriorityType, ShipRoleType, + get_priority_production_types, ) +from aistate_interface import get_aistate +from character.character_module import Aggression +from common.print_utils import Sequence, Table, Text +from freeorion_tools import AITimer, chat_human, ppstring, tech_is_complete +from turn_state import state _best_military_design_rating_cache = {} # indexed by turn, values are rating of the military design of the turn _design_cost_cache = {0: {(-1, -1): 0}} # outer dict indexed by cur_turn (currently only one turn kept); inner dict indexed by (design_id, pid) @@ -119,10 +116,10 @@ def cur_best_military_design_rating(): def get_best_ship_info(priority, loc=None): """ Returns 3 item tuple: designID, design, buildLocList.""" if loc is None: - planet_ids = AIstate.popCtrIDs + planet_ids = state.get_inhabited_planets() elif isinstance(loc, list): - planet_ids = set(loc).intersection(AIstate.popCtrIDs) - elif isinstance(loc, int) and loc in AIstate.popCtrIDs: + planet_ids = set(loc).intersection(state.get_inhabited_planets()) + elif isinstance(loc, int) and loc in state.get_inhabited_planets(): planet_ids = [loc] else: # problem return None, None, None @@ -139,8 +136,8 @@ def get_best_ship_info(priority, loc=None): break else: return None, None, None # apparently can't build for this priority within the desired planet group - valid_locs = [pid for rating, pid, design_id, _, _ in best_designs if - rating == top_rating and design_id == top_id and pid in planet_ids] + valid_locs = [pid_ for rating, pid_, design_id, _, _ in best_designs if + rating == top_rating and design_id == top_id and pid_ in planet_ids] return top_id, fo.getShipDesign(top_id), valid_locs else: return None, None, None # must be missing a Shipyard or other orbital (or missing tech) @@ -207,7 +204,7 @@ def generate_production_orders(): else: homeworld = universe.getPlanet(capital_id) capital_system_id = homeworld.systemID - print "Production Queue Management:" + debug("Production Queue Management:") empire = fo.getEmpire() production_queue = empire.productionQueue total_pp = empire.productionPoints @@ -216,10 +213,11 @@ def generate_production_orders(): # allocated_pp = dict_from_map(production_queue.allocated_pp) # objectsWithWastedPP = production_queue.objectsWithWastedPP(prodResPool) current_turn = fo.currentTurn() - print - print " Total Available Production Points: %s" % total_pp + debug('') + debug(" Total Available Production Points: %s" % total_pp) - claimed_stars = foAI.foAIstate.misc.get('claimedStars', {}) + aistate = get_aistate() + claimed_stars = aistate.misc.get('claimedStars', {}) if claimed_stars == {}: for sType in AIstate.empireStars: claimed_stars[sType] = list(AIstate.empireStars[sType]) @@ -229,26 +227,32 @@ def generate_production_orders(): continue claimed_stars.setdefault(t_sys.starType, []).append(sys_id) - if current_turn == 1 and len(AIstate.opponentPlanetIDs) == 0: - best_design_id, _, build_choices = get_best_ship_info(PriorityType.PRODUCTION_EXPLORATION) - if best_design_id is not None: - for _ in range(3): - fo.issueEnqueueShipProductionOrder(best_design_id, build_choices[0]) - fo.updateProductionQueue() + if current_turn == 1 and len(AIstate.opponentPlanetIDs) == 0 and len(production_queue) == 0: + init_build_nums = [(PriorityType.PRODUCTION_EXPLORATION, 2)] + if list(ColonisationAI.empire_colonizers) == ["SP_SLY"]: + init_build_nums.append((PriorityType.PRODUCTION_COLONISATION, 1)) + else: + init_build_nums.append((PriorityType.PRODUCTION_OUTPOST, 1)) + for ship_type, num_ships in init_build_nums: + best_design_id, _, build_choices = get_best_ship_info(ship_type) + if best_design_id is not None: + for _ in range(num_ships): + fo.issueEnqueueShipProductionOrder(best_design_id, build_choices[0]) + fo.updateProductionQueue() building_expense = 0.0 - building_ratio = foAI.foAIstate.character.preferred_building_ratio([0.4, 0.35, 0.30]) - print "Buildings present on all owned planets:" - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): + building_ratio = aistate.character.preferred_building_ratio([0.4, 0.35, 0.30]) + debug("Buildings present on all owned planets:") + for pid in state.get_all_empire_planets(): planet = universe.getPlanet(pid) if planet: - print "%30s: %s" % (planet.name, [universe.getBuilding(bldg).name for bldg in planet.buildingIDs]) - print + debug("%30s: %s" % (planet.name, [universe.getBuilding(bldg).name for bldg in planet.buildingIDs])) + debug('') if not homeworld: - print "if no capital, no place to build, should get around to capturing or colonizing a new one" # TODO + debug("if no capital, no place to build, should get around to capturing or colonizing a new one") # TODO else: - print "Empire priority_id %d has current Capital %s:" % (empire.empireID, homeworld.name) + debug("Empire priority_id %d has current Capital %s:" % (empire.empireID, homeworld.name)) table = Table([ Text('Id', description='Building id'), Text('Name'), @@ -270,8 +274,7 @@ def generate_production_orders(): building.owner )) - table.print_table() - print + info(table) capital_buildings = [universe.getBuilding(bldg).buildingTypeName for bldg in homeworld.buildingIDs] possible_building_type_ids = [] @@ -279,46 +282,49 @@ def generate_production_orders(): try: if fo.getBuildingType(type_id).canBeProduced(empire.empireID, homeworld.id): possible_building_type_ids.append(type_id) - except: + except: # noqa: E722 if fo.getBuildingType(type_id) is None: - print "For empire %d, 'available Building Type priority_id' %s returns None from fo.getBuildingType(type_id)" % (empire.empireID, type_id) + debug("For empire %d, 'available Building Type priority_id' %s returns None from fo.getBuildingType(type_id)" % (empire.empireID, type_id)) else: - print "For empire %d, problem getting BuildingTypeID for 'available Building Type priority_id' %s" % (empire.empireID, type_id) + debug("For empire %d, problem getting BuildingTypeID for 'available Building Type priority_id' %s" % (empire.empireID, type_id)) if possible_building_type_ids: - print "Possible building types to build:" + debug("Possible building types to build:") for type_id in possible_building_type_ids: building_type = fo.getBuildingType(type_id) - print " %s cost: %s time: %s" % (building_type.name, + debug(" %s cost: %s time: %s" % (building_type.name, building_type.productionCost(empire.empireID, homeworld.id), - building_type.productionTime(empire.empireID, homeworld.id)) + building_type.productionTime(empire.empireID, homeworld.id))) possible_building_types = [fo.getBuildingType(type_id) and fo.getBuildingType(type_id).name for type_id in possible_building_type_ids] # makes sure is not None before getting name - print - print "Buildings already in Production Queue:" + debug('') + debug("Buildings already in Production Queue:") capital_queued_buildings = [] for element in [e for e in production_queue if (e.buildType == EmpireProductionTypes.BT_BUILDING)]: building_expense += element.allocation if element.locationID == homeworld.id: capital_queued_buildings.append(element) for bldg in capital_queued_buildings: - print " %s turns: %s PP: %s" % (bldg.name, bldg.turnsLeft, bldg.allocation) + debug(" %s turns: %s PP: %s" % (bldg.name, bldg.turnsLeft, bldg.allocation)) if not capital_queued_buildings: - print "No capital queued buildings" - print + debug("No capital queued buildings") + debug('') queued_building_names = [bldg.name for bldg in capital_queued_buildings] if "BLD_AUTO_HISTORY_ANALYSER" in possible_building_types: for pid in find_automatic_historic_analyzer_candidates(): res = fo.issueEnqueueBuildingProductionOrder("BLD_AUTO_HISTORY_ANALYSER", pid) - print "Enqueueing BLD_AUTO_HISTORY_ANALYSER at planet %s - result %d" % (universe.getPlanet(pid), res) + debug("Enqueueing BLD_AUTO_HISTORY_ANALYSER at planet %s - result %d" % (universe.getPlanet(pid), res)) if res: cost, time = empire.productionCostAndTime(production_queue[production_queue.size - 1]) building_expense += cost / time + res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front + debug("Requeueing %s to front of build queue, with result %d" % ("BLD_AUTO_HISTORY_ANALYSER", res)) - if (total_pp > 40 or ((current_turn > 40) and (ColonisationAI.empire_status.get('industrialists', 0) >= 20))) and ("BLD_INDUSTRY_CENTER" in possible_building_types) and ("BLD_INDUSTRY_CENTER" not in (capital_buildings+queued_building_names)) and (building_expense < building_ratio*total_pp): + # TODO: check existence of BLD_INDUSTRY_CENTER (and other buildings) in other locations in case we captured it + if (total_pp > 40 or ((current_turn > 40) and (state.population_with_industry_focus() >= 20))) and ("BLD_INDUSTRY_CENTER" in possible_building_types) and ("BLD_INDUSTRY_CENTER" not in (capital_buildings+queued_building_names)) and (building_expense < building_ratio*total_pp): res = fo.issueEnqueueBuildingProductionOrder("BLD_INDUSTRY_CENTER", homeworld.id) - print "Enqueueing BLD_INDUSTRY_CENTER, with result %d" % res + debug("Enqueueing BLD_INDUSTRY_CENTER, with result %d" % res) if res: cost, time = empire.productionCostAndTime(production_queue[production_queue.size - 1]) building_expense += cost / time @@ -326,73 +332,75 @@ def generate_production_orders(): if ("BLD_SHIPYARD_BASE" in possible_building_types) and ("BLD_SHIPYARD_BASE" not in (capital_buildings + queued_building_names)): try: res = fo.issueEnqueueBuildingProductionOrder("BLD_SHIPYARD_BASE", homeworld.id) - print "Enqueueing BLD_SHIPYARD_BASE, with result %d" % res - except: - print >> sys.stderr, "Can't build shipyard at new capital, probably no population; we're hosed" - print >> sys.stderr, "Exception triggered and caught: ", traceback.format_exc() + debug("Enqueueing BLD_SHIPYARD_BASE, with result %d" % res) + except: # noqa: E722 + warning("Can't build shipyard at new capital, probably no population; we're hosed") for building_name in ["BLD_SHIPYARD_ORG_ORB_INC"]: if (building_name in possible_building_types) and (building_name not in (capital_buildings + queued_building_names)) and (building_expense < building_ratio * total_pp): try: res = fo.issueEnqueueBuildingProductionOrder(building_name, homeworld.id) - print "Enqueueing %s at capital, with result %d" % (building_name, res) + debug("Enqueueing %s at capital, with result %d" % (building_name, res)) if res: cost, time = empire.productionCostAndTime(production_queue[production_queue.size - 1]) building_expense += cost / time res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) - except: - print >> sys.stderr, "Exception triggered and caught: ", traceback.format_exc() + debug("Requeueing %s to front of build queue, with result %d" % (building_name, res)) + except: # noqa: E722 + error("Exception triggered and caught: ", exc_info=True) if ("BLD_IMPERIAL_PALACE" in possible_building_types) and ("BLD_IMPERIAL_PALACE" not in (capital_buildings + queued_building_names)): res = fo.issueEnqueueBuildingProductionOrder("BLD_IMPERIAL_PALACE", homeworld.id) - print "Enqueueing BLD_IMPERIAL_PALACE at %s, with result %d" % (homeworld.name, res) + debug("Enqueueing BLD_IMPERIAL_PALACE at %s, with result %d" % (homeworld.name, res)) if res: res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing BLD_IMPERIAL_PALACE to front of build queue, with result %d" % res + debug("Requeueing BLD_IMPERIAL_PALACE to front of build queue, with result %d" % res) # ok, BLD_NEUTRONIUM_SYNTH is not currently unlockable, but just in case... ;-p if ("BLD_NEUTRONIUM_SYNTH" in possible_building_types) and ("BLD_NEUTRONIUM_SYNTH" not in (capital_buildings + queued_building_names)): res = fo.issueEnqueueBuildingProductionOrder("BLD_NEUTRONIUM_SYNTH", homeworld.id) - print "Enqueueing BLD_NEUTRONIUM_SYNTH, with result %d" % res + debug("Enqueueing BLD_NEUTRONIUM_SYNTH, with result %d" % res) if res: res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing BLD_NEUTRONIUM_SYNTH to front of build queue, with result %d" % res + debug("Requeueing BLD_NEUTRONIUM_SYNTH to front of build queue, with result %d" % res) # TODO: add total_pp checks below, so don't overload queue best_pilot_facilities = ColonisationAI.facilities_by_species_grade.get( "WEAPONS_%.1f" % state.best_pilot_rating, {}) - print "best_pilot_facilities: \n %s" % best_pilot_facilities + debug("best_pilot_facilities: \n %s" % best_pilot_facilities) - max_defense_portion = foAI.foAIstate.character.max_defense_portion() - if foAI.foAIstate.character.check_orbital_production(): + max_defense_portion = aistate.character.max_defense_portion() + if aistate.character.check_orbital_production(): sys_orbital_defenses = {} queued_defenses = {} defense_allocation = 0.0 - target_orbitals = foAI.foAIstate.character.target_number_of_orbitals() - print "Orbital Defense Check -- target Defense Orbitals: ", target_orbitals + target_orbitals = aistate.character.target_number_of_orbitals() + debug("Orbital Defense Check -- target Defense Orbitals: %s" % target_orbitals) for element in production_queue: - if (element.buildType == EmpireProductionTypes.BT_SHIP) and (foAI.foAIstate.get_ship_role(element.designID) == ShipRoleType.BASE_DEFENSE): + if (element.buildType == EmpireProductionTypes.BT_SHIP) and ( + aistate.get_ship_role(element.designID) == ShipRoleType.BASE_DEFENSE): planet = universe.getPlanet(element.locationID) if not planet: - print >> sys.stderr, "Problem getting Planet for build loc %s" % element.locationID + error("Problem getting Planet for build loc %s" % element.locationID) continue sys_id = planet.systemID queued_defenses[sys_id] = queued_defenses.get(sys_id, 0) + element.blocksize*element.remaining defense_allocation += element.allocation - print "Queued Defenses:", [(ppstring(PlanetUtilsAI.sys_name_ids([sys_id])), num) for sys_id, num in queued_defenses.items()] - for sys_id, pids in state.get_empire_inhabited_planets_by_system().items(): - if foAI.foAIstate.systemStatus.get(sys_id, {}).get('fleetThreat', 1) > 0: + debug("Queued Defenses: %s", ppstring([(str(universe.getSystem(sid)), num) + for sid, num in queued_defenses.items()])) + for sys_id, pids in state.get_empire_planets_by_system(include_outposts=False).items(): + if aistate.systemStatus.get(sys_id, {}).get('fleetThreat', 1) > 0: continue # don't build orbital shields if enemy fleet present if defense_allocation > max_defense_portion * total_pp: break sys_orbital_defenses[sys_id] = 0 - fleets_here = foAI.foAIstate.systemStatus.get(sys_id, {}).get('myfleets', []) + fleets_here = aistate.systemStatus.get(sys_id, {}).get('myfleets', []) for fid in fleets_here: - if foAI.foAIstate.get_fleet_role(fid) == MissionType.ORBITAL_DEFENSE: - print "Found %d existing Orbital Defenses in %s :" % (foAI.foAIstate.fleetStatus.get(fid, {}).get('nships', 0), ppstring(PlanetUtilsAI.sys_name_ids([sys_id]))) - sys_orbital_defenses[sys_id] += foAI.foAIstate.fleetStatus.get(fid, {}).get('nships', 0) + if aistate.get_fleet_role(fid) == MissionType.ORBITAL_DEFENSE: + debug("Found %d existing Orbital Defenses in %s :" % ( + aistate.fleetStatus.get(fid, {}).get('nships', 0), universe.getSystem(sys_id))) + sys_orbital_defenses[sys_id] += aistate.fleetStatus.get(fid, {}).get('nships', 0) for pid in pids: sys_orbital_defenses[sys_id] += queued_defenses.get(pid, 0) if sys_orbital_defenses[sys_id] < target_orbitals: @@ -400,10 +408,10 @@ def generate_production_orders(): for pid in pids: best_design_id, col_design, build_choices = get_best_ship_info(PriorityType.PRODUCTION_ORBITAL_DEFENSE, pid) if not best_design_id: - print "no orbital defenses can be built at ", ppstring(PlanetUtilsAI.planet_name_ids([pid])) + debug("no orbital defenses can be built at %s", PlanetUtilsAI.planet_string(pid)) continue retval = fo.issueEnqueueShipProductionOrder(best_design_id, pid) - print "queueing %d Orbital Defenses at %s" % (num_needed, ppstring(PlanetUtilsAI.planet_name_ids([pid]))) + debug("queueing %d Orbital Defenses at %s" % (num_needed, PlanetUtilsAI.planet_string(pid))) if retval != 0: if num_needed > 1: fo.issueChangeProductionQuantityOrder(production_queue.size - 1, 1, num_needed) @@ -417,20 +425,23 @@ def generate_production_orders(): system_colonies = {} colony_systems = {} empire_species = state.get_empire_planets_by_species() - systems_with_species = state.get_empire_inhabited_planets_by_system() + systems_with_species = state.get_empire_planets_by_system(include_outposts=False).keys() for spec_name in ColonisationAI.empire_colonizers: if (len(ColonisationAI.empire_colonizers[spec_name]) == 0) and (spec_name in empire_species): # not enough current shipyards for this species#TODO: also allow orbital incubators and/or asteroid ships for pid in state.get_empire_planets_with_species(spec_name): # SP_EXOBOT may not actually have a colony yet but be in empireColonizers if pid in queued_shipyard_locs: break # won't try building more than one shipyard at once, per colonizer else: # no queued shipyards, get planets with target pop >=3, and queue a shipyard on the one with biggest current pop - planets = map(universe.getPlanet, state.get_empire_planets_with_species(spec_name)) - pops = sorted([(planet.currentMeterValue(fo.meterType.population), planet.id) for planet in planets if (planet and planet.currentMeterValue(fo.meterType.targetPopulation) >= 3.0)]) + planets = (universe.getPlanet(x) for x in state.get_empire_planets_with_species(spec_name)) + pops = sorted( + (planet_.initialMeterValue(fo.meterType.population), planet_.id) for planet_ in planets if + (planet_ and planet_.initialMeterValue(fo.meterType.targetPopulation) >= 3.0) + ) pids = [pid for pop, pid in pops if building_type.canBeProduced(empire.empireID, pid)] if pids: build_loc = pids[-1] res = fo.issueEnqueueBuildingProductionOrder("BLD_SHIPYARD_BASE", build_loc) - print "Enqueueing BLD_SHIPYARD_BASE at planet %d (%s) for colonizer species %s, with result %d" % (build_loc, universe.getPlanet(build_loc).name, spec_name, res) + debug("Enqueueing BLD_SHIPYARD_BASE at planet %d (%s) for colonizer species %s, with result %d" % (build_loc, universe.getPlanet(build_loc).name, spec_name, res)) if res: queued_shipyard_locs.append(build_loc) break # only start at most one new shipyard per species per turn @@ -446,11 +457,11 @@ def generate_production_orders(): if (pid in queued_shipyard_locs) or not building_type.canBeProduced(empire.empireID, pid): continue # but not 'break' because we want to build shipyards at *every* Acirema planet res = fo.issueEnqueueBuildingProductionOrder("BLD_SHIPYARD_BASE", pid) - print "Enqueueing BLD_SHIPYARD_BASE at planet %d (%s) for Acirema, with result %d" % (pid, universe.getPlanet(pid).name, res) + debug("Enqueueing BLD_SHIPYARD_BASE at planet %d (%s) for Acirema, with result %d" % (pid, universe.getPlanet(pid).name, res)) if res: queued_shipyard_locs.append(pid) res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing Acirema BLD_SHIPYARD_BASE to front of build queue, with result %d" % res + debug("Requeueing Acirema BLD_SHIPYARD_BASE to front of build queue, with result %d" % res) top_pilot_systems = {} for pid, _ in ColonisationAI.pilot_ratings.items(): @@ -460,13 +471,13 @@ def generate_production_orders(): if (pid in queued_shipyard_locs) or not building_type.canBeProduced(empire.empireID, pid): continue # but not 'break' because we want to build shipyards all top pilot planets res = fo.issueEnqueueBuildingProductionOrder("BLD_SHIPYARD_BASE", pid) - print "Enqueueing BLD_SHIPYARD_BASE at planet %d (%s) for top pilot, with result %d" % (pid, universe.getPlanet(pid).name, res) + debug("Enqueueing BLD_SHIPYARD_BASE at planet %d (%s) for top pilot, with result %d" % (pid, universe.getPlanet(pid).name, res)) if res: queued_shipyard_locs.append(pid) res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing BLD_SHIPYARD_BASE to front of build queue, with result %d" % res + debug("Requeueing BLD_SHIPYARD_BASE to front of build queue, with result %d" % res) - pop_ctrs = list(AIstate.popCtrIDs) + pop_ctrs = list(state.get_inhabited_planets()) red_popctrs = sorted([(ColonisationAI.pilot_ratings.get(pid, 0), pid) for pid in pop_ctrs if colony_systems.get(pid, INVALID_ID) in AIstate.empireStars.get(fo.starType.red, [])], reverse=True) @@ -493,7 +504,7 @@ def generate_production_orders(): enrgy_shipyard_locs.setdefault(this_planet.systemID, []).append(pid) if pid not in queued_building_locs and building_type.canBeProduced(empire.empireID, pid): res = fo.issueEnqueueBuildingProductionOrder(building_name, pid) - print "Enqueueing %s at planet %s, with result %d" % (building_name, universe.getPlanet(pid).name, res) + debug("Enqueueing %s at planet %s, with result %d" % (building_name, universe.getPlanet(pid).name, res)) if _CHAT_DEBUG: chat_human("Enqueueing %s at planet %s, with result %d" % (building_name, universe.getPlanet(pid), res)) if res: @@ -501,7 +512,7 @@ def generate_production_orders(): cost, time = empire.productionCostAndTime(production_queue[production_queue.size - 1]) building_expense += cost / time # production_queue[production_queue.size -1].blocksize * res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) + debug("Requeueing %s to front of build queue, with result %d", building_name, res) bld_name = "BLD_SHIPYARD_ENRG_SOLAR" queued_bld_locs = [element.locationID for element in production_queue if (element.name == bld_name)] @@ -514,14 +525,14 @@ def generate_production_orders(): continue if bld_type.canBeProduced(empire.empireID, pid): res = fo.issueEnqueueBuildingProductionOrder(bld_name, pid) - print "Enqueueing %s at planet %s, with result %d" % (bld_name, universe.getPlanet(pid), res) + debug("Enqueueing %s at planet %s, with result %d", bld_name, universe.getPlanet(pid), res) if _CHAT_DEBUG: chat_human("Enqueueing %s at planet %s, with result %d" % (bld_name, universe.getPlanet(pid), res)) if res: cost, time = empire.productionCostAndTime(production_queue[production_queue.size - 1]) building_expense += cost / time # production_queue[production_queue.size -1].blocksize * res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (bld_name, res) + debug("Requeueing %s to front of build queue, with result %d", bld_name, res) break building_name = "BLD_SHIPYARD_BASE" @@ -531,13 +542,13 @@ def generate_production_orders(): for pid in enrgy_shipyard_locs[sys_id][:2]: if pid not in queued_shipyard_locs and building_type.canBeProduced(empire.empireID, pid): # TODO: verify that canBeProduced() checks for prexistence of a barring building res = fo.issueEnqueueBuildingProductionOrder(building_name, pid) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, pid, universe.getPlanet(pid).name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, pid, universe.getPlanet(pid).name, res) if res: queued_shipyard_locs.append(pid) cost, time = empire.productionCostAndTime(production_queue[production_queue.size - 1]) building_expense += cost / time # production_queue[production_queue.size -1].blocksize * res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) + debug("Requeueing %s to front of build queue, with result %d", building_name, res) break # only start one per turn for bld_name in ["BLD_SHIPYARD_ORG_ORB_INC"]: @@ -550,7 +561,7 @@ def generate_production_orders(): shipyard_type = fo.getBuildingType("BLD_SHIPYARD_BASE") building_name = "BLD_SHIPYARD_AST" - if empire.buildingTypeAvailable(building_name) and foAI.foAIstate.character.may_build_building(building_name): + if empire.buildingTypeAvailable(building_name) and aistate.character.may_build_building(building_name): queued_building_locs = [element.locationID for element in production_queue if (element.name == building_name)] if not queued_building_locs: building_type = fo.getBuildingType(building_name) @@ -558,7 +569,7 @@ def generate_production_orders(): asteroid_yards = {} shipyard_systems = {} builder_systems = {} - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): + for pid in state.get_all_empire_planets(): planet = universe.getPlanet(pid) this_spec = planet.speciesName sys_id = planet.systemID @@ -588,7 +599,7 @@ def generate_production_orders(): for pid, _ in top_pilot_systems[sys_id]: if pid not in queued_shipyard_locs: # will catch it later if shipyard already present need_yard[sys_id] = pid - if (not yard_locs) and len(asteroid_yards.values()) <= int(current_turn / 50): # not yet building & not enough current locs, find a location to build one + if (not yard_locs) and len(asteroid_yards.values()) <= int(current_turn // 50): # not yet building & not enough current locs, find a location to build one colonizer_loc_choices = [] builder_loc_choices = [] bld_systems = set(asteroid_systems.keys()).difference(asteroid_yards.keys()) @@ -616,30 +627,30 @@ def generate_production_orders(): pid2 = need_yard[sys_id] if shipyard_type.canBeProduced(empire.empireID, pid2): res = fo.issueEnqueueBuildingProductionOrder("BLD_SHIPYARD_BASE", pid2) - print "Enqueueing %s at planet %d (%s) to go with Asteroid Processor , with result %d" % ("BLD_SHIPYARD_BASE", pid2, universe.getPlanet(pid2).name, res) + debug("Enqueueing %s at planet %d (%s) to go with Asteroid Processor , with result %d", "BLD_SHIPYARD_BASE", pid2, universe.getPlanet(pid2).name, res) if res: queued_shipyard_locs.append(pid2) cost, time = empire.productionCostAndTime(production_queue[production_queue.size - 1]) building_expense += cost / time # production_queue[production_queue.size -1].blocksize * res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % ("BLD_SHIPYARD_BASE", res) + debug("Requeueing %s to front of build queue, with result %d", "BLD_SHIPYARD_BASE", res) if pid not in queued_building_locs and building_type.canBeProduced(empire.empireID, pid): res = fo.issueEnqueueBuildingProductionOrder(building_name, pid) - print "Enqueueing %s at planet %d (%s) , with result %d on turn %d" % (building_name, pid, universe.getPlanet(pid).name, res, current_turn) + debug("Enqueueing %s at planet %d (%s) , with result %d on turn %d", building_name, pid, universe.getPlanet(pid).name, res, current_turn) if res: new_yard_count += 1 queued_building_locs.append(pid) cost, time = empire.productionCostAndTime(production_queue[production_queue.size - 1]) building_expense += cost / time # production_queue[production_queue.size -1].blocksize * res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) + debug("Requeueing %s to front of build queue, with result %d", building_name, res) building_name = "BLD_GAS_GIANT_GEN" max_gggs = 1 - if empire.buildingTypeAvailable(building_name) and foAI.foAIstate.character.may_build_building(building_name): + if empire.buildingTypeAvailable(building_name) and aistate.character.may_build_building(building_name): queued_building_locs = [element.locationID for element in production_queue if (element.name == building_name)] building_type = fo.getBuildingType(building_name) - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): # TODO: check to ensure that a resource center exists in system, or GGG would be wasted + for pid in state.get_all_empire_planets(): # TODO: check to ensure that a resource center exists in system, or GGG would be wasted if pid not in queued_building_locs and building_type.canBeProduced(empire.empireID, pid): # TODO: verify that canBeProduced() checks for preexistence of a barring building planet = universe.getPlanet(pid) if planet.systemID in systems_with_species: @@ -650,7 +661,7 @@ def generate_production_orders(): other_planet = universe.getPlanet(opid) if other_planet.size == fo.planetSize.gasGiant: gg_list.append(opid) - if opid != pid and other_planet.owner == empire.empireID and (FocusType.FOCUS_INDUSTRY in list(other_planet.availableFoci) + [other_planet.focus]): + if other_planet.owner == empire.empireID and (FocusType.FOCUS_INDUSTRY in list(other_planet.availableFoci) + [other_planet.focus]): can_use_gg = True if pid in sorted(gg_list)[:max_gggs] and can_use_gg: res = fo.issueEnqueueBuildingProductionOrder(building_name, pid) @@ -659,13 +670,13 @@ def generate_production_orders(): cost, time = empire.productionCostAndTime(production_queue[production_queue.size - 1]) building_expense += cost / time # production_queue[production_queue.size -1].blocksize * res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, pid, universe.getPlanet(pid).name, res) + debug("Requeueing %s to front of build queue, with result %d", building_name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, pid, universe.getPlanet(pid).name, res) building_name = "BLD_SOL_ORB_GEN" - if empire.buildingTypeAvailable(building_name) and foAI.foAIstate.character.may_build_building(building_name): + if empire.buildingTypeAvailable(building_name) and aistate.character.may_build_building(building_name): already_got_one = 99 - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): + for pid in state.get_all_empire_planets(): planet = universe.getPlanet(pid) if planet and building_name in [bld.buildingTypeName for bld in map(universe.getBuilding, planet.buildingIDs)]: system = universe.getSystem(planet.systemID) @@ -705,31 +716,31 @@ def generate_production_orders(): continue try: distance_map[sys_id] = universe.jumpDistance(homeworld.systemID, sys_id) - except: + except: # noqa: E722 pass use_sys = ([(-1, INVALID_ID)] + sorted([(dist, sys_id) for sys_id, dist in distance_map.items()]))[:2][-1][-1] # kinda messy, but ensures a value if use_sys != INVALID_ID: try: - use_loc = AIstate.colonizedSystems[use_sys][0] + use_loc = state.get_empire_planets_by_system(use_sys)[0] res = fo.issueEnqueueBuildingProductionOrder(building_name, use_loc) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, use_loc, universe.getPlanet(use_loc).name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, use_loc, universe.getPlanet(use_loc).name, res) if res: cost, time = empire.productionCostAndTime(production_queue[production_queue.size - 1]) building_expense += cost / time # production_queue[production_queue.size -1].blocksize * res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) - except: - print "problem queueing BLD_SOL_ORB_GEN at planet", use_loc, "of system ", use_sys + debug("Requeueing %s to front of build queue, with result %d", building_name, res) + except: # noqa: E722 + debug("problem queueing BLD_SOL_ORB_GEN at planet %s of system", use_loc, use_sys) pass building_name = "BLD_ART_BLACK_HOLE" if ( empire.buildingTypeAvailable(building_name) and - foAI.foAIstate.character.may_build_building(building_name) and + aistate.character.may_build_building(building_name) and len(AIstate.empireStars.get(fo.starType.red, [])) > 0 ): already_got_one = False - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): + for pid in state.get_all_empire_planets(): planet = universe.getPlanet(pid) if planet and building_name in [bld.buildingTypeName for bld in map(universe.getBuilding, planet.buildingIDs)]: already_got_one = True # has been built, needs one turn to activate @@ -737,18 +748,18 @@ def generate_production_orders(): if not bh_pilots and len(queued_building_locs) == 0 and (red_pilots or not already_got_one): use_loc = None nominal_home = homeworld or universe.getPlanet( - (red_pilots + AIstate.colonizedSystems[AIstate.empireStars[fo.starType.red][0]])[0]) + (red_pilots + state.get_empire_planets_by_system(AIstate.empireStars[fo.starType.red][0]))[0]) distance_map = {} for sys_id in AIstate.empireStars.get(fo.starType.red, []): if sys_id == INVALID_ID: continue try: distance_map[sys_id] = universe.jumpDistance(nominal_home.systemID, sys_id) - except: + except: # noqa: E722 pass red_sys_list = sorted([(dist, sys_id) for sys_id, dist in distance_map.items()]) for dist, sys_id in red_sys_list: - for loc in AIstate.colonizedSystems[sys_id]: + for loc in state.get_empire_planets_by_system(sys_id): planet = universe.getPlanet(loc) if planet and planet.speciesName not in ["", None]: species = fo.getSpecies(planet.speciesName) @@ -756,7 +767,8 @@ def generate_production_orders(): break else: use_loc = list( - set(red_pilots).intersection(AIstate.colonizedSystems[sys_id]) or AIstate.colonizedSystems[sys_id] + set(red_pilots).intersection(state.get_empire_planets_by_system(sys_id)) + or state.get_empire_planets_by_system(sys_id) )[0] if use_loc is not None: break @@ -764,19 +776,19 @@ def generate_production_orders(): planet_used = universe.getPlanet(use_loc) try: res = fo.issueEnqueueBuildingProductionOrder(building_name, use_loc) - print "Enqueueing %s at planet %s , with result %d" % (building_name, planet_used, res) + debug("Enqueueing %s at planet %s , with result %d", building_name, planet_used, res) if res: if _CHAT_DEBUG: chat_human("Enqueueing %s at planet %s , with result %d" % (building_name, planet_used, res)) res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) - except: - print "problem queueing %s at planet %s" % (building_name, planet_used) + debug("Requeueing %s to front of build queue, with result %d", building_name, res) + except: # noqa: E722 + debug("problem queueing %s at planet %s" % (building_name, planet_used)) building_name = "BLD_BLACK_HOLE_POW_GEN" - if empire.buildingTypeAvailable(building_name) and foAI.foAIstate.character.may_build_building(building_name): + if empire.buildingTypeAvailable(building_name) and aistate.character.may_build_building(building_name): already_got_one = False - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): + for pid in state.get_all_empire_planets(): planet = universe.getPlanet(pid) if planet and building_name in [bld.buildingTypeName for bld in map(universe.getBuilding, planet.buildingIDs)]: already_got_one = True @@ -791,25 +803,25 @@ def generate_production_orders(): continue try: distance_map[sys_id] = universe.jumpDistance(homeworld.systemID, sys_id) - except: + except: # noqa: E722 pass use_sys = ([(-1, INVALID_ID)] + sorted([(dist, sys_id) for sys_id, dist in distance_map.items()]))[:2][-1][-1] # kinda messy, but ensures a value if use_sys != INVALID_ID: try: - use_loc = AIstate.colonizedSystems[use_sys][0] + use_loc = state.get_empire_planets_by_system(use_sys)[0] res = fo.issueEnqueueBuildingProductionOrder(building_name, use_loc) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, use_loc, universe.getPlanet(use_loc).name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, use_loc, universe.getPlanet(use_loc).name, res) if res: res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) - except: - print "problem queueing BLD_BLACK_HOLE_POW_GEN at planet", use_loc, "of system ", use_sys + debug("Requeueing %s to front of build queue, with result %d" % (building_name, res)) + except: # noqa: E722 + warning("problem queueing BLD_BLACK_HOLE_POW_GEN at planet %s of system %s", use_loc, use_sys) pass building_name = "BLD_ENCLAVE_VOID" if empire.buildingTypeAvailable(building_name): already_got_one = False - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): + for pid in state.get_all_empire_planets(): planet = universe.getPlanet(pid) if planet and building_name in [bld.buildingTypeName for bld in map(universe.getBuilding, planet.buildingIDs)]: already_got_one = True @@ -817,17 +829,17 @@ def generate_production_orders(): if len(queued_locs) == 0 and homeworld and not already_got_one: try: res = fo.issueEnqueueBuildingProductionOrder(building_name, capital_id) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, capital_id, universe.getPlanet(capital_id).name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, capital_id, universe.getPlanet(capital_id).name, res) if res: res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) - except: + debug("Requeueing %s to front of build queue, with result %d", building_name, res) + except: # noqa: E722 pass building_name = "BLD_GENOME_BANK" if empire.buildingTypeAvailable(building_name): already_got_one = False - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): + for pid in state.get_all_empire_planets(): planet = universe.getPlanet(pid) if planet and building_name in [bld.buildingTypeName for bld in map(universe.getBuilding, planet.buildingIDs)]: already_got_one = True @@ -835,11 +847,11 @@ def generate_production_orders(): if len(queued_locs) == 0 and homeworld and not already_got_one: try: res = fo.issueEnqueueBuildingProductionOrder(building_name, capital_id) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, capital_id, universe.getPlanet(capital_id).name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, capital_id, universe.getPlanet(capital_id).name, res) if res: res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) - except: + debug("Requeueing %s to front of build queue, with result %d", building_name, res) + except: # noqa: E722 pass building_name = "BLD_NEUTRONIUM_EXTRACTOR" @@ -850,7 +862,7 @@ def generate_production_orders(): AIstate.empireStars.get(fo.starType.neutron, []) ): # building_type = fo.getBuildingType(building_name) - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): + for pid in state.get_all_empire_planets(): planet = universe.getPlanet(pid) if planet: building_names = [bld.buildingTypeName for bld in map(universe.getBuilding, planet.buildingIDs)] @@ -869,20 +881,20 @@ def generate_production_orders(): continue try: distance_map[sys_id] = universe.jumpDistance(homeworld.systemID, sys_id) - except Exception as e: - error(e, exc_info=True) - print ([INVALID_ID] + sorted([(dist, sys_id) for sys_id, dist in distance_map.items()])) + except Exception: + warning("Could not get jump distance from %d to %d", homeworld.systemID, sys_id, exc_info=True) + debug([INVALID_ID] + sorted([(dist, sys_id) for sys_id, dist in distance_map.items()])) use_sys = ([(-1, INVALID_ID)] + sorted([(dist, sys_id) for sys_id, dist in distance_map.items()]))[:2][-1][-1] # kinda messy, but ensures a value if use_sys != INVALID_ID: try: - use_loc = AIstate.colonizedSystems[use_sys][0] + use_loc = state.get_empire_planets_by_system(use_sys)[0] res = fo.issueEnqueueBuildingProductionOrder(building_name, use_loc) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, use_loc, universe.getPlanet(use_loc).name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, use_loc, universe.getPlanet(use_loc).name, res) if res: res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) - except: - print "problem queueing BLD_NEUTRONIUM_EXTRACTOR at planet", use_loc, "of system ", use_sys + debug("Requeueing %s to front of build queue, with result %d", building_name, res) + except: # noqa: E722 + warning("problem queueing BLD_NEUTRONIUM_EXTRACTOR at planet %s of system %s" % (use_loc, use_sys)) pass bld_name = "BLD_SHIPYARD_CON_GEOINT" @@ -910,19 +922,19 @@ def generate_production_orders(): continue for shipID in fleet.shipIDs: ship = universe.getShip(shipID) - if ship and (foAI.foAIstate.get_ship_role(ship.design.id) == ShipRoleType.CIVILIAN_COLONISATION): + if ship and (aistate.get_ship_role(ship.design.id) == ShipRoleType.CIVILIAN_COLONISATION): colony_ship_map.setdefault(ship.speciesName, []).append(1) building_name = "BLD_CONC_CAMP" verbose_camp = False building_type = fo.getBuildingType(building_name) - for pid in AIstate.popCtrIDs: + for pid in state.get_inhabited_planets(): planet = universe.getPlanet(pid) if not planet: continue can_build_camp = building_type.canBeProduced(empire.empireID, pid) and empire.buildingTypeAvailable(building_name) - t_pop = planet.currentMeterValue(fo.meterType.targetPopulation) - c_pop = planet.currentMeterValue(fo.meterType.population) + t_pop = planet.initialMeterValue(fo.meterType.targetPopulation) + c_pop = planet.initialMeterValue(fo.meterType.population) t_ind = planet.currentMeterValue(fo.meterType.targetIndustry) c_ind = planet.currentMeterValue(fo.meterType.industry) pop_disqualified = (c_pop <= 32) or (c_pop < 0.9*t_pop) @@ -933,22 +945,22 @@ def generate_production_orders(): if can_build_camp: if pop_disqualified: if verbose_camp: - print "Conc Camp disqualified at %s due to low pop: current %.1f target: %.1f" % (planet.name, c_pop, t_pop) + debug("Conc Camp disqualified at %s due to low pop: current %.1f target: %.1f", planet.name, c_pop, t_pop) else: if verbose_camp: - print "Conc Camp disqualified at %s due to safety margin; species %s, colonizing planets %s, with %d colony ships" % (planet.name, planet.speciesName, state.get_empire_planets_with_species(planet.speciesName), len(colony_ship_map.get(planet.speciesName, []))) + debug("Conc Camp disqualified at %s due to safety margin; species %s, colonizing planets %s, with %d colony ships", planet.name, planet.speciesName, state.get_empire_planets_with_species(planet.speciesName), len(colony_ship_map.get(planet.speciesName, []))) for bldg in planet.buildingIDs: if universe.getBuilding(bldg).buildingTypeName == building_name: res = fo.issueScrapOrder(bldg) - print "Tried scrapping %s at planet %s, got result %d" % (building_name, planet.name, res) - elif foAI.foAIstate.character.may_build_building(building_name) and can_build_camp and (t_pop >= 36): - if (planet.focus == FocusType.FOCUS_GROWTH) or ("COMPUTRONIUM_SPECIAL" in planet.specials) or (pid == capital_id): + debug("Tried scrapping %s at planet %s, got result %d", building_name, planet.name, res) + elif aistate.character.may_build_building(building_name) and can_build_camp and (t_pop >= 36): + if (planet.focus == FocusType.FOCUS_GROWTH) or (AIDependencies.COMPUTRONIUM_SPECIAL in planet.specials) or (pid == capital_id): continue # pass # now that focus setting takes these into account, probably works ok to have conc camp, but let's not push it queued_building_locs = [element.locationID for element in production_queue if (element.name == building_name)] if c_pop < 0.95 * t_pop: if verbose_camp: - print "Conc Camp disqualified at %s due to pop: current %.1f target: %.1f" % (planet.name, c_pop, t_pop) + debug("Conc Camp disqualified at %s due to pop: current %.1f target: %.1f", planet.name, c_pop, t_pop) else: if pid not in queued_building_locs: if planet.focus in [FocusType.FOCUS_INDUSTRY]: @@ -965,40 +977,40 @@ def generate_production_orders(): continue res = fo.issueEnqueueBuildingProductionOrder(building_name, pid) built_camp = res - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, pid, universe.getPlanet(pid).name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, pid, universe.getPlanet(pid).name, res) if res: queued_building_locs.append(pid) fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front else: # TODO: enable location condition reporting a la mapwnd BuildDesignatorWnd - print >> sys.stderr, "Enqueing Conc Camp at %s despite building_type.canBeProduced(empire.empireID, pid) reporting " % planet.name, can_build_camp + warning("Enqueing Conc Camp at %s despite building_type.canBeProduced(empire.empireID, pid) reporting %s" % (planet, can_build_camp)) if verbose_camp: - print "conc camp status at %s : checkedCamp: %s, built_camp: %s" % (planet.name, can_build_camp, built_camp) + debug("conc camp status at %s : checkedCamp: %s, built_camp: %s", planet.name, can_build_camp, built_camp) building_name = "BLD_SCANNING_FACILITY" if empire.buildingTypeAvailable(building_name): queued_locs = [element.locationID for element in production_queue if (element.name == building_name)] scanner_locs = {} - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): + for pid in state.get_all_empire_planets(): planet = universe.getPlanet(pid) if planet: if (pid in queued_locs) or (building_name in [bld.buildingTypeName for bld in map(universe.getBuilding, planet.buildingIDs)]): scanner_locs[planet.systemID] = True max_scanner_builds = max(1, int(empire.productionPoints / 30)) - for sys_id in AIstate.colonizedSystems: + for sys_id in state.get_empire_planets_by_system().keys(): if len(queued_locs) >= max_scanner_builds: break if sys_id in scanner_locs: continue need_scanner = False - for nSys in dict_from_map(universe.getSystemNeighborsMap(sys_id, empire.empireID)): + for nSys in universe.getImmediateNeighbors(sys_id, empire.empireID): if universe.getVisibility(nSys, empire.empireID) < fo.visibility.partial: need_scanner = True break if not need_scanner: continue build_locs = [] - for pid in AIstate.colonizedSystems[sys_id]: + for pid in state.get_empire_planets_by_system(sys_id): planet = universe.getPlanet(pid) if not planet: continue @@ -1008,10 +1020,10 @@ def generate_production_orders(): for troops, loc in sorted(build_locs): planet = universe.getPlanet(loc) res = fo.issueEnqueueBuildingProductionOrder(building_name, loc) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, loc, planet.name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, loc, planet.name, res) if res: res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) + debug("Requeueing %s to front of build queue, with result %d", building_name, res) queued_locs.append(planet.systemID) break @@ -1023,22 +1035,23 @@ def generate_production_orders(): dd_planet = universe.getPlanet(pid) if dd_planet: queued_sys.add(dd_planet.systemID) - cur_drydoc_sys = set(ColonisationAI.empire_dry_docks.keys()).union(queued_sys) + cur_drydoc_sys = set(state.get_empire_drydocks().keys()).union(queued_sys) covered_drydoc_locs = set() for start_set, dest_set in [(cur_drydoc_sys, covered_drydoc_locs), (covered_drydoc_locs, covered_drydoc_locs)]: # coverage of neighbors up to 2 jumps away from a drydock for dd_sys_id in start_set.copy(): dest_set.add(dd_sys_id) - dd_neighbors = dict_from_map(universe.getSystemNeighborsMap(dd_sys_id, empire.empireID)) - dest_set.update(dd_neighbors.keys()) + neighbors = universe.getImmediateNeighbors(dd_sys_id, empire.empireID) + dest_set.update(neighbors) max_dock_builds = int(0.8 + empire.productionPoints/120.0) - print "Considering building %s, found current and queued systems %s" % (building_name, ppstring(PlanetUtilsAI.sys_name_ids(cur_drydoc_sys.union(queued_sys)))) - for sys_id, pids in state.get_empire_inhabited_planets_by_system().items(): # TODO: sort/prioritize in some fashion + debug("Considering building %s, found current and queued systems %s", + building_name, PlanetUtilsAI.sys_name_ids(cur_drydoc_sys.union(queued_sys))) + for sys_id, pids in state.get_empire_planets_by_system(include_outposts=False).items(): # TODO: sort/prioritize in some fashion local_top_pilots = dict(top_pilot_systems.get(sys_id, [])) - local_drydocks = ColonisationAI.empire_dry_docks.get(sys_id, []) + local_drydocks = state.get_empire_drydocks().get(sys_id, []) if len(queued_locs) >= max_dock_builds: - print "Drydock enqueing halted with %d of max %d" % (len(queued_locs), max_dock_builds) + debug("Drydock enqueing halted with %d of max %d", len(queued_locs), max_dock_builds) break if (sys_id in covered_drydoc_locs) and not local_top_pilots: continue @@ -1051,37 +1064,43 @@ def generate_production_orders(): break planet = universe.getPlanet(pid) res = fo.issueEnqueueBuildingProductionOrder(building_name, pid) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, pid, planet.name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, pid, planet.name, res) if res: queued_locs.append(planet.systemID) covered_drydoc_locs.add(planet.systemID) - dd_neighbors = dict_from_map(universe.getSystemNeighborsMap(planet.systemID, empire.empireID)) - covered_drydoc_locs.update(dd_neighbors.keys()) + neighboring_systems = universe.getImmediateNeighbors(planet.systemID, empire.empireID) + covered_drydoc_locs.update(neighboring_systems) if max_dock_builds >= 2: res = fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) + debug("Requeueing %s to front of build queue, with result %d", building_name, res) break else: - print >> sys.stderr, "Failed enqueueing %s at planet %d (%s) , with result %d" % (building_name, pid, planet.name, res) + warning("Failed enqueueing %s at %s, with result %d" % (building_name, planet, res)) building_name = "BLD_XENORESURRECTION_LAB" queued_xeno_lab_locs = [element.locationID for element in production_queue if element.name == building_name] - for pid in list(AIstate.popCtrIDs)+list(AIstate.outpostIDs): + for pid in state.get_all_empire_planets(): if pid in queued_xeno_lab_locs or not empire.canBuild(fo.buildType.building, building_name, pid): continue res = fo.issueEnqueueBuildingProductionOrder(building_name, pid) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, pid, planet.name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, pid, planet.name, res) if res: res = fo.issueRequeueProductionOrder(production_queue.size-1, 2) # move to near front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) + debug("Requeueing %s to front of build queue, with result %d", building_name, res) break else: - print >> sys.stderr, "Failed enqueueing %s at planet %d (%s) , with result %d" % (building_name, pid, planet.name, res) - - queued_clny_bld_locs = [element.locationID for element in production_queue if element.name.startswith('BLD_COL_')] - colony_bldg_entries = ([entry for entry in foAI.foAIstate.colonisablePlanetIDs.items() if entry[1][0] > 60 and - entry[0] not in queued_clny_bld_locs and entry[0] in ColonisationAI.empire_outpost_ids] - [:PriorityAI.allottedColonyTargets+2]) + warning("Failed enqueueing %s at planet %s, got result %d", building_name, planet, res) + + # ignore acquired-under-construction colony buildings for which our empire lacks the species + queued_clny_bld_locs = [element.locationID for element in production_queue + if (element.name.startswith('BLD_COL_') and + empire_has_colony_bld_species(element.name))] + colony_bldg_entries = [entry for entry in aistate.colonisablePlanetIDs.items() if + entry[1][0] > 60 and + entry[0] not in queued_clny_bld_locs and + entry[0] in state.get_empire_outposts() and + not already_has_completed_colony_building(entry[0])] + colony_bldg_entries = colony_bldg_entries[:PriorityAI.allottedColonyTargets + 2] for entry in colony_bldg_entries: pid = entry[0] building_name = "BLD_COL_" + entry[1][1][3:] @@ -1090,41 +1109,41 @@ def generate_production_orders(): if not (building_type and building_type.canBeEnqueued(empire.empireID, pid)): continue res = fo.issueEnqueueBuildingProductionOrder(building_name, pid) - print "Enqueueing %s at planet %d (%s) , with result %d" % (building_name, pid, planet.name, res) + debug("Enqueueing %s at planet %d (%s) , with result %d", building_name, pid, planet.name, res) if res: res = fo.issueRequeueProductionOrder(production_queue.size - 1, 2) # move to near front - print "Requeueing %s to front of build queue, with result %d" % (building_name, res) + debug("Requeueing %s to front of build queue, with result %d", building_name, res) break else: - print >> sys.stderr, "Failed enqueueing %s at planet %d (%s) , with result %d" % (building_name, pid, planet.name, res) + warning("Failed enqueueing %s at planet %s, got result %d" % (building_name, planet, res)) - building_name = "BLD_EVACUATION" - for pid in AIstate.popCtrIDs: + buildings_to_scrap = ("BLD_EVACUATION", "BLD_GATEWAY_VOID") + for pid in state.get_inhabited_planets(): planet = universe.getPlanet(pid) if not planet: continue for bldg in planet.buildingIDs: - if universe.getBuilding(bldg).buildingTypeName == building_name: + building_name = universe.getBuilding(bldg).buildingTypeName + if building_name in buildings_to_scrap: res = fo.issueScrapOrder(bldg) - print "Tried scrapping %s at planet %s, got result %d" % (building_name, planet.name, res) + debug("Tried scrapping %s at planet %s, got result %d", building_name, planet.name, res) total_pp_spent = fo.getEmpire().productionQueue.totalSpent - print " Total Production Points Spent: %s" % total_pp_spent + debug(" Total Production Points Spent: %s", total_pp_spent) wasted_pp = max(0, total_pp - total_pp_spent) - print " Wasted Production Points: %s" % wasted_pp # TODO: add resource group analysis + debug(" Wasted Production Points: %s", wasted_pp) # TODO: add resource group analysis avail_pp = total_pp - total_pp_spent - 0.0001 - print + debug('') if False: - print "Possible ship designs to build:" + debug("Possible ship designs to build:") if homeworld: for ship_design_id in empire.availableShipDesigns: design = fo.getShipDesign(ship_design_id) - print " %s cost: %s time: %s" % (design.name, - design.productionCost(empire.empireID, homeworld.id), - design.productionTime(empire.empireID, homeworld.id)) - print + debug(" %s cost: %s time: %s", design.name, design.productionCost(empire.empireID, homeworld.id), + design.productionTime(empire.empireID, homeworld.id)) + debug('') production_queue = empire.productionQueue queued_colony_ships = {} queued_outpost_ships = 0 @@ -1137,17 +1156,17 @@ def generate_production_orders(): for queue_index in range(len(production_queue)): element = production_queue[queue_index] block_str = "%d x " % element.blocksize # ["a single ", "in blocks of %d "%element.blocksize][element.blocksize>1] - print " %s%s requiring %s more turns; alloc: %.2f PP with cum. progress of %.1f being built at %s" % ( - block_str, element.name, element.turnsLeft, element.allocation, - element.progress, universe.getObject(element.locationID).name) + debug(" %s%s requiring %s more turns; alloc: %.2f PP with cum. progress of %.1f being built at %s", + block_str, element.name, element.turnsLeft, element.allocation, + element.progress, universe.getObject(element.locationID).name) if element.turnsLeft == -1: - if element.locationID not in AIstate.popCtrIDs + AIstate.outpostIDs: + if element.locationID not in state.get_all_empire_planets(): # dequeue_list.append(queue_index) #TODO add assessment of recapture -- invasion target etc. - print "element %s will never be completed as stands and location %d no longer owned; could consider deleting from queue" % (element.name, element.locationID) # TODO: + debug("element %s will never be completed as stands and location %d no longer owned; could consider deleting from queue", element.name, element.locationID) # TODO: else: - print "element %s is projected to never be completed as currently stands, but will remain on queue " % element.name + debug("element %s is projected to never be completed as currently stands, but will remain on queue ", element.name) elif element.buildType == EmpireProductionTypes.BT_SHIP: - this_role = foAI.foAIstate.get_ship_role(element.designID) + this_role = aistate.get_ship_role(element.designID) if this_role == ShipRoleType.CIVILIAN_COLONISATION: this_spec = universe.getPlanet(element.locationID).speciesName queued_colony_ships[this_spec] = queued_colony_ships.get(this_spec, 0) + element.remaining * element.blocksize @@ -1161,55 +1180,53 @@ def generate_production_orders(): if len(AIstate.opponentPlanetIDs) > 0: can_prioritize_troops = True if queued_colony_ships: - print "\nFound colony ships in build queue: %s" % queued_colony_ships + debug("\nFound colony ships in build queue: %s", queued_colony_ships) if queued_outpost_ships: - print "\nFound outpost ships and bases in build queue: %s" % queued_outpost_ships + debug("\nFound outpost ships and bases in build queue: %s", queued_outpost_ships) for queue_index in dequeue_list[::-1]: fo.issueDequeueProductionOrder(queue_index) all_military_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.MILITARY) - total_military_ships = sum([foAI.foAIstate.fleetStatus.get(fid, {}).get('nships', 0) for fid in all_military_fleet_ids]) + total_military_ships = sum([aistate.fleetStatus.get(fid, {}).get('nships', 0) for fid in all_military_fleet_ids]) all_troop_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role(MissionType.INVASION) - total_troop_ships = sum([foAI.foAIstate.fleetStatus.get(fid, {}).get('nships', 0) for fid in all_troop_fleet_ids]) + total_troop_ships = sum([aistate.fleetStatus.get(fid, {}).get('nships', 0) for fid in all_troop_fleet_ids]) avail_troop_fleet_ids = list(FleetUtilsAI.extract_fleet_ids_without_mission_types(all_troop_fleet_ids)) - total_available_troops = sum([foAI.foAIstate.fleetStatus.get(fid, {}).get('nships', 0) for fid in avail_troop_fleet_ids]) - print "Trooper Status turn %d: %d total, with %d unassigned. %d queued, compared to %d total Military Attack Ships" % (current_turn, total_troop_ships, - total_available_troops, queued_troop_ships, total_military_ships) + total_available_troops = sum([aistate.fleetStatus.get(fid, {}).get('nships', 0) for fid in avail_troop_fleet_ids]) + debug("Trooper Status turn %d: %d total, with %d unassigned. %d queued, compared to %d total Military Attack Ships", + current_turn, total_troop_ships, total_available_troops, queued_troop_ships, total_military_ships) if ( capital_id is not None and (current_turn >= 40 or can_prioritize_troops) and - foAI.foAIstate.systemStatus.get(capital_system_id, {}).get('fleetThreat', 0) == 0 and - foAI.foAIstate.systemStatus.get(capital_system_id, {}).get('neighborThreat', 0) == 0 + aistate.systemStatus.get(capital_system_id, {}).get('fleetThreat', 0) == 0 and + aistate.systemStatus.get(capital_system_id, {}).get('neighborThreat', 0) == 0 ): best_design_id, best_design, build_choices = get_best_ship_info(PriorityType.PRODUCTION_INVASION) if build_choices is not None and len(build_choices) > 0: loc = random.choice(build_choices) prod_time = best_design.productionTime(empire.empireID, loc) prod_cost = best_design.productionCost(empire.empireID, loc) - troopers_needed = max(0, int(min(0.99 + (current_turn/20.0 - total_available_troops)/max(2, prod_time - 1), total_military_ships/3 - total_troop_ships))) + troopers_needed = max(0, int(min(0.99 + (current_turn/20.0 - total_available_troops)/max(2, prod_time - 1), total_military_ships//3 - total_troop_ships))) ship_number = troopers_needed per_turn_cost = (float(prod_cost) / prod_time) - if troopers_needed > 0 and total_pp > 3*per_turn_cost*queued_troop_ships and foAI.foAIstate.character.may_produce_troops(): + if troopers_needed > 0 and total_pp > 3*per_turn_cost*queued_troop_ships and aistate.character.may_produce_troops(): retval = fo.issueEnqueueShipProductionOrder(best_design_id, loc) if retval != 0: - print "forcing %d new ship(s) to production queue: %s; per turn production cost %.1f" % (ship_number, best_design.name, ship_number*per_turn_cost) - print + debug("forcing %d new ship(s) to production queue: %s; per turn production cost %.1f\n", ship_number, best_design.name, ship_number*per_turn_cost) if ship_number > 1: fo.issueChangeProductionQuantityOrder(production_queue.size - 1, 1, ship_number) avail_pp -= ship_number * per_turn_cost fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front fo.updateProductionQueue() - print + debug('') - print + debug('') # get the highest production priorities production_priorities = {} for priority_type in get_priority_production_types(): - production_priorities[priority_type] = int(max(0, (foAI.foAIstate.get_priority(priority_type)) ** 0.5)) + production_priorities[priority_type] = int(max(0, (aistate.get_priority(priority_type)) ** 0.5)) - sorted_priorities = production_priorities.items() - sorted_priorities.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) + sorted_priorities = sorted(production_priorities.items(), key=itemgetter(1), reverse=True) top_score = -1 @@ -1224,7 +1241,7 @@ def generate_production_orders(): _, _, colony_build_choices = get_best_ship_info(PriorityType.PRODUCTION_COLONISATION) military_emergency = PriorityAI.unmetThreat > (2.0 * MilitaryAI.get_tot_mil_rating()) - print "Production Queue Priorities:" + debug("Production Queue Priorities:") filtered_priorities = {} for priority_id, score in sorted_priorities: if military_emergency: @@ -1234,7 +1251,7 @@ def generate_production_orders(): score /= 2.0 if top_score < score: top_score = score # don't really need top_score nor sorting with current handling - print " Score: %4d -- %s " % (score, priority_id) + debug(" Score: %4d -- %s ", score, priority_id) if priority_id != PriorityType.PRODUCTION_BUILDINGS: if (priority_id == PriorityType.PRODUCTION_COLONISATION) and (total_colony_fleets < max_colony_fleets) and (colony_build_choices is not None) and len(colony_build_choices) > 0: filtered_priorities[priority_id] = score @@ -1243,7 +1260,7 @@ def generate_production_orders(): elif priority_id not in [PriorityType.PRODUCTION_OUTPOST, PriorityType.PRODUCTION_COLONISATION]: filtered_priorities[priority_id] = score if filtered_priorities == {}: - print "No non-building-production priorities with nonzero score, setting to default: Military" + debug("No non-building-production priorities with nonzero score, setting to default: Military") filtered_priorities[PriorityType.PRODUCTION_MILITARY] = 1 if top_score <= 100: scaling_power = 1.0 @@ -1255,30 +1272,30 @@ def generate_production_orders(): available_pp = dict([(tuple(el.key()), el.data()) for el in empire.planetsWithAvailablePP]) # keys are sets of ints; data is doubles allocated_pp = dict([(tuple(el.key()), el.data()) for el in empire.planetsWithAllocatedPP]) # keys are sets of ints; data is doubles planets_with_wasted_pp = set([tuple(pidset) for pidset in empire.planetsWithWastedPP]) - print "avail_pp ( : pp ):" + debug("avail_pp ( : pp ):") for planet_set in available_pp: - print "\t%s\t%.2f" % (ppstring(PlanetUtilsAI.sys_name_ids(set(PlanetUtilsAI.get_systems(planet_set)))), available_pp[planet_set]) - print "\nallocated_pp ( : pp ):" + debug("\t%s\t%.2f", PlanetUtilsAI.sys_name_ids(set(PlanetUtilsAI.get_systems(planet_set))), available_pp[planet_set]) + debug("\nallocated_pp ( : pp ):") for planet_set in allocated_pp: - print "\t%s\t%.2f" % (ppstring(PlanetUtilsAI.sys_name_ids(set(PlanetUtilsAI.get_systems(planet_set)))), allocated_pp[planet_set]) + debug("\t%s\t%.2f", PlanetUtilsAI.sys_name_ids(set(PlanetUtilsAI.get_systems(planet_set))), allocated_pp[planet_set]) - print "\n\nBuilding Ships in system groups with remaining PP:" + debug("\n\nBuilding Ships in system groups with remaining PP:") for planet_set in planets_with_wasted_pp: total_pp = available_pp.get(planet_set, 0) avail_pp = total_pp - allocated_pp.get(planet_set, 0) if avail_pp <= 0.01: continue - print "%.2f PP remaining in system group: %s" % (avail_pp, ppstring(PlanetUtilsAI.sys_name_ids(set(PlanetUtilsAI.get_systems(planet_set))))) - print "\t owned planets in this group are:" - print "\t %s" % (ppstring(PlanetUtilsAI.planet_name_ids(planet_set))) + debug("%.2f PP remaining in system group: %s", avail_pp, PlanetUtilsAI.sys_name_ids(set(PlanetUtilsAI.get_systems(planet_set)))) + debug("\t owned planets in this group are:") + debug("\t %s", PlanetUtilsAI.planet_string(planet_set)) best_design_id, best_design, build_choices = get_best_ship_info(PriorityType.PRODUCTION_COLONISATION, list(planet_set)) species_map = {} for loc in (build_choices or []): this_spec = universe.getPlanet(loc).speciesName species_map.setdefault(this_spec, []).append(loc) colony_build_choices = [] - for pid, (score, this_spec) in foAI.foAIstate.colonisablePlanetIDs.items(): - colony_build_choices.extend(int(math.ceil(score))*[pid2 for pid2 in species_map.get(this_spec, []) if pid2 in planet_set]) + for pid, (score, this_spec) in aistate.colonisablePlanetIDs.items(): + colony_build_choices.extend(int(math.ceil(score))*[pid_ for pid_ in species_map.get(this_spec, []) if pid_ in planet_set]) local_priorities = {} local_priorities.update(filtered_priorities) @@ -1299,10 +1316,10 @@ def generate_production_orders(): del local_priorities[priority] # must be missing a shipyard -- TODO build a shipyard if necessary continue best_ships[priority] = [best_design_id, best_design, build_choices] - print "best_ships[%s] = %s \t locs are %s from %s" % (priority, best_design.name, build_choices, planet_set) + debug("best_ships[%s] = %s \t locs are %s from %s", priority, best_design.name, build_choices, planet_set) if len(local_priorities) == 0: - print "Alert!! need shipyards in systemSet ", ppstring(PlanetUtilsAI.sys_name_ids(set(PlanetUtilsAI.get_systems(sorted(planet_set))))) + debug("Alert!! need shipyards in systemSet %s", PlanetUtilsAI.sys_name_ids(set(PlanetUtilsAI.get_systems(planet_set)))) priority_choices = [] for priority in local_priorities: priority_choices.extend(int(local_priorities[priority]) * [priority]) @@ -1310,15 +1327,14 @@ def generate_production_orders(): loop_count = 0 while (avail_pp > 0) and (loop_count < max(100, current_turn)) and (priority_choices != []): # make sure don't get stuck in some nonbreaking loop like if all shipyards captured loop_count += 1 - print "Beginning build enqueue loop %d; %.1f PP available" % (loop_count, avail_pp) + debug("Beginning build enqueue loop %d; %.1f PP available", loop_count, avail_pp) this_priority = random.choice(priority_choices) - print "selected priority: ", this_priority + debug("selected priority: %s", this_priority) making_colony_ship = False making_outpost_ship = False if this_priority == PriorityType.PRODUCTION_COLONISATION: if total_colony_fleets >= max_colony_fleets: - print "Already sufficient colony ships in queue, trying next priority choice" - print + debug("Already sufficient colony ships in queue, trying next priority choice\n") for i in range(len(priority_choices) - 1, -1, -1): if priority_choices[i] == PriorityType.PRODUCTION_COLONISATION: del priority_choices[i] @@ -1332,8 +1348,7 @@ def generate_production_orders(): making_colony_ship = True if this_priority == PriorityType.PRODUCTION_OUTPOST: if total_outpost_fleets >= max_outpost_fleets: - print "Already sufficient outpost ships in queue, trying next priority choice" - print + debug("Already sufficient outpost ships in queue, trying next priority choice\n") for i in range(len(priority_choices) - 1, -1, -1): if priority_choices[i] == PriorityType.PRODUCTION_OUTPOST: del priority_choices[i] @@ -1352,7 +1367,10 @@ def generate_production_orders(): break loc, best_design_id, best_design = choice[1:4] if best_design is None: - print >> sys.stderr, "problem with mil_build_choices; with selector (%s) chose loc (%s), best_design_id (%s), best_design (None) from mil_build_choices: %s" % (selector, loc, best_design_id, mil_build_choices) + warning("problem with mil_build_choices;" + " with selector (%s) chose loc (%s), " + "best_design_id (%s), best_design (None) " + "from mil_build_choices: %s" % (selector, loc, best_design_id, mil_build_choices)) continue else: loc = random.choice(build_choices) @@ -1368,16 +1386,16 @@ def generate_production_orders(): pname = loc_planet.name this_rating = ColonisationAI.rate_planetary_piloting(loc) rating_ratio = float(this_rating) / state.best_pilot_rating - qualifier = ["", "suboptimal"][rating_ratio < 1.0] - print "Building mil ship at loc %d (%s) with %s pilot Rating: %.1f; ratio to empire best is %.1f" % (loc, pname, qualifier, this_rating, rating_ratio) + qualifier = "suboptimal " if rating_ratio < 1.0 else "" + debug("Building mil ship at loc %d (%s) with %spilot Rating: %.1f; ratio to empire best is %.1f", loc, pname, qualifier, this_rating, rating_ratio) while total_pp > 40 * per_turn_cost: ship_number *= 2 per_turn_cost *= 2 retval = fo.issueEnqueueShipProductionOrder(best_design_id, loc) if retval != 0: prioritized = False - print "adding %d new ship(s) at location %s to production queue: %s; per turn production cost %.1f" % (ship_number, ppstring(PlanetUtilsAI.planet_name_ids([loc])), best_design.name, per_turn_cost) - print + debug("adding %d new ship(s) at location %s to production queue: %s; per turn production cost %.1f\n", + ship_number, PlanetUtilsAI.planet_string(loc), best_design.name, per_turn_cost) if ship_number > 1: fo.issueChangeProductionQuantityOrder(production_queue.size - 1, 1, ship_number) avail_pp -= per_turn_cost @@ -1399,13 +1417,81 @@ def generate_production_orders(): if leading_block_pp > 0.5 * total_pp or (military_emergency and this_priority == PriorityType.PRODUCTION_MILITARY): prioritized = True fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - if (not prioritized) and (this_priority == PriorityType.PRODUCTION_INVASION): - fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front - print + if this_priority == PriorityType.PRODUCTION_INVASION: + queued_troop_ships += ship_number + if not prioritized: + fo.issueRequeueProductionOrder(production_queue.size - 1, 0) # move to front + # The AI will normally only consider queuing additional ships (above) the current queue is not using all the + # available PP; this can delay the AI from pursuing easy invasion targets. + # Queue an extra troopship if the following conditions are met: + # i) currently dealing with our Capital ResourceGroup + # ii) the invasion priority for this group is nonzero and the max priority, and + # iii) there are minimal troopships already enqueued + invasion_priority = local_priorities.get(PriorityType.PRODUCTION_INVASION, 0) + if (capital_id in planet_set and + invasion_priority and + invasion_priority == max(local_priorities.values()) and + queued_troop_ships <= 2): # todo get max from character module or otherwise calculate + best_design_id, best_design, build_choices = best_ships[PriorityType.PRODUCTION_INVASION] + loc = random.choice(build_choices) + retval = fo.issueEnqueueShipProductionOrder(best_design_id, loc) + if retval != 0: + per_turn_cost = (float(best_design.productionCost(empire.empireID, loc))/best_design.productionTime( + empire.empireID, loc)) + avail_pp -= per_turn_cost + debug("adding extra trooper at location %s to production queue: %s; per turn production cost %.1f\n", + PlanetUtilsAI.planet_string(loc), best_design.name, per_turn_cost) + + debug('') + update_stockpile_use() fo.updateProductionQueue() _print_production_queue(after_turn=True) +def update_stockpile_use(): + """Decide which elements in the production_queue will be enabled for drawing from the imperial stockpile. This + initial version simply ensures that every resource group with at least one item on the queue has its highest + priority item be stockpile-enabled. + + :return: None + """ + # TODO: Do a priority and risk evaluation to decide on enabling stockpile draws + empire = fo.getEmpire() + production_queue = empire.productionQueue + resource_groups = set(tuple(el.key()) for el in empire.planetsWithAvailablePP) + planets_in_stockpile_enabled_group = set() + for queue_index, element in enumerate(production_queue): + if element.locationID in planets_in_stockpile_enabled_group: + # TODO: evaluate possibly disabling stockpile for current element if was previously enabled, perhaps + # only allowing multiple stockpile enabled items in empire-capital-resource-group, or considering some + # priority analysis + continue + group = next((_g for _g in resource_groups if element.locationID in _g), None) + if group is None: + continue # we don't appear to own the location any more + if fo.issueAllowStockpileProductionOrder(queue_index, True): + planets_in_stockpile_enabled_group.update(group) + + +def empire_has_colony_bld_species(building_name): + """Checks if this building is a colony building for which this empire has the required source species available. + :rtype: bool + """ + if not building_name.startswith('BLD_COL_'): + return False + species_name = 'SP_' + building_name.split('BLD_COL_')[1] + return species_name in ColonisationAI.empire_colonizers + + +def already_has_completed_colony_building(planet_id): + """Checks if a planet has an already-completed (but not yet 'hatched') colony building. + :rtype: bool + """ + universe = fo.getUniverse() + planet = universe.getPlanet(planet_id) + return any(universe.getBuilding(bldg).name.startswith('BLD_COL_') for bldg in planet.buildingIDs) + + def build_ship_facilities(bld_name, best_pilot_facilities, top_locs=None): if top_locs is None: top_locs = [] @@ -1413,7 +1499,7 @@ def build_ship_facilities(bld_name, best_pilot_facilities, top_locs=None): empire = fo.getEmpire() total_pp = empire.productionPoints __, prereq_bldg, this_cost, time = AIDependencies.SHIP_FACILITIES.get(bld_name, (None, '', -1, -1)) - if not foAI.foAIstate.character.may_build_building(bld_name): + if not get_aistate().character.may_build_building(bld_name): return bld_type = fo.getBuildingType(bld_name) if not empire.buildingTypeAvailable(bld_name): @@ -1426,21 +1512,21 @@ def build_ship_facilities(bld_name, best_pilot_facilities, top_locs=None): for pid in best_pilot_facilities.get("BLD_SHIPYARD_BASE", [])).difference(current_coverage) try_systems = open_systems.intersection(ColonisationAI.system_facilities.get( prereq_bldg, {}).get('systems', [])) if prereq_bldg else open_systems - try_locs = set(pid for sys_id in try_systems for pid in AIstate.colonizedSystems.get(sys_id, [])) + try_locs = set(pid for sys_id in try_systems for pid in state.get_empire_planets_by_system(sys_id)) else: current_locs = best_pilot_facilities.get(bld_name, []) try_locs = set(best_pilot_facilities.get(prereq_bldg, [])).difference( queued_bld_locs, current_locs) - print "Considering constructing a %s, have %d already built and %d queued" % ( - bld_name, len(current_locs), len(queued_bld_locs)) + debug("Considering constructing a %s, have %d already built and %d queued", + bld_name, len(current_locs), len(queued_bld_locs)) max_under_construction = max(1, (time * total_pp) // (5 * this_cost)) max_total = max(1, (time * total_pp) // (2 * this_cost)) - print "Allowances: max total: %d, max under construction: %d" % (max_total, max_under_construction) + debug("Allowances: max total: %d, max under construction: %d", max_total, max_under_construction) if len(current_locs) >= max_total: return valid_locs = (list(loc for loc in try_locs.intersection(top_locs) if bld_type.canBeProduced(empire.empireID, loc)) + list(loc for loc in try_locs.difference(top_locs) if bld_type.canBeProduced(empire.empireID, loc))) - print "Have %d potential locations: %s" % (len(valid_locs), map(universe.getPlanet, valid_locs)) + debug("Have %d potential locations: %s", len(valid_locs), [universe.getPlanet(x) for x in valid_locs]) # TODO: rank by defense ability, etc. num_queued = len(queued_bld_locs) already_covered = [] # just those covered on this turn @@ -1451,10 +1537,10 @@ def build_ship_facilities(bld_name, best_pilot_facilities, top_locs=None): if pid in already_covered: continue res = fo.issueEnqueueBuildingProductionOrder(bld_name, pid) - print "Enqueueing %s at planet %s , with result %d" % (bld_name, universe.getPlanet(pid), res) + debug("Enqueueing %s at planet %s , with result %d", bld_name, universe.getPlanet(pid), res) if res: num_queued += 1 - already_covered.extend(AIstate.colonizedSystems[universe.getPlanet(pid).systemID]) + already_covered.extend(state.get_empire_planets_by_system(universe.getPlanet(pid).systemID)) def _print_production_queue(after_turn=False): @@ -1479,11 +1565,11 @@ def _print_production_queue(after_turn=False): element.name, universe.getPlanet(element.locationID), "%dx %d" % (element.remaining, element.blocksize), - "%.1f / %.1f" %(element.progress, cost), + "%.1f / %.1f" % (element.progress*cost, cost), "%.1f" % element.allocation, "%d" % element.turnsLeft, ]) - prod_queue_table.print_table() + info(prod_queue_table) def find_automatic_historic_analyzer_candidates(): @@ -1510,22 +1596,8 @@ def find_automatic_historic_analyzer_candidates(): fo.aggression.maniacal: (8, 5, 30) } - min_pp, turn_trigger, min_pp_per_additional = conditions.get(foAI.foAIstate.character.get_trait(Aggression).key, + min_pp, turn_trigger, min_pp_per_additional = conditions.get(get_aistate().character.get_trait(Aggression).key, (ARB_LARGE_NUMBER, ARB_LARGE_NUMBER, ARB_LARGE_NUMBER)) - # If we can colonize good planets instead, do not build this. - num_colony_targets = 0 - for pid in ColonisationAI.all_colony_opportunities: - try: - best_species_score = ColonisationAI.all_colony_opportunities[pid][0][0] - except IndexError: - continue - if best_species_score > 500: - num_colony_targets += 1 - - num_covered = get_number_of_existing_outpost_and_colony_ships() + get_number_of_queued_outpost_and_colony_ships() - remaining_targets = num_colony_targets - num_covered - min_pp *= remaining_targets - max_enqueued = 1 if total_pp > min_pp or fo.currentTurn() > turn_trigger else 0 max_enqueued += int(total_pp / min_pp_per_additional) @@ -1534,7 +1606,7 @@ def find_automatic_historic_analyzer_candidates(): # find possible locations possible_locations = set() - for pid in list(AIstate.popCtrIDs) + list(AIstate.outpostIDs): + for pid in state.get_all_empire_planets(): planet = universe.getPlanet(pid) if not planet or planet.currentMeterValue(fo.meterType.targetPopulation) < 1: continue @@ -1559,12 +1631,11 @@ def get_number_of_queued_outpost_and_colony_ships(): :rtype: int """ num_ships = 0 + considered_ship_roles = (ShipRoleType.CIVILIAN_OUTPOST, ShipRoleType.BASE_OUTPOST, + ShipRoleType.BASE_COLONISATION, ShipRoleType.CIVILIAN_COLONISATION) for element in fo.getEmpire().productionQueue: if element.turnsLeft >= 0 and element.buildType == EmpireProductionTypes.BT_SHIP: - if foAI.foAIstate.get_ship_role(element.designID) in (ShipRoleType.CIVILIAN_OUTPOST, - ShipRoleType.BASE_OUTPOST, - ShipRoleType.BASE_COLONISATION, - ShipRoleType.CIVILIAN_COLONISATION): + if get_aistate().get_ship_role(element.designID) in considered_ship_roles: num_ships += element.blocksize return num_ships diff --git a/default/python/AI/ResearchAI.py b/default/python/AI/ResearchAI.py index bf55170f60c..c23540a5171 100644 --- a/default/python/AI/ResearchAI.py +++ b/default/python/AI/ResearchAI.py @@ -1,28 +1,25 @@ -from functools import partial import math import random -import sys -import traceback +from functools import partial +from logging import warning, debug import freeOrionAIInterface as fo # pylint: disable=import-error -import FreeOrionAI as foAI +from common.print_utils import print_in_columns + import AIDependencies as Dep +from AIDependencies import Tags import AIstate import ColonisationAI -import ProductionAI +from aistate_interface import get_aistate import ShipDesignAI import TechsListsAI +from freeorion_tools import chat_human, get_species_tag_grade, tech_is_complete from turn_state import state -from freeorion_tools import tech_is_complete, get_ai_tag_grade, chat_human -from common.print_utils import print_in_columns - -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) inProgressTechs = {} -class Choices(object): +class Choices: # Cannot construct on import, because fo.getEmpire() is None at this time def init(self): rng = random.Random() @@ -62,11 +59,11 @@ def init(self): # TODO research AI no longer use this method, rename and move this method elsewhere def get_research_index(): - return foAI.foAIstate.character.get_research_index() + return get_aistate().character.get_research_index() def has_low_aggression(): - return foAI.foAIstate.character.prefer_research_low_aggression() + return get_aistate().character.prefer_research_low_aggression() def conditional_priority(func_if_true, func_if_false, cond_func): @@ -124,7 +121,7 @@ def has_star(star_type): def if_enemies(true_val, false_val): return conditional_priority(true_val, false_val, - cond_func=lambda: foAI.foAIstate.misc.get('enemies_sighted', {})) + cond_func=lambda: get_aistate().misc.get('enemies_sighted', {})) def if_dict(this_dict, this_key, true_val, false_val): @@ -160,13 +157,10 @@ def get_max_stealth_species(): stealth_grades = {'BAD': -15, 'GOOD': 15, 'GREAT': 40, 'ULTIMATE': 60} stealth = -999 stealth_species = "" - for specName in ColonisationAI.empire_colonizers: - this_spec = fo.getSpecies(specName) - if not this_spec: - continue - this_stealth = stealth_grades.get(get_ai_tag_grade(list(this_spec.tags), "STEALTH"), 0) + for species_name in ColonisationAI.empire_colonizers: + this_stealth = stealth_grades.get(get_species_tag_grade(species_name, Tags.STEALTH), 0) if this_stealth > stealth: - stealth_species = specName + stealth_species = species_name stealth = this_stealth result = (stealth_species, stealth) return result @@ -180,7 +174,7 @@ def get_initial_research_target(): def get_ship_tech_usefulness(tech, ship_designer): this_tech = fo.getTech(tech) if not this_tech: - print "Invalid Tech specified" + debug("Invalid Tech specified") return 0 unlocked_items = this_tech.unlockedItems unlocked_hulls = [] @@ -216,18 +210,18 @@ def get_population_boost_priority(tech_name=""): def get_stealth_priority(tech_name=""): max_stealth_species = get_max_stealth_species() if max_stealth_species[1] > 0: - print "Has a stealthy species %s. Increase stealth tech priority for %s" % (max_stealth_species[0], tech_name) + debug("Has a stealthy species %s. Increase stealth tech priority for %s", max_stealth_species[0], tech_name) return 1.5 else: return 0.1 def get_xeno_genetics_priority(tech_name=""): - if not foAI.foAIstate.character.may_research_xeno_genetics_variances(): + if not get_aistate().character.may_research_xeno_genetics_variances(): return get_population_boost_priority() if has_only_bad_colonizers(): # Empire only have lousy colonisers, xeno-genetics are really important for them - print "Empire has only lousy colonizers, increase priority to xeno_genetics" + debug("Empire has only lousy colonizers, increase priority to xeno_genetics") return get_population_boost_priority() * 3 else: # TODO: assess number of planets with Adequate/Poor planets owned or considered for colonies @@ -236,11 +230,11 @@ def get_xeno_genetics_priority(tech_name=""): def get_artificial_black_hole_priority(tech_name=""): if has_star(fo.starType.blackHole) or not has_star(fo.starType.red): - print "Already have black hole, or does not have a red star to turn to black hole. Skipping ART_BLACK_HOLE" + debug("Already have black hole, or does not have a red star to turn to black hole. Skipping ART_BLACK_HOLE") return 0 for tech in Dep.SHIP_TECHS_REQUIRING_BLACK_HOLE: if tech_is_complete(tech): - print "Solar hull is researched, needs a black hole to produce it. Research ART_BLACK_HOLE now!" + debug("Solar hull is researched, needs a black hole to produce it. Research ART_BLACK_HOLE now!") return 999 return 1 @@ -275,7 +269,7 @@ def get_hull_priority(tech_name): get_ship_tech_usefulness(tech_name, ShipDesignAI.StandardTroopShipDesigner()), get_ship_tech_usefulness(tech_name, ShipDesignAI.StandardColonisationShipDesigner())) - if foAI.foAIstate.misc.get('enemies_sighted', {}): + if get_aistate().misc.get('enemies_sighted', {}): aggression = 1 else: aggression = 0.1 @@ -352,7 +346,7 @@ def init(): """ choices.init() # prefixes for tech search. Check for prefix will be applied in same order as they defined - defensive = foAI.foAIstate.character.prefer_research_defensive() + defensive = get_aistate().character.prefer_research_defensive() prefixes = [ (Dep.DEFENSE_TECHS_PREFIX, 2.0 if defensive else if_enemies(1.0, 0.2)), (Dep.WEAPON_PREFIX, ship_usefulness(if_enemies(1.0, 0.2), MIL_IDX)) @@ -371,7 +365,7 @@ def init(): (Dep.UNRESEARCHABLE_TECHS, -1.0), (Dep.UNUSED_TECHS, ZERO), (Dep.THEORY_TECHS, ZERO), - (Dep.PRODUCTION_BOOST_TECHS, if_dict(ColonisationAI.empire_status, 'industrialists', 1.5, 0.6)), + (Dep.PRODUCTION_BOOST_TECHS, conditional_priority(1.5, 0.6, state.population_with_industry_focus())), (Dep.RESEARCH_BOOST_TECHS, if_tech_target(get_initial_research_target(), 2.1, 2.5)), (Dep.PRODUCTION_AND_RESEARCH_BOOST_TECHS, 2.5), (Dep.POPULATION_BOOST_TECHS, get_population_boost_priority), @@ -393,7 +387,7 @@ def init(): ) for k, v in tech_handlers: - if isinstance(k, basestring): + if isinstance(k, str): k = (k, ) # wrap single techs to tuple for tech in k: priority_funcs[tech] = v @@ -402,7 +396,7 @@ def init(): # if tech already in priority_funcs do nothing # if tech starts with prefix add prefix handler # otherwise print warning and add DEFAULT_PRIORITY - for tech in [tech for tech in fo.techs() if not tech_is_complete(tech)]: + for tech in [tech_ for tech_ in fo.techs() if not tech_is_complete(tech_)]: if tech in priority_funcs: continue for prefix, handler in prefixes: @@ -410,7 +404,7 @@ def init(): priority_funcs[tech] = handler break else: - print "Tech %s does not have a priority, falling back to default." % tech + debug("Tech %s does not have a priority, falling back to default." % tech) priority_funcs[tech] = DEFAULT_PRIORITY @@ -418,11 +412,11 @@ def generate_research_orders(): """Generate research orders.""" if use_classic_research_approach(): - print "Classical research approach is used" + debug("Classical research approach is used") generate_classic_research_orders() return else: - print 'New research approach is used' + debug('New research approach is used') # initializing priority functions here within generate_research_orders() to avoid import race if not priority_funcs: @@ -431,15 +425,15 @@ def generate_research_orders(): empire = fo.getEmpire() empire_id = empire.empireID completed_techs = get_completed_techs() - print "Research Queue Management on turn %d:" % fo.currentTurn() - print "ColonisationAI survey:" - print ' have asteroids:', state.have_asteroids - print ' have gas giant:', state.have_gas_giant - print ' have ruins', state.have_ruins + debug("Research Queue Management on turn %d:", fo.currentTurn()) + debug("ColonisationAI survey:") + debug(' have asteroids: %s', state.have_asteroids) + debug(' have gas giant: %s', state.have_gas_giant) + debug(' have ruins: %s', state.have_ruins) resource_production = empire.resourceProduction(fo.resourceType.research) - print "\nTotal Current Research Points: %.2f\n" % resource_production - print "Techs researched and available for use:" + debug("\nTotal Current Research Points: %.2f\n", resource_production) + debug("Techs researched and available for use:") print_in_columns(sorted(completed_techs)) # @@ -449,21 +443,21 @@ def generate_research_orders(): research_queue_list = get_research_queue_techs() tech_turns_left = {} if research_queue_list: - print "Techs currently at head of Research Queue:" + debug("Techs currently at head of Research Queue:") for element in list(research_queue)[:10]: tech_turns_left[element.tech] = element.turnsLeft this_tech = fo.getTech(element.tech) if not this_tech: - print >> sys.stderr, "Can't retrieve tech ", element.tech + warning("Can't retrieve tech ", element.tech) continue missing_prereqs = [preReq for preReq in this_tech.recursivePrerequisites(empire_id) if preReq not in completed_techs] # unlocked_items = [(uli.name, uli.type) for uli in this_tech.unlocked_items] unlocked_items = [uli.name for uli in this_tech.unlockedItems] if not missing_prereqs: - print " %25s allocated %6.2f RP -- unlockable items: %s " % (element.tech, element.allocation, unlocked_items) + debug(" %25s allocated %6.2f RP -- unlockable items: %s", element.tech, element.allocation, unlocked_items) else: - print " %25s allocated %6.2f RP -- missing preReqs: %s -- unlockable items: %s " % (element.tech, element.allocation, missing_prereqs, unlocked_items) - print + debug(" %25s allocated %6.2f RP -- missing preReqs: %s -- unlockable items: %s", element.tech, element.allocation, missing_prereqs, unlocked_items) + debug('') # # calculate all research priorities, as in get_priority(tech) / total cost of tech (including prereqs) @@ -485,7 +479,7 @@ def generate_research_orders(): # inherited priorities are modestly attenuated by total time timescale_period = 30.0 - for tech_name, priority in base_priorities.iteritems(): + for tech_name, priority in base_priorities.items(): if priority >= 0: turns_needed = max(research_reqs[tech_name][REQS_TIME_IDX], math.ceil(float(research_reqs[tech_name][REQS_COST_IDX]) / total_rp)) time_attenuation = 2**(-max(0.0, turns_needed - 5) / timescale_period) @@ -496,7 +490,7 @@ def generate_research_orders(): on_path_to[prereq] = tech_name # final priorities are scaled by a combination of relative per-turn cost and relative total cost - for tech_name, priority in priorities.iteritems(): + for tech_name, priority in priorities.items(): if priority >= 0: relative_turn_cost = max(research_reqs[tech_name][REQS_PER_TURN_COST_IDX], 0.1) / total_rp relative_total_cost = max(research_reqs[tech_name][REQS_COST_IDX], 0.1) / total_rp @@ -514,35 +508,33 @@ def generate_research_orders(): possible.sort(key=priorities.__getitem__, reverse=True) missing_prereq_list = [] - print "Research priorities" - print " %-25s %8s %8s %8s %-25s %s" % ("Name", "Priority", "Cost", "Time", "As Prereq To", "Missing Prerequisties") + debug("Research priorities") + debug(" %-25s %8s %8s %8s %-25s %s", "Name", "Priority", "Cost", "Time", "As Prereq To", "Missing Prerequisties") for idx, tech_name in enumerate(possible[:20]): tech_info = research_reqs[tech_name] - print " %-25s %8.4f %8.2f %8.2f %-25s %s" % (tech_name, priorities[tech_name], tech_info[1], tech_info[2], on_path_to.get(tech_name, ""), tech_info[0]) + debug(" %-25s %8.4f %8.2f %8.2f %-25s %s", tech_name, priorities[tech_name], tech_info[1], tech_info[2], on_path_to.get(tech_name, ""), tech_info[0]) missing_prereq_list.extend([prereq for prereq in tech_info[0] if prereq not in possible[:idx] and not tech_is_complete(prereq)]) - print + debug('') if missing_prereq_list: - print 'Prerequirements seeming out of order:' - print " %-25s %8s %8s %8s %8s %-25s %s" % ("Name", "Priority", "Base Prio", "Cost", "Time", "As Prereq To", "Missing Prerequisties") + debug('Prerequirements seeming out of order:') + debug(" %-25s %8s %8s %8s %8s %-25s %s", "Name", "Priority", "Base Prio", "Cost", "Time", "As Prereq To", "Missing Prerequisties") for tech_name in missing_prereq_list: tech_info = research_reqs[tech_name] - print " %-25s %8.4f %8.4f %8.2f %8.2f %-25s %s" % (tech_name, priorities[tech_name], base_priorities[tech_name], tech_info[1], tech_info[2], on_path_to.get(tech_name, ""), tech_info[0]) + debug(" %-25s %8.4f %8.4f %8.2f %8.2f %-25s %s", tech_name, priorities[tech_name], base_priorities[tech_name], tech_info[1], tech_info[2], on_path_to.get(tech_name, ""), tech_info[0]) - print "Enqueuing techs, already spent %.2f RP of %.2f RP" % (fo.getEmpire().researchQueue.totalSpent, total_rp) + debug("Enqueuing techs, already spent %.2f RP of %.2f RP", fo.getEmpire().researchQueue.totalSpent, total_rp) possible = [x for x in possible if x not in set(get_research_queue_techs())] # some floating point issues can cause AI to enqueue every tech...... while empire.resourceProduction(fo.resourceType.research) - empire.researchQueue.totalSpent > 0.001 and possible: to_research = possible.pop(0) # get tech with highest priority fo.issueEnqueueTechOrder(to_research, -1) fo.updateResearchQueue() - print " %-25s %6.2f RP/turn %6.2f RP" % (to_research, - fo.getTech(to_research).perTurnCost(empire.empireID), - fo.getTech(to_research).researchCost(empire.empireID)) + debug(" %-25s %6.2f RP/turn %6.2f RP", to_research, fo.getTech(to_research).perTurnCost(empire.empireID), + fo.getTech(to_research).researchCost(empire.empireID)) - print "Finish research orders, spent %.2f RP of %.2f RP" % (fo.getEmpire().researchQueue.totalSpent, - empire.resourceProduction(fo.resourceType.research)) - print + debug("Finish research orders, spent %.2f RP of %.2f RP\n", fo.getEmpire().researchQueue.totalSpent, + empire.resourceProduction(fo.resourceType.research)) def generate_default_research_order(): @@ -570,22 +562,22 @@ def is_possible(tech_name): [(fo.getTech(tech).researchCost(empire.empireID), tech) for tech in fo.techs() if is_possible(tech)], reverse=True) - print "Techs in possible list after enqueues to Research Queue:" + debug("Techs in possible list after enqueues to Research Queue:") for _, tech in possible: - print " " + tech - print + debug(" " + tech) + debug('') # iterate through techs in order of cost fo.updateResearchQueue() total_spent = fo.getEmpire().researchQueue.totalSpent - print "Enqueuing techs. already spent RP: %s total RP: %s" % (total_spent, total_rp) + debug("Enqueuing techs. already spent RP: %s total RP: %s", total_spent, total_rp) while total_rp > 0 and possible: cost, name = possible.pop() # get chipest total_rp -= cost fo.issueEnqueueTechOrder(name, -1) - print " enqueued tech " + name + " : cost: " + str(cost) + "RP" - print + debug(" enqueued tech %s : cost: %s RP", name, cost) + debug('') def get_possible_projects(): @@ -609,7 +601,7 @@ def get_research_queue_techs(): def exclude_tech(tech_name): - return ((not foAI.foAIstate.character.may_research_tech(tech_name)) + return ((not get_aistate().character.may_research_tech(tech_name)) or tech_name in TechsListsAI.unusable_techs() or tech_name in Dep.UNRESEARCHABLE_TECHS) @@ -619,18 +611,18 @@ def generate_classic_research_orders(): report_adjustments = False empire = fo.getEmpire() empire_id = empire.empireID - enemies_sighted = foAI.foAIstate.misc.get('enemies_sighted', {}) + aistate = get_aistate() + enemies_sighted = aistate.misc.get('enemies_sighted', {}) galaxy_is_sparse = ColonisationAI.galaxy_is_sparse() - print "Research Queue Management:" + debug("Research Queue Management:") resource_production = empire.resourceProduction(fo.resourceType.research) - print "\nTotal Current Research Points: %.2f\n" % resource_production - print "Techs researched and available for use:" + debug("\nTotal Current Research Points: %.2f\n", resource_production) + debug("Techs researched and available for use:") completed_techs = sorted(list(get_completed_techs())) tlist = completed_techs + [" "] * 3 - tlines = zip(tlist[0::3], tlist[1::3], tlist[2::3]) - for tline in tlines: - print "%25s %25s %25s" % tline - print + for tline in zip(tlist[0::3], tlist[1::3], tlist[2::3]): + debug("%25s %25s %25s", *tline) + debug('') # # report techs currently at head of research queue @@ -641,35 +633,35 @@ def generate_classic_research_orders(): inProgressTechs.clear() tech_turns_left = {} if research_queue_list: - print "Techs currently at head of Research Queue:" + debug("Techs currently at head of Research Queue:") for element in list(research_queue)[:10]: tech_turns_left[element.tech] = element.turnsLeft if element.allocation > 0.0: inProgressTechs[element.tech] = True this_tech = fo.getTech(element.tech) if not this_tech: - print >> sys.stderr, "Can't retrieve tech ", element.tech + warning("Can't retrieve tech ", element.tech) continue missing_prereqs = [preReq for preReq in this_tech.recursivePrerequisites(empire_id) if preReq not in completed_techs] # unlocked_items = [(uli.name, uli.type) for uli in this_tech.unlocked_items] unlocked_items = [uli.name for uli in this_tech.unlockedItems] if not missing_prereqs: - print " %25s allocated %6.2f RP -- unlockable items: %s " % (element.tech, element.allocation, unlocked_items) + debug(" %25s allocated %6.2f RP -- unlockable items: %s ", element.tech, element.allocation, unlocked_items) else: - print " %25s allocated %6.2f RP -- missing preReqs: %s -- unlockable items: %s " % (element.tech, element.allocation, missing_prereqs, unlocked_items) - print + debug(" %25s allocated %6.2f RP -- missing preReqs: %s -- unlockable items: %s ", element.tech, element.allocation, missing_prereqs, unlocked_items) + debug('') # # set starting techs, or after turn 100 add any additional default techs # - if (fo.currentTurn() == 1) or ((total_rp - research_queue.totalSpent) > 0): + if (fo.currentTurn() <= 2) or ((total_rp - research_queue.totalSpent) > 0): research_index = get_research_index() if fo.currentTurn() == 1: # do only this one on first turn, to facilitate use of a turn-1 savegame for testing of alternate # research strategies - new_tech = ["GRO_PLANET_ECOL", "LRN_ALGO_ELEGANCE"] + new_tech = ["LRN_PHYS_BRAIN", "LRN_ALGO_ELEGANCE"] else: new_tech = TechsListsAI.sparse_galaxy_techs(research_index) if galaxy_is_sparse else TechsListsAI.primary_meta_techs(research_index) - print "Empire %s (%d) is selecting research index %d" % (empire.name, empire_id, research_index) + debug("Empire %s (%d) is selecting research index %d", empire.name, empire_id, research_index) # techs_to_enqueue = (set(new_tech)-(set(completed_techs)|set(research_queue_list))) techs_to_enqueue = new_tech[:] tech_base = set(completed_techs + research_queue_list) @@ -678,13 +670,13 @@ def generate_classic_research_orders(): if tech not in tech_base: this_tech = fo.getTech(tech) if this_tech is None: - print >> sys.stderr, "Desired tech '%s' appears to not exist" % tech + warning("Desired tech '%s' appears to not exist" % tech) continue missing_prereqs = [preReq for preReq in this_tech.recursivePrerequisites(empire_id) if preReq not in tech_base] techs_to_add.extend(missing_prereqs + [tech]) tech_base.update(missing_prereqs + [tech]) cum_cost = 0 - print " Enqueued Tech: %20s \t\t %8s \t %s" % ("Name", "Cost", "CumulativeCost") + debug(" Enqueued Tech: %20s \t\t %8s \t %s", "Name", "Cost", "CumulativeCost") for name in techs_to_add: try: enqueue_res = fo.issueEnqueueTechOrder(name, -1) @@ -694,23 +686,22 @@ def generate_classic_research_orders(): if this_tech: this_cost = this_tech.researchCost(empire_id) cum_cost += this_cost - print " Enqueued Tech: %20s \t\t %8.0f \t %8.0f" % (name, this_cost, cum_cost) + debug(" Enqueued Tech: %20s \t\t %8.0f \t %8.0f", name, this_cost, cum_cost) else: - print >> sys.stderr, " Failed attempt to enqueued Tech: " + name - except: - print >> sys.stderr, " Failed attempt to enqueued Tech: " + name - print >> sys.stderr, " Exception triggered and caught: ", traceback.format_exc() + warning(" Failed attempt to enqueued Tech: " + name) + except: # noqa: E722 + warning(" Failed attempt to enqueued Tech: " + name, exc_info=True) - print '\n\nAll techs:' - print '=' * 20 + debug('\n\nAll techs:') + debug('=' * 20) alltechs = fo.techs() print_in_columns(sorted(fo.techs()), columns=3) - print '\n\nAll unqueued techs:' - print '=' * 20 + debug('\n\nAll unqueued techs:') + debug('=' * 20) # coveredTechs = new_tech+completed_techs print_in_columns([tn for tn in alltechs if tn not in tech_base], columns=3) - print + debug('') if fo.currentTurn() == 1: return @@ -718,27 +709,27 @@ def generate_classic_research_orders(): research_queue_list = get_research_queue_techs() def_techs = TechsListsAI.defense_techs_1() for def_tech in def_techs: - if (foAI.foAIstate.character.may_research_tech_classic(def_tech) - and def_tech not in research_queue_list[:5] and not tech_is_complete(def_tech)): + if (aistate.character.may_research_tech_classic(def_tech) + and def_tech not in research_queue_list[:5] and not tech_is_complete(def_tech)): res = fo.issueEnqueueTechOrder(def_tech, min(3, len(research_queue_list))) - print "Empire is very defensive, so attempted to fast-track %s, got result %d" % (def_tech, res) + debug("Empire is very defensive, so attempted to fast-track %s, got result %d", def_tech, res) if False: # with current stats of Conc Camps, disabling this fast-track research_queue_list = get_research_queue_techs() - if "CON_CONC_CAMP" in research_queue_list and foAI.foAIstate.character.may_research_tech_classic("CON_CONC_CAMP"): + if "CON_CONC_CAMP" in research_queue_list and aistate.character.may_research_tech_classic("CON_CONC_CAMP"): insert_idx = min(40, research_queue_list.index("CON_CONC_CAMP")) else: insert_idx = max(0, min(40, len(research_queue_list) - 10)) - if "SHP_DEFLECTOR_SHIELD" in research_queue_list and foAI.foAIstate.character.may_research_tech_classic("SHP_DEFLECTOR_SHIELD"): + if "SHP_DEFLECTOR_SHIELD" in research_queue_list and aistate.character.may_research_tech_classic("SHP_DEFLECTOR_SHIELD"): insert_idx = min(insert_idx, research_queue_list.index("SHP_DEFLECTOR_SHIELD")) for cc_tech in ["CON_ARCH_PSYCH", "CON_CONC_CAMP"]: if (cc_tech not in research_queue_list[:insert_idx + 1] and not tech_is_complete(cc_tech) - and foAI.foAIstate.character.may_research_tech_classic(cc_tech)): + and aistate.character.may_research_tech_classic(cc_tech)): res = fo.issueEnqueueTechOrder(cc_tech, insert_idx) msg = "Empire is very aggressive, so attempted to fast-track %s, got result %d" % (cc_tech, res) if report_adjustments: chat_human(msg) else: - print msg + debug(msg) elif fo.currentTurn() > 100: generate_default_research_order() @@ -762,22 +753,6 @@ def generate_classic_research_orders(): nest_tech = Dep.NEST_DOMESTICATION_TECH artif_minds = Dep.ART_MINDS - # fast-track LRN_PHYS_BRAIN if we want to build the Automatic History Analyzer - # The decision-making is made by the relevant ProductionAI.py scripts which also enqueue the building. - # Both LRN_ART_MINDS and GRO_SUBTER_HAB are prioritized over this. - if not tech_is_complete(Dep.LRN_PHYS_BRAIN): - print "Considering whether to fast-track %s to build Automatic History Analyzer" % Dep.LRN_PHYS_BRAIN - if ProductionAI.find_automatic_historic_analyzer_candidates(): - artif_mind_idx = artif_minds in research_queue_list and research_queue_list.index(artif_minds) or 0 - subter_hab_idx = Dep.GRO_SUBTER_HAB in research_queue_list and research_queue_list.index(Dep.GRO_SUBTER_HAB) or 0 - insert_idx = 1 + max(artif_mind_idx, subter_hab_idx) - res = fo.issueEnqueueTechOrder(Dep.LRN_PHYS_BRAIN, insert_idx) - num_techs_accelerated += 1 - print "Got possible locations to build the Automatic History Analyzer. Enqueuing, got result %d" % res - research_queue_list = get_research_queue_techs() - else: - print "Currently, no interest in building Automatic History Analyzer. Do not fast-track." - if state.have_nest and not tech_is_complete(nest_tech): if artif_minds in research_queue_list: insert_idx = 1 + research_queue_list.index(artif_minds) @@ -789,20 +764,20 @@ def generate_classic_research_orders(): if report_adjustments: chat_human(msg) else: - print msg + debug(msg) research_queue_list = get_research_queue_techs() # # Supply range and detection range if False: # disabled for now, otherwise just to help with cold-folding / organization - if len(foAI.foAIstate.colonisablePlanetIDs) == 0: + if len(aistate.colonisablePlanetIDs) == 0: best_colony_site_score = 0 else: - best_colony_site_score = foAI.foAIstate.colonisablePlanetIDs.items()[0][1] - if len(foAI.foAIstate.colonisableOutpostIDs) == 0: + best_colony_site_score = next(iter(aistate.colonisablePlanetIDs.items()))[1] + if len(aistate.colonisableOutpostIDs) == 0: best_outpost_site_score = 0 else: - best_outpost_site_score = foAI.foAIstate.colonisableOutpostIDs.items()[0][1] + best_outpost_site_score = next(iter(aistate.colonisableOutpostIDs.items()))[1] need_improved_scouting = (best_colony_site_score < 150 or best_outpost_site_score < 200) if need_improved_scouting: @@ -815,7 +790,7 @@ def generate_classic_research_orders(): if report_adjustments: chat_human(msg) else: - print msg + debug(msg) elif not tech_is_complete("CON_CONTGRAV_ARCH"): num_techs_accelerated += 1 if ("CON_CONTGRAV_ARCH" not in research_queue_list[:1+num_techs_accelerated]) and ( @@ -826,7 +801,7 @@ def generate_classic_research_orders(): if report_adjustments: chat_human(msg) else: - print msg + debug(msg) elif not tech_is_complete("CON_GAL_INFRA"): num_techs_accelerated += 1 if ("CON_GAL_INFRA" not in research_queue_list[:1+num_techs_accelerated]) and ( @@ -836,7 +811,7 @@ def generate_classic_research_orders(): if report_adjustments: chat_human(msg) else: - print msg + debug(msg) else: pass research_queue_list = get_research_queue_techs() @@ -854,14 +829,14 @@ def generate_classic_research_orders(): if report_adjustments: chat_human(msg) else: - print msg + debug(msg) research_queue_list = get_research_queue_techs() # # check to accelerate xeno_arch if True: # just to help with cold-folding / organization if (state.have_ruins and not tech_is_complete("LRN_XENOARCH") - and foAI.foAIstate.character.may_research_tech_classic("LRN_XENOARCH")): + and aistate.character.may_research_tech_classic("LRN_XENOARCH")): if artif_minds in research_queue_list: insert_idx = 7 + research_queue_list.index(artif_minds) elif "GRO_SYMBIOTIC_BIO" in research_queue_list: @@ -877,7 +852,7 @@ def generate_classic_research_orders(): if report_adjustments: chat_human(msg) else: - print msg + debug(msg) research_queue_list = get_research_queue_techs() if False and not enemies_sighted: # curently disabled @@ -898,7 +873,7 @@ def generate_classic_research_orders(): else: target_index = num_techs_accelerated for move_tech in add_tech_list: - print "for tech %s, target_slot %s, target_index:%s ; num_techs_accelerated:%s" % (move_tech, target_slot, target_index, num_techs_accelerated) + debug("for tech %s, target_slot %s, target_index:%s ; num_techs_accelerated:%s", move_tech, target_slot, target_index, num_techs_accelerated) if tech_is_complete(move_tech): continue if target_index <= num_techs_accelerated: @@ -909,7 +884,7 @@ def generate_classic_research_orders(): if report_adjustments: chat_human(msg) else: - print msg + debug(msg) target_index += 1 # # check to accelerate asteroid or GG tech @@ -924,7 +899,7 @@ def generate_classic_research_orders(): if report_adjustments: chat_human(msg) else: - print msg + debug(msg) research_queue_list = get_research_queue_techs() elif tech_is_complete("SHP_ZORTRIUM_PLATE"): insert_idx = (1 + insert_idx) if "LRN_FORCE_FIELD" not in research_queue_list else max(1 + insert_idx, research_queue_list.index("LRN_FORCE_FIELD") - 1) @@ -934,7 +909,7 @@ def generate_classic_research_orders(): num_techs_accelerated += 1 insert_idx += 1 msg = "Asteroids: plan to colonize an asteroid belt, so attempted to fast-track %s , got result %d" % (ast_tech, res) - print msg + debug(msg) if report_adjustments: chat_human(msg) research_queue_list = get_research_queue_techs() @@ -946,7 +921,7 @@ def generate_classic_research_orders(): res = fo.issueEnqueueTechOrder("PRO_ORBITAL_GEN", insert_idx) num_techs_accelerated += 1 msg = "GasGiant: plan to colonize a gas giant, so attempted to fast-track %s, got result %d" % ("PRO_ORBITAL_GEN", res) - print msg + debug(msg) if report_adjustments: chat_human(msg) research_queue_list = get_research_queue_techs() @@ -967,12 +942,12 @@ def generate_classic_research_orders(): if most_adequate == 0: insert_idx = num_techs_accelerated for xg_tech in ["GRO_XENO_GENETICS", "GRO_GENETIC_ENG"]: - if (xg_tech not in research_queue_list[:1+num_techs_accelerated] and not tech_is_complete(xg_tech) - and foAI.foAIstate.character.may_research_tech_classic(xg_tech)): + if (xg_tech not in research_queue_list[:1 + num_techs_accelerated] and not tech_is_complete(xg_tech) + and aistate.character.may_research_tech_classic(xg_tech)): res = fo.issueEnqueueTechOrder(xg_tech, insert_idx) num_techs_accelerated += 1 msg = "Empire has poor colonizers, so attempted to fast-track %s, got result %d" % (xg_tech, res) - print msg + debug(msg) if report_adjustments: chat_human(msg) research_queue_list = get_research_queue_techs() @@ -986,25 +961,26 @@ def generate_classic_research_orders(): if this_spec and ("TELEPATHIC" in list(this_spec.tags)): got_telepathy = True break - if empire.population() > ([300, 100][got_telepathy]): + pop_threshold = 100 if got_telepathy else 300 + if empire.population() > pop_threshold: insert_idx = num_techs_accelerated for dt_ech in ["LRN_PHYS_BRAIN", "LRN_TRANSLING_THT", "LRN_PSIONICS", "LRN_DISTRIB_THOUGHT"]: if (dt_ech not in research_queue_list[:insert_idx + 2] and not tech_is_complete(dt_ech) - and foAI.foAIstate.character.may_research_tech_classic(dt_ech)): + and aistate.character.may_research_tech_classic(dt_ech)): res = fo.issueEnqueueTechOrder(dt_ech, insert_idx) num_techs_accelerated += 1 insert_idx += 1 fmt_str = "Empire has a telepathic race, so attempted to fast-track %s (got result %d)" fmt_str += " with current target_RP %.1f and current pop %.1f, on turn %d" msg = fmt_str % (dt_ech, res, resource_production, empire.population(), fo.currentTurn()) - print msg + debug(msg) if report_adjustments: chat_human(msg) research_queue_list = get_research_queue_techs() # # check to accelerate quant net if False: # disabled for now, otherwise just to help with cold-folding / organization - if foAI.foAIstate.character.may_research_tech_classic("LRN_QUANT_NET") and (ColonisationAI.empire_status.get('researchers', 0) >= 40): + if aistate.character.may_research_tech_classic("LRN_QUANT_NET") and (state.population_with_research_focus() >= 40): if not tech_is_complete("LRN_QUANT_NET"): insert_idx = num_techs_accelerated # TODO determine min target slot if reenabling for qnTech in ["LRN_NDIM_SUBSPACE", "LRN_QUANT_NET"]: @@ -1013,7 +989,7 @@ def generate_classic_research_orders(): num_techs_accelerated += 1 insert_idx += 1 msg = "Empire has many researchers, so attempted to fast-track %s (got result %d) on turn %d" % (qnTech, res, fo.currentTurn()) - print msg + debug(msg) if report_adjustments: chat_human(msg) research_queue_list = get_research_queue_techs() @@ -1022,7 +998,7 @@ def generate_classic_research_orders(): # if we own a blackhole, accelerate sing_gen and conc camp if True: # just to help with cold-folding / organization if (fo.currentTurn() > 50 and len(AIstate.empireStars.get(fo.starType.blackHole, [])) != 0 and - foAI.foAIstate.character.may_research_tech_classic("PRO_SINGULAR_GEN") and not tech_is_complete(Dep.PRO_SINGULAR_GEN) and + aistate.character.may_research_tech_classic("PRO_SINGULAR_GEN") and not tech_is_complete(Dep.PRO_SINGULAR_GEN) and tech_is_complete(Dep.PRO_SOL_ORB_GEN)): # sing_tech_list = [ "LRN_GRAVITONICS" , "PRO_SINGULAR_GEN"] # formerly also "CON_ARCH_PSYCH", "CON_CONC_CAMP", sing_gen_tech = fo.getTech(Dep.PRO_SINGULAR_GEN) @@ -1033,7 +1009,7 @@ def generate_classic_research_orders(): res = fo.issueEnqueueTechOrder(singTech, num_techs_accelerated) num_techs_accelerated += 1 msg = "have a black hole star outpost/colony, so attempted to fast-track %s, got result %d" % (singTech, res) - print msg + debug(msg) if report_adjustments: chat_human(msg) research_queue_list = get_research_queue_techs() @@ -1060,25 +1036,25 @@ def generate_classic_research_orders(): for tech in techs: this_tech = fo.getTech(tech) if not this_tech: - print "Invalid Tech specified" + debug("Invalid Tech specified") continue unlocked_items = this_tech.unlockedItems unlocked_hulls = [] unlocked_parts = [] for item in unlocked_items: if item.type == fo.unlockableItemType.shipPart: - print "Tech %s unlocks a ShipPart: %s" % (tech, item.name) + debug("Tech %s unlocks a ShipPart: %s", tech, item.name) unlocked_parts.append(item.name) elif item.type == fo.unlockableItemType.shipHull: - print "Tech %s unlocks a ShipHull: %s" % (tech, item.name) + debug("Tech %s unlocks a ShipHull: %s", tech, item.name) unlocked_hulls.append(item.name) if not (unlocked_parts or unlocked_hulls): - print "No new ship parts/hulls unlocked by tech %s" % tech + debug("No new ship parts/hulls unlocked by tech %s", tech) continue old_designs = ShipDesignAI.WarShipDesigner().optimize_design(consider_fleet_count=False) new_designs = ShipDesignAI.WarShipDesigner().optimize_design(additional_hulls=unlocked_hulls, - additional_parts=unlocked_parts, - consider_fleet_count=False) + additional_parts=unlocked_parts, + consider_fleet_count=False) if not (old_designs and new_designs): # AI is likely defeated; don't bother with logging error message continue @@ -1087,13 +1063,13 @@ def generate_classic_research_orders(): new_rating, new_pid, new_design_id, new_cost, new_stats = new_designs[0] new_design = fo.getShipDesign(new_design_id) if new_rating > old_rating: - print "Tech %s gives access to a better design!" % tech - print "old best design: Rating %.5f" % old_rating - print "old design specs: %s - " % old_design.hull, list(old_design.parts) - print "new best design: Rating %.5f" % new_rating - print "new design specs: %s - " % new_design.hull, list(new_design.parts) + debug("Tech %s gives access to a better design!", tech) + debug("old best design: Rating %.5f", old_rating) + debug("old design specs: %s - %s", old_design.hull, list(old_design.parts)) + debug("new best design: Rating %.5f", new_rating) + debug("new design specs: %s - %s", new_design.hull, list(new_design.parts)) else: - print "Tech %s gives access to new parts or hulls but there seems to be no military advantage." % tech + debug("Tech %s gives access to new parts or hulls but there seems to be no military advantage.", tech) def use_classic_research_approach(): diff --git a/default/python/AI/ResourcesAI.py b/default/python/AI/ResourcesAI.py index 4a44c98e139..0a97ff06827 100644 --- a/default/python/AI/ResourcesAI.py +++ b/default/python/AI/ResourcesAI.py @@ -9,20 +9,18 @@ """ # Note: The algorithm is not stable with respect to pid order. i.e. Two empire with # exactly the same colonies, but different pids may make different choices. -import sys +from logging import info, warning, debug +from operator import itemgetter import freeOrionAIInterface as fo # pylint: disable=import-error -import FreeOrionAI as foAI +from aistate_interface import get_aistate from EnumsAI import PriorityType, get_priority_resource_types, FocusType import PlanetUtilsAI -import random import ColonisationAI import AIDependencies import CombatRatingsAI -from common.print_utils import Table, Text, Float +from common.print_utils import Table, Text from freeorion_tools import tech_is_complete, AITimer -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) resource_timer = AITimer('timer_bucket') @@ -30,19 +28,14 @@ INDUSTRY = FocusType.FOCUS_INDUSTRY RESEARCH = FocusType.FOCUS_RESEARCH GROWTH = FocusType.FOCUS_GROWTH -PRODUCTION = FocusType.FOCUS_PROTECTION -_focus_names = {INDUSTRY: "Industry", RESEARCH: "Research", GROWTH: "Growth", PRODUCTION: "Defense"} +PROTECTION = FocusType.FOCUS_PROTECTION +_focus_names = {INDUSTRY: "Industry", RESEARCH: "Research", GROWTH: "Growth", PROTECTION: "Defense"} # TODO use the priorityRatio to weight RESEARCH_WEIGHTING = 2.3 -useGrowth = True -limitAssessments = False -lastFociCheck = [0] - - -class PlanetFocusInfo(object): +class PlanetFocusInfo: """ The current, possible and future foci and output of one planet.""" def __init__(self, planet): self.planet = planet @@ -56,7 +49,7 @@ def __init__(self, planet): self.future_focus = self.current_focus -class PlanetFocusManager(object): +class PlanetFocusManager: """PlanetFocusManager tracks all of the empire's planets, what their current and future focus will be.""" def __init__(self): @@ -71,8 +64,8 @@ def __init__(self): self.raw_planet_info = dict(self.all_planet_info) self.baked_planet_info = {} - for pid, info in self.raw_planet_info.items(): - if not info.planet.availableFoci: + for pid, pinfo in list(self.raw_planet_info.items()): + if not pinfo.planet.availableFoci: self.baked_planet_info[pid] = self.raw_planet_info.pop(pid) def bake_future_focus(self, pid, focus, update=True): @@ -85,20 +78,20 @@ def bake_future_focus(self, pid, focus, update=True): production or research special), then update should be True. Return success or failure """ - info = self.raw_planet_info.get(pid) - success = bool(info is not None and - (info.current_focus == focus - or (focus in info.planet.availableFoci + pinfo = self.raw_planet_info.get(pid) + success = bool(pinfo is not None and + (pinfo.current_focus == focus + or (focus in pinfo.planet.availableFoci and fo.issueChangeFocusOrder(pid, focus)))) if success: - if update and info.current_focus != focus: + if update and pinfo.current_focus != focus: universe = fo.getUniverse() universe.updateMeterEstimates(self.raw_planet_info.keys()) - industry_target = info.planet.currentMeterValue(fo.meterType.targetIndustry) - research_target = info.planet.currentMeterValue(fo.meterType.targetResearch) - info.possible_output[focus] = (industry_target, research_target) + industry_target = pinfo.planet.currentMeterValue(fo.meterType.targetIndustry) + research_target = pinfo.planet.currentMeterValue(fo.meterType.targetResearch) + pinfo.possible_output[focus] = (industry_target, research_target) - info.future_focus = focus + pinfo.future_focus = focus self.baked_planet_info[pid] = self.raw_planet_info.pop(pid) return success @@ -111,43 +104,43 @@ def calculate_planet_infos(self, pids): universe = fo.getUniverse() unbaked_pids = [pid for pid in pids if pid not in self.baked_planet_info] planet_infos = [(pid, self.all_planet_info[pid], self.all_planet_info[pid].planet) for pid in unbaked_pids] - for pid, info, planet in planet_infos: + for pid, pinfo, planet in planet_infos: if INDUSTRY in planet.availableFoci and planet.focus != INDUSTRY: fo.issueChangeFocusOrder(pid, INDUSTRY) # may not be able to take, but try universe.updateMeterEstimates(unbaked_pids) - for pid, info, planet in planet_infos: + for pid, pinfo, planet in planet_infos: industry_target = planet.currentMeterValue(fo.meterType.targetIndustry) research_target = planet.currentMeterValue(fo.meterType.targetResearch) if planet.focus == INDUSTRY: - info.possible_output[INDUSTRY] = (industry_target, research_target) - info.possible_output[GROWTH] = research_target + pinfo.possible_output[INDUSTRY] = (industry_target, research_target) + pinfo.possible_output[GROWTH] = research_target else: - info.possible_output[INDUSTRY] = (0, 0) - info.possible_output[GROWTH] = 0 + pinfo.possible_output[INDUSTRY] = (0, 0) + pinfo.possible_output[GROWTH] = 0 if RESEARCH in planet.availableFoci and planet.focus != RESEARCH: fo.issueChangeFocusOrder(pid, RESEARCH) # may not be able to take, but try universe.updateMeterEstimates(unbaked_pids) - for pid, info, planet in planet_infos: + for pid, pinfo, planet in planet_infos: industry_target = planet.currentMeterValue(fo.meterType.targetIndustry) research_target = planet.currentMeterValue(fo.meterType.targetResearch) if planet.focus == RESEARCH: - info.possible_output[RESEARCH] = (industry_target, research_target) - info.possible_output[GROWTH] = (industry_target, info.possible_output[GROWTH]) + pinfo.possible_output[RESEARCH] = (industry_target, research_target) + pinfo.possible_output[GROWTH] = (industry_target, pinfo.possible_output[GROWTH]) else: - info.possible_output[RESEARCH] = (0, 0) - info.possible_output[GROWTH] = (0, info.possible_output[GROWTH]) - if info.planet.availableFoci and info.current_focus != planet.focus: - fo.issueChangeFocusOrder(pid, info.current_focus) # put it back to what it was + pinfo.possible_output[RESEARCH] = (0, 0) + pinfo.possible_output[GROWTH] = (0, pinfo.possible_output[GROWTH]) + if pinfo.planet.availableFoci and pinfo.current_focus != planet.focus: + fo.issueChangeFocusOrder(pid, pinfo.current_focus) # put it back to what it was universe.updateMeterEstimates(unbaked_pids) # Protection focus will give the same off-focus Industry and Research targets as Growth Focus - for pid, info, planet in planet_infos: - info.possible_output[PRODUCTION] = info.possible_output[GROWTH] + for pid, pinfo, planet in planet_infos: + pinfo.possible_output[PROTECTION] = pinfo.possible_output[GROWTH] -class Reporter(object): +class Reporter: """Reporter contains some file scope functions to report""" def __init__(self, focus_manager): @@ -167,14 +160,14 @@ def capture_section_info(self, title): @staticmethod def print_resource_ai_header(): - print "\n============================" - print "Collecting info to assess Planet Focus Changes\n" + debug("\n============================") + debug("Collecting info to assess Planet Focus Changes\n") @staticmethod def print_table_header(): - print "===================================" - print Reporter.table_format % ("Planet", "current RP/PP", "old target RP/PP", - "current Focus", "newFocus", "new target RP/PP") + debug("===================================") + debug(Reporter.table_format, "Planet", "current RP/PP", "old target RP/PP", "current Focus", "newFocus", + "new target RP/PP") def print_table_footer(self, priority_ratio): current_industry_target = 0 @@ -186,62 +179,66 @@ def print_table_footer(self, priority_ratio): all_research_industry_target = 0 all_research_research_target = 0 total_changed = 0 - for info in self.focus_manager.all_planet_info.values(): - if info.current_focus != info.future_focus: + for pinfo in self.focus_manager.all_planet_info.values(): + if pinfo.current_focus != pinfo.future_focus: total_changed += 1 - old_pp, old_rp = info.possible_output[info.current_focus] + old_pp, old_rp = pinfo.possible_output[pinfo.current_focus] current_industry_target += old_pp current_research_target += old_rp - future_pp, future_rp = info.possible_output[info.future_focus] + future_pp, future_rp = pinfo.possible_output[pinfo.future_focus] new_industry_target += future_pp new_research_target += future_rp - industry_pp, industry_rp = info.possible_output[INDUSTRY] if INDUSTRY in info.possible_output else (future_pp, future_rp) + industry_pp, industry_rp = (pinfo.possible_output[INDUSTRY] if INDUSTRY in pinfo.possible_output + else (future_pp, future_rp)) all_industry_industry_target += industry_pp all_industry_research_target += industry_rp - research_pp, research_rp = info.possible_output[RESEARCH] if RESEARCH in info.possible_output else (future_pp, future_rp) + research_pp, research_rp = (pinfo.possible_output[RESEARCH] if RESEARCH in pinfo.possible_output + else (future_pp, future_rp)) all_research_industry_target += research_pp all_research_research_target += research_rp - print "-----------------------------------" - print "Planet Focus Assignments to achieve target RP/PP ratio of %.2f from current target ratio of %.2f ( %.1f / %.1f )" \ - % (priority_ratio, current_research_target / (current_industry_target + 0.0001), - current_research_target, current_industry_target) - print "Max Industry assignments would result in target RP/PP ratio of %.2f ( %.1f / %.1f )" \ - % (all_industry_research_target / (all_industry_industry_target + 0.0001), - all_industry_research_target, all_industry_industry_target) - print "Max Research assignments would result in target RP/PP ratio of %.2f ( %.1f / %.1f )" \ - % (all_research_research_target / (all_research_industry_target + 0.0001), - all_research_research_target, all_research_industry_target) - print "-----------------------------------" - print "Final Ratio Target (turn %4d) RP/PP : %.2f ( %.1f / %.1f ) after %d Focus changes" \ - % (fo.currentTurn(), new_research_target / (new_industry_target + 0.0001), - new_research_target, new_industry_target, total_changed) + debug("-----------------------------------") + debug("Planet Focus Assignments to achieve target RP/PP ratio of %.2f" + " from current target ratio of %.2f ( %.1f / %.1f )", + priority_ratio, current_research_target / (current_industry_target + 0.0001), + current_research_target, current_industry_target) + debug("Max Industry assignments would result in target RP/PP ratio of %.2f ( %.1f / %.1f )", + all_industry_research_target / (all_industry_industry_target + 0.0001), + all_industry_research_target, all_industry_industry_target) + debug("Max Research assignments would result in target RP/PP ratio of %.2f ( %.1f / %.1f )", + all_research_research_target / (all_research_industry_target + 0.0001), + all_research_research_target, all_research_industry_target) + debug("-----------------------------------") + debug("Final Ratio Target (turn %4d) RP/PP : %.2f ( %.1f / %.1f ) after %d Focus changes", + fo.currentTurn(), new_research_target / (new_industry_target + 0.0001), + new_research_target, new_industry_target, total_changed) def print_table(self, priority_ratio): """Prints a table of all of the captured sections of assignments.""" self.print_table_header() for title, id_set in self.sections: - print Reporter.table_format % (("---------- " + title + " ------------------------------")[:33], "", "", "", "", "") + debug(Reporter.table_format, ("---------- " + title + " ------------------------------")[:33], + "", "", "", "", "") id_set.sort() # pay sort cost only when printing for pid in id_set: - info = self.focus_manager.baked_planet_info[pid] - old_focus = info.current_focus - new_focus = info.future_focus - current_pp, curren_rp = info.current_output - ot_pp, ot_rp = info.possible_output.get(old_focus, (0, 0)) - nt_pp, nt_rp = info.possible_output[new_focus] - print (Reporter.table_format % - ("pID (%3d) %22s" % (pid, info.planet.name[-22:]), - "c: %5.1f / %5.1f" % (curren_rp, current_pp), - "cT: %5.1f / %5.1f" % (ot_rp, ot_pp), - "cF: %8s" % _focus_names.get(old_focus, 'unknown'), - "nF: %8s" % _focus_names.get(new_focus, 'unset'), - "cT: %5.1f / %5.1f" % (nt_rp, nt_pp))) + pinfo = self.focus_manager.baked_planet_info[pid] + old_focus = pinfo.current_focus + new_focus = pinfo.future_focus + current_pp, curren_rp = pinfo.current_output + ot_pp, ot_rp = pinfo.possible_output.get(old_focus, (0, 0)) + nt_pp, nt_rp = pinfo.possible_output[new_focus] + debug(Reporter.table_format, + "pID (%3d) %22s" % (pid, pinfo.planet.name[-22:]), + "c: %5.1f / %5.1f" % (curren_rp, current_pp), + "cT: %5.1f / %5.1f" % (ot_rp, ot_pp), + "cF: %8s" % _focus_names.get(old_focus, 'unknown'), + "nF: %8s" % _focus_names.get(new_focus, 'unset'), + "cT: %5.1f / %5.1f" % (nt_rp, nt_pp)) self.print_table_footer(priority_ratio) @staticmethod @@ -249,9 +246,9 @@ def print_resource_ai_footer(): empire = fo.getEmpire() pp, rp = empire.productionPoints, empire.resourceProduction(fo.resourceType.research) # Next string used in charts. Don't modify it! - print "Current Output (turn %4d) RP/PP : %.2f ( %.1f / %.1f )" % (fo.currentTurn(), rp / (pp + 0.0001), rp, pp) - print "------------------------" - print "ResourcesAI Time Requirements:" + debug("Current Output (turn %4d) RP/PP : %.2f ( %.1f / %.1f )", fo.currentTurn(), rp / (pp + 0.0001), rp, pp) + debug("------------------------") + debug("ResourcesAI Time Requirements:") @staticmethod def print_resources_priority(): @@ -259,21 +256,21 @@ def print_resources_priority(): universe = fo.getUniverse() empire = fo.getEmpire() empire_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire(universe.planetIDs) - print "Resource Priorities:" + debug("Resource Priorities:") resource_priorities = {} + aistate = get_aistate() for priority_type in get_priority_resource_types(): - resource_priorities[priority_type] = foAI.foAIstate.get_priority(priority_type) + resource_priorities[priority_type] = aistate.get_priority(priority_type) - sorted_priorities = resource_priorities.items() - sorted_priorities.sort(lambda x, y: cmp(x[1], y[1]), reverse=True) + sorted_priorities = sorted(resource_priorities.items(), key=itemgetter(1), reverse=True) top_priority = -1 for evaluation_priority, evaluation_score in sorted_priorities: if top_priority < 0: top_priority = evaluation_priority - print " %s: %.2f" % (evaluation_priority, evaluation_score) + debug(" %s: %.2f", evaluation_priority, evaluation_score) # what is the focus of available resource centers? - print + debug('') warnings = {} foci_table = Table([ Text('Planet'), @@ -297,11 +294,11 @@ def print_resources_priority(): planet.speciesName, "%.1f/%.1f" % (population, max_population) ]) - foci_table.print_table() - print "Empire Totals:\nPopulation: %5d \nProduction: %5d\nResearch: %5d\n" % ( - empire.population(), empire.productionPoints, empire.resourceProduction(fo.resourceType.research)) - for name, (cp, mp) in warnings.iteritems(): - print "Population Warning! -- %s has unsustainable current pop %d -- target %d" % (name, cp, mp) + info(foci_table) + debug("Empire Totals:\nPopulation: %5d \nProduction: %5d\nResearch: %5d\n", + empire.population(), empire.productionPoints, empire.resourceProduction(fo.resourceType.research)) + for name, (cp, mp) in warnings.items(): + warning("Population Warning! -- %s has unsustainable current pop %d -- target %d", name, cp, mp) def weighted_sum_output(outputs): @@ -312,75 +309,77 @@ def weighted_sum_output(outputs): return outputs[0] + RESEARCH_WEIGHTING * outputs[1] -def assess_protection_focus(pid, info): +def assess_protection_focus(pinfo): """Return True if planet should use Protection Focus.""" - this_planet = info.planet - sys_status = foAI.foAIstate.systemStatus.get(this_planet.systemID, {}) - threat_from_supply = (0.25 * foAI.foAIstate.empire_standard_enemy_rating * + this_planet = pinfo.planet + aistate = get_aistate() + sys_status = aistate.systemStatus.get(this_planet.systemID, {}) + threat_from_supply = (0.25 * aistate.empire_standard_enemy_rating * min(2, len(sys_status.get('enemies_nearly_supplied', [])))) - print "Planet %s has regional+supply threat of %.1f" % ('P_%d<%s>' % (pid, this_planet.name), threat_from_supply) + debug("%s has regional+supply threat of %.1f", this_planet, threat_from_supply) regional_threat = sys_status.get('regional_threat', 0) + threat_from_supply if not regional_threat: # no need for protection - if info.current_focus == PRODUCTION: - print "Advising dropping Protection Focus at %s due to no regional threat" % this_planet + if pinfo.current_focus == PROTECTION: + debug("Advising dropping Protection Focus at %s due to no regional threat", this_planet) return False - cur_prod_val = weighted_sum_output(info.current_output) - target_prod_val = max(map(weighted_sum_output, [info.possible_output[INDUSTRY], info.possible_output[RESEARCH]])) - prot_prod_val = weighted_sum_output(info.possible_output[PRODUCTION]) + cur_prod_val = weighted_sum_output(pinfo.current_output) + target_prod_val = max(map(weighted_sum_output, [pinfo.possible_output[INDUSTRY], pinfo.possible_output[RESEARCH]])) + prot_prod_val = weighted_sum_output(pinfo.possible_output[PROTECTION]) local_production_diff = 0.8 * cur_prod_val + 0.2 * target_prod_val - prot_prod_val fleet_threat = sys_status.get('fleetThreat', 0) # TODO: relax the below rejection once the overall determination of PFocus is better tuned if not fleet_threat and local_production_diff > 8: - if info.current_focus == PRODUCTION: - print "Advising dropping Protection Focus at %s due to excessive productivity loss" % this_planet + if pinfo.current_focus == PROTECTION: + debug("Advising dropping Protection Focus at %s due to excessive productivity loss", this_planet) return False local_p_defenses = sys_status.get('mydefenses', {}).get('overall', 0) # TODO have adjusted_p_defenses take other in-system planets into account - adjusted_p_defenses = local_p_defenses * (1.0 if info.current_focus != PRODUCTION else 0.5) + adjusted_p_defenses = local_p_defenses * (1.0 if pinfo.current_focus != PROTECTION else 0.5) local_fleet_rating = sys_status.get('myFleetRating', 0) combined_local_defenses = sys_status.get('all_local_defenses', 0) my_neighbor_rating = sys_status.get('my_neighbor_rating', 0) neighbor_threat = sys_status.get('neighborThreat', 0) - safety_factor = 1.2 if info.current_focus == PRODUCTION else 0.5 - cur_shield = this_planet.currentMeterValue(fo.meterType.shield) - max_shield = this_planet.currentMeterValue(fo.meterType.maxShield) - cur_troops = this_planet.currentMeterValue(fo.meterType.troops) - max_troops = this_planet.currentMeterValue(fo.meterType.maxTroops) - cur_defense = this_planet.currentMeterValue(fo.meterType.defense) - max_defense = this_planet.currentMeterValue(fo.meterType.maxDefense) + safety_factor = 1.2 if pinfo.current_focus == PROTECTION else 0.5 + cur_shield = this_planet.initialMeterValue(fo.meterType.shield) + max_shield = this_planet.initialMeterValue(fo.meterType.maxShield) + cur_troops = this_planet.initialMeterValue(fo.meterType.troops) + max_troops = this_planet.initialMeterValue(fo.meterType.maxTroops) + cur_defense = this_planet.initialMeterValue(fo.meterType.defense) + max_defense = this_planet.initialMeterValue(fo.meterType.maxDefense) def_meter_pairs = [(cur_troops, max_troops), (cur_shield, max_shield), (cur_defense, max_defense)] use_protection = True reason = "" if (fleet_threat and # i.e., an enemy is sitting on us - (info.current_focus != PRODUCTION or # too late to start protection TODO: but maybe regen worth it + (pinfo.current_focus != PROTECTION or # too late to start protection TODO: but maybe regen worth it # protection focus only useful here if it maintains an elevated level all([AIDependencies.PROT_FOCUS_MULTIPLIER * a <= b for a, b in def_meter_pairs]))): use_protection = False reason = "A" - elif ((info.current_focus != PRODUCTION and cur_shield < max_shield - 2 and + elif ((pinfo.current_focus != PROTECTION and cur_shield < max_shield - 2 and not tech_is_complete(AIDependencies.PLANET_BARRIER_I_TECH)) and (cur_defense < max_defense - 2 and not tech_is_complete(AIDependencies.DEFENSE_REGEN_1_TECH)) and (cur_troops < max_troops - 2)): use_protection = False reason = "B1" - elif ((info.current_focus == PRODUCTION and cur_shield * AIDependencies.PROT_FOCUS_MULTIPLIER < max_shield - 2 and + elif ((pinfo.current_focus == PROTECTION and cur_shield * AIDependencies.PROT_FOCUS_MULTIPLIER < max_shield - 2 and not tech_is_complete(AIDependencies.PLANET_BARRIER_I_TECH)) and (cur_defense * AIDependencies.PROT_FOCUS_MULTIPLIER < max_defense - 2 and not tech_is_complete(AIDependencies.DEFENSE_REGEN_1_TECH)) and (cur_troops * AIDependencies.PROT_FOCUS_MULTIPLIER < max_troops - 2)): use_protection = False reason = "B2" - elif max(max_shield, max_troops, max_defense) < 3: # joke defenses, don't bother with protection focus + elif max(max_shield, max_troops, max_defense) < 3: + # joke defenses, don't bother with protection focus use_protection = False reason = "C" elif regional_threat and local_production_diff <= 2.0: + use_protection = True reason = "D" - pass # i.e., use_protection = True elif safety_factor * regional_threat <= local_fleet_rating: use_protection = False reason = "E" elif (safety_factor * regional_threat <= combined_local_defenses and - (info.current_focus != PRODUCTION or + (pinfo.current_focus != PROTECTION or (0.5 * safety_factor * regional_threat <= local_fleet_rating and fleet_threat == 0 and neighbor_threat < combined_local_defenses and local_production_diff > 5))): @@ -392,47 +391,83 @@ def assess_protection_focus(pid, info): local_production_diff > 5): use_protection = False reason = "G" - if use_protection or info.current_focus == PRODUCTION: - print ("Advising %sProtection Focus (reason %s) for planet %s, with local_prod_diff of %.1f, comb. local" - " defenses %.1f, local fleet rating %.1f and regional threat %.1f, threat sources: %s") % ( - ["dropping ", ""][use_protection], reason, this_planet, local_production_diff, combined_local_defenses, - local_fleet_rating, regional_threat, sys_status['regional_fleet_threats']) + if use_protection or pinfo.current_focus == PROTECTION: + debug("Advising %sProtection Focus (reason %s) for planet %s, with local_prod_diff of %.1f, comb. local" + " defenses %.1f, local fleet rating %.1f and regional threat %.1f, threat sources: %s", + ["dropping ", ""][use_protection], reason, this_planet, local_production_diff, combined_local_defenses, + local_fleet_rating, regional_threat, sys_status['regional_fleet_threats']) return use_protection def set_planet_growth_specials(focus_manager): """set resource foci of planets with potentially useful growth factors. Remove planets from list of candidates.""" - if useGrowth: - # TODO: also consider potential future benefit re currently unpopulated planets - for metab, metab_inc_pop in ColonisationAI.empire_metabolisms.items(): - for special in [aspec for aspec in AIDependencies.metabolismBoostMap.get(metab, []) if aspec in ColonisationAI.available_growth_specials]: - ranked_planets = [] - for pid in ColonisationAI.available_growth_specials[special]: - info = focus_manager.all_planet_info[pid] - planet = info.planet - pop = planet.currentMeterValue(fo.meterType.population) - if (pop > metab_inc_pop - 2 * planet.size) or (GROWTH not in planet.availableFoci): # not enough benefit to lose local production, or can't put growth focus here - continue - for special2 in ["COMPUTRONIUM_SPECIAL"]: - if special2 in planet.specials: - break - else: # didn't have any specials that would override interest in growth special - print "Considering Growth Focus for %s (%d) with special %s; planet has pop %.1f and %s metabolism incremental pop is %.1f" % ( - planet.name, pid, special, pop, metab, metab_inc_pop) - if info.current_focus == GROWTH: - pop -= 4 # discourage changing current focus to minimize focus-changing penalties - ranked_planets.append((pop, pid, info.current_focus)) - if not ranked_planets: - continue - ranked_planets.sort() - print "Considering Growth Focus choice for special %s; possible planet pop, id pairs are %s" % (metab, ranked_planets) - for _spPop, spPID, cur_focus in ranked_planets: # index 0 should be able to set focus, but just in case... - planet = focus_manager.all_planet_info[spPID].planet - if focus_manager.bake_future_focus(spPID, GROWTH): - print "%s focus of planet %s (%d) at Growth Focus" % (["set", "left"][cur_focus == GROWTH], planet.name, spPID) - break - else: - print "failed setting focus of planet %s (%d) at Growth Focus; focus left at %s" % (planet.name, spPID, planet.focus) + if not get_aistate().character.may_use_growth_focus(): + return + + # TODO Consider actual resource output of the candidate locations rather than only population + for special, locations in ColonisationAI.available_growth_specials.items(): + # Find which metabolism is boosted by this special + metabolism = AIDependencies.metabolismBoosts.get(special) + if not metabolism: + warning("Entry in available growth special not mapped to a metabolism") + continue + + # Find the total population bonus we could get by using growth focus + potential_pop_increase = ColonisationAI.empire_metabolisms.get(metabolism, 0) + if not potential_pop_increase: + continue + + debug("Considering setting growth focus for %s at locations %s for potential population bonus of %.1f" % ( + special, locations, potential_pop_increase)) + + # Find the best suited planet to use growth special on, i.e. the planet where + # we will lose the least amount of resource generation when using growth focus. + def _print_evaluation(evaluation): + """Local helper function printing a formatted evaluation.""" + debug(" - %s %s" % (planet, evaluation)) + ranked_planets = [] + for pid in locations: + pinfo = focus_manager.all_planet_info[pid] + planet = pinfo.planet + if GROWTH not in planet.availableFoci: + _print_evaluation("has no growth focus available.") + continue + + # the increased population on the planet using this growth focus + # is mostly wasted, so ignore it for now. + pop = planet.currentMeterValue(fo.meterType.population) + pop_gain = potential_pop_increase - planet.habitableSize + if pop > pop_gain: + _print_evaluation("would lose more pop (%.1f) than gain everywhere else (%.1f)." % (pop, pop_gain)) + continue + + # If we have a computronium special here, then research focus will have higher priority. + if AIDependencies.COMPUTRONIUM_SPECIAL in planet.specials and RESEARCH in planet.availableFoci: + _print_evaluation("has a usable %s" % AIDependencies.COMPUTRONIUM_SPECIAL) + continue + + _print_evaluation("considered (pop %.1f, growth gain %.1f, current focus %s)" % ( + pop, pop_gain, pinfo.current_focus)) + + # add a bias to discourage switching out growth focus to avoid focus change penalties + if pinfo.current_focus == GROWTH: + pop -= 4 + + ranked_planets.append((pop, pid, planet)) + + if not ranked_planets: + debug(" --> No suitable location found.") + continue + + # sort possible locations by population in ascending order and set population + # bonus at the planet with lowest possible population loss. + ranked_planets.sort() + for pop, pid, planet in ranked_planets: + if focus_manager.bake_future_focus(pid, GROWTH): + debug(" --> Using growth focus at %s" % planet) + break + else: + warning(" --> Failed to set growth focus at all candidate locations.") def set_planet_production_and_research_specials(focus_manager): @@ -440,23 +475,23 @@ def set_planet_production_and_research_specials(focus_manager): Sets production/research specials for known (COMPUTRONIUM, HONEYCOMB and CONC_CAMP) production/research specials. Remove planets from list of candidates using bake_future_focus.""" - # TODO remove reliance on rules knowledge. Just scan for specials with production - # and research bonuses and use what you find. Perhaps maintain a list - # of know types of specials # TODO use "best" COMPUTRON planet instead of first found, where "best" means least industry loss, # least threatened, no foci change penalty etc. universe = fo.getUniverse() already_have_comp_moon = False - for pid, info in focus_manager.raw_planet_info.items(): - planet = info.planet - if "COMPUTRONIUM_SPECIAL" in planet.specials and RESEARCH in planet.availableFoci and not already_have_comp_moon: + for pid, pinfo in list(focus_manager.raw_planet_info.items()): + planet = pinfo.planet + if (AIDependencies.COMPUTRONIUM_SPECIAL in planet.specials and + RESEARCH in planet.availableFoci and not already_have_comp_moon): if focus_manager.bake_future_focus(pid, RESEARCH): already_have_comp_moon = True - print "%s focus of planet %s (%d) (with Computronium Moon) at Research Focus" % (["set", "left"][info.current_focus == RESEARCH], planet.name, pid) + debug("%s focus of planet %s (%d) (with Computronium Moon) at Research Focus", + ["set", "left"][pinfo.current_focus == RESEARCH], planet.name, pid) continue if "HONEYCOMB_SPECIAL" in planet.specials and INDUSTRY in planet.availableFoci: if focus_manager.bake_future_focus(pid, INDUSTRY): - print "%s focus of planet %s (%d) (with Honeycomb) at Industry Focus" % (["set", "left"][info.current_focus == INDUSTRY], planet.name, pid) + debug("%s focus of planet %s (%d) (with Honeycomb) at Industry Focus", + ["set", "left"][pinfo.current_focus == INDUSTRY], planet.name, pid) continue if ((([bld.buildingTypeName for bld in map(universe.getBuilding, planet.buildingIDs) if bld.buildingTypeName in ["BLD_CONC_CAMP", "BLD_CONC_CAMP_REMNANT"]]) @@ -464,44 +499,43 @@ def set_planet_production_and_research_specials(focus_manager): ["CONC_CAMP_MASTER_SPECIAL", "CONC_CAMP_SLAVE_SPECIAL"]])) and INDUSTRY in planet.availableFoci): if focus_manager.bake_future_focus(pid, INDUSTRY): - print "%s focus of planet %s (%d) (with Concentration Camps/Remnants) at Industry Focus" % (["set", "left"][info.current_focus == INDUSTRY], planet.name, pid) + debug("%s focus of planet %s (%d) (with Concentration Camps/Remnants) at Industry Focus", + ["set", "left"][pinfo.current_focus == INDUSTRY], planet.name, pid) continue else: new_planet = universe.getPlanet(pid) - print >> sys.stderr, ("Failed setting %s for Concentration Camp planet %s (%d) with species %s and current focus %s, but new planet copy shows %s" % - (info.future_focus, planet.name, pid, planet.speciesName, planet.focus, new_planet.focus)) + warning("Failed setting %s for Concentration Camp planet %s (%d) " + "with species %s and current focus %s, but new planet copy shows %s", + pinfo.future_focus, planet.name, pid, planet.speciesName, planet.focus, new_planet.focus) def set_planet_protection_foci(focus_manager): """Assess and set protection foci""" universe = fo.getUniverse() - for pid, info in focus_manager.raw_planet_info.items(): - planet = info.planet - if PRODUCTION in planet.availableFoci and assess_protection_focus(pid, info): + for pid, pinfo in list(focus_manager.raw_planet_info.items()): + planet = pinfo.planet + if PROTECTION in planet.availableFoci and assess_protection_focus(pinfo): current_focus = planet.focus - if focus_manager.bake_future_focus(pid, PRODUCTION): - if current_focus != PRODUCTION: - print ("Tried setting %s for planet %s (%d) with species %s and current focus %s, got result %d and focus %s" % - (info.future_focus, planet.name, pid, planet.speciesName, current_focus, True, planet.focus)) - print "%s focus of planet %s (%d) at Protection(Defense) Focus" % (["set", "left"][current_focus == PRODUCTION], planet.name, pid) + if focus_manager.bake_future_focus(pid, PROTECTION): + if current_focus != PROTECTION: + debug("Tried setting %s for planet %s (%d) with species %s and current focus %s, " + "got result %d and focus %s", pinfo.future_focus, planet.name, pid, planet.speciesName, + current_focus, True, planet.focus) + debug("%s focus of planet %s (%d) at Protection(Defense) Focus", + ["set", "left"][current_focus == PROTECTION], planet.name, pid) continue else: - newplanet = universe.getPlanet(pid) - print >> sys.stderr, ("Error: Failed setting %s for planet %s (%d) with species %s and current focus %s, but new planet copy shows %s" % - (focus_manager.new_foci[pid], planet.name, pid, planet.speciesName, planet.focus, newplanet.focus)) - - -def set_planet_happiness_foci(focus_manager): - """Assess and set planet focus to preferred focus depending on happiness.""" - # TODO Assess need to set planet to preferred focus to improve happiness - pass + new_planet = universe.getPlanet(pid) + warning("Failed setting %s for planet %s (%d) with species %s and current focus %s, " + "but new planet copy shows %s", focus_manager.new_foci[pid], planet.name, pid, + planet.speciesName, planet.focus, new_planet.focus) def set_planet_industry_and_research_foci(focus_manager, priority_ratio): """Adjust planet's industry versus research focus while targeting the given ratio and avoiding penalties from changing focus.""" - print "\n-----------------------------------------" - print "Making Planet Focus Change Determinations\n" + debug("\n-----------------------------------------") + debug("Making Planet Focus Change Determinations\n") ratios = [] # for each planet, calculate RP:PP value ratio at which industry focus and @@ -515,33 +549,42 @@ def set_planet_industry_and_research_foci(focus_manager, priority_ratio): cumulative_pp, cumulative_rp = 0, 0 # Handle presets which only have possible output for preset focus - for pid, info in focus_manager.baked_planet_info.items(): - future_pp, future_rp = info.possible_output[info.future_focus] + for pid, pinfo in focus_manager.baked_planet_info.items(): + future_pp, future_rp = pinfo.possible_output[pinfo.future_focus] target_pp += future_pp target_rp += future_rp cumulative_pp += future_pp cumulative_rp += future_rp # tally max Industry - for pid, info in focus_manager.raw_planet_info.items(): - i_pp, i_rp = info.possible_output[INDUSTRY] + for pid, pinfo in list(focus_manager.raw_planet_info.items()): + i_pp, i_rp = pinfo.possible_output[INDUSTRY] cumulative_pp += i_pp cumulative_rp += i_rp - if RESEARCH not in info.planet.availableFoci: - focus_manager.bake_future_focus(pid, info.current_focus, False) + if RESEARCH not in pinfo.planet.availableFoci: + if focus_manager.bake_future_focus(pid, INDUSTRY, False): + target_pp += i_pp + target_rp += i_rp # smallest possible ratio of research to industry with an all industry focus maxi_ratio = cumulative_rp / max(0.01, cumulative_pp) - for adj_round in [2, 3, 4]: - for pid, info in focus_manager.raw_planet_info.items(): - ii, tr = info.possible_output[INDUSTRY] - ri, rr = info.possible_output[RESEARCH] - ci, cr = info.current_output - research_penalty = (info.current_focus != RESEARCH) + aistate = get_aistate() + for adj_round in [1, 2, 3, 4]: + for pid, pinfo in list(focus_manager.raw_planet_info.items()): + ii, tr = pinfo.possible_output[INDUSTRY] + ri, rr = pinfo.possible_output[RESEARCH] + ci, cr = pinfo.current_output + research_penalty = AIDependencies.FOCUS_CHANGE_PENALTY if (pinfo.current_focus != RESEARCH) else 0 # calculate factor F at which ii + F * tr == ri + F * rr =====> F = ( ii-ri ) / (rr-tr) - factor = (ii - ri) / max(0.01, rr - tr) # don't let denominator be zero for planets where focus doesn't change RP - planet = info.planet + factor = (ii - ri) / max(0.01, rr - tr) + planet = pinfo.planet + if adj_round == 1: # take research at planets that can not use industry focus + if INDUSTRY not in pinfo.planet.availableFoci: + target_pp += ri + target_rp += rr + focus_manager.bake_future_focus(pid, RESEARCH, False) + continue if adj_round == 2: # take research at planets with very cheap research if (maxi_ratio < priority_ratio) and (target_rp < priority_ratio * cumulative_pp) and (factor <= 1.0): target_pp += ri @@ -549,14 +592,50 @@ def set_planet_industry_and_research_foci(focus_manager, priority_ratio): focus_manager.bake_future_focus(pid, RESEARCH, False) continue if adj_round == 3: # take research at planets where can do reasonable balance - if has_force or foAI.foAIstate.character.may_dither_focus_to_gain_research() or (target_rp >= priority_ratio * cumulative_pp): + # if this planet in range where temporary Research focus ("research dithering") can get some additional + # RP at a good PP cost, and still need some RP, then consider doing it. + # Won't really work if AI has researched Force Energy Structures (meters fall too fast) + # TODO: add similar decision points by which research-rich planets + # might possibly choose to dither for industry points + if any((has_force, + not aistate.character.may_dither_focus_to_gain_research(), + target_rp >= priority_ratio * cumulative_pp)): continue - pop = planet.currentMeterValue(fo.meterType.population) - t_pop = planet.currentMeterValue(fo.meterType.targetPopulation) - # if AI is aggressive+, and this planet in range where temporary Research focus can get an additional RP at cost of 1 PP, and still need some RP, then do it - if pop < t_pop - 5: + + pop = planet.initialMeterValue(fo.meterType.population) + t_pop = planet.initialMeterValue(fo.meterType.targetPopulation) + # let pop stabilize before trying to dither; the calculations that determine whether dithering will be + # advantageous assume a stable population, so a larger gap means a less reliable decision + MAX_DITHER_POP_GAP = 5 # some smallish number + if pop < t_pop - MAX_DITHER_POP_GAP: continue - if (ci > ii + 8) or (((rr > ii) or ((rr - cr) >= 1 + 2 * research_penalty)) and ((rr - tr) >= 3) and ((cr - tr) >= 0.7 * ((ii - ci) * (1 + 0.1 * research_penalty)))): + + # if gap between R-focus and I-focus target research levels is too narrow, don't research dither. + # A gap of 1 would provide a single point of RP, at a cost of 3 PP; a gap of 2 the cost is 2.7 PP/RP; + # a gap of 3 at 2.1 PP/RP; a gap of 4, 1.8 PP/RP; a gap of 5, 1.7 PP/RP. The bigger the gap, the + # better; a gap of 10 would provide RP at a cost of 1.3 PP/RP (or even less if the target PP gap + # were smaller). + MIN_DITHER_TARGET_RESEARCH_GAP = 3 + if (rr - tr) < MIN_DITHER_TARGET_RESEARCH_GAP: + continue + + # could double check that planet even has Industry Focus available, but no harm even if not + + # So at this point we have determined the planet has research targets compatible with employing focus + # dither. The research focus phase will last until current research reaches the Research-Focus + # research target, determined by the 'research_capped' indicator, at which point the focus is + # changed to Industry (currently left to be handled by standard focus code later). The research_capped + # indicator, though, is a spot indicator whose value under normal dither operation would revert on the + # next turn, so we need another indicator to maintain the focus change until the Industry meter has + # recovered to its max target level; the indicator to keep the research phase turned off needs to have + # some type of hysteresis component or otherwise be sensitive to the direction of meter change; in the + # indicator below this is accomplished primarily by comparing a difference of changes on both the + # research and industry side, the 'research_penalty' adjustment in the industry_recovery_phase + # calculation prevents the indicator from stopping recovery mode one turn too early. + research_capped = (rr - cr) <= 0.5 + industry_recovery_phase = (ii - ci) - (cr - tr) > AIDependencies.FOCUS_CHANGE_PENALTY - research_penalty + + if not (research_capped or industry_recovery_phase): target_pp += ci - 1 - research_penalty target_rp += cr + 1 focus_manager.bake_future_focus(pid, RESEARCH, False) @@ -564,90 +643,87 @@ def set_planet_industry_and_research_foci(focus_manager, priority_ratio): if adj_round == 4: # assume default IFocus target_pp += ii # icurTargets initially calculated by Industry focus, which will be our default focus target_rp += tr - ratios.append((factor, pid, info)) + ratios.append((factor, pid, pinfo)) ratios.sort() printed_header = False got_algo = tech_is_complete("LRN_ALGO_ELEGANCE") - for ratio, pid, info in ratios: + for ratio, pid, pinfo in ratios: if priority_ratio < (target_rp / (target_pp + 0.0001)): # we have enough RP - if ratio < 1.1 and foAI.foAIstate.character.may_research_heavily(): # but wait, RP is still super cheap relative to PP, maybe will take more RP - if priority_ratio < 1.5 * (target_rp / (target_pp + 0.0001)): # yeah, really a glut of RP, stop taking RP + if ratio < 1.1 and aistate.character.may_research_heavily(): + # but wait, RP is still super cheap relative to PP, maybe will take more RP + if priority_ratio < 1.5 * (target_rp / (target_pp + 0.0001)): + # yeah, really a glut of RP, stop taking RP break else: # RP not super cheap & we have enough, stop taking it break - ii, tr = info.possible_output[INDUSTRY] - ri, rr = info.possible_output[RESEARCH] - # if focus_manager.current_focus[pid] == MFocus: - # ii = max( ii, focus_manager.possible_output[MFocus][0] ) + ii, tr = pinfo.possible_output[INDUSTRY] + ri, rr = pinfo.possible_output[RESEARCH] if ((ratio > 2.0 and target_pp < 15 and got_algo) or - (ratio > 2.5 and target_pp < 25 and ii > 5 and got_algo) or - (ratio > 3.0 and target_pp < 40 and ii > 5 and got_algo) or - (ratio > 4.0 and target_pp < 100 and ii > 10) or - ((target_rp + rr - tr) / max(0.001, target_pp - ii + ri) > 2 * priority_ratio)): # we already have algo elegance and more RP would be too expensive, or overkill + (ratio > 2.5 and target_pp < 25 and ii > 5 and got_algo) or + (ratio > 3.0 and target_pp < 40 and ii > 5 and got_algo) or + (ratio > 4.0 and target_pp < 100 and ii > 10) or + ((target_rp + rr - tr) / max(0.001, target_pp - ii + ri) > 2 * priority_ratio)): + # we already have algo elegance and more RP would be too expensive, or overkill if not printed_header: printed_header = True - print "Rejecting further Research Focus choices as too expensive:" - print "%34s|%20s|%15s |%15s|%15s |%15s |%15s" % (" Planet ", " current RP/PP ", " current target RP/PP ", "current Focus ", " rejectedFocus ", " rejected target RP/PP ", "rejected RP-PP EQF") - old_focus = info.current_focus - c_pp, c_rp = info.current_output - ot_pp, ot_rp = info.possible_output[old_focus] - nt_pp, nt_rp = info.possible_output[RESEARCH] - print "pID (%3d) %22s | c: %5.1f / %5.1f | cT: %5.1f / %5.1f | cF: %8s | nF: %8s | cT: %5.1f / %5.1f | %.2f" % (pid, info.planet.name, c_rp, c_pp, ot_rp, ot_pp, _focus_names.get(old_focus, 'unknown'), _focus_names[RESEARCH], nt_rp, nt_pp, ratio) - continue # RP is getting too expensive, but might be willing to still allocate from a planet with less PP to lose - # if focus_manager.planet_map[pid].currentMeterValue(fo.meterType.targetPopulation) >0: #only set to research if pop won't die out + debug("Rejecting further Research Focus choices as too expensive:") + debug("%34s|%20s|%15s |%15s|%15s |%15s |%15s", + " Planet ", + " current RP/PP ", " current target RP/PP ", + "current Focus ", " rejectedFocus ", + " rejected target RP/PP ", "rejected RP-PP EQF") + old_focus = pinfo.current_focus + c_pp, c_rp = pinfo.current_output + ot_pp, ot_rp = pinfo.possible_output[old_focus] + nt_pp, nt_rp = pinfo.possible_output[RESEARCH] + debug("pID (%3d) %22s | c: %5.1f / %5.1f | cT: %5.1f / %5.1f" + " | cF: %8s | nF: %8s | cT: %5.1f / %5.1f | %.2f", + pid, pinfo.planet.name, c_rp, c_pp, ot_rp, ot_pp, + _focus_names.get(old_focus, 'unknown'), + _focus_names[RESEARCH], nt_rp, nt_pp, ratio) + # RP is getting too expensive, but might be willing to still allocate from a planet with less PP to lose + continue focus_manager.bake_future_focus(pid, RESEARCH, False) target_rp += (rr - tr) target_pp -= (ii - ri) - # Any planet in the ratios list and still raw is set to industry - for ratio, pid, info in ratios: - if pid in focus_manager.raw_planet_info: - focus_manager.bake_future_focus(pid, INDUSTRY, False) + # Any planet still raw is set to industry + for pid in list(focus_manager.raw_planet_info.keys()): + focus_manager.bake_future_focus(pid, INDUSTRY, False) def set_planet_resource_foci(): """set resource focus of planets """ Reporter.print_resource_ai_header() - turn = fo.currentTurn() - # set the random seed (based on galaxy seed, empire ID and current turn) - # for game-reload consistency - freq = min(3, (max(5, turn - 80)) / 4.0) ** (1.0 / 3) - if not (limitAssessments and (abs(turn - lastFociCheck[0]) < 1.5 * freq) and (random.random() < 1.0 / freq)): - lastFociCheck[0] = turn - resource_timer.start("Filter") - resource_timer.start("Priority") - # TODO: take into acct splintering of resource groups - # fleetSupplyableSystemIDs = empire.fleetSupplyableSystemIDs - # fleetSupplyablePlanetIDs = PlanetUtilsAI.get_planets_in__systems_ids(fleetSupplyableSystemIDs) - production_priority = foAI.foAIstate.get_priority(PriorityType.RESOURCE_PRODUCTION) - research_priority = foAI.foAIstate.get_priority(PriorityType.RESOURCE_RESEARCH) - priority_ratio = float(research_priority) / (production_priority + 0.0001) + resource_timer.start("Priority") + # TODO: take into acct splintering of resource groups + aistate = get_aistate() + production_priority = aistate.get_priority(PriorityType.RESOURCE_PRODUCTION) + research_priority = aistate.get_priority(PriorityType.RESOURCE_RESEARCH) + priority_ratio = float(research_priority) / (production_priority + 0.0001) - focus_manager = PlanetFocusManager() + focus_manager = PlanetFocusManager() - reporter = Reporter(focus_manager) - reporter.capture_section_info("Unfocusable") + reporter = Reporter(focus_manager) + reporter.capture_section_info("Unfocusable") - set_planet_growth_specials(focus_manager) - set_planet_production_and_research_specials(focus_manager) - reporter.capture_section_info("Specials") + set_planet_growth_specials(focus_manager) + set_planet_production_and_research_specials(focus_manager) + reporter.capture_section_info("Specials") - focus_manager.calculate_planet_infos(focus_manager.raw_planet_info.keys()) + focus_manager.calculate_planet_infos(focus_manager.raw_planet_info.keys()) - set_planet_protection_foci(focus_manager) - reporter.capture_section_info("Protection") + set_planet_protection_foci(focus_manager) + reporter.capture_section_info("Protection") - set_planet_happiness_foci(focus_manager) - reporter.capture_section_info("Happiness") + set_planet_industry_and_research_foci(focus_manager, priority_ratio) + reporter.capture_section_info("Typical") - set_planet_industry_and_research_foci(focus_manager, priority_ratio) - reporter.capture_section_info("Typical") + reporter.print_table(priority_ratio) - reporter.print_table(priority_ratio) - - resource_timer.stop_print_and_clear() + resource_timer.stop_print_and_clear() Reporter.print_resource_ai_footer() @@ -655,20 +731,6 @@ def set_planet_resource_foci(): def generate_resources_orders(): """generate resources focus orders""" - # calculate top resource priority - # topResourcePriority() - - # set resource foci of planets - # setCapitalIDResourceFocus() - - # ----------------------------- - # setGeneralPlanetResourceFocus() set_planet_resource_foci() - # ------------------------------ - # setAsteroidsResourceFocus() - - # setGasGiantsResourceFocus() - Reporter.print_resources_priority() - # print "ResourcesAI Time Requirements:" diff --git a/default/python/AI/ShipDesignAI.py b/default/python/AI/ShipDesignAI.py index a63dbb5ebab..a441eeb7a5e 100644 --- a/default/python/AI/ShipDesignAI.py +++ b/default/python/AI/ShipDesignAI.py @@ -44,21 +44,19 @@ # - Filtering the weapon parts must be updated: current cache does not consider tech upgrades, weapons are ignored import copy import math -import sys -import traceback from collections import Counter, defaultdict +from logging import debug, error, info, warning +from typing import Iterable import freeOrionAIInterface as fo -import FreeOrionAI as foAI + import AIDependencies -import AIstate import CombatRatingsAI import FleetUtilsAI -from AIDependencies import INVALID_ID -from freeorion_tools import UserString, tech_is_complete - -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) +from AIDependencies import INVALID_ID, Tags +from aistate_interface import get_aistate +from freeorion_tools import UserString, get_species_tag_grade, tech_is_complete, assertion_fails, cache_for_session +from turn_state import state # Define meta classes for the ship parts TODO storing as set may not be needed anymore ARMOUR = frozenset({fo.shipPartClass.armour}) @@ -76,14 +74,6 @@ ALL_META_CLASSES = frozenset({WEAPONS, ARMOUR, DETECTION, FUEL, STEALTH, SHIELDS, COLONISATION, ENGINES, TROOPS, GENERAL}) -# Prefixes for the test ship designs -TESTDESIGN_NAME_BASE = "AI_TESTDESIGN" -TESTDESIGN_NAME_HULL = TESTDESIGN_NAME_BASE + "_HULL" -TESTDESIGN_NAME_PART = TESTDESIGN_NAME_BASE + "_PART" - -# Hardcoded preferred hullname for testdesigns, should be a hull without conditions but with maximum different slottypes -TESTDESIGN_PREFERRED_HULL = "SH_BASIC_MEDIUM" - MISSING_REQUIREMENT_MULTIPLIER = -1000 INVALID_DESIGN_RATING = -999 # this needs to be negative but greater than MISSING_REQUIREMENT_MULTIPLIER @@ -104,18 +94,20 @@ WITHOUT_UPKEEP = "not considering fleet upkeep" -class ShipDesignCache(object): +def _get_capacity(x): + return x.capacity + + +class ShipDesignCache: """This class handles the caching of information used to assess and build shipdesigns in this module. Important methods: update_for_new_turn(self): Updates the cache for the current turn, to be called once at the beginning of each turn. Important members: - testhulls: # set of all hullnames used for testdesigns design_id_by_name # {"designname": designid} part_by_partname # {"partname": part object} map_reference_design_name # {"reference_designname": "ingame_designname"}, cf. _build_reference_name() - strictly_worse_parts # strictly worse parts: {"part": ["worsePart1", "worsePart2"]} hulls_for_planets # buildable hulls per planet {planetID: ["buildableHull1", "buildableHull2", ...]} parts_for_planets # buildable parts per planet and slot: {planetID: {slottype1: ["part1", "part2"]}} best_designs # {shipclass: {reqTup: {species: {available_parts: {hull: (rating, best_parts)}}}}} @@ -123,17 +115,15 @@ class ShipDesignCache(object): production_time # {planetID: {"partname1": local_production_time, "hullname1": local_production_time}} Debug methods: - print_CACHENAME(self), e.g. print_testhulls: prints content of the cache in some nicer format + print_CACHENAME(self), e.g. print_hulls_for_planets: prints content of the cache in some nicer format print_all(self): calls all the printing functions """ def __init__(self): """Cache is empty on creation""" - self.testhulls = set() self.design_id_by_name = {} self.part_by_partname = {} self.map_reference_design_name = {} - self.strictly_worse_parts = {} self.hulls_for_planets = {} self.parts_for_planets = {} self.best_designs = {} @@ -147,35 +137,24 @@ def update_for_new_turn(self): Make sure this function is called once at the beginning of the turn, i.e. before any other function of this module is used. """ - print - print 10 * "=", "Updating ShipDesignCache for new turn", 10 * "=" + info(10 * "=" + "Updating ShipDesignCache for new turn" + 10 * "=") if not self.map_reference_design_name: self._build_cache_after_load() self._check_cache_for_consistency() self.update_cost_cache() - self._update_buildable_items_this_turn(verbose=False) - - def print_testhulls(self): - """Print the testhulls cache.""" - print "Testhull cache:", self.testhulls + self._update_buildable_items_this_turn() def print_design_id_by_name(self): """Print the design_id_by_name cache.""" - print "DesignID cache:", self.design_id_by_name + debug("DesignID cache: %s" % self.design_id_by_name) def print_part_by_partname(self): """Print the part_by_partname cache.""" - print "Parts cached by name:", self.part_by_partname - - def print_strictly_worse_parts(self): - """Print the strictly_worse_parts cache.""" - print "List of strictly worse parts (ignoring slots):" - for part in self.strictly_worse_parts: - print " %s:" % part, self.strictly_worse_parts[part] + debug("Parts cached by name: %s" % self.part_by_partname) def print_map_reference_design_name(self): """Print the ingame, reference name map of shipdesigns.""" - print "Design name map:", self.map_reference_design_name + debug("Design name map: %s" % self.map_reference_design_name) def print_hulls_for_planets(self, pid=None): """Print the hulls buildable on each planet. @@ -183,18 +162,18 @@ def print_hulls_for_planets(self, pid=None): :param pid: None, int or list of ints """ if pid is None: - planets = [pid for pid in self.hulls_for_planets] + planets = list(self.hulls_for_planets) elif isinstance(pid, int): planets = [pid] elif isinstance(pid, list): planets = pid else: - print >> sys.stderr, "Invalid parameter 'pid' for 'print_hulls_for_planets'. Expected int, list or None." + error("Invalid parameter 'pid' for 'print_hulls_for_planets'. Expected int, list or None.") return - print "Hull-cache:" + debug("Hull-cache:") get_planet = fo.getUniverse().getPlanet for pid in planets: - print "%s:" % get_planet(pid).name, self.hulls_for_planets[pid] + debug("%s: %s" % (get_planet(pid).name, self.hulls_for_planets[pid])) def print_parts_for_planets(self, pid=None): """Print the parts buildable on each planet. @@ -202,20 +181,21 @@ def print_parts_for_planets(self, pid=None): :param pid: int or list of ints """ if pid is None: - planets = [pid for pid in self.parts_for_planets] + planets = list(self.parts_for_planets) elif isinstance(pid, int): planets = [pid] elif isinstance(pid, list): planets = pid else: - print >> sys.stderr, "Invalid parameter 'pid' for 'print_parts_for_planets'. Expected int, list or None." + error("Invalid parameter 'pid' for 'print_parts_for_planets'. Expected int, list or None.") return - print "Available parts per planet:" + debug("Available parts per planet:") get_planet = fo.getUniverse().getPlanet + for pid in planets: - print " %s:" % get_planet(pid).name, + debug(" %s:" % get_planet(pid).name) for slot in self.parts_for_planets[pid]: - print slot, ":", self.parts_for_planets[pid][slot] + debug(" %s: %s" % (slot, self.parts_for_planets[pid][slot])) def print_best_designs(self, print_diff_only=True): """Print the best designs that were previously found. @@ -223,65 +203,59 @@ def print_best_designs(self, print_diff_only=True): :param print_diff_only: Print only changes to cache since last print :type print_diff_only: bool """ - print "Currently cached best designs:" + debug("Currently cached best designs:") if print_diff_only: print_dict = {} recursive_dict_diff(self.best_designs, self.last_printed, print_dict, diff_level_threshold=1) else: print_dict = self.best_designs for classname in print_dict: - print classname + debug(classname) cache_name = print_dict[classname] for consider_fleet in cache_name: - print 4*" ", consider_fleet + debug(4*" " + str(consider_fleet)) cache_upkeep = cache_name[consider_fleet] for req_tuple in cache_upkeep: - print 8*" ", req_tuple + debug(8*" " + str(req_tuple)) cache_reqs = cache_upkeep[req_tuple] for tech_tuple in cache_reqs: - print 12*" ", tech_tuple, " # relevant tech upgrades" + debug(12*" " + str(tech_tuple) + " # relevant tech upgrades") cache_techs = cache_reqs[tech_tuple] for species_tuple in cache_techs: - print 16*" ", species_tuple, " # relevant species stats" + debug(16*" " + str(species_tuple) + " # relevant species stats") cache_species = cache_techs[species_tuple] for av_parts in cache_species: - print 20*" ", av_parts + debug(20*" " + str(av_parts)) cache_parts = cache_species[av_parts] for hullname in sorted(cache_parts, reverse=True, key=lambda x: cache_parts[x][0]): - print 24*" ", hullname, ":", - print cache_parts[hullname] + debug(24*" " + hullname + ":" + str(cache_parts[hullname])) self.last_printed = copy.deepcopy(self.best_designs) def print_production_cost(self): """Print production_cost cache.""" universe = fo.getUniverse() - print "Cached production cost per planet:" + debug("Cached production cost per planet:") for pid in self.production_cost: - print " %s:" % universe.getPlanet(pid).name, self.production_cost[pid] + debug(" %s: %s" % (universe.getPlanet(pid).name, self.production_cost[pid])) def print_production_time(self): """Print production_time cache.""" universe = fo.getUniverse() - print "Cached production cost per planet:" + debug("Cached production cost per planet:") for pid in self.production_time: - print " %s:" % universe.getPlanet(pid).name, self.production_time[pid] + debug(" %s: %s" % (universe.getPlanet(pid).name, self.production_time[pid])) def print_all(self): """Print the entire ship design cache.""" - print - print "Printing the ShipDesignAI cache..." - self.print_testhulls() + debug("Printing the ShipDesignAI cache...") self.print_design_id_by_name() self.print_part_by_partname() - self.print_strictly_worse_parts() self.print_map_reference_design_name() self.print_hulls_for_planets() self.print_parts_for_planets() self.print_best_designs() self.print_production_cost() self.print_production_time() - print "-----" - print def update_cost_cache(self, partnames=None, hullnames=None): """Cache the production cost and time for each part and hull for each inhabited planet for this turn. @@ -309,22 +283,22 @@ def update_cost_cache(self, partnames=None, hullnames=None): hulls_to_update.update(hullnames) # no need to update items we already cached in this turn - pids = AIstate.popCtrIDs + pids = list(state.get_inhabited_planets()) if self.production_cost and pids: cached_items = set(self.production_cost[pids[0]].keys()) parts_to_update -= cached_items hulls_to_update -= cached_items for partname in parts_to_update: - part = get_part_type(partname) + part = get_ship_part(partname) for pid in pids: - self.production_cost.setdefault(pid, {})[partname] = part.productionCost(empire_id, pid) - self.production_time.setdefault(pid, {})[partname] = part.productionTime(empire_id, pid) + self.production_cost.setdefault(pid, {})[partname] = part.productionCost(empire_id, pid, INVALID_ID) + self.production_time.setdefault(pid, {})[partname] = part.productionTime(empire_id, pid, INVALID_ID) for hullname in hulls_to_update: - hull = fo.getHullType(hullname) + hull = fo.getShipHull(hullname) for pid in pids: - self.production_cost.setdefault(pid, {})[hullname] = hull.productionCost(empire_id, pid) - self.production_time.setdefault(pid, {})[hullname] = hull.productionTime(empire_id, pid) + self.production_cost.setdefault(pid, {})[hullname] = hull.productionCost(empire_id, pid, INVALID_ID) + self.production_time.setdefault(pid, {})[hullname] = hull.productionTime(empire_id, pid, INVALID_ID) def _build_cache_after_load(self): """Build cache after loading or starting a game. @@ -335,11 +309,9 @@ def _build_cache_after_load(self): - design_id_by_name """ if self.map_reference_design_name or self.design_id_by_name: - print >> sys.stderr, "ShipDesignAI.Cache._build_cache_after_load() called but cache is not empty." + warning("ShipDesignAI.Cache._build_cache_after_load() called but cache is not empty.") for design_id in fo.getEmpire().allShipDesigns: design = fo.getShipDesign(design_id) - if TESTDESIGN_NAME_BASE in design.name: - continue reference_name = _build_reference_name(design.hull, design.parts) self.map_reference_design_name[reference_name] = design.name self.design_id_by_name[design.name] = design_id @@ -350,14 +322,13 @@ def _check_cache_for_consistency(self): This function should be called once at the beginning of the turn (before update_shipdesign_cache()). Especially (only?) in multiplayer games, the shipDesignIDs may sometimes change across turns. """ - print "Checking persistent cache for consistency..." + debug("Checking persistent cache for consistency...") try: for partname in self.part_by_partname: cached_name = self.part_by_partname[partname].name if cached_name != partname: - self.part_by_partname[partname] = fo.getPartType(partname) - print " WARNING: Part cache corrupted." - print " Expected: %s, got: %s. Cache was repaired." % (partname, cached_name) + self.part_by_partname[partname] = fo.getShipPart(partname) + error("Part cache corrupted. Expected: %s, got: %s. Cache was repaired." % (partname, cached_name)) except Exception as e: self.part_by_partname.clear() error(e, exc_info=True) @@ -372,8 +343,7 @@ def _check_cache_for_consistency(self): try: cached_name = fo.getShipDesign(self.design_id_by_name[designname]).name if cached_name != designname: - print " WARNING: ShipID cache corrupted." - print " Expected: %s, got: %s. Repairing cache." % (designname, cached_name) + warning("ShipID cache corrupted. Expected: %s, got: %s." % (designname, cached_name)) design_id = next(iter([shipDesignID for shipDesignID in fo.getEmpire().allShipDesigns if designname == fo.getShipDesign(shipDesignID).name]), None) if design_id is not None: @@ -381,8 +351,7 @@ def _check_cache_for_consistency(self): else: corrupted.append(designname) except AttributeError: - print " WARNING: ShipID cache corrupted. Could not get cached shipdesign. Repairing Cache." - print traceback.format_exc() # do not print to stderr as this is an "expected" exception. + warning("ShipID cache corrupted. Could not get cached shipdesign. Repairing Cache.", exc_info=True) design_id = next(iter([shipDesignID for shipDesignID in fo.getEmpire().allShipDesigns if designname == fo.getShipDesign(shipDesignID).name]), None) if design_id is not None: @@ -391,230 +360,43 @@ def _check_cache_for_consistency(self): corrupted.append(designname) for corrupted_entry in corrupted: del self.design_id_by_name[corrupted_entry] + bad_ref = next(iter([_key for _key, _val in self.map_reference_design_name.items() + if _val == corrupted_entry]), None) + if bad_ref is not None: + del self.map_reference_design_name[bad_ref] - def _update_buildable_items_this_turn(self, verbose=False): - """Calculate which parts and hulls can be built on each planet this turn. - - :param verbose: toggles detailed debugging output. - :type verbose: bool - """ - # TODO: Refactor this function - # The AI currently has no way of checking building requirements of individual parts and hulls directly. - # It can only check if we can build a design. Therefore, we use specific testdesigns to check if we can - # build a hull or part. - # The building requirements are constant so calculate this only once at the beginning of each turn. - # - # Code structure: - # 1. Update hull test designs - # 2. Get a list of buildable ship hulls for each planet - # 3. Update ship part test designs - # 4. Cache the list of buildable ship parts for each planet - # + def _update_buildable_items_this_turn(self): + """Calculate which parts and hulls can be built on each planet this turn.""" self.hulls_for_planets.clear() self.parts_for_planets.clear() - inhabited_planets = AIstate.popCtrIDs - if not inhabited_planets: - print "No inhabited planets found. The design process was aborted." - return - get_shipdesign = fo.getShipDesign - get_hulltype = fo.getHullType empire = fo.getEmpire() - empire_id = empire.empireID - universe = fo.getUniverse() - available_hulls = list(empire.availableShipHulls) # copy so we can sort it locally - # Later on in the code, we need to find suitable testhulls, i.e. buildable hulls for all slottypes. - # To reduce the number of lookups, move the hardcoded TESTDESIGN_PREFERED_HULL to the front of the list. - # This hull should be buildable on each planet and also cover the most common slottypes. - try: - idx = available_hulls.index(TESTDESIGN_PREFERRED_HULL) - available_hulls[0], available_hulls[idx] = available_hulls[idx], available_hulls[0] - except ValueError: - print "WARNING: Tried to use '%s' as testhull but not in available_hulls." % TESTDESIGN_PREFERRED_HULL, - print "Please update ShipDesignAI.py according to the new content." - traceback.print_exc() - testdesign_names = [get_shipdesign(design_id).name for design_id in empire.allShipDesigns - if get_shipdesign(design_id).name.startswith(TESTDESIGN_NAME_BASE)] - testdesign_names_hull = [name for name in testdesign_names if name.startswith(TESTDESIGN_NAME_HULL)] - testdesign_names_part = [name for name in testdesign_names if name.startswith(TESTDESIGN_NAME_PART)] - available_slot_types = {slottype for slotlist in [get_hulltype(hull).slots for hull in available_hulls] - for slottype in slotlist} - new_parts = [get_part_type(part) for part in empire.availableShipParts - if part not in self.strictly_worse_parts] - pid = self.production_cost.keys()[0] # as only location invariant parts are considered, use arbitrary planet. - for new_part in new_parts: - self.strictly_worse_parts[new_part.name] = [] - if new_part.partClass in WEAPONS: - continue # TODO: Update cache-functionality to handle tech upgrades - if not new_part.costTimeLocationInvariant: - print "new part %s not location invariant!" % new_part.name - continue - for part_class in ALL_META_CLASSES: - if new_part.partClass in part_class: - for old_part in [get_part_type(part) for part in self.strictly_worse_parts - if part != new_part.name]: - if not old_part.costTimeLocationInvariant: - print "old part %s not location invariant!" % old_part.name - continue - if old_part.partClass in part_class: - if new_part.capacity >= old_part.capacity: - a = new_part - b = old_part - else: - a = old_part - b = new_part - if (self.production_cost[pid][a.name] <= self.production_cost[pid][b.name] - and {x for x in a.mountableSlotTypes} >= {x for x in b.mountableSlotTypes} - and self.production_time[pid][a.name] <= self.production_time[pid][b.name]): - self.strictly_worse_parts[a.name].append(b.name) - print "Part %s is strictly worse than part %s" % (b.name, a.name) - break - available_parts = sorted(self.strictly_worse_parts.keys(), - key=lambda item: get_part_type(item).capacity, reverse=True) + all_hulls = list(empire.availableShipHulls) + all_parts = list(empire.availableShipParts) - # in case of a load, we need to rebuild our Cache. - if not self.testhulls: - print "Testhull cache not found. This may happen only at first turn after game start or load." - for hullname in available_hulls: - des = [des for des in testdesign_names_part if des.endswith(hullname)] - if des: - self.testhulls.add(hullname) - if verbose: - print "Rebuilt Cache. The following hulls are used in testdesigns for parts: ", self.testhulls - - # 1. Update hull test designs - print "Updating Testdesigns for hulls..." - if verbose: - print "Available Hulls: ", available_hulls - print "Existing Designs (prefix: %s): " % TESTDESIGN_NAME_HULL, - print [x.replace(TESTDESIGN_NAME_HULL, "") for x in testdesign_names_hull] - for hull in [get_hulltype(hullname) for hullname in available_hulls - if "%s_%s" % (TESTDESIGN_NAME_HULL, hullname) not in testdesign_names_hull]: - partlist = len(hull.slots) * [""] - testdesign_name = "%s_%s" % (TESTDESIGN_NAME_HULL, hull.name) - _create_ship_design(testdesign_name, hull.name, partlist, - description="TESTPURPOSE ONLY", verbose=verbose) - - # 2. Cache the list of buildable ship hulls for each planet - print "Caching buildable hulls per planet..." - testname = "%s_%s" % (TESTDESIGN_NAME_HULL, "%s") - for pid in inhabited_planets: - self.hulls_for_planets[pid] = [] - for hullname in available_hulls: - testdesign = _get_design_by_name(testname % hullname) - if testdesign: - for pid in inhabited_planets: - if _can_build(testdesign, empire_id, pid): - self.hulls_for_planets[pid].append(hullname) - else: - print "Missing testdesign for hull %s!" % hullname - - # 3. Update ship part test designs - # Because there are different slottypes, we need to find a hull that can host said slot. - # However, not every planet can build every hull. Thus, for each inhabited planet: - # I. Check which parts do not have a testdesign yet with a hull we can build on this planet - # II. If there are parts, find out which slots we need - # III. For each slot type, try to find a hull we can build on this planet - # and use this hull for all the parts hostable in this type. - print "Updating test designs for ship parts..." - if verbose: - print "Available parts: ", available_parts - print "Existing Designs (prefix: %s): " % TESTDESIGN_NAME_PART, - print [x.replace(TESTDESIGN_NAME_PART, "") for x in testdesign_names_part] - for pid in inhabited_planets: - planetname = universe.getPlanet(pid).name - local_hulls = self.hulls_for_planets[pid] - needs_update = [get_part_type(partname) for partname in available_parts - if not any(["%s_%s_%s" % (TESTDESIGN_NAME_PART, partname, hullname) in testdesign_names_part - for hullname in local_hulls])] - if not needs_update: - if verbose: - print "Planet %s: Test designs are up to date" % planetname - continue - if verbose: - print "Planet %s: The following parts appear to need a new design: " % planetname, - print [part.name for part in needs_update] - for slot in available_slot_types: - testhull = next((hullname for hullname in local_hulls if slot in get_hulltype(hullname).slots), None) - if testhull is None: - if verbose: - print "Failure: Could not find a hull with slots of type '%s' for this planet" % slot.name + for pid in state.get_inhabited_planets(): + for hull_name in all_hulls: + hull = fo.getShipHull(hull_name) + if assertion_fails(hull is not None): continue - else: - if verbose: - print "Using hull %s for slots of type '%s'" % (testhull, slot.name) - self.testhulls.add(testhull) - slotlist = [s for s in get_hulltype(testhull).slots] - slot_index = slotlist.index(slot) - num_slots = len(slotlist) - for part in [part for part in needs_update if slot in part.mountableSlotTypes]: - partlist = num_slots * [""] - partlist[slot_index] = part.name - testdesign_name = "%s_%s_%s" % (TESTDESIGN_NAME_PART, part.name, testhull) - res = _create_ship_design(testdesign_name, testhull, partlist, - description="TESTPURPOSE ONLY", verbose=verbose) - if res: - testdesign_names_part.append(testdesign_name) - else: - continue - needs_update.remove(part) # We only need one design per part, not for every possible slot - # later on in the code, we will have to check multiple times if the test hulls are in - # the list of buildable hulls for the planet. As the ordering is preserved, move the - # testhulls to the front of the availableHull list to save some time in the checks. - for i, s in enumerate(self.testhulls): - try: - idx = available_hulls.index(s) - if i != idx: - available_hulls[i], available_hulls[idx] = available_hulls[idx], available_hulls[i] - except ValueError: - print >> sys.stderr, ("hull in testhull cache not in available_hulls" - "even though it is supposed to be a proper subset.") - traceback.print_exc() - - # 4. Cache the list of buildable ship parts for each planet - print "Caching buildable ship parts per planet..." - for pid in inhabited_planets: - local_testhulls = [hull for hull in self.testhulls - if hull in self.hulls_for_planets[pid]] - this_planet = universe.getPlanet(pid) - if verbose: - print "Testhulls for %s are %s" % (this_planet, local_testhulls) - self.parts_for_planets[pid] = {} - local_ignore = set() - local_cache = self.parts_for_planets[pid] - for slot in available_slot_types: - local_cache[slot] = [] - for partname in available_parts: - if partname in local_ignore: + if hull.productionLocation(pid): + self.hulls_for_planets.setdefault(pid, []).append(hull_name) + + for part_name in all_parts: + ship_part = get_ship_part(part_name) + if assertion_fails(ship_part is not None): continue - part_slottypes = get_part_type(partname).mountableSlotTypes - ship_design = None - for hullname in local_testhulls: - if not any((slot in get_hulltype(hullname).slots for slot in part_slottypes)): - continue - ship_design = _get_design_by_name("%s_%s_%s" % (TESTDESIGN_NAME_PART, partname, hullname)) - if ship_design: - if _can_build(ship_design, empire_id, pid): - for slot in part_slottypes: - local_cache.setdefault(slot, []).append(partname) - local_ignore.update(self.strictly_worse_parts[partname]) - break - if verbose and not ship_design: - planetname = universe.getPlanet(pid).name - print "Failure: Couldn't find a testdesign for part %s on planet %s." % (partname, planetname) - # make sure we do not edit the list later on this turn => tuple: immutable - # This also allows to shallowcopy the cache. - for slot in local_cache: - local_cache[slot] = tuple(local_cache[slot]) - if verbose: - print "Parts for Planet: %s: " % universe.getPlanet(pid).name, self.parts_for_planets[pid] + slot_types = ship_part.mountableSlotTypes + if ship_part.productionLocation(pid): + for slot_type in slot_types: + self.parts_for_planets.setdefault(pid, {}).setdefault(slot_type, []).append(part_name) Cache = ShipDesignCache() -class AdditionalSpecifications(object): +class AdditionalSpecifications: """This class is a container for all kind of additional information and requirements we may want to use when assessing ship designs. @@ -627,6 +409,7 @@ def __init__(self): # TODO: Extend this framework according to needs of future implementations self.minimum_fuel = 0 self.minimum_speed = 0 + self.orbital = False self.minimum_structure = 1 self.minimum_fighter_launch_rate = 0 self.enemy_shields = 1 # to avoid spamming flak cannons @@ -643,7 +426,7 @@ def __init__(self): else: self.enemy_mine_dmg = 14 self.enemy = None - self.update_enemy(foAI.foAIstate.get_standard_enemy()) + self.update_enemy(get_aistate().get_standard_enemy()) def update_enemy(self, enemy): """Read out the enemies stats and save them. @@ -658,10 +441,10 @@ def update_enemy(self, enemy): self.max_enemy_weapon_strength = max(enemy_attack_stats.keys()) n = 0 d = 0 - for dmg, count in enemy_attack_stats.iteritems(): + for dmg, count in enemy_attack_stats.items(): d += dmg*count n += count - self.avg_enemy_weapon_strength = d/n + self.avg_enemy_weapon_strength = d // n # TODO check if we need floor division here def convert_to_tuple(self): """Create a tuple of this class' attributes (e.g. to use as key in dict). @@ -673,7 +456,7 @@ def convert_to_tuple(self): "enemyMineDmg: %s" % self.enemy_mine_dmg) -class DesignStats(object): +class DesignStats: def __init__(self): self.attacks = {} # {damage: shots_per_round} @@ -699,14 +482,20 @@ def reset(self): self.fighter_capacity = 0 self.fighter_launch_rate = 0 self.fighter_damage = 0 + self.flak_shots = 0 + self.has_interceptors = False + self.damage_vs_planets = 0 + self.has_bomber = False def convert_to_combat_stats(self): """Return a tuple as expected by CombatRatingsAI""" return (self.attacks, self.structure, self.shields, - self.fighter_capacity, self.fighter_launch_rate, self.fighter_damage) + self.fighter_capacity, self.fighter_launch_rate, self.fighter_damage, + self.flak_shots, self.has_interceptors, + self.damage_vs_planets, self.has_bomber) -class ShipDesigner(object): +class ShipDesigner: """This class and its subclasses implement the building of a ship design and its rating. Specialised Designs with their own rating system or optimizing algorithms should inherit from this class. @@ -748,7 +537,7 @@ def __init__(self): self.partnames = [] # list of partnames (string) self.parts = [] # list of actual part objects self.design_stats = DesignStats() - self.production_cost = 9999 + self.production_cost = 9999.0 self.production_time = 1 self.pid = INVALID_ID # planetID for checks on production cost if not LocationInvariant. self.additional_specifications = AdditionalSpecifications() @@ -769,11 +558,13 @@ def evaluate(self): # However, we also need to make sure, that the closer we are to requirements, # the better our rating is so the optimizing heuristic finds "the right way". rating = MISSING_REQUIREMENT_MULTIPLIER * sum([ - max(0, self._minimum_fuel() - self.design_stats.fuel), + max(0., self._minimum_fuel() - self.design_stats.fuel), max(0, self._minimum_speed() - self.design_stats.speed), max(0, self._minimum_structure() - (self.design_stats.structure + self._expected_organic_growth())), max(0, self._minimum_fighter_launch_rate() - self.design_stats.fighter_launch_rate) ]) + if self._orbital() and self.design_stats.speed > 0: + rating = min(-100, rating - 99999) return rating if rating < 0 else self._rating_function() def _minimum_fuel(self): @@ -782,6 +573,9 @@ def _minimum_fuel(self): def _minimum_speed(self): return self.additional_specifications.minimum_speed + def _orbital(self): + return self.additional_specifications.orbital + def _minimum_structure(self): return self.additional_specifications.minimum_structure @@ -804,13 +598,20 @@ def _set_stats_to_default(self): self.production_cost = 9999 self.production_time = 1 + def _hull_fuel_efficiency(self): + for tag in AIDependencies.HULL_TAG_EFFECTS: + if self.hull.hasTag(tag): + if AIDependencies.FUEL_EFFICIENCY in AIDependencies.HULL_TAG_EFFECTS[tag]: + return AIDependencies.HULL_TAG_EFFECTS[tag][AIDependencies.FUEL_EFFICIENCY] + return AIDependencies.DEFAULT_FUEL_EFFICIENCY + def update_hull(self, hullname): """Set hull of the design. :param hullname: :type hullname: str """ - self.hull = fo.getHullType(hullname) + self.hull = fo.getShipHull(hullname) def update_parts(self, partname_list): """Set both partnames and parts attributes. @@ -818,7 +619,7 @@ def update_parts(self, partname_list): :param partname_list: contains partnames as strings :type partname_list: list""" self.partnames = partname_list - self.parts = [get_part_type(part) for part in partname_list if part] + self.parts = [get_ship_part(part) for part in partname_list if part] def update_species(self, species): """Set the piloting species. @@ -839,7 +640,7 @@ def update_stats(self, ignore_species=False): self._set_stats_to_default() if not self.hull: - print >> sys.stderr, "Tried to update stats of design without hull. Reset values to default." + warning("Tried to update stats of design without hull. Reset values to default.") return local_cost_cache = Cache.production_cost[self.pid] @@ -850,20 +651,21 @@ def update_stats(self, ignore_species=False): self.design_stats.fuel = self.hull.fuel self.design_stats.speed = self.hull.speed self.design_stats.stealth = self.hull.stealth - self.production_cost = local_cost_cache.get(self.hull.name, self.hull.productionCost(fo.empireID(), self.pid)) - self.production_time = local_time_cache.get(self.hull.name, self.hull.productionTime(fo.empireID(), self.pid)) + self.production_cost = local_cost_cache.get(self.hull.name, self.hull.productionCost(fo.empireID(), self.pid, INVALID_ID)) + self.production_time = local_time_cache.get(self.hull.name, self.hull.productionTime(fo.empireID(), self.pid, INVALID_ID)) # read out part stats shield_counter = cloak_counter = detection_counter = colonization_counter = engine_counter = 0 # to deal with Non-stacking parts - hangar_parts = set() + hangar_part_names = set() + bay_parts = list() for part in self.parts: - self.production_cost += local_cost_cache.get(part.name, part.productionCost(fo.empireID(), self.pid)) + self.production_cost += local_cost_cache.get(part.name, part.productionCost(fo.empireID(), self.pid, INVALID_ID)) self.production_time = max(self.production_time, - local_time_cache.get(part.name, part.productionTime(fo.empireID(), self.pid))) + local_time_cache.get(part.name, part.productionTime(fo.empireID(), self.pid, INVALID_ID))) partclass = part.partClass capacity = part.capacity if partclass not in WEAPONS else self._calculate_weapon_strength(part) if partclass in FUEL: - self.design_stats.fuel += capacity + self.design_stats.fuel += self._calculate_fuel_part_capacity(part) elif partclass in ENGINES: engine_counter += 1 if engine_counter == 1: @@ -886,7 +688,13 @@ def update_stats(self, ignore_species=False): self.design_stats.structure += capacity elif partclass in WEAPONS: shots = self._calculate_weapon_shots(part) - self.design_stats.attacks[capacity] = self.design_stats.attacks.get(capacity, 0) + shots + allowed_targets = CombatRatingsAI.get_allowed_targets(part.name) + if allowed_targets & AIDependencies.CombatTarget.SHIP: + self.design_stats.attacks[capacity] = self.design_stats.attacks.get(capacity, 0) + shots + if allowed_targets & AIDependencies.CombatTarget.FIGHTER: + self.design_stats.flak_shots += shots + if allowed_targets & AIDependencies.CombatTarget.PLANET: + self.design_stats.damage_vs_planets += capacity*shots elif partclass in SHIELDS: shield_counter += 1 if shield_counter == 1: @@ -902,27 +710,42 @@ def update_stats(self, ignore_species=False): else: self.design_stats.stealth = 0 elif partclass in FIGHTER_BAY: - self.design_stats.fighter_launch_rate += capacity + bay_parts.append(part) elif partclass in FIGHTER_HANGAR: - hangar_parts.add(part.name) - if len(hangar_parts) > 1: + hangar_part_names.add(part.name) + if len(hangar_part_names) > 1: # enforce only one hangar part per design self.design_stats.fighter_capacity = 0 self.design_stats.fighter_damage = 0 + self.design_stats.fighter_launch_rate = 0 + self.design_stats.has_interceptors = False + self.design_stats.has_bomber = False else: + allowed_targets = CombatRatingsAI.get_allowed_targets(part.name) self.design_stats.fighter_capacity += self._calculate_hangar_capacity(part) - self.design_stats.fighter_damage = self._calculate_hangar_damage(part) + if allowed_targets & AIDependencies.CombatTarget.SHIP: + self.design_stats.fighter_damage = self._calculate_hangar_damage(part) + if allowed_targets & AIDependencies.CombatTarget.FIGHTER: + self.design_stats.has_interceptors = True + if allowed_targets & AIDependencies.CombatTarget.PLANET: + self.design_stats.has_bomber = True + + if len(bay_parts) > 0: + hangar_part_name = None + for hangar_part_name in hangar_part_names: + break + self.design_stats.fighter_launch_rate = self._calculate_fighter_launch_rate(bay_parts, hangar_part_name) - self._apply_hardcoded_effects() + self._apply_hardcoded_effects(ignore_species) if self.species and not ignore_species: - shields_grade = CombatRatingsAI.get_species_shield_grade(self.species) + shields_grade = get_species_tag_grade(self.species, Tags.SHIELDS) self.design_stats.shields = CombatRatingsAI.weight_shields(self.design_stats.shields, shields_grade) if self.design_stats.troops: - troops_grade = CombatRatingsAI.get_species_troops_grade(self.species) + troops_grade = get_species_tag_grade(self.species, Tags.ATTACKTROOPS) self.design_stats.troops = CombatRatingsAI.weight_attack_troops(self.design_stats.troops, troops_grade) - def _apply_hardcoded_effects(self): + def _apply_hardcoded_effects(self, ignore_species=False): """Update stats that can not be read out by the AI yet, i.e. applied by effects. This function should contain *all* hardcoded effects for hulls/parts to be considered by the AI @@ -946,7 +769,7 @@ def parse_complex_tokens(tup): elif dependency == AIDependencies.FUEL: dep_val = self.design_stats.fuel else: - print >> sys.stderr, "Can't parse dependent token: ", tup + warning("Can't parse dependent token:" + str(tup)) return dep_val * value def parse_tokens(tokendict, is_hull=False): @@ -989,13 +812,13 @@ def parse_tokens(tokendict, is_hull=False): elif token == AIDependencies.STRUCTURE: self.design_stats.structure += value else: - print >> sys.stderr, "Failed to parse token: %s" % token + warning("Failed to parse token: %s" % token) # if the hull has no special detection specified, then it has base detection. if is_hull and AIDependencies.DETECTION not in tokendict: self.design_stats.detection += AIDependencies.BASE_DETECTION # TODO establish framework for conditional effects and get rid of this - if self.hull.name == "SH_SPATIAL_FLUX": + if self.hull.name in ["SH_SPATIAL_FLUX", "SH_SPACE_FLUX_BUBBLE"]: self.design_stats.stealth += 10 relevant_stealth_techs = [ "SPY_STEALTH_PART_1", "SPY_STEALTH_PART_2", @@ -1018,7 +841,16 @@ def parse_tokens(tokendict, is_hull=False): if tech_is_complete(tech): parse_tokens(AIDependencies.TECH_EFFECTS[tech]) - def add_design(self, verbose=False): + # fuel effects (besides already handled FUEL TECH_EFFECTS e.g. GRO_ENERGY_META) + if not ignore_species: + self.design_stats.fuel += _get_species_fuel_bonus(self.species) + # set fuel to zero for NO_FUEL species (-100 fuel bonus) + if self.design_stats.fuel < 0: + self.design_stats.fuel = 0 + else: + self.design_stats.fuel = (self.design_stats.fuel - self.hull.fuel) * self._hull_fuel_efficiency() + self.hull.fuel + + def add_design(self, verbose=True): """Add a real (i.e. gameobject) ship design of the current configuration. :param verbose: toggles detailed debugging output @@ -1037,19 +869,18 @@ def add_design(self, verbose=False): if reference_name in Cache.map_reference_design_name: if verbose: - print "Design with reference name %s is cached: %s" % (reference_name, - Cache.map_reference_design_name[reference_name]) + debug("Design with reference name %s is cached: %s" % ( + reference_name, Cache.map_reference_design_name[reference_name])) try: return _get_design_by_name(Cache.map_reference_design_name[reference_name]).id except AttributeError: cached_name = Cache.map_reference_design_name[reference_name] - print >> sys.stderr, "%s maps to %s in Cache.map_reference_design_name." % (reference_name, cached_name), - print >> sys.stderr, "But the design seems not to exist..." - traceback.print_exc() + error("%s maps to %s in Cache.map_reference_design_name." + " But the design seems not to exist..." % (reference_name, cached_name), exc_info=True) return None if verbose: - print "Trying to add Design... %s" % design_name + debug("Trying to add Design... %s" % design_name) res = _create_ship_design(design_name, self.hull.name, self.partnames, description=self.description, verbose=verbose) if not res: @@ -1059,7 +890,7 @@ def add_design(self, verbose=False): Cache.map_reference_design_name[reference_name] = design_name return new_design.id else: - print >> sys.stderr, "Tried to get just created design %s but got None" % design_name + warning("Tried to get just created design %s but got None" % design_name) return None def _class_specific_filter(self, partname_dict): @@ -1087,13 +918,13 @@ def optimize_design(self, additional_parts=(), additional_hulls=(), :type consider_fleet_count: bool """ if loc is None: - planets = AIstate.popCtrIDs + planets = state.get_inhabited_planets() elif isinstance(loc, int): planets = [loc] elif isinstance(loc, list): planets = loc else: - print >> sys.stderr, "Invalid loc parameter for optimize_design(). Expected int or list but got", loc + error("Invalid loc parameter for optimize_design(). Expected int or list but got %s" % loc) return [] self.consider_fleet_count = consider_fleet_count @@ -1102,7 +933,7 @@ def optimize_design(self, additional_parts=(), additional_hulls=(), additional_part_dict = {} for partname in additional_parts: - for slot in get_part_type(partname).mountableSlotTypes: + for slot in get_ship_part(partname).mountableSlotTypes: additional_part_dict.setdefault(slot, []).append(partname) # TODO: Rework caching to only cache raw stats of designs, then evaluate them @@ -1114,41 +945,51 @@ def optimize_design(self, additional_parts=(), additional_hulls=(), best_design_list = [] if verbose: - print "Trying to find optimum designs for shiptype class %s" % self.__class__.__name__ + debug("Trying to find optimum designs for shiptype class %s" % self.__class__.__name__) + + relevant_techs = [] + + def extend_completed_techs(techs: Iterable): + relevant_techs.extend(_tech for _tech in techs if tech_is_complete(_tech)) + + if WEAPONS & self.useful_part_classes: + extend_completed_techs(AIDependencies.WEAPON_UPGRADE_TECHS) + if FIGHTER_HANGAR & self.useful_part_classes: + extend_completed_techs(AIDependencies.FIGHTER_UPGRADE_TECHS) + if FUEL & self.useful_part_classes: + extend_completed_techs(AIDependencies.FUEL_UPGRADE_TECHS) + extend_completed_techs(AIDependencies.TECH_EFFECTS) + + relevant_techs = tuple(set(relevant_techs)) + design_cache_tech = design_cache_reqs.setdefault(relevant_techs, {}) + for pid in planets: planet = universe.getPlanet(pid) self.pid = pid self.update_species(planet.speciesName) - relevant_techs = [] - if WEAPONS & self.useful_part_classes: - relevant_techs = [tech for tech in AIDependencies.WEAPON_UPGRADE_TECHS if tech_is_complete(tech)] - relevant_techs += [tech for tech in AIDependencies.TECH_EFFECTS if tech_is_complete(tech)] - relevant_techs = tuple(relevant_techs) - design_cache_tech = design_cache_reqs.setdefault(relevant_techs, {}) - # The piloting species is only important if its modifiers are of any use to the design # Therefore, consider only those treats that are actually useful. Note that the # canColonize trait is covered by the parts we can build, so no need to consider it here. # The same is true for the canProduceShips trait which simply means no hull can be built. relevant_grades = [] if WEAPONS & self.useful_part_classes: - weapons_grade = CombatRatingsAI.get_pilot_weapons_grade(self.species) + weapons_grade = get_species_tag_grade(self.species, Tags.WEAPONS) relevant_grades.append("WEAPON: %s" % weapons_grade) if SHIELDS & self.useful_part_classes: - shields_grade = CombatRatingsAI.get_species_shield_grade(self.species) + shields_grade = get_species_tag_grade(self.species, Tags.SHIELDS) relevant_grades.append("SHIELDS: %s" % shields_grade) if TROOPS & self.useful_part_classes: - troops_grade = CombatRatingsAI.get_species_troops_grade(self.species) + troops_grade = get_species_tag_grade(self.species, Tags.ATTACKTROOPS) relevant_grades.append("TROOPS: %s" % troops_grade) species_tuple = tuple(relevant_grades) design_cache_species = design_cache_tech.setdefault(species_tuple, {}) available_hulls = list(Cache.hulls_for_planets[pid]) + list(additional_hulls) if verbose: - print "Evaluating planet %s" % planet.name - print "Species:", planet.speciesName - print "Available Ship Hulls: ", available_hulls + debug("Evaluating planet %s" % planet.name) + debug("Species: %s" % planet.speciesName) + debug("Available Ship Hulls: %s" % available_hulls) available_parts = copy.copy(Cache.parts_for_planets[pid]) # this is a dict! {slottype:(partnames)} available_slots = set(available_parts.keys()) | set(additional_part_dict.keys()) for slot in available_slots: @@ -1163,32 +1004,38 @@ def optimize_design(self, additional_parts=(), additional_hulls=(), best_hull = None best_parts = None for hullname in available_hulls: + # TODO: Expose FOCS Exclusions and replace manually maintained AIDependencies dict + hull_excluded_part_classes = AIDependencies.HULL_EXCLUDED_SHIP_PART_CLASSES.get(hullname, []) + available_parts_in_hull = { + slot: [part_name for part_name in available_parts[slot] + if get_ship_part(part_name).partClass not in hull_excluded_part_classes] + for slot in available_parts} if hullname in design_cache_parts: cache = design_cache_parts[hullname] best_hull_rating = cache[0] current_parts = cache[1] if verbose: - print "Best rating for hull %s: %f (read from Cache)" % (hullname, best_hull_rating), - print current_parts + debug("Best rating for hull %s: %f (read from Cache) %s" % ( + hullname, best_hull_rating, current_parts)) else: self.update_hull(hullname) - best_hull_rating, current_parts = self._filling_algorithm(available_parts) + best_hull_rating, current_parts = self._filling_algorithm(available_parts_in_hull) design_cache_parts.update({hullname: (best_hull_rating, current_parts)}) if verbose: - print "Best rating for hull %s: %f" % (hullname, best_hull_rating), current_parts + debug("Best rating for hull %s: %f %s" % (hullname, best_hull_rating, current_parts)) if best_hull_rating > best_rating_for_planet: best_rating_for_planet = best_hull_rating best_hull = hullname best_parts = current_parts if verbose: - print "Best overall rating for this planet: %f" % best_rating_for_planet, - print "(", best_hull, " with", best_parts, ")" + debug("Best overall rating for this planet: %f (%s with %s)" % ( + best_rating_for_planet, best_hull, best_parts)) if best_hull: self.update_hull(best_hull) self.update_parts(best_parts) design_id = self.add_design(verbose=verbose) if verbose: - print "For best design got got design id %s" % design_id + debug("For best design got got design id %s" % design_id) if design_id is not None: best_design_list.append((best_rating_for_planet, pid, design_id, self.production_cost, copy.deepcopy(self.design_stats))) @@ -1196,7 +1043,7 @@ def optimize_design(self, additional_parts=(), additional_hulls=(), error("The best design for %s on planet %d could not be added." % (self.__class__.__name__, pid)) elif verbose: - print "Could not find a suitable design of type %s for planet %s." % (self.__class__.__name__, planet) + debug("Could not find a suitable design of type %s for planet %s." % (self.__class__.__name__, planet)) sorted_design_list = sorted(best_design_list, key=lambda x: x[0], reverse=True) return sorted_design_list @@ -1218,12 +1065,18 @@ def _filter_parts(self, partname_dict, verbose=False): """ empire_id = fo.empireID() if verbose: - print "Available parts:" + debug("Available parts:") for x in partname_dict: - print x, ":", partname_dict[x] + debug(" %s: %s" % (x, partname_dict[x])) - part_dict = {slottype: zip(partname_dict[slottype], map(get_part_type, partname_dict[slottype])) - for slottype in partname_dict} # {slottype: [(partname, parttype_object)]} + part_dict = { + slottype: list( + zip( + partname_dict[slottype], + (get_ship_part(x) for x in partname_dict[slottype]) + ) + ) for slottype in partname_dict + } # {slottype: [(partname, shippart_object)]} for slottype in part_dict: part_dict[slottype] = [tup for tup in part_dict[slottype] if tup[1].partClass in self.useful_part_classes] @@ -1241,33 +1094,33 @@ def _filter_parts(self, partname_dict, verbose=False): partclass = tup[1].partClass if partclass in check_for_redundance: partclass_dict[partclass].append(tup[1]) - for shipPartsPerClass in partclass_dict.itervalues(): + for shipPartsPerClass in partclass_dict.values(): for a in shipPartsPerClass: if a.capacity == 0: # TODO: Modify this if effects of items get hardcoded part_dict[slottype].remove((a.name, a)) if verbose: - print "removing %s because capacity is zero." % a.name + debug("removing %s because capacity is zero." % a.name) continue if len(shipPartsPerClass) == 1: break # cost_a = local_cost_cache[a.name] - cost_a = local_cost_cache.get(a.name, a.productionCost(empire_id, self.pid)) + cost_a = local_cost_cache.get(a.name, a.productionCost(empire_id, self.pid, INVALID_ID)) for b in shipPartsPerClass: - cost_b = local_cost_cache.get(b.name, b.productionCost(empire_id, self.pid)) + cost_b = local_cost_cache.get(b.name, b.productionCost(empire_id, self.pid, INVALID_ID)) if (b is not a and (b.capacity/cost_b - a.capacity/cost_a) > -1e-6 and b.capacity >= a.capacity): if verbose: - print "removing %s because %s is better." % (a.name, b.name) + debug("removing %s because %s is better." % (a.name, b.name)) part_dict[slottype].remove((a.name, a)) break for slottype in part_dict: partname_dict[slottype] = [tup[0] for tup in part_dict[slottype]] self._class_specific_filter(partname_dict) if verbose: - print "Available parts after filtering:" + debug("Available parts after filtering:") for x in partname_dict: - print x, ":", partname_dict[x] + debug(" %s: %s" % (x, partname_dict[x])) def _starting_guess(self, available_parts, num_slots): """Return an initial guess for the filling of the slots. @@ -1339,7 +1192,7 @@ def _combinatorial_filling(self, available_parts): break current_filling = total_filling[slot] num_parts = len(current_filling) - range_parts = range(num_parts) + range_parts = list(range(num_parts)) current_parts = [] other_parts = [] for s in number_of_slots_by_slottype: @@ -1347,7 +1200,7 @@ def _combinatorial_filling(self, available_parts): for j in range_parts: current_parts += current_filling[j] * [parts[s][j]] else: - for j in xrange(len(total_filling[s])): + for j in range(len(total_filling[s])): other_parts += total_filling[s][j] * [parts[s][j]] self.update_parts(other_parts + current_parts) current_rating = self.evaluate() @@ -1383,7 +1236,7 @@ def _combinatorial_filling(self, available_parts): slot_filling = {} for slot in number_of_slots_by_slottype: slot_filling[slot] = [] - for j in xrange(len(total_filling[slot])): + for j in range(len(total_filling[slot])): slot_filling[slot] += total_filling[slot][j] * [parts[slot][j]] for slot in self.hull.slots: partlist.append(slot_filling[slot].pop()) @@ -1406,7 +1259,7 @@ def _total_dmg_vs_shields(self): :return: summed up damage vs shielded enemy """ - total_dmg = 0 + total_dmg = 0.0 for dmg, count in self.design_stats.attacks.items(): total_dmg += max(0, dmg - self.additional_specifications.enemy_shields) * count return total_dmg @@ -1416,7 +1269,7 @@ def _total_dmg(self): :return: Total damage of the design (against no shields) """ - total_dmg = 0 + total_dmg = 0.0 for dmg, count in self.design_stats.attacks.items(): total_dmg += dmg * count return total_dmg @@ -1482,14 +1335,6 @@ def _adjusted_production_cost(self): else: return self.production_cost / FleetUtilsAI.get_fleet_upkeep() # base cost - def _shield_factor(self): - """Calculate the effective factor by which structure is increased by shields. - - :rtype: float - """ - enemy_dmg = self.additional_specifications.max_enemy_weapon_strength - return max(enemy_dmg / max(0.01, enemy_dmg - self.design_stats.shields), 1) - def _effective_fuel(self): """Return the number of turns the ship can move without refueling. @@ -1506,14 +1351,6 @@ def _expected_organic_growth(self): return min(self.additional_specifications.expected_turns_till_fight * self.design_stats.organic_growth, self.design_stats.maximum_organic_growth) - def _remaining_growth(self): - """Get growth potential after _expected_organic_growth() took place. - - :return: Remaining growth after _expected_organic_growth() - :rtype: float - """ - return self.design_stats.maximum_organic_growth - self._expected_organic_growth() - def _effective_mine_damage(self): """Return enemy mine damage corrected by self-repair-rate. @@ -1530,6 +1367,14 @@ def _partclass_in_design(self, partclass): """ return any(part.partClass in partclass for part in self.parts) + def _calculate_fuel_part_capacity(self, fuel_part, ignore_species=False): + # base fuel + tank_name = fuel_part.name + base = fuel_part.capacity + tech_bonus = _get_tech_bonus(AIDependencies.FUEL_TANK_UPGRADE_DICT, tank_name) + # There is no per part species modifier + return base + tech_bonus + def _calculate_weapon_strength(self, weapon_part, ignore_species=False): # base damage weapon_name = weapon_part.name @@ -1537,7 +1382,7 @@ def _calculate_weapon_strength(self, weapon_part, ignore_species=False): tech_bonus = _get_tech_bonus(AIDependencies.WEAPON_UPGRADE_DICT, weapon_name) # species modifiers if not ignore_species: - weapons_grade = CombatRatingsAI.get_pilot_weapons_grade(self.species) + weapons_grade = get_species_tag_grade(self.species, Tags.WEAPONS) species_modifier = AIDependencies.PILOT_DAMAGE_MODIFIER_DICT.get(weapons_grade, {}).get(weapon_name, 0) else: species_modifier = 0 @@ -1547,26 +1392,37 @@ def _calculate_weapon_shots(self, weapon_part, ignore_species=False): weapon_name = weapon_part.name base = weapon_part.secondaryStat if not base: - print "WARNING: Queried weapon %s for number of shots but didn't return any." % weapon_name + warning("Queried weapon %s for number of shots but didn't return any." % weapon_name) base = 1 tech_bonus = _get_tech_bonus(AIDependencies.WEAPON_ROF_UPGRADE_DICT, weapon_name) # species modifier if not ignore_species: - weapons_grade = CombatRatingsAI.get_pilot_weapons_grade(self.species) + weapons_grade = get_species_tag_grade(self.species, Tags.WEAPONS) species_modifier = AIDependencies.PILOT_ROF_MODIFIER_DICT.get(weapons_grade, {}).get(weapon_name, 0) else: species_modifier = 0 return base + species_modifier + tech_bonus + def _calculate_fighter_launch_rate(self, bay_parts, hangar_part_name): + launch_rate = 0 + bay_launch_capacity_modifier_dict = {} + if hangar_part_name: + bay_launch_capacity_modifier_dict = AIDependencies.HANGAR_LAUNCH_CAPACITY_MODIFIER_DICT.get(hangar_part_name, {}) + for bay_part in bay_parts: + launch_rate += bay_part.capacity + if bay_launch_capacity_modifier_dict: + launch_rate += _get_tech_bonus(bay_launch_capacity_modifier_dict, bay_part.name) + return launch_rate + def _calculate_hangar_damage(self, hangar_part, ignore_species=False): hangar_name = hangar_part.name base = hangar_part.secondaryStat tech_bonus = _get_tech_bonus(AIDependencies.FIGHTER_DAMAGE_UPGRADE_DICT, hangar_name) # species modifier if not ignore_species: - weapons_grade = CombatRatingsAI.get_pilot_weapons_grade(self.species) - species_modifier = AIDependencies.PILOT_FIGHTERDAMAGE_MODIFIER_DICT.get(weapons_grade, - {}).get(hangar_name, 0) + weapons_grade = get_species_tag_grade(self.species, Tags.WEAPONS) + species_modifier = AIDependencies.PILOT_FIGHTERDAMAGE_MODIFIER_DICT.get( + weapons_grade, {}).get(hangar_name, 0) else: species_modifier = 0 return base + species_modifier + tech_bonus @@ -1577,9 +1433,9 @@ def _calculate_hangar_capacity(self, hangar_part, ignore_species=False): tech_bonus = _get_tech_bonus(AIDependencies.FIGHTER_CAPACITY_UPGRADE_DICT, hangar_name) # species modifier if not ignore_species: - weapons_grade = CombatRatingsAI.get_pilot_weapons_grade(self.species) - species_modifier = AIDependencies.PILOT_FIGHTER_CAPACITY_MODIFIER_DICT.get(weapons_grade, - {}).get(hangar_name, 0) + weapons_grade = get_species_tag_grade(self.species, Tags.WEAPONS) + species_modifier = AIDependencies.PILOT_FIGHTER_CAPACITY_MODIFIER_DICT.get( + weapons_grade, {}).get(hangar_name, 0) else: species_modifier = 0 return base + species_modifier + tech_bonus @@ -1595,18 +1451,15 @@ class MilitaryShipDesignerBaseClass(ShipDesigner): def __init__(self): super(MilitaryShipDesignerBaseClass, self).__init__() + self.additional_specifications.minimum_fuel = 2 + self.additional_specifications.minimum_speed = 30 def _adjusted_production_cost(self): # as military ships are grouped up in fleets, their power rating scales quadratic in numbers. # To account for this, we need to maximize rating/cost_squared not rating/cost as usual. - exponent = foAI.foAIstate.character.warship_adjusted_production_cost_exponent() + exponent = get_aistate().character.warship_adjusted_production_cost_exponent() return super(MilitaryShipDesignerBaseClass, self)._adjusted_production_cost()**exponent - def _effective_structure(self): - effective_structure = self.design_stats.structure + self._expected_organic_growth() + self._remaining_growth() / 5 - effective_structure *= self._shield_factor() - return effective_structure - def _speed_factor(self): return 1 + 0.005*(self.design_stats.speed - 85) @@ -1638,9 +1491,7 @@ class WarShipDesigner(MilitaryShipDesignerBaseClass): 15000, 20000, 25000, 30000, 35000, 40000, 50000, 70000, 1000000]) def __init__(self): - ShipDesigner.__init__(self) - self.additional_specifications.minimum_fuel = 1 - self.additional_specifications.minimum_speed = 30 + super(WarShipDesigner, self).__init__() self.additional_specifications.expected_turns_till_fight = 10 if fo.currentTurn() < 50 else 5 def _rating_function(self): @@ -1670,25 +1521,24 @@ def _starting_guess(self, available_parts, num_slots): # As this is a simple rational function in n, the maximizing problem can be solved analytically. # The analytical solution (after rounding to the nearest integer)is a good starting guess for our best design. ret_val = (len(available_parts) + 1) * [0] - parts = [get_part_type(part) for part in available_parts] + parts = [get_ship_part(part) for part in available_parts] weapons = [part for part in parts if part.partClass in WEAPONS] armours = [part for part in parts if part.partClass in ARMOUR] - cap = lambda x: x.capacity if weapons: weapon_part = max(weapons, key=self._calculate_weapon_strength) weapon = weapon_part.name idxweapon = available_parts.index(weapon) - cw = Cache.production_cost[self.pid].get(weapon, weapon_part.productionCost(fo.empireID(), self.pid)) + cw = Cache.production_cost[self.pid].get(weapon, weapon_part.productionCost(fo.empireID(), self.pid, INVALID_ID)) if armours: - armour_part = max(armours, key=cap) + armour_part = max(armours, key=_get_capacity) armour = armour_part.name idxarmour = available_parts.index(armour) - a = get_part_type(armour).capacity - ca = Cache.production_cost[self.pid].get(armour, armour_part.productionCost(fo.empireID(), self.pid)) + a = get_ship_part(armour).capacity + ca = Cache.production_cost[self.pid].get(armour, armour_part.productionCost(fo.empireID(), self.pid, INVALID_ID)) s = num_slots h = self.hull.structure ch = Cache.production_cost[self.pid].get(self.hull.name, - self.hull.productionCost(fo.empireID(), self.pid)) + self.hull.productionCost(fo.empireID(), self.pid, INVALID_ID)) if ca == cw: n = (s+h/a)/2 else: @@ -1699,13 +1549,13 @@ def _starting_guess(self, available_parts, num_slots): n = int(round(n)) n = max(n, 1) n = min(n, s) - # print "estimated weapon slots for %s: %d" % (self.hull.name, n) + debug("estimated weapon slots for %s: %d" % (self.hull.name, n)) ret_val[idxarmour] = s-n ret_val[idxweapon] = n else: ret_val[idxweapon] = num_slots elif armours: - armour = max(armours, key=cap).name + armour = max(armours, key=_get_capacity).name idxarmour = available_parts.index(armour) ret_val[idxarmour] = num_slots else: @@ -1735,9 +1585,7 @@ class CarrierShipDesigner(MilitaryShipDesignerBaseClass): NAME_THRESHOLDS = sorted([0, 1000]) def __init__(self): - ShipDesigner.__init__(self) - self.additional_specifications.minimum_fuel = 1 - self.additional_specifications.minimum_speed = 30 + super(CarrierShipDesigner, self).__init__() self.additional_specifications.expected_turns_till_fight = 10 if fo.currentTurn() < 50 else 5 self.additional_specifications.minimum_fighter_launch_rate = 1 @@ -1757,17 +1605,17 @@ def _filling_algorithm(self, available_parts, verbose=False): # Therefore, after using (multiple) entries of one hangar part, the algorithm won't consider different parts. # Workaround: Do multiple passes with only one hangar part each and choose the best rated one. if verbose: - print "Calling _filling_algorithm() for Carrier-Style ships!" - print "Available parts: ", available_parts + debug("Calling _filling_algorithm() for Carrier-Style ships!") + debug("Available parts: %s" % available_parts) # first, get all available hangar parts. hangar_parts = set() for partlist in available_parts.values(): for partname in partlist: - part = get_part_type(partname) + part = get_ship_part(partname) if part.partClass == fo.shipPartClass.fighterHangar: hangar_parts.add(partname) if verbose: - print "Found the following hangar parts: ", hangar_parts + debug("Found the following hangar parts: %s" % hangar_parts) # now, call the standard-algorithm with only one hangar part at a time and choose the best rated one. best_rating = INVALID_DESIGN_RATING @@ -1775,11 +1623,11 @@ def _filling_algorithm(self, available_parts, verbose=False): for this_hangar_part in hangar_parts: current_available_parts = {} forbidden_hangar_parts = {part for part in hangar_parts if part != this_hangar_part} - for slot, partlist in available_parts.iteritems(): - current_available_parts[slot] = [part for part in partlist if part not in forbidden_hangar_parts] + for slot, partlist in available_parts.items(): + current_available_parts[slot] = [part_ for part_ in partlist if part_ not in forbidden_hangar_parts] this_rating, this_partlist = ShipDesigner._filling_algorithm(self, current_available_parts) if verbose: - print "Best rating for part %s is %.2f with partlist %s" % (this_hangar_part, this_rating, this_partlist) + debug("Best rating for part %s is %.2f with partlist %s" % (this_hangar_part, this_rating, this_partlist)) if this_rating > best_rating: best_rating = this_rating best_partlist = this_partlist @@ -1816,16 +1664,15 @@ def _rating_function(self): def _starting_guess(self, available_parts, num_slots): # fill completely with biggest troop pods. If none are available for this slot type, leave empty. - troop_pods = [get_part_type(part) for part in available_parts if get_part_type(part).partClass in TROOPS] + troop_pods = [get_ship_part(part) for part in available_parts if get_ship_part(part).partClass in TROOPS] ret_val = (len(available_parts)+1)*[0] if troop_pods: - cap = lambda x: x.capacity - biggest_troop_pod = max(troop_pods, key=cap).name + biggest_troop_pod = max(troop_pods, key=_get_capacity).name try: # we could use an if-check here but since we usually have troop pods for the slot, try is faster idx = available_parts.index(biggest_troop_pod) except ValueError: idx = len(available_parts) - traceback.print_exc() + warning("Found no valid troop pod.", exc_info=True) else: idx = len(available_parts) ret_val[idx] = num_slots @@ -1834,7 +1681,7 @@ def _starting_guess(self, available_parts, num_slots): def _class_specific_filter(self, partname_dict): for slot in partname_dict: remaining_parts = [part for part in partname_dict[slot] if - get_part_type(part).partClass in TROOPS.union(ARMOUR)] + get_ship_part(part).partClass in TROOPS.union(ARMOUR)] partname_dict[slot] = remaining_parts @@ -1854,6 +1701,9 @@ def __init__(self): TroopShipDesignerBaseClass.__init__(self) self.additional_specifications.minimum_speed = 0 self.additional_specifications.minimum_fuel = 0 + # require that the orbital trooper base have zero speed, otherwise once completed it won't be recognized as an + # orbital trooper base + self.additional_specifications.orbital = True class StandardTroopShipDesigner(TroopShipDesignerBaseClass): @@ -1904,7 +1754,7 @@ def _starting_guess(self, available_parts, num_slots): ret_val = (len(available_parts)+1)*[0] if num_slots == 0: return ret_val - parts = [get_part_type(part) for part in available_parts] + parts = [get_ship_part(part) for part in available_parts] colo_parts = [part for part in parts if part.partClass in COLONISATION and part.capacity > 0] if colo_parts: colo_part = max(colo_parts, key=lambda x: x.capacity) @@ -1918,7 +1768,7 @@ def _starting_guess(self, available_parts, num_slots): def _class_specific_filter(self, partname_dict): # remove outpost pods for slot in partname_dict: - parts = [get_part_type(part) for part in partname_dict[slot]] + parts = [get_ship_part(part) for part in partname_dict[slot]] for part in parts: if part.partClass in COLONISATION and part.capacity == 0: partname_dict[slot].remove(part.name) @@ -1990,7 +1840,7 @@ def _rating_function(self): def _class_specific_filter(self, partname_dict): # filter all colo pods for slot in partname_dict: - parts = [get_part_type(part) for part in partname_dict[slot]] + parts = [get_ship_part(part) for part in partname_dict[slot]] for part in parts: if part.partClass in COLONISATION and part.capacity != 0: partname_dict[slot].remove(part.name) @@ -2000,7 +1850,7 @@ def _starting_guess(self, available_parts, num_slots): ret_val = (len(available_parts)+1)*[0] if num_slots == 0: return ret_val - parts = [get_part_type(part) for part in available_parts] + parts = [get_ship_part(part) for part in available_parts] colo_parts = [part for part in parts if part.partClass in COLONISATION and part.capacity == 0] if colo_parts: colo_part = colo_parts[0] @@ -2128,7 +1978,7 @@ def _rating_function(self): structure_factor = (1 + self.design_stats.structure - self._minimum_structure())**0.03 # nice to have but not too important fuel_factor = self._effective_fuel() speed_factor = 1 + (self.design_stats.speed - self._minimum_speed())**0.1 - stealth_factor = 1 + (self.design_stats.stealth + self.design_stats.asteroid_stealth / 2) # TODO: Adjust for enemy detection strength + stealth_factor = 1 + (self.design_stats.stealth + self.design_stats.asteroid_stealth // 2) # TODO: Adjust for enemy detection strength detection_factor = self.design_stats.detection**1.5 return (structure_factor * fuel_factor * speed_factor * stealth_factor * detection_factor / self.production_cost) @@ -2181,16 +2031,16 @@ def _create_ship_design(design_name, hull_name, part_names, model="fighter", model, name_desc_in_string_table)) if res: if verbose: - print "Success: Added Design %s, with result %d" % (design_name, res) + debug("Success: Added Design %s, with result %d" % (design_name, res)) # update cache design = _update_design_by_name_cache(design_name, verbose=verbose) if design: if verbose: - print "Success: Design %s stored in design_by_name_cache" % design_name + debug("Success: Design %s stored in design_by_name_cache" % design_name) else: - print >> sys.stderr, "Tried to get just created design %s but got None" % design_name + warning("Tried to get just created design %s but got None" % design_name) else: - print >> sys.stderr, "Tried to add design %s but returned %s, expected 1" % (design_name, res) + warning("Tried to add design %s but returned %s, expected 1" % (design_name, res)) return res @@ -2209,7 +2059,7 @@ def _update_design_by_name_cache(design_name, verbose=False, cache_as_invalid=Tr design = None for design_id in fo.getEmpire().allShipDesigns: if verbose: - print "Checking design %s in search for %s" % (fo.getShipDesign(design_id).name, design_name) + debug("Checking design %s in search for %s" % (fo.getShipDesign(design_id).name, design_name)) if fo.getShipDesign(design_id).name == design_name: design = fo.getShipDesign(design_id) break @@ -2217,7 +2067,7 @@ def _update_design_by_name_cache(design_name, verbose=False, cache_as_invalid=Tr Cache.design_id_by_name[design_name] = design.id elif cache_as_invalid: # invalid design - print "Shipdesign %s seems not to exist: Caching as invalid design." % design_name + debug("Shipdesign %s seems not to exist: Caching as invalid design." % design_name) Cache.design_id_by_name[design_name] = INVALID_ID return design @@ -2248,24 +2098,20 @@ def _get_design_by_name(design_name, update_invalid=False, looking_for_new_desig return design -def get_part_type(partname): - """Return the partType object (fo.getPartType(partname)) of the given partname. +@cache_for_session +def get_ship_part(part_name: str): + """Return the shipPart object (fo.getShipPart(part_name)) of the given part_name. As the function in late game may be called some thousand times, the results are cached. - - :param partname: string - :returns: partType object """ - if partname in Cache.part_by_partname: - return Cache.part_by_partname[partname] - else: - parttype = fo.getPartType(partname) - if parttype: - Cache.part_by_partname[partname] = parttype - return Cache.part_by_partname[partname] - else: - print >> sys.stderr, "Could not find part", partname - return None + if not part_name: + return None + + part_type = fo.getShipPart(part_name) + if not part_type: + warning("Could not find part %s" % part_name) + + return part_type def _build_reference_name(hullname, partlist): @@ -2317,8 +2163,8 @@ def recursive_dict_diff(dict_new, dict_old, dict_diff, diff_level_threshold=0): """ NO_DIFF = 9999 min_diff_level = NO_DIFF - for key, value in dict_new.iteritems(): - if not key in dict_old: + for key, value in dict_new.items(): + if key not in dict_old: dict_diff[key] = copy.deepcopy(value) min_diff_level = 0 elif isinstance(value, dict): @@ -2326,9 +2172,9 @@ def recursive_dict_diff(dict_new, dict_old, dict_diff, diff_level_threshold=0): min_diff_level = min(min_diff_level, this_diff_level) if this_diff_level > NO_DIFF and min_diff_level > diff_level_threshold: del dict_diff[key] - elif not key in dict_old or value != dict_old[key]: - dict_diff[key] = copy.deepcopy(value) - min_diff_level = 0 + elif key not in dict_old or value != dict_old[key]: + dict_diff[key] = copy.deepcopy(value) + min_diff_level = 0 return min_diff_level @@ -2340,10 +2186,15 @@ def _get_tech_bonus(upgrade_dict, part_name): _raised_warnings.add(part_name) error(("WARNING: Encountered unknown part (%s): " "The AI can play on but its damage estimates may be incorrect leading to worse decision-making. " - "Please update AIDependencies.py") % part_name, exc_info=True) + "Please update AIDependencies.py - %s") % (part_name, upgrade_dict), exc_info=True) return 0 total_tech_bonus = 0 for tech, bonus in upgrades: total_tech_bonus += bonus if tech_is_complete(tech) else 0 # TODO: Error checking if tech is actually a valid tech (tech_is_complete simply returns false) return total_tech_bonus + + +def _get_species_fuel_bonus(species_name): + return AIDependencies.SPECIES_FUEL_MODIFIER.get( + get_species_tag_grade(species_name, Tags.FUEL), 0) diff --git a/default/python/AI/TechsListsAI.py b/default/python/AI/TechsListsAI.py index 3121f912b6e..353c4fd58cf 100644 --- a/default/python/AI/TechsListsAI.py +++ b/default/python/AI/TechsListsAI.py @@ -3,15 +3,10 @@ various technologies to help the AI decide which technologies should be researched next. """ -import sys +from logging import warning, debug import freeOrionAIInterface as fo # pylint: disable=import-error -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) - -EXOBOT_TECH_NAME = "PRO_EXOBOTS" - def unusable_techs(): """ @@ -31,7 +26,7 @@ def defense_techs_1(): ] -class TechGroup(object): +class TechGroup: """ Base class for Tech groups. A TechGroup consists of some techs which need to be researched before progressing to the next TechGroup. @@ -87,12 +82,12 @@ def enqueue(self, *tech_lists): # Do not display error message as those should be shown only once per game session # by the initial test_tech_integrity() call. msg = "Try to enqueue tech from empty list" - print >> sys.stderr, msg + warning(msg) self._errors.append(msg) continue if tech_name in self._tech_queue: msg = "Tech is already in queue: %s" % tech_name - print >> sys.stderr, msg + warning(msg) self._errors.append(msg) else: self._tech_queue.append(tech_name) @@ -111,27 +106,29 @@ class TechGroup1(TechGroup): def __init__(self): super(TechGroup1, self).__init__() self.economy.extend([ + "LRN_PHYS_BRAIN", "GRO_PLANET_ECOL", - "GRO_SUBTER_HAB", "LRN_ALGO_ELEGANCE", - "LRN_PHYS_BRAIN", + "GRO_SUBTER_HAB", "LRN_ARTIF_MINDS", - "CON_ORBITAL_CON", # not a economy tech in the strictest sense but bonus supply often equals more planets "PRO_ROBOTIC_PROD", ]) self.weapon.extend([ + "SHP_WEAPON_ARC_DISRUPTOR_1", "SHP_WEAPON_1_2", - "SHP_FIGHTERS_1", "SHP_WEAPON_1_3", + "SHP_FIGHTERS_1", "SHP_WEAPON_1_4", ]) self.defense.extend([ "DEF_GARRISON_1", + "DEF_DEFENSE_NET_1", ]) self.hull.extend([ "SHP_MIL_ROBO_CONT", + "SHP_ORG_HULL", ]) - # always start with the same first 7 techs; leaves 2 econ, 3 weap, 1 hull + # always start with the same first 8 techs; leaves 1 econ, 4 weap, 2 hull self.enqueue( self.economy, self.economy, @@ -140,6 +137,7 @@ def __init__(self): self.economy, self.weapon, self.defense, + self.defense, ) @@ -150,7 +148,7 @@ def __init__(self): self.weapon, self.weapon, self.weapon, - self.economy, + self.weapon, self.economy, self.hull, ) @@ -165,7 +163,7 @@ def __init__(self): self.economy, self.weapon, self.weapon, - self.economy, + self.weapon, ) @@ -173,14 +171,14 @@ class TechGroup1SparseA(TechGroup1): def __init__(self): super(TechGroup1SparseA, self).__init__() self.enqueue( - self.economy, self.economy, self.hull, self.weapon, self.weapon, self.weapon, - "SHP_SPACE_FLUX_DRIVE" + "SHP_SPACE_FLUX_DRIVE", + self.weapon, ) @@ -190,16 +188,31 @@ def __init__(self): self.enqueue( self.economy, self.economy, - "PRO_FUSION_GEN", - "GRO_SYMBIOTIC_BIO", self.weapon, - self.weapon, - self.weapon, - "PRO_ORBITAL_GEN", self.hull, "SHP_ZORTRIUM_PLATE", - "SHP_SPACE_FLUX_DRIVE" + self.weapon, + "PRO_NANOTECH_PROD", + "PRO_SENTIENT_AUTOMATION", + "PRO_EXOBOTS", + "CON_ORBITAL_CON", # not a economy tech in the strictest sense but bonus supply often equals more planets + "GRO_GENETIC_MED", + "GRO_SYMBIOTIC_BIO", + "PRO_MICROGRAV_MAN", + "GRO_XENO_GENETICS", + "LRN_FORCE_FIELD", + "SHP_ASTEROID_HULLS", + "PRO_FUSION_GEN", + "SHP_WEAPON_2_1", + self.hull, + "SHP_MULTICELL_CAST", + "SHP_DEUTERIUM_TANK", + "SHP_WEAPON_2_2", + "PRO_ORBITAL_GEN", + "SPY_DETECT_2", + "SHP_SPACE_FLUX_DRIVE", ) + self.weapon = [] class TechGroup1SparseC(TechGroup1): @@ -208,22 +221,23 @@ def __init__(self): self.enqueue( self.economy, self.economy, - self.weapon, "SHP_ORG_HULL", - self.weapon, + "SHP_MULTICELL_CAST", "PRO_NANOTECH_PROD", - "GRO_GENETIC_ENG", "PRO_SENTIENT_AUTOMATION", + self.weapon, + "CON_ORBITAL_CON", # not a economy tech in the strictest sense but bonus supply often equals more planets "GRO_GENETIC_MED", "GRO_SYMBIOTIC_BIO", - "PRO_MICROGRAV_MAN", "PRO_EXOBOTS", + "SHP_DEUTERIUM_TANK", # more reach for more scouting and planets + "PRO_MICROGRAV_MAN", "GRO_XENO_GENETICS", "SHP_ASTEROID_HULLS", "PRO_FUSION_GEN", "SHP_WEAPON_2_1", "SHP_ZORTRIUM_PLATE", - self.hull, + "SHP_MIL_ROBO_CONT", "LRN_FORCE_FIELD", "SHP_WEAPON_2_2", "PRO_ORBITAL_GEN", @@ -237,10 +251,11 @@ class TechGroup2(TechGroup): def __init__(self): super(TechGroup2, self).__init__() self.economy.extend([ - "GRO_SYMBIOTIC_BIO", "PRO_FUSION_GEN", "PRO_SENTIENT_AUTOMATION", "PRO_EXOBOTS", + "GRO_SYMBIOTIC_BIO", + "CON_ORBITAL_CON", # not a economy tech in the strictest sense but bonus supply often equals more planets # "PRO_MICROGRAV_MAN", # handled by fast-forwarding when we have asteroids # "PRO_ORBITAL_GEN", # handled by fast-forwarding when we have a GG @@ -248,13 +263,8 @@ def __init__(self): self.armor.extend([ "SHP_ZORTRIUM_PLATE", ]) - self.defense.extend([ - "DEF_DEFENSE_NET_1", - "SPY_DETECT_2", - "DEF_GARRISON_2", - "LRN_FORCE_FIELD", - ]) self.hull.extend([ + "SHP_MULTICELL_CAST", "SHP_SPACE_FLUX_DRIVE", # "SHP_ASTEROID_HULLS", # should be handled by fast-forwarding when having ASteroids # "SHP_DOMESTIC_MONSTER", # should be handled by fast-forwarding when having nest @@ -271,44 +281,57 @@ def __init__(self): class TechGroup2A(TechGroup2): def __init__(self): super(TechGroup2A, self).__init__() + self.defense.extend([ + "SPY_DETECT_2", + "DEF_GARRISON_2", + "LRN_FORCE_FIELD", + ]) self.enqueue( - self.economy, self.armor, - self.defense, - self.hull, + self.economy, + self.economy, self.economy, self.defense, self.defense, + self.hull, self.weapon, self.weapon, self.defense, self.economy, + self.economy, self.weapon, self.weapon, self.weapon, - self.economy + self.economy, + self.hull, ) class TechGroup2B(TechGroup2): def __init__(self): super(TechGroup2B, self).__init__() + self.defense.extend([ + "LRN_FORCE_FIELD", + "SPY_DETECT_2", + "DEF_GARRISON_2", + ]) self.enqueue( self.armor, - self.hull, + self.economy, self.economy, self.defense, + self.hull, self.weapon, self.weapon, self.economy, self.defense, self.defense, - self.defense, self.weapon, self.weapon, self.weapon, self.economy, - self.economy + self.economy, + self.hull, ) @@ -316,11 +339,11 @@ class TechGroup2SparseA(TechGroup2): def __init__(self): super(TechGroup2SparseA, self).__init__() self.enqueue( + self.armor, self.hull, self.economy, self.economy, - self.armor, - self.defense, + self.economy, self.economy, self.economy, self.defense, @@ -330,7 +353,8 @@ def __init__(self): self.defense, self.weapon, self.weapon, - self.weapon + self.weapon, + self.hull, ) @@ -338,21 +362,22 @@ class TechGroup2SparseB(TechGroup2): def __init__(self): super(TechGroup2SparseB, self).__init__() self.enqueue( + self.armor, self.hull, self.economy, self.economy, self.economy, self.economy, - self.defense, + self.economy, self.defense, self.weapon, self.weapon, self.defense, self.defense, - self.armor, self.weapon, self.weapon, - self.weapon + self.weapon, + self.hull, ) @@ -360,7 +385,6 @@ class TechGroup3(TechGroup): def __init__(self): super(TechGroup3, self).__init__() self.hull.extend([ - "SHP_ORG_HULL", "SHP_ASTEROID_REFORM", "SHP_HEAVY_AST_HULL", "SHP_CONTGRAV_MAINT", @@ -391,6 +415,7 @@ def __init__(self): "DEF_PLAN_BARRIER_SHLD_3", ]) self.misc.extend([ + "SHP_DEUTERIUM_TANK", "SHP_BASIC_DAM_CONT", "SHP_INTSTEL_LOG", "SHP_FLEET_REPAIR", @@ -419,6 +444,7 @@ def __init__(self): self.enqueue( self.hull, self.economy, + self.misc, self.defense, self.defense, self.economy, @@ -456,7 +482,7 @@ def __init__(self): self.weapon, self.weapon, self.weapon, - self.weapon + self.weapon, ) @@ -466,6 +492,7 @@ def __init__(self): self.enqueue( self.hull, self.economy, + self.misc, self.defense, self.defense, self.economy, @@ -503,7 +530,7 @@ def __init__(self): self.defense, self.economy, self.misc, - self.economy + self.economy, ) @@ -512,6 +539,7 @@ def __init__(self): super(TechGroup3Sparse, self).__init__() self.enqueue( self.hull, + self.misc, self.defense, self.defense, self.economy, @@ -564,7 +592,8 @@ def __init__(self): ]) self.enqueue( self.hull, - self.hull + self.hull, + "SHP_ANTIMATTER_TANK", ) @@ -640,27 +669,27 @@ def test_tech_integrity(): TechGroup4, TechGroup5 ] - print "Checking TechGroup integrity..." + debug("Checking TechGroup integrity...") for group in tech_groups: - print "Checking %s: " % group.__name__, + debug("Checking %s: " % group.__name__) error_occured = False this_group = group() techs = this_group.get_techs() for tech in techs: if not fo.getTech(tech): - error("In %s: Tech %s seems not to exist!" % (group.__name__, tech)) + warning("In %s: Tech %s seems not to exist!" % (group.__name__, tech)) error_occured = True for err in this_group.get_errors(): - error(e, exc_info=True) + warning(err, exc_info=True) error_occured = True if not error_occured: - print "Seems to be OK!" + debug("Seems to be OK!") def sparse_galaxy_techs(index): # return primary_meta_techs() result = [] - print "Choosing Research Techlist Index %d" % index + debug("Choosing Research Techlist Index %d" % index) if index == 0: result = TechGroup1a().get_techs() # early org_hull result += TechGroup2A().get_techs() # prioritizes growth & defense over weapons @@ -680,13 +709,13 @@ def sparse_galaxy_techs(index): result += TechGroup4().get_techs() result += TechGroup5().get_techs() # elif index == 3: - result = TechGroup1SparseC().get_techs() # early org_hull + result = TechGroup1SparseB().get_techs() # early org_hull result += TechGroup2SparseB().get_techs() result += TechGroup3A().get_techs() result += TechGroup4().get_techs() result += TechGroup5().get_techs() # elif index == 4: - result = TechGroup1SparseC().get_techs() # early _lrn_artif_minds + result = TechGroup1SparseC().get_techs() # early pro_sent_auto result += TechGroup2SparseB().get_techs() result += TechGroup3B().get_techs() # faster plasma weaps result += TechGroup4().get_techs() @@ -701,7 +730,7 @@ def primary_meta_techs(index=0): # index = 1 - index result = [] - print "Choosing Research Techlist Index %d" % index + debug("Choosing Research Techlist Index %d" % index) if index == 0: result = TechGroup1a().get_techs() # early org_hull result += TechGroup2A().get_techs() # prioritizes growth & defense over weapons diff --git a/default/python/AI/ai_debug_config.ini b/default/python/AI/ai_debug_config.ini new file mode 100644 index 00000000000..3760c3e7aa1 --- /dev/null +++ b/default/python/AI/ai_debug_config.ini @@ -0,0 +1,3 @@ +[main] +allow_debug_chat=1 +replay_turn_after_load=1 diff --git a/default/python/AI/aistate_interface.py b/default/python/AI/aistate_interface.py new file mode 100644 index 00000000000..bdef354ee6c --- /dev/null +++ b/default/python/AI/aistate_interface.py @@ -0,0 +1,37 @@ +""" +This module provides an interface for a quasi-singleton AIstate instance. + +The AIstate must be initialized after module imports using either + * create_new_aistate + * load_aistate +This should happen only once per session and be triggered by the game client. + +Access to the AIstate is provided via get_aistate(). + +Example usage: + from aistate_interface import get_aistate() + get_aistate().get_all_fleet_missions() + +""" +_aistate = None + + +def create_new_aistate(*args, **kwargs): + import AIstate + global _aistate + _aistate = AIstate.AIstate(*args, **kwargs) + return _aistate + + +def load_aistate(savegame_string): + import savegame_codec + global _aistate + _aistate = savegame_codec.load_savegame_string(savegame_string) + return _aistate + + +def get_aistate(): + """ + :rtype: AIstate.AIstate + """ + return _aistate diff --git a/default/python/AI/character/character_module.py b/default/python/AI/character/character_module.py index 9d17b8efbf5..38ae703755a 100644 --- a/default/python/AI/character/character_module.py +++ b/default/python/AI/character/character_module.py @@ -42,8 +42,6 @@ a mandatory Genocidal trait. Perhaps add a probabilty distribution of trait components to the FOCS description of a playable species. """ - - # Some ideas for future trait modules are: # TODO: challenge/difficulty -- to replace the difficulty related portions # of aggression. @@ -83,19 +81,16 @@ # empires to protect the galaxy? # - -import sys import abc from collections import Counter import math import random +from logging import warning, debug import freeOrionAIInterface as fo # pylint: disable=import-error -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) -class Trait(object): +class Trait(metaclass=abc.ABCMeta): """An abstract class representing a type of trait of the AI. Traits give the AI personality along some dimension. @@ -120,7 +115,6 @@ class Trait(object): Any given Trait class should not implement all the taps, only those it needs to override to cause the relevant trait. """ - __metaclass__ = abc.ABCMeta def __repr__(self): return "Trait" @@ -240,6 +234,10 @@ def may_research_heavily(self): # pylint: disable=no-self-use,unused-argument """Return true if allowed to target research/industry > 1.5""" return True + def may_use_growth_focus(self): + """Return True if permitted to use growth focus.""" + return True + def may_travel_beyond_supply(self, distance): # pylint: disable=no-self-use,unused-argument """Return True if able to travel distance hops beyond empire supply""" # TODO Remove this tap it is one of the empire id dependent taps. See EmpireIDTrait. @@ -280,6 +278,12 @@ def warship_adjusted_production_cost_exponent(self): # pylint: disable=no-self- """Return an exponent to scale the production cost of a warship in ShipDesignAI.""" return None + def secondary_valuation_factor_for_invasion_targets(self): # pylint: disable=no-self-use,unused-argument + """Return a value in range [0.0 : 1.0], used in colonization scoring calculations where subscores for a primary + planet depend on traits of secondary planets, for what portion of the subscore should be assigned if the secondary + planet would need to be acquired via invasion""" + return None + class Aggression(Trait): """A trait that models level of difficulty and aggression.""" @@ -315,7 +319,7 @@ def may_maximize_research(self): def max_number_colonies(self): # significant growth barrier for low aggression, negligible for high aggression - #TODO: consider further changes, including a dependency on galaxy size and planet density + # TODO: consider further changes, including a dependency on galaxy size and planet density return 2 + ((0.5 + 1.4*self.aggression) ** 2) * fo.currentTurn() / 50.0 def may_invade(self): @@ -376,11 +380,16 @@ def military_safety_factor(self): return [4.0, 3.0, 2.0, 1.5, 1.2, 1.0][self.aggression] def may_dither_focus_to_gain_research(self): - return self.aggression < fo.aggression.aggressive + return self.aggression >= fo.aggression.aggressive def may_research_heavily(self): return self.aggression > fo.aggression.cautious + def may_use_growth_focus(self): + # For now, allow using growth focus for all aggression settings + # but leaving this here for easier modification. + return self.aggression >= fo.aggression.beginner + def may_research_xeno_genetics_variances(self): return self.aggression >= fo.aggression.cautious @@ -429,10 +438,11 @@ def attitude_to_empire(self, other_empire_id, diplomatic_logs): if self.aggression == fo.aggression.beginner: return 9 log_index = (other_empire_id, fo.empireID()) + num_alliance_requests = len(diplomatic_logs.get('alliance_requests', {}).get(log_index, [])) num_peace_requests = len(diplomatic_logs.get('peace_requests', {}).get(log_index, [])) num_war_declarations = len(diplomatic_logs.get('war_declarations', {}).get(log_index, [])) # Too many requests for peace irritate the AI, as do any war declarations - irritation = (self.aggression * (2.0 + num_peace_requests / 10.0 + 2.0 * num_war_declarations) + 0.5) + irritation = (self.aggression * (2.0 + num_alliance_requests / 5.0 + num_peace_requests / 10.0 + 2.0 * num_war_declarations) + 0.5) attitude = 10 * random.random() - irritation return min(10, max(-10, attitude)) @@ -447,6 +457,20 @@ def warship_adjusted_production_cost_exponent(self): # pylint: disable=no-self- exponent = 1.0 return exponent + def secondary_valuation_factor_for_invasion_targets(self): # pylint: disable=no-self-use,unused-argument + """Return a value in range [0.0 : 1.0], used in colonization scoring calculations where subscores for a primary + planet depend on traits of secondary planets, for what portion of the subscore should be assigned if the secondary + planet would need to be acquired via invasion""" + if self.aggression == fo.aggression.maniacal: + factor = 0.8 + elif self.aggression == fo.aggression.aggressive: + factor = 0.4 + elif self.aggression == fo.aggression.typical: + factor = 0.2 + else: + factor = 0.0 + return factor + class EmpireIDTrait(Trait): """A trait that models empire id influence. @@ -467,7 +491,7 @@ class EmpireIDTrait(Trait): # can describe, "Look the 'Continuum' is behaving like a 1 modulo 2 character." def __init__(self, empire_id, aggression): - print "EmpireIDTrait initialized." + debug("EmpireIDTrait initialized.") self.id = empire_id self.aggression = aggression # TODO remove when old research style get_research_index is removed @@ -547,10 +571,20 @@ def func(self, *args, **kwargs): return func +def _maxmin_not_none(f_combo): + """Make a combiner that collects not None items and applies min or max""" + def func(llin): + ll = [x for x in llin if x is not None] + if not ll: + return 0 + return f_combo(ll) + return func + + # Create combiners for traits that all must be true for funcname in ["may_explore_system", "may_surge_industry", "may_maximize_research", "may_invade", "may-invade_with_bases", "may_build_building", "may_produce_troops", - "may_dither_focus_to_gain_research", "may_research_heavily", + "may_dither_focus_to_gain_research", "may_research_heavily", "may_use_growth_focus", "may_travel_beyond_supply", "may_research_xeno_genetics_variances", "prefer_research_defensive", "prefer_research_low_aggression", "may_research_tech", "may_research_tech_classic"]: @@ -559,11 +593,11 @@ def func(self, *args, **kwargs): # Create combiners for traits that take min result for funcname in ["max_number_colonies", "invasion_priority_scaling", "military_priority_scaling", "max_defense_portion"]: - setattr(Character, funcname, _make_single_function_combiner(funcname, min)) + setattr(Character, funcname, _make_single_function_combiner(funcname, _maxmin_not_none(min))) # Create combiners for traits that take max result for funcname in ["target_number_of_orbitals", "military_safety_factor", "get_research_index"]: - setattr(Character, funcname, _make_single_function_combiner(funcname, max)) + setattr(Character, funcname, _make_single_function_combiner(funcname, _maxmin_not_none(max))) # Create combiners for traits that take any result for funcname in ["check_orbital_production"]: @@ -578,7 +612,7 @@ def average_not_none(llin): return sum(ll) / float(len(ll)) -for funcname in ["attitude_to_empire"]: +for funcname in ["attitude_to_empire", "secondary_valuation_factor_for_invasion_targets"]: setattr(Character, funcname, _make_single_function_combiner(funcname, average_not_none)) @@ -587,8 +621,7 @@ def geometric_mean_not_none(llin): ll_not_none = [x for x in llin if x is not None] ll = [x for x in ll_not_none if x > 0] if len(ll_not_none) != len(ll): - print >> sys.stderr, ("In AI Character calculating the geometric mean of ", ll_not_none, - " contains negative numbers which will be ignored.") + warning("Calculating the geometric mean of %s contains negative numbers which will be ignored." % ll_not_none) if not ll: return 1 return math.exp(sum(map(math.log, ll)) / float(len(ll))) @@ -635,21 +668,21 @@ def create_character(aggression=fo.aggression.maniacal, empire_id=0): def get_trait_bypass_value(name, default, sentinel): """Fetch a bypassed trait value or return the default from OptionsDB. - In OptionsDB a section AI.config.trait can contain default trait + In OptionsDB a section ai.config.trait can contain default trait values for all of the AIs or specific AIs which will override the default value passed into this function. If there is an XML element in config.xml/persistent_config.xml - AI.config.trait..force + ai.trait..force.enabled with a non zero value - ,then the value of AI.config.trait.. + ,then the value of ai.trait..ai_ will be checked. If it is not the sentinel value (typically -1) the it will be returned as the trait's value. Otherwise the value of - AI.config.trait..all + ai.trait..default is checked. Again if it is not the sentinel value it will ovverride the returned value for trait. @@ -659,31 +692,33 @@ def get_trait_bypass_value(name, default, sentinel): Here is an example section providing override values aggression and the empire-id trait. - - - - - 1 - 4 - 5 - 4 - 3 - 2 - 1 - 0 - - - 1 - 5 - 4 - 3 - 2 - 1 - 0 - - - - + + + + + 1 + + 4 + 5 + 4 + 3 + 2 + 1 + 0 + + + + 1 + + 5 + 4 + 3 + 2 + 1 + 0 + + + :param name: Name of the trait. :type name: string @@ -696,12 +731,12 @@ def get_trait_bypass_value(name, default, sentinel): """ - force_option = "AI.config.trait.%s.force" % (name,) + force_option = "ai.trait.%s.force.enabled" % (name.lower(),) if not fo.getOptionsDBOptionBool(force_option): return default - per_id_option = "AI.config.trait.%s.%s" % (name, fo.playerName()) - all_id_option = "AI.config.trait.%s.all" % (name,) + per_id_option = "ai.trait.%s.%s" % (name.lower(), fo.playerName().lower()) + all_id_option = "ai.trait.%s.default" % (name.lower(),) trait = fo.getOptionsDBOptionInt(per_id_option) if trait is None or trait == sentinel: @@ -710,5 +745,5 @@ def get_trait_bypass_value(name, default, sentinel): if trait is None or trait == sentinel: trait = default else: - print "%s trait bypassed and set to %s for %s" % (name, repr(trait), fo.playerName()) + debug("%s trait bypassed and set to %s for %s", name, repr(trait), fo.playerName()) return trait diff --git a/default/python/AI/character/character_strings_module.py b/default/python/AI/character/character_strings_module.py index 8a6258de2a3..5a23e1f9e07 100644 --- a/default/python/AI/character/character_strings_module.py +++ b/default/python/AI/character/character_strings_module.py @@ -21,14 +21,13 @@ For example: possible_capitals(Character([Aggression(0)])) returns ['Royal', 'Imperial']. """ +from logging import debug import character as character_package import freeOrionAIInterface as fo # pylint: disable=import-error -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) -class _CharacterTableFunction(object): +class _CharacterTableFunction: """A table indexed by a particular trait of a Character that is used like a function. @@ -54,7 +53,7 @@ def __call__(self, character): elem = self.table[trait.key] if trait is not None else self.table[None] if self.post_process: elem = self.post_process(elem) - print "CharacterTable returns ", elem + debug("CharacterTable returns %s", elem) return elem diff --git a/default/python/AI/fleet_orders.py b/default/python/AI/fleet_orders.py index f6af73b66f6..ab83c595c92 100644 --- a/default/python/AI/fleet_orders.py +++ b/default/python/AI/fleet_orders.py @@ -1,21 +1,14 @@ -import sys +from logging import debug, error, warning from EnumsAI import ShipRoleType, MissionType -import AIstate +import EspionageAI import FleetUtilsAI import freeOrionAIInterface as fo # pylint: disable=import-error -import FreeOrionAI as foAI +from aistate_interface import get_aistate import MilitaryAI import MoveUtilsAI -import PlanetUtilsAI import CombatRatingsAI -from universe_object import Fleet, System, Planet -from AIDependencies import INVALID_ID - -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) - -dumpTurn = 0 +from target import TargetFleet, TargetSystem, TargetPlanet def trooper_move_reqs_met(main_fleet_mission, order, verbose): @@ -41,9 +34,9 @@ def trooper_move_reqs_met(main_fleet_mission, order, verbose): military_support_fleets = MilitaryAI.get_military_fleets_with_target_system(invasion_system.id) if not military_support_fleets: if verbose: - print ("trooper_move_reqs_met() holding Invasion fleet %d before leaving supply " - "because target (%s) has nonzero max shields and there is not yet a military fleet " - "assigned to secure the target system.") % (order.fleet.id, invasion_planet) + debug("trooper_move_reqs_met() holding Invasion fleet %d before leaving supply " + "because target (%s) has nonzero max shields and there is not yet a military fleet " + "assigned to secure the target system." % (order.fleet.id, invasion_planet)) return False # if there is a threat in the enemy system, do give military ships at least 1 turn to clear it @@ -53,34 +46,38 @@ def eta(fleet_id): return FleetUtilsAI.calculate_estimated_time_of_arrival(fleet_id, invasion_system.id) eta_this_fleet = eta(order.fleet.id) - if all(((eta_this_fleet - delay_to_move_troops) <= eta(fid) and eta(fid)) for fid in military_support_fleets): + if all(((eta_this_fleet - delay_to_move_troops) <= eta(fid) and eta(fid)) + for fid in military_support_fleets): if verbose: - print ("trooper_move_reqs_met() holding Invasion fleet %d before leaving supply " - "because target (%s) has nonzero max shields and no assigned military fleet would arrive" - "at least %d turn earlier than the invasion fleet") % ( - order.fleet.id, invasion_planet, delay_to_move_troops) + debug("trooper_move_reqs_met() holding Invasion fleet %d before leaving supply " + "because target (%s) has nonzero max shields and no assigned military fleet would arrive" + "at least %d turn earlier than the invasion fleet" % (order.fleet.id, invasion_planet, + delay_to_move_troops)) return False if verbose: - print ("trooper_move_reqs_met() allowing Invasion fleet %d to leave supply " - "because target (%s) has zero max shields or there is a military fleet assigned to secure " - "the target system which will arrive at least 1 turn before the invasion fleet.") % (order.fleet.id, - invasion_planet) + debug("trooper_move_reqs_met() allowing Invasion fleet %d to leave supply " + "because target (%s) has zero max shields or there is a military fleet assigned to secure " + "the target system which will arrive at least 1 turn before the invasion fleet.", + order.fleet.id, invasion_planet) return True -class AIFleetOrder(object): +class AIFleetOrder: """Stores information about orders which can be executed.""" TARGET_TYPE = None + ORDER_NAME = '' + fleet = None # type: target.TargetFleet + target = None # type: target.Target def __init__(self, fleet, target): """ :param fleet: fleet to execute order - :type fleet: universe_object.Fleet + :type fleet: target.TargetFleet :param target: fleet target, depends of order type - :type target: universe_object.UniverseObject + :type target: target.Target """ - if not isinstance(fleet, Fleet): + if not isinstance(fleet, TargetFleet): error("Order required fleet got %s" % type(fleet)) if not isinstance(target, self.TARGET_TYPE): @@ -91,20 +88,36 @@ def __init__(self, fleet, target): self.executed = False self.order_issued = False - def ship_in_fleet(self): - universe = fo.getUniverse() - fleet = universe.getFleet(self.fleet.id) - return self.target.id in fleet.shipIDs + def __setstate__(self, state): + # construct the universe objects from stored ids + state["fleet"] = TargetFleet(state["fleet"]) + target_type = state.pop("target_type") + if state["target"] is not None: + assert self.TARGET_TYPE.object_name == target_type + state["target"] = self.TARGET_TYPE(state["target"]) + self.__dict__ = state + + def __getstate__(self): + retval = dict(self.__dict__) + # do not store the universe object but only the fleet id + retval['fleet'] = self.fleet.id + if self.target is not None: + retval["target"] = self.target.id + retval["target_type"] = self.target.object_name + else: + retval["target_type"] = None + return retval def is_valid(self): """Check if FleetOrder could be somehow in future issued = is valid.""" if self.executed and self.order_issued: - print "\t\t order not valid because already executed and completed" + debug("\t\t order not valid because already executed and completed") return False if self.fleet and self.target: return True else: - print "\t\t order not valid: fleet validity: %s and target validity %s" % (bool(self.fleet), bool(self.target)) + debug("\t\t order not valid: fleet validity: %s and target validity %s" % ( + bool(self.fleet), bool(self.target))) return False def can_issue_order(self, verbose=False): @@ -117,19 +130,22 @@ def can_issue_order(self, verbose=False): if verbose: sys1 = self.fleet.get_system() - main_fleet_mission = foAI.foAIstate.get_fleet_mission(self.fleet.id) - print " Can issue %s - Mission Type %s (%s), current loc sys %d - %s" % ( + main_fleet_mission = get_aistate().get_fleet_mission(self.fleet.id) + debug(" Can issue %s - Mission Type %s (%s), current loc sys %d - %s" % ( self, main_fleet_mission.type, - main_fleet_mission.type, self.fleet.id, sys1) + main_fleet_mission.type, self.fleet.id, sys1)) return True def issue_order(self): if not self.can_issue_order(): # appears to be redundant with check in IAFleetMission? - print " can't issue %s" % self + debug(" can't issue %s" % self) return False - else: - self.executed = True # TODO check that it is really executed - return True + # by default we now set the order as issue and executed. For any subclass where order issuence and execution + # is not necessarily sure, these values can be reset after any appropriate checks in the respective + # subclass issue_order() + self.order_issued = True + self.executed = True + return True def __str__(self): execute_status = 'in progress' @@ -137,45 +153,53 @@ def __str__(self): execute_status = 'executed' elif self.order_issued: execute_status = 'order issued' - return "[%s] of %s to %s %s" % (self.ORDER_NAME, self.fleet.get_object(), self.target.get_object(), execute_status) + return "[%s] of %s to %s %s" % (self.ORDER_NAME, self.fleet.get_object(), + self.target.get_object(), execute_status) def __eq__(self, other): return type(self) == type(other) and self.fleet == other.fleet and self.target == other.target + def __hash__(self): + return hash(self.fleet) + class OrderMove(AIFleetOrder): ORDER_NAME = 'move' - TARGET_TYPE = System + TARGET_TYPE = TargetSystem def can_issue_order(self, verbose=False): if not super(OrderMove, self).can_issue_order(verbose=verbose): return False - # TODO: figure out better way to have invasions (& possibly colonizations) require visibility on target without needing visibility of all intermediate systems + # TODO: figure out better way to have invasions (& possibly colonizations) + # require visibility on target without needing visibility of all intermediate systems # if False and main_mission_type not in [MissionType.ATTACK, # TODO: consider this later # MissionType.MILITARY, # MissionType.SECURE, # MissionType.HIT_AND_RUN, # MissionType.EXPLORATION]: - # if not universe.getVisibility(target_id, foAI.foAIstate.empireID) >= fo.visibility.partial: + # if not universe.getVisibility(target_id, get_aistate().empireID) >= fo.visibility.partial: # #if not target_id in interior systems - # foAI.foAIstate.needsEmergencyExploration.append(fleet.systemID) + # get_aistate().needsEmergencyExploration.append(fleet.systemID) # return False system_id = self.fleet.get_system().id if system_id == self.target.get_system().id: return True # TODO: already there, but could consider retreating - main_fleet_mission = foAI.foAIstate.get_fleet_mission(self.fleet.id) + aistate = get_aistate() + main_fleet_mission = aistate.get_fleet_mission(self.fleet.id) + # TODO: Rate against specific enemies here fleet_rating = CombatRatingsAI.get_fleet_rating(self.fleet.id) fleet_rating_vs_planets = CombatRatingsAI.get_fleet_rating_against_planets(self.fleet.id) - target_sys_status = foAI.foAIstate.systemStatus.get(self.target.id, {}) + target_sys_status = aistate.systemStatus.get(self.target.id, {}) f_threat = target_sys_status.get('fleetThreat', 0) m_threat = target_sys_status.get('monsterThreat', 0) p_threat = target_sys_status.get('planetThreat', 0) threat = f_threat + m_threat + p_threat - safety_factor = foAI.foAIstate.character.military_safety_factor() + safety_factor = aistate.character.military_safety_factor() universe = fo.getUniverse() - if main_fleet_mission.type == MissionType.INVASION and not trooper_move_reqs_met(main_fleet_mission, self, verbose): + if main_fleet_mission.type == MissionType.INVASION and not trooper_move_reqs_met(main_fleet_mission, + self, verbose): return False if fleet_rating >= safety_factor * threat and fleet_rating_vs_planets >= p_threat: return True @@ -187,54 +211,80 @@ def can_issue_order(self, verbose=False): target_system = self.target.get_system() target_system_name = (target_system and target_system.get_object().name) or "unknown" # TODO: adjust calc for any departing fleets - my_other_fleet_rating = foAI.foAIstate.systemStatus.get(self.target.id, {}).get('myFleetRating', 0) - my_other_fleet_rating_vs_planets = foAI.foAIstate.systemStatus.get(self.target.id, {}).get('myFleetRatingVsPlanets', 0) - is_military = foAI.foAIstate.get_fleet_role(self.fleet.id) == MissionType.MILITARY - - # TODO(Morlic): Is there any reason to add this linearly instead of using CombineRatings? - total_rating = my_other_fleet_rating + fleet_rating - total_rating_vs_planets = my_other_fleet_rating_vs_planets + fleet_rating_vs_planets + my_other_fleet_rating = aistate.systemStatus.get(self.target.id, {}).get('myFleetRating', 0) + my_other_fleet_rating_vs_planets = aistate.systemStatus.get(self.target.id, {}).get( + 'myFleetRatingVsPlanets', 0) + is_military = aistate.get_fleet_role(self.fleet.id) == MissionType.MILITARY + + total_rating = CombatRatingsAI.combine_ratings(my_other_fleet_rating, fleet_rating) + total_rating_vs_planets = CombatRatingsAI.combine_ratings(my_other_fleet_rating_vs_planets, + fleet_rating_vs_planets) if (my_other_fleet_rating > 3 * safety_factor * threat or (is_military and total_rating_vs_planets > 2.5*p_threat and total_rating > safety_factor * threat)): - if verbose: - print "\tAdvancing fleet %d (rating %d) at system %d (%s) into system %d (%s) with threat %d because of sufficient empire fleet strength already at destination" % ( - self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat) + debug(("\tAdvancing fleet %d (rating %d) at system %d (%s) into system %d (%s) with threat %d" + " because of sufficient empire fleet strength already at destination" % + (self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat))) return True - elif threat == p_threat and not self.fleet.get_object().aggressive and not my_other_fleet_rating and not target_sys_status.get('localEnemyFleetIDs', [-1]): + elif (threat == p_threat and + not self.fleet.get_object().aggressive and + not my_other_fleet_rating and + not target_sys_status.get('localEnemyFleetIDs', [-1])): if verbose: - print ("\tAdvancing fleet %d (rating %d) at system %d (%s) into system %d (%s) with planet threat %d because nonaggressive" + - " and no other fleets present to trigger combat") % (self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat) + debug("\tAdvancing fleet %d (rating %d) at system %d (%s) " + "into system %d (%s) with planet threat %d because non aggressive" + " and no other fleets present to trigger combat" % ( + self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, + threat)) return True else: if verbose: - print "\tHolding fleet %d (rating %d) at system %d (%s) before travelling to system %d (%s) with threat %d" % (self.fleet.id, fleet_rating, system_id, sys1_name, self.target.id, target_system_name, threat) - needs_vis = foAI.foAIstate.misc.setdefault('needs_vis', []) + _info = (self.fleet.id, fleet_rating, system_id, sys1_name, + self.target.id, target_system_name, threat) + debug("\tHolding fleet %d (rating %d) at system %d (%s) " + "before travelling to system %d (%s) with threat %d" % _info) + needs_vis = aistate.misc.setdefault('needs_vis', []) if self.target.id not in needs_vis: needs_vis.append(self.target.id) return False - return True def issue_order(self): if not super(OrderMove, self).issue_order(): - return + return False fleet_id = self.fleet.id system_id = self.target.get_system().id fleet = self.fleet.get_object() if system_id not in [fleet.systemID, fleet.nextSystemID]: dest_id = system_id fo.issueFleetMoveOrder(fleet_id, dest_id) - print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) - + debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) + aistate = get_aistate() if system_id == fleet.systemID: - if foAI.foAIstate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: - if system_id in foAI.foAIstate.needsEmergencyExploration: - del foAI.foAIstate.needsEmergencyExploration[foAI.foAIstate.needsEmergencyExploration.index(system_id)] - self.order_issued = True + if aistate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: + if system_id in aistate.needsEmergencyExploration: + aistate.needsEmergencyExploration.remove(system_id) + return True + + +class OrderPause(AIFleetOrder): + """Ensure Fleet at least temporarily halts movement at the target system.""" + ORDER_NAME = 'pause' + TARGET_TYPE = TargetSystem + + def is_valid(self): + if not super(OrderPause, self).is_valid(): + return False + return bool(self.target.get_system().get_object()) + + def issue_order(self): + if not super(OrderPause, self).issue_order(): + return False + # not executed until actually arrives at target system + self.executed = self.fleet.get_current_system_id() == self.target.get_system().id class OrderResupply(AIFleetOrder): ORDER_NAME = 'resupply' - TARGET_TYPE = System + TARGET_TYPE = TargetSystem def is_valid(self): if not super(OrderResupply, self).is_valid(): @@ -243,67 +293,38 @@ def is_valid(self): def issue_order(self): if not super(OrderResupply, self).issue_order(): - return + return False fleet_id = self.fleet.id system_id = self.target.get_system().id fleet = self.fleet.get_object() - if system_id not in [fleet.systemID, fleet.nextSystemID]: - # if self.order_type == AIFleetOrderType.ORDER_MOVE: - # dest_id = system_id - # else: - # if self.order_type == AIFleetOrderType.ORDER_REPAIR: - # fo.issueAggressionOrder(fleet_id, False) - start_id = [fleet.systemID, fleet.nextSystemID][fleet.systemID == INVALID_ID] + aistate = get_aistate() + if system_id == fleet.systemID: + if aistate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: + if system_id in aistate.needsEmergencyExploration: + aistate.needsEmergencyExploration.remove(system_id) + return True + if system_id != fleet.nextSystemID: + self.executed = False + start_id = FleetUtilsAI.get_fleet_system(fleet) dest_id = MoveUtilsAI.get_safe_path_leg_to_dest(fleet_id, start_id, system_id) - print "fleet %d with order type(%s) sent to safe leg dest %s and ultimate dest %s" % (fleet_id, self.ORDER_NAME, - PlanetUtilsAI.sys_name_ids([dest_id]), - PlanetUtilsAI.sys_name_ids([system_id])) + universe = fo.getUniverse() + debug("fleet %d with order type(%s) sent to safe leg dest %s and ultimate dest %s" % ( + fleet_id, self.ORDER_NAME, universe.getSystem(dest_id), universe.getSystem(system_id))) fo.issueFleetMoveOrder(fleet_id, dest_id) - print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) - - if system_id == fleet.systemID: - if foAI.foAIstate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: - if system_id in foAI.foAIstate.needsEmergencyExploration: - del foAI.foAIstate.needsEmergencyExploration[foAI.foAIstate.needsEmergencyExploration.index(system_id)] - self.order_issued = True - - -class OrderSplitFleet(AIFleetOrder): - ORDER_NAME = 'split_fleet' - TARGET_TYPE = System # TODO check real usage - - def can_issue_order(self, verbose=False): - if not super(OrderSplitFleet, self).is_valid(): - return False - return len(self.fleet.get_object().shipIDs) > 1 - - def issue_order(self): - if not super(OrderSplitFleet, self).issue_order(): - return - ship_id = self.target.id - fleet = self.fleet.get_object() - if ship_id in fleet.shipIDs: - fo.issueNewFleetOrder(str(ship_id), ship_id) - print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) - self.order_issued = True - - -# class OrderMergeFleet(AIFleetOrder): # TODO check remove -# pass + debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) + return True class OrderOutpost(AIFleetOrder): ORDER_NAME = 'outpost' - TARGET_TYPE = Planet + TARGET_TYPE = TargetPlanet def is_valid(self): if not super(OrderOutpost, self).is_valid(): return False - universe = fo.getUniverse() planet = self.target.get_object() - sys_partial_vis_turn = universe.getVisibilityTurnsMap(planet.systemID, fo.empireID()).get(fo.visibility.partial, -9999) - planet_partial_vis_turn = universe.getVisibilityTurnsMap(planet.id, fo.empireID()).get(fo.visibility.partial, -9999) - if not (planet_partial_vis_turn == sys_partial_vis_turn and planet.unowned): + if not planet.unowned: + # terminate early self.executed = True self.order_issued = True return False @@ -323,55 +344,60 @@ def can_issue_order(self, verbose=False): def issue_order(self): if not super(OrderOutpost, self).issue_order(): - return + return False + # we can't know yet if the order will actually execute; instead, rely on the fact that if the order does get + # executed, then next turn it will be invalid + self.executed = False planet = self.target.get_object() if not planet.unowned: - self.order_issued = True - return + return False fleet_id = self.fleet.id ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.CIVILIAN_OUTPOST) if ship_id is None: ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.BASE_OUTPOST) - result = fo.issueColonizeOrder(ship_id, self.target.id) - print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) - if not result: - self.executed = False + if fo.issueColonizeOrder(ship_id, self.target.id): + debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) + return True + else: + self.order_issued = False + warning("Order issuance failed: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) + return False class OrderColonize(AIFleetOrder): ORDER_NAME = 'colonize' - TARGET_TYPE = Planet + TARGET_TYPE = TargetPlanet def issue_order(self): if not super(OrderColonize, self).issue_order(): - return + return False + # we can't know yet if the order will actually execute; instead, rely on the fact that if the order does get + # executed, then next turn it will be invalid + self.executed = False fleet_id = self.fleet.id ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.CIVILIAN_COLONISATION) if ship_id is None: ship_id = FleetUtilsAI.get_ship_id_with_role(fleet_id, ShipRoleType.BASE_COLONISATION) - planet = self.target.get_object() - planet_name = planet and planet.name or "apparently invisible" - result = fo.issueColonizeOrder(ship_id, self.target.id) - print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) - print "Ordered colony ship ID %d to colonize %s, got result %d" % (ship_id, planet_name, result) - if not result: - self.executed = False + if fo.issueColonizeOrder(ship_id, self.target.id): + debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) + return True + else: + self.order_issued = False + warning("Order issuance failed: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) + return False def is_valid(self): if not super(OrderColonize, self).is_valid(): return False - universe = fo.getUniverse() planet = self.target.get_object() - sys_partial_vis_turn = universe.getVisibilityTurnsMap(planet.systemID, fo.empireID()).get(fo.visibility.partial, -9999) - planet_partial_vis_turn = universe.getVisibilityTurnsMap(planet.id, fo.empireID()).get(fo.visibility.partial, -9999) - if not (planet_partial_vis_turn == sys_partial_vis_turn and planet.unowned or (planet.ownedBy(fo.empireID()) and not planet.currentMeterValue(fo.meterType.population))): - self.executed = True - self.order_issued = True - return False - else: + if (planet.unowned or planet.ownedBy(fo.empireID())) and not planet.currentMeterValue(fo.meterType.population): return self.fleet.get_object().hasColonyShips + # Otherwise, terminate early + self.executed = True + self.order_issued = True + return False def can_issue_order(self, verbose=False): if not super(OrderColonize, self).is_valid(): @@ -383,32 +409,21 @@ def can_issue_order(self, verbose=False): universe = fo.getUniverse() ship = universe.getShip(ship_id) if ship and not ship.canColonize: - print >> sys.stderr, "colonization fleet %d has no colony ship" % self.fleet.id + warning("colonization fleet %d has no colony ship" % self.fleet.id) return ship is not None and self.fleet.get_object().systemID == self.target.get_system().id and ship.canColonize -class OrderAttack(AIFleetOrder): - ORDER_NAME = 'attack' - TARGET_TYPE = System - - def issue_order(self, verbose=False): - if not super(OrderAttack, self).is_valid(): - return - fo.issueFleetMoveOrder(self.fleet.id, self.target.get_system().id) - print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) - - class OrderDefend(AIFleetOrder): """ Used for orbital defense, have no real orders. """ ORDER_NAME = 'defend' - TARGET_TYPE = System + TARGET_TYPE = TargetSystem class OrderInvade(AIFleetOrder): ORDER_NAME = 'invade' - TARGET_TYPE = Planet + TARGET_TYPE = TargetPlanet def is_valid(self): if not super(OrderInvade, self).is_valid(): @@ -416,7 +431,9 @@ def is_valid(self): planet = self.target.get_object() planet_population = planet.currentMeterValue(fo.meterType.population) if planet.unowned and not planet_population: - print "\t\t invasion order not valid due to target planet status-- owned: %s and population %.1f" % (not planet.unowned, planet_population) + debug("\t\t invasion order not valid due to target planet status-- owned: %s and population %.1f" % ( + not planet.unowned, planet_population)) + # terminate early self.executed = True self.order_issued = True return False @@ -437,59 +454,79 @@ def can_issue_order(self, verbose=False): ship is not None, self.fleet.get_object().systemID == planet.systemID, ship.canInvade, - not planet.currentMeterValue(fo.meterType.shield) + not planet.initialMeterValue(fo.meterType.shield) )) def issue_order(self): if not super(OrderInvade, self).can_issue_order(): - return - result = False + return False + + universe = fo.getUniverse() planet_id = self.target.id planet = self.target.get_object() - planet_name = planet and planet.name or "invisible" fleet = self.fleet.get_object() - detail_str = "" - universe = fo.getUniverse() - global dumpTurn - for ship_id in fleet.shipIDs: - ship = universe.getShip(ship_id) - if foAI.foAIstate.get_ship_role(ship.design.id) in [ShipRoleType.MILITARY_INVASION, ShipRoleType.BASE_INVASION]: - result = fo.issueInvadeOrder(ship_id, planet_id) or result # will track if at least one invasion troops successfully deployed - print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) - shields = planet.currentMeterValue(fo.meterType.shield) - owner = planet.owner + invasion_roles = (ShipRoleType.BASE_INVASION, + ShipRoleType.MILITARY_INVASION) + + debug("Issuing order: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) + # will track if at least one invasion troops successfully deployed + result = True + aistate = get_aistate() + overkill_margin = 2 # TODO: get from character module; allows a handful of extra troops to be immediately + # defending planet + # invasion orders processed before regen takes place, so use initialMeterValue() here + troops_wanted = planet.initialMeterValue(fo.meterType.troops) + overkill_margin + troops_already_assigned = 0 # TODO: get from other fleets in same system + troops_assigned = 0 + # Todo: evaluate all local troop ships (including other fleets) before using any, make sure base invasion troops + # are used first, and that not too many altogether are used (choosing an optimal collection to use). + for invasion_role in invasion_roles: # first checks base troops, then regular + if not result: + break + for ship_id in fleet.shipIDs: + if troops_already_assigned + troops_assigned >= troops_wanted: + break + ship = universe.getShip(ship_id) + if aistate.get_ship_role(ship.design.id) != invasion_role: + continue + + debug("Ordering troop ship %d to invade %s" % (ship_id, planet)) + result = fo.issueInvadeOrder(ship_id, planet_id) and result if not result: + shields = planet.currentMeterValue(fo.meterType.shield) planet_stealth = planet.currentMeterValue(fo.meterType.stealth) pop = planet.currentMeterValue(fo.meterType.population) - detail_str = " -- planet has %.1f stealth, shields %.1f, %.1f population and is owned by empire %d" % (planet_stealth, shields, pop, owner) - print "Ordered troop ship ID %d to invade %s, got result %d" % (ship_id, planet_name, result), detail_str - if not result: - if 'needsEmergencyExploration' not in dir(foAI.foAIstate): - foAI.foAIstate.needsEmergencyExploration = [] - if fleet.systemID not in foAI.foAIstate.needsEmergencyExploration: - foAI.foAIstate.needsEmergencyExploration.append(fleet.systemID) - print "Due to trouble invading, adding system %d to Emergency Exploration List" % fleet.systemID - self.executed = False - if shields > 0 and owner == -1 and dumpTurn < fo.currentTurn(): - dumpTurn = fo.currentTurn() - print "Universe Dump to debug invasions:" - universe.dump() + warning("Invasion order failed!") + debug(" -- planet has %.1f stealth, shields %.1f, %.1f population and " + "is owned by empire %d" % (planet_stealth, shields, pop, planet.owner)) + if 'needsEmergencyExploration' not in dir(aistate): + aistate.needsEmergencyExploration = [] + if fleet.systemID not in aistate.needsEmergencyExploration: + aistate.needsEmergencyExploration.append(fleet.systemID) + debug("Due to trouble invading, added system %d to Emergency Exploration List" % fleet.systemID) + self.executed = False + # debug(universe.getPlanet(planet_id).dump()) # TODO: fix fo.UniverseObject.dump() break + troops_assigned += ship.troopCapacity + # TODO: split off unused troop ships into new fleet and give new orders this cycle if result: - print "Successfully ordered troop ship(s) to invade %s, with detail %s" % (planet_name, detail_str) + debug("Successfully ordered %d troopers to invade %s" % (troops_assigned, planet)) + return True + else: + return False class OrderMilitary(AIFleetOrder): ORDER_NAME = 'military' - TARGET_TYPE = System + TARGET_TYPE = TargetSystem def is_valid(self): if not super(OrderMilitary, self).is_valid(): return False - thisFleet = self.fleet.get_object() + fleet = self.fleet.get_object() # TODO: consider bombardment-only fleets/orders - return thisFleet is not None and (thisFleet.hasArmedShips or thisFleet.hasFighterShips) + return fleet is not None and (fleet.hasArmedShips or fleet.hasFighterShips) def can_issue_order(self, verbose=False): # TODO: consider bombardment @@ -497,23 +534,44 @@ def can_issue_order(self, verbose=False): ship_id = FleetUtilsAI.get_ship_id_with_role(self.fleet.id, ShipRoleType.MILITARY) universe = fo.getUniverse() ship = universe.getShip(ship_id) - return ship is not None and self.fleet.get_object().systemID == self.target.id and (ship.isArmed or ship.hasFighters) + return (ship is not None and + self.fleet.get_object().systemID == self.target.id and + (ship.isArmed or ship.hasFighters)) def issue_order(self): if not super(OrderMilitary, self).issue_order(): - return + return False target_sys_id = self.target.id fleet = self.target.get_object() - system_status = foAI.foAIstate.systemStatus.get(target_sys_id, {}) - if all((fleet, fleet.systemID == target_sys_id, system_status.get('currently_visible', False), - not (system_status.get('fleetThreat', 0) + system_status.get('planetThreat', 0) + - system_status.get('monsterThreat', 0)))): - self.order_issued = True + system_status = get_aistate().systemStatus.get(target_sys_id, {}) + total_threat = sum(system_status.get(threat, 0) for threat in ('fleetThreat', 'planetThreat', 'monsterThreat')) + combat_trigger = system_status.get('fleetThreat', 0) or system_status.get('monsterThreat', 0) + if not combat_trigger and system_status.get('planetThreat', 0): + universe = fo.getUniverse() + system = universe.getSystem(target_sys_id) + for planet_id in system.planetIDs: + planet = universe.getPlanet(planet_id) + if planet.ownedBy(fo.empireID()): # TODO: also exclude at-peace planets + continue + if planet.unowned and not EspionageAI.colony_detectable_by_empire(planet_id, empire=fo.empireID()): + continue + if sum([planet.currentMeterValue(meter_type) for meter_type in + [fo.meterType.defense, fo.meterType.shield, fo.meterType.construction]]): + combat_trigger = True + break + if not all(( + fleet, + fleet.systemID == target_sys_id, + system_status.get('currently_visible', False), + not (total_threat and combat_trigger) + )): + self.executed = False + return True class OrderRepair(AIFleetOrder): ORDER_NAME = 'repair' - TARGET_TYPE = System + TARGET_TYPE = TargetSystem def is_valid(self): if not super(OrderRepair, self).is_valid(): @@ -522,22 +580,24 @@ def is_valid(self): def issue_order(self): if not super(OrderRepair, self).issue_order(): - return + return False fleet_id = self.fleet.id system_id = self.target.get_system().id - fleet = self.fleet.get_object() - if system_id not in [fleet.systemID, fleet.nextSystemID]: + fleet = self.fleet.get_object() # type: fo.fleet + if system_id == fleet.systemID: + aistate = get_aistate() + if aistate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: + if system_id in aistate.needsEmergencyExploration: + aistate.needsEmergencyExploration.remove(system_id) + elif system_id != fleet.nextSystemID: fo.issueAggressionOrder(fleet_id, False) - start_id = [fleet.systemID, fleet.nextSystemID][fleet.systemID == INVALID_ID] + start_id = FleetUtilsAI.get_fleet_system(fleet) dest_id = MoveUtilsAI.get_safe_path_leg_to_dest(fleet_id, start_id, system_id) - print "fleet %d with order type(%s) sent to safe leg dest %s and ultimate dest %s" % (fleet_id, self.ORDER_NAME, - PlanetUtilsAI.sys_name_ids([dest_id]), - PlanetUtilsAI.sys_name_ids([system_id])) + universe = fo.getUniverse() + debug("fleet %d with order type(%s) sent to safe leg dest %s and ultimate dest %s" % ( + fleet_id, self.ORDER_NAME, universe.getSystem(dest_id), universe.getSystem(system_id))) fo.issueFleetMoveOrder(fleet_id, dest_id) - print "Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target) - - if system_id == fleet.systemID: - if foAI.foAIstate.get_fleet_role(fleet_id) == MissionType.EXPLORATION: - if system_id in foAI.foAIstate.needsEmergencyExploration: - del foAI.foAIstate.needsEmergencyExploration[foAI.foAIstate.needsEmergencyExploration.index(system_id)] - self.order_issued = True + debug("Order issued: %s fleet: %s target: %s" % (self.ORDER_NAME, self.fleet, self.target)) + ships_cur_health, ships_max_health = FleetUtilsAI.get_current_and_max_structure(fleet_id) + self.executed = (ships_cur_health == ships_max_health) + return True diff --git a/default/python/interface_mock/result/freeOrionAIInterface.py b/default/python/AI/freeOrionAIInterface.pyi similarity index 82% rename from default/python/interface_mock/result/freeOrionAIInterface.py rename to default/python/AI/freeOrionAIInterface.pyi index 50346cd113e..d0723a422b5 100644 --- a/default/python/interface_mock/result/freeOrionAIInterface.py +++ b/default/python/AI/freeOrionAIInterface.pyi @@ -1,32 +1,122 @@ +# Autogenerated do not modify manually! +# This is a type-hinting python stub file, used by python IDEs to provide type hints. For more information +# about stub files, see https://www.python.org/dev/peps/pep-0484/#stub-files +# During execution, the actual freeOrionAIInterface module is made available via +# a C++ Boost-python process as part of the launch. + +# type: ignore + +class AccountingInfoVec(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() + + def __delitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + + def __iter__(self): + """ + :rtype: object + """ + return object() + + def __len__(self): + """ + :rtype: int + """ + return int() + + def __setitem__(self, obj1, obj2): + """ + :param obj1: + :type obj1: object + :param obj2: + :type obj2: object + :rtype: None + """ + return None + + def append(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def extend(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + +class EffectCause(object): + @property + def causeType(self): + pass + + @property + def customLabel(self): + pass + + @property + def specificCause(self): + pass + + class GGColor(object): @property def a(self): return int() @property - def r(self): + def b(self): return int() @property - def b(self): + def g(self): return int() @property - def g(self): + def r(self): return int() class GalaxySetupData(object): @property - def specialsFrequency(self): + def age(self): return galaxySetupOption() @property - def age(self): - return galaxySetupOption() + def gameUID(self): + return str() @property - def starlaneFrequency(self): + def maxAIAggression(self): + return aggression() + + @property + def monsterFrequency(self): return galaxySetupOption() @property @@ -37,28 +127,106 @@ def nativeFrequency(self): def planetDensity(self): return galaxySetupOption() + @property + def seed(self): + return str() + @property def shape(self): return galaxyShape() @property - def seed(self): - return str() + def size(self): + return int() @property - def monsterFrequency(self): + def specialsFrequency(self): return galaxySetupOption() @property - def size(self): - return int() + def starlaneFrequency(self): + return galaxySetupOption() + +class GameRules(object): @property - def maxAIAggression(self): - return aggression() + def empty(self): + pass + + def getDescription(self, string): + """ + :param string: + :type string: str + :rtype: str + """ + return str() + + def getDouble(self, string): + """ + :param string: + :type string: str + :rtype: float + """ + return float() + + def getInt(self, string): + """ + :param string: + :type string: str + :rtype: int + """ + return int() + + def getRulesAsStrings(self): + """ + :rtype: StringsMap + """ + return StringsMap() + + def getString(self, string): + """ + :param string: + :type string: str + :rtype: str + """ + return str() + + def getToggle(self, string): + """ + :param string: + :type string: str + :rtype: bool + """ + return bool() + + def ruleExists(self, string): + """ + :param string: + :type string: str + :rtype: bool + """ + return bool() + + def ruleExistsWithType(self, string, rule_type): + """ + :param string: + :type string: str + :param rule_type: + :type rule_type: ruleType + :rtype: bool + """ + return bool() class IntBoolMap(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() + def __delitem__(self, obj): """ :param obj: @@ -75,20 +243,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -99,14 +265,16 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): + +class IntDblMap(object): + def __contains__(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: bool """ - return int() - + return bool() -class IntDblMap(object): def __delitem__(self, obj): """ :param obj: @@ -123,20 +291,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -147,14 +313,16 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): + +class IntFltMap(object): + def __contains__(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: bool """ - return int() - + return bool() -class IntFltMap(object): def __delitem__(self, obj): """ :param obj: @@ -171,20 +339,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -195,14 +361,16 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): + +class IntIntMap(object): + def __contains__(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: bool """ - return int() - + return bool() -class IntIntMap(object): def __delitem__(self, obj): """ :param obj: @@ -219,20 +387,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -243,23 +409,17 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): - """ - :rtype: int - """ - return int() - class IntPairVec(object): - def __delitem__(self, obj): + def __contains__(self, obj): """ :param obj: :type obj: object - :rtype: None + :rtype: bool """ - return None + return bool() - def extend(self, obj): + def __delitem__(self, obj): """ :param obj: :type obj: object @@ -275,20 +435,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -307,22 +465,16 @@ def append(self, obj): """ return None - def __len__(self): + def extend(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: None """ - return int() + return None class IntSet(object): - def count(self, number): - """ - :param number: - :type number: int - :rtype: int - """ - return int() - def __contains__(self, number): """ :param number: @@ -343,6 +495,14 @@ def __len__(self): """ return int() + def count(self, number): + """ + :param number: + :type number: int + :rtype: int + """ + return int() + def empty(self): """ :rtype: bool @@ -357,14 +517,6 @@ def size(self): class IntSetSet(object): - def count(self, int_set): - """ - :param int_set: - :type int_set: IntSet - :rtype: int - """ - return int() - def __contains__(self, int_set): """ :param int_set: @@ -385,6 +537,14 @@ def __len__(self): """ return int() + def count(self, int_set): + """ + :param int_set: + :type int_set: IntSet + :rtype: int + """ + return int() + def empty(self): """ :rtype: bool @@ -399,15 +559,15 @@ def size(self): class IntVec(object): - def __delitem__(self, obj): + def __contains__(self, obj): """ :param obj: :type obj: object - :rtype: None + :rtype: bool """ - return None + return bool() - def extend(self, obj): + def __delitem__(self, obj): """ :param obj: :type obj: object @@ -423,20 +583,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: iter """ return iter() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -455,24 +613,34 @@ def append(self, obj): """ return None - def __len__(self): + def extend(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: None """ - return int() + return None -class ItemSpec(object): +class UnlockableItem(object): + @property + def name(self): + return str() + @property def type(self): return unlockableItemType() - @property - def name(self): - return str() +class UnlockableItemVec(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() -class ItemSpecVec(object): def __delitem__(self, obj): """ :param obj: @@ -481,7 +649,37 @@ def __delitem__(self, obj): """ return None - def extend(self, obj): + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + + def __iter__(self): + """ + :rtype: iter + """ + return iter() + + def __len__(self): + """ + :rtype: int + """ + return int() + + def __setitem__(self, obj1, obj2): + """ + :param obj1: + :type obj1: object + :param obj2: + :type obj2: object + :rtype: None + """ + return None + + def append(self, obj): """ :param obj: :type obj: object @@ -489,14 +687,16 @@ def extend(self, obj): """ return None - def __getitem__(self, obj): + def extend(self, obj): """ :param obj: :type obj: object - :rtype: object + :rtype: None """ - return object() + return None + +class MeterTypeAccountingInfoVecMap(object): def __contains__(self, obj): """ :param obj: @@ -505,11 +705,33 @@ def __contains__(self, obj): """ return bool() + def __delitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + def __iter__(self): """ - :rtype: iter + :rtype: object """ - return iter() + return object() + + def __len__(self): + """ + :rtype: int + """ + return int() def __setitem__(self, obj1, obj2): """ @@ -521,7 +743,17 @@ def __setitem__(self, obj1, obj2): """ return None - def append(self, obj): + +class MeterTypeMeterMap(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() + + def __delitem__(self, obj): """ :param obj: :type obj: object @@ -529,14 +761,70 @@ def append(self, obj): """ return None + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + + def __iter__(self): + """ + :rtype: object + """ + return object() + def __len__(self): """ :rtype: int """ return int() + def __setitem__(self, obj1, obj2): + """ + :param obj1: + :type obj1: object + :param obj2: + :type obj2: object + :rtype: None + """ + return None + + +class MeterTypeStringPair(object): + @property + def meterType(self): + pass + + @property + def string(self): + pass + + +class Order(object): + @property + def empireID(self): + pass + + @property + def executed(self): + pass + + +class OrderSet(object): + @property + def size(self): + pass + + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() -class MeterTypeMeterMap(object): def __delitem__(self, obj): """ :param obj: @@ -553,20 +841,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -577,24 +863,16 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): + +class PairIntInt_IntMap(object): + def __contains__(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: bool """ - return int() - - -class MeterTypeStringPair(object): - @property - def meterType(self): - pass - - @property - def string(self): - pass - + return bool() -class PairIntInt_IntMap(object): def __delitem__(self, obj): """ :param obj: @@ -611,20 +889,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -635,14 +911,16 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): + +class ShipPartMeterMap(object): + def __contains__(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: bool """ - return int() - + return bool() -class ShipPartMeterMap(object): def __delitem__(self, obj): """ :param obj: @@ -659,20 +937,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -683,23 +959,17 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): - """ - :rtype: int - """ - return int() - class ShipSlotVec(object): - def __delitem__(self, obj): + def __contains__(self, obj): """ :param obj: :type obj: object - :rtype: None + :rtype: bool """ - return None + return bool() - def extend(self, obj): + def __delitem__(self, obj): """ :param obj: :type obj: object @@ -715,20 +985,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -747,22 +1015,16 @@ def append(self, obj): """ return None - def __len__(self): + def extend(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: None """ - return int() + return None class StringSet(object): - def count(self, string): - """ - :param string: - :type string: str - :rtype: int - """ - return int() - def __contains__(self, string): """ :param string: @@ -783,6 +1045,14 @@ def __len__(self): """ return int() + def count(self, string): + """ + :param string: + :type string: str + :rtype: int + """ + return int() + def empty(self): """ :rtype: bool @@ -797,15 +1067,15 @@ def size(self): class StringVec(object): - def __delitem__(self, obj): + def __contains__(self, obj): """ :param obj: :type obj: object - :rtype: None + :rtype: bool """ - return None + return bool() - def extend(self, obj): + def __delitem__(self, obj): """ :param obj: :type obj: object @@ -821,20 +1091,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: iter """ return iter() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -853,14 +1121,72 @@ def append(self, obj): """ return None + def extend(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + +class StringsMap(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() + + def __delitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + + def __iter__(self): + """ + :rtype: object + """ + return object() + def __len__(self): """ :rtype: int """ return int() + def __setitem__(self, obj1, obj2): + """ + :param obj1: + :type obj1: object + :param obj2: + :type obj2: object + :rtype: None + """ + return None + + +class TargetIDAccountingMapMap(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() -class VisibilityIntMap(object): def __delitem__(self, obj): """ :param obj: @@ -877,6 +1203,30 @@ def __getitem__(self, obj): """ return object() + def __iter__(self): + """ + :rtype: object + """ + return object() + + def __len__(self): + """ + :rtype: int + """ + return int() + + def __setitem__(self, obj1, obj2): + """ + :param obj1: + :type obj1: object + :param obj2: + :type obj2: object + :rtype: None + """ + return None + + +class VisibilityIntMap(object): def __contains__(self, obj): """ :param obj: @@ -885,12 +1235,34 @@ def __contains__(self, obj): """ return bool() + def __delitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -901,12 +1273,6 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): - """ - :rtype: int - """ - return int() - class buildingType(object): @property @@ -919,13 +1285,13 @@ def description(self): @property def dump(self): - return str() + pass @property def name(self): return str() - def canBeProduced(self, number1, number2): + def canBeEnqueued(self, number1, number2): """ :param number1: :type number1: int @@ -935,25 +1301,15 @@ def canBeProduced(self, number1, number2): """ return bool() - def productionTime(self, number1, number2): - """ - :param number1: - :type number1: int - :param number2: - :type number2: int - :rtype: int - """ - return int() - - def perTurnCost(self, number1, number2): + def canBeProduced(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: float + :rtype: bool """ - return float() + return bool() def captureResult(self, number1, number2, number3, boolean): """ @@ -969,6 +1325,16 @@ def captureResult(self, number1, number2, number3, boolean): """ return captureResult() + def perTurnCost(self, number1, number2): + """ + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: float + """ + return float() + def productionCost(self, number1, number2): """ :param number1: @@ -979,22 +1345,18 @@ def productionCost(self, number1, number2): """ return float() - def canBeEnqueued(self, number1, number2): + def productionTime(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: bool + :rtype: int """ - return bool() + return int() class diplomaticMessage(object): - @property - def type(self): - return diplomaticMessageType() - @property def recipient(self): return int() @@ -1003,10 +1365,14 @@ def recipient(self): def sender(self): return int() + @property + def type(self): + return diplomaticMessageType() + class diplomaticStatusUpdate(object): @property - def status(self): + def empire1(self): pass @property @@ -1014,136 +1380,158 @@ def empire2(self): pass @property - def empire1(self): + def status(self): pass class empire(object): @property - def capitalID(self): - return int() - - @property - def productionPoints(self): - return float() + def allShipDesigns(self): + return IntSet() @property - def productionQueue(self): - return productionQueue() + def availableBuildingTypes(self): + return StringSet() @property - def exploredSystemIDs(self): + def availableShipDesigns(self): return IntSet() @property - def planetsWithWastedPP(self): - return IntSetSet() + def availableShipHulls(self): + return StringSet() @property - def playerName(self): - return str() + def availableShipParts(self): + return StringSet() @property - def fleetSupplyableSystemIDs(self): - return IntSet() + def availableTechs(self): + return StringSet() @property - def planetsWithAllocatedPP(self): - return resPoolMap() + def capitalID(self): + return int() @property - def won(self): - return bool() + def colour(self): + return GGColor() @property - def availableShipParts(self): - return StringSet() + def eliminated(self): + return bool() @property - def availableBuildingTypes(self): - return StringSet() + def empireID(self): + return int() @property - def systemSupplyRanges(self): - return IntFltMap() + def exploredSystemIDs(self): + return IntSet() @property - def allShipDesigns(self): + def fleetSupplyableSystemIDs(self): return IntSet() @property - def planetsWithAvailablePP(self): - return resPoolMap() + def name(self): + return str() @property - def researchQueue(self): - return researchQueue() + def planetsWithAllocatedPP(self): + return resPoolMap() @property - def eliminated(self): - return bool() + def planetsWithAvailablePP(self): + return resPoolMap() @property - def empireID(self): - return int() + def planetsWithWastedPP(self): + return IntSetSet() @property - def name(self): + def playerName(self): return str() @property - def colour(self): - return GGColor() + def productionPoints(self): + return float() @property - def availableShipHulls(self): - return StringSet() + def productionQueue(self): + return productionQueue() @property - def availableShipDesigns(self): - return IntSet() + def researchQueue(self): + return researchQueue() @property def supplyUnobstructedSystems(self): return IntSet() @property - def availableTechs(self): - return StringSet() + def systemSupplyRanges(self): + return IntFltMap() - def resourceAvailable(self, resource_type): + @property + def won(self): + return bool() + + def buildingTypeAvailable(self, string): """ - :param resource_type: - :type resource_type: resourceType - :rtype: float + :param string: + :type string: str + :rtype: bool """ - return float() + return bool() - def techResearched(self, string): + def canBuild(self, build_type, string, number): """ + :param build_type: + :type build_type: buildType :param string: :type string: str + :param number: + :type number: int :rtype: bool """ return bool() - def resourceProduction(self, resource_type): + def getMeter(self, string): + """ + Returns the empire meter with the indicated name (string). + + :param string: + :type string: str + :rtype: meter + """ + return meter() + + def getResourcePool(self, resource_type): """ :param resource_type: :type resource_type: resourceType - :rtype: float + :rtype: resPool """ - return float() + return resPool() - def productionCostAndTime(self, production_queue_element): + def getSitRep(self, number): """ - :param production_queue_element: - :type production_queue_element: productionQueueElement - :rtype: object + :param number: + :type number: int + :rtype: sitrep """ - return object() + return sitrep() - def shipDesignAvailable(self, number): + def getTechStatus(self, string): + """ + :param string: + :type string: str + :rtype: techStatus + """ + return techStatus() + + def hasExploredSystem(self, number): """ :param number: :type number: int @@ -1159,91 +1547,89 @@ def numSitReps(self, number): """ return int() - def getResourcePool(self, resource_type): + def obstructedStarlanes(self): """ - :param resource_type: - :type resource_type: resourceType - :rtype: resPool + :rtype: IntPairVec """ - return resPool() + return IntPairVec() - def resourceStockpile(self, resource_type): + def population(self): """ - :param resource_type: - :type resource_type: resourceType :rtype: float """ return float() - def hasExploredSystem(self, number): + def preservedLaneTravel(self, number1, number2): """ - :param number: - :type number: int + :param number1: + :type number1: int + :param number2: + :type number2: int :rtype: bool """ return bool() - def buildingTypeAvailable(self, string): + def productionCostAndTime(self, production_queue_element): + """ + :param production_queue_element: + :type production_queue_element: productionQueueElement + :rtype: object + """ + return object() + + def researchProgress(self, string): """ :param string: :type string: str - :rtype: bool + :rtype: float """ - return bool() + return float() - def obstructedStarlanes(self): + def resourceAvailable(self, resource_type): """ - :rtype: IntPairVec + :param resource_type: + :type resource_type: resourceType + :rtype: float """ - return IntPairVec() + return float() - def population(self): + def resourceProduction(self, resource_type): """ + :param resource_type: + :type resource_type: resourceType :rtype: float """ return float() - def supplyProjections(self): + def resourceStockpile(self, resource_type): """ - :rtype: IntIntMap + :param resource_type: + :type resource_type: resourceType + :rtype: float """ - return IntIntMap() + return float() - def canBuild(self, build_type, string, number): + def shipDesignAvailable(self, number): """ - :param build_type: - :type build_type: buildType - :param string: - :type string: str :param number: :type number: int :rtype: bool """ return bool() - def getSitRep(self, number): - """ - :param number: - :type number: int - :rtype: sitrep - """ - return sitrep() - - def getTechStatus(self, string): + def supplyProjections(self): """ - :param string: - :type string: str - :rtype: techStatus + :rtype: dict[int, int] """ - return techStatus() + return dict() - def researchProgress(self, string): + def techResearched(self, string): """ :param string: :type string: str - :rtype: float + :rtype: bool """ - return float() + return bool() class fieldType(object): @@ -1253,18 +1639,22 @@ def description(self): @property def dump(self): - return str() + pass @property def name(self): return str() -class hullType(object): +class shipHull(object): @property def costTimeLocationInvariant(self): return bool() + @property + def fuel(self): + return float() + @property def name(self): return str() @@ -1274,29 +1664,41 @@ def numSlots(self): return int() @property - def structure(self): - return float() + def slots(self): + return ShipSlotVec() @property - def stealth(self): + def speed(self): return float() @property - def fuel(self): + def starlaneSpeed(self): return float() @property - def slots(self): - return ShipSlotVec() - - @property - def speed(self): + def stealth(self): return float() @property - def starlaneSpeed(self): + def structure(self): return float() + def hasTag(self, string): + """ + :param string: + :type string: str + :rtype: bool + """ + return bool() + + def numSlotsOfSlotType(self, ship_slot_type): + """ + :param ship_slot_type: + :type ship_slot_type: shipSlotType + :rtype: int + """ + return int() + def productionCost(self, number1, number2): """ :param number1: @@ -1317,22 +1719,6 @@ def productionTime(self, number1, number2): """ return int() - def hasTag(self, string): - """ - :param string: - :type string: str - :rtype: bool - """ - return bool() - - def numSlotsOfSlotType(self, ship_slot_type): - """ - :param ship_slot_type: - :type ship_slot_type: shipSlotType - :rtype: int - """ - return int() - class meter(object): @property @@ -1340,26 +1726,26 @@ def current(self): return float() @property - def initial(self): - return float() + def dump(self): + pass @property - def dump(self): - return str() + def initial(self): + return float() -class partType(object): +class shipPart(object): @property - def mountableSlotTypes(self): - return ShipSlotVec() + def capacity(self): + return float() @property def costTimeLocationInvariant(self): return bool() @property - def capacity(self): - return float() + def mountableSlotTypes(self): + return ShipSlotVec() @property def name(self): @@ -1373,15 +1759,13 @@ def partClass(self): def secondaryStat(self): return float() - def productionTime(self, number1, number2): + def canMountInSlotType(self, ship_slot_type): """ - :param number1: - :type number1: int - :param number2: - :type number2: int - :rtype: int + :param ship_slot_type: + :type ship_slot_type: shipSlotType + :rtype: bool """ - return int() + return bool() def productionCost(self, number1, number2): """ @@ -1393,20 +1777,18 @@ def productionCost(self, number1, number2): """ return float() - def canMountInSlotType(self, ship_slot_type): + def productionTime(self, number1, number2): """ - :param ship_slot_type: - :type ship_slot_type: shipSlotType - :rtype: bool + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: int """ - return bool() + return int() class popCenter(object): - @property - def nextTurnPopGrowth(self): - pass - @property def speciesName(self): pass @@ -1418,20 +1800,20 @@ def allocatedPP(self): return resPoolMap() @property - def empty(self): - return bool() + def empireID(self): + return int() @property - def totalSpent(self): - return float() + def empty(self): + return bool() @property - def empireID(self): + def size(self): return int() @property - def size(self): - return int() + def totalSpent(self): + return float() def __getitem__(self, number): """ @@ -1447,13 +1829,11 @@ def __iter__(self): """ return object() - def objectsWithWastedPP(self, res_pool): + def __len__(self): """ - :param res_pool: - :type res_pool: resPool - :rtype: IntSetSet + :rtype: int """ - return IntSetSet() + return int() def availablePP(self, res_pool): """ @@ -1463,33 +1843,31 @@ def availablePP(self, res_pool): """ return resPoolMap() - def __len__(self): + def objectsWithWastedPP(self, res_pool): """ - :rtype: int + :param res_pool: + :type res_pool: resPool + :rtype: IntSetSet """ - return int() + return IntSetSet() class productionQueueElement(object): @property - def buildType(self): - return buildType() + def allocation(self): + return float() @property - def name(self): - return str() + def allowedStockpile(self): + return bool() @property def blocksize(self): return int() @property - def turnsLeft(self): - return int() - - @property - def allocation(self): - return float() + def buildType(self): + return buildType() @property def designID(self): @@ -1499,6 +1877,14 @@ def designID(self): def locationID(self): return int() + @property + def name(self): + return str() + + @property + def paused(self): + return bool() + @property def progress(self): return float() @@ -1507,12 +1893,24 @@ def progress(self): def remaining(self): return int() + @property + def turnsLeft(self): + return int() + class resPool(object): pass class resPoolMap(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() + def __delitem__(self, obj): """ :param obj: @@ -1529,20 +1927,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -1553,28 +1949,30 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): - """ - :rtype: int - """ - return int() - class researchQueue(object): @property - def size(self): + def empireID(self): return int() @property - def totalSpent(self): - return float() + def empty(self): + return bool() @property - def empireID(self): + def size(self): return int() @property - def empty(self): + def totalSpent(self): + return float() + + def __contains__(self, research_queue_element): + """ + :param research_queue_element: + :type research_queue_element: researchQueueElement + :rtype: bool + """ return bool() def __getitem__(self, number): @@ -1585,14 +1983,6 @@ def __getitem__(self, number): """ return researchQueueElement() - def __contains__(self, research_queue_element): - """ - :param research_queue_element: - :type research_queue_element: researchQueueElement - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object @@ -1630,136 +2020,140 @@ def turnsLeft(self): class resourceCenter(object): @property - def turnsSinceFocusChange(self): + def availableFoci(self): pass @property - def availableFoci(self): + def focus(self): pass @property - def focus(self): + def turnsSinceFocusChange(self): pass class shipDesign(object): @property - def costTimeLocationInvariant(self): - return bool() + def attack(self): + return float() @property - def dump(self): - return str() + def attackStats(self): + return IntVec() @property def canColonize(self): return bool() @property - def hull_type(self): - return hullType() + def canInvade(self): + return bool() @property - def tradeGeneration(self): + def colonyCapacity(self): return float() @property - def detection(self): - return float() + def costTimeLocationInvariant(self): + return bool() @property def defense(self): return float() @property - def speed(self): - return float() + def description(self): + return str() @property - def id(self): + def designedOnTurn(self): return int() @property - def isMonster(self): - return bool() - - @property - def stealth(self): - return float() - - @property - def attack(self): + def detection(self): return float() @property - def parts(self): - return StringVec() + def dump(self): + pass @property def fuel(self): return float() @property - def shields(self): - return float() + def hasFighters(self): + return bool() @property - def description(self): + def hull(self): return str() + @property + def ship_hull(self): + return shipHull() + + @property + def id(self): + return int() + + @property + def industryGeneration(self): + return float() + @property def isArmed(self): return bool() @property - def hull(self): - return str() + def isMonster(self): + return bool() @property - def designedOnTurn(self): - return int() + def name(self): + return str() @property - def colonyCapacity(self): - return float() + def parts(self): + return StringVec() @property - def structure(self): + def researchGeneration(self): return float() @property - def researchGeneration(self): + def shields(self): return float() @property - def canInvade(self): - return bool() + def speed(self): + return float() @property - def name(self): - return str() + def stealth(self): + return float() @property - def attackStats(self): - return IntVec() + def structure(self): + return float() @property - def troopCapacity(self): + def tradeGeneration(self): return float() @property - def industryGeneration(self): + def troopCapacity(self): return float() - def productionTime(self, number1, number2): + def perTurnCost(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: int + :rtype: float """ - return int() + return float() def productionCost(self, number1, number2): """ @@ -1781,15 +2175,15 @@ def productionLocationForEmpire(self, number1, number2): """ return bool() - def perTurnCost(self, number1, number2): + def productionTime(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: float + :rtype: int """ - return float() + return int() class sitrep(object): @@ -1797,51 +2191,51 @@ class sitrep(object): def getTags(self): pass - @property - def typeString(self): - pass - @property def getTurn(self): return int() - def getDataString(self, string): + @property + def typeString(self): + pass + + def getDataIDNumber(self, string): """ :param string: :type string: str - :rtype: str + :rtype: int """ - return str() + return int() - def getDataIDNumber(self, string): + def getDataString(self, string): """ :param string: :type string: str - :rtype: int + :rtype: str """ - return int() + return str() class special(object): @property - def name(self): + def description(self): return str() @property def dump(self): - return str() + pass @property - def spawnrate(self): - return float() + def name(self): + return str() @property def spawnlimit(self): return int() @property - def description(self): - return str() + def spawnrate(self): + return float() def initialCapacity(self, number): """ @@ -1854,40 +2248,40 @@ def initialCapacity(self, number): class species(object): @property - def description(self): - return str() + def canColonize(self): + return bool() @property - def dump(self): - return str() + def canProduceShips(self): + return bool() @property - def tags(self): - return StringSet() + def description(self): + return str() @property - def canColonize(self): - return bool() + def dump(self): + pass @property def foci(self): return StringVec() @property - def canProduceShips(self): - return bool() + def homeworlds(self): + return IntSet() @property - def preferredFocus(self): + def name(self): return str() @property - def homeworlds(self): - return IntSet() + def preferredFocus(self): + return str() @property - def name(self): - return str() + def tags(self): + return StringSet() def getPlanetEnvironment(self, planet_type): """ @@ -1903,17 +2297,13 @@ class tech(object): def category(self): return str() - @property - def unlockedItems(self): - return ItemSpecVec() - @property def description(self): return str() @property - def unlockedTechs(self): - return StringSet() + def name(self): + return str() @property def prerequisites(self): @@ -1924,10 +2314,14 @@ def shortDescription(self): return str() @property - def name(self): - return str() + def unlockedItems(self): + return UnlockableItemVec() - def researchCost(self, number): + @property + def unlockedTechs(self): + return StringSet() + + def perTurnCost(self, number): """ :param number: :type number: int @@ -1943,7 +2337,7 @@ def recursivePrerequisites(self, number): """ return StringVec() - def perTurnCost(self, number): + def researchCost(self, number): """ :param number: :type number: int @@ -1962,42 +2356,44 @@ def researchTime(self, number): class universe(object): @property - def shipIDs(self): + def allObjectIDs(self): return IntVec() @property - def fieldIDs(self): + def buildingIDs(self): return IntVec() @property - def fleetIDs(self): + def effectAccounting(self): + return TargetIDAccountingMapMap() + + @property + def fieldIDs(self): return IntVec() @property - def planetIDs(self): + def fleetIDs(self): return IntVec() @property - def buildingIDs(self): + def planetIDs(self): return IntVec() @property - def allObjectIDs(self): + def shipIDs(self): return IntVec() @property def systemIDs(self): return IntVec() - def jumpDistance(self, number1, number2): + def destroyedObjectIDs(self, number): """ - :param number1: - :type number1: int - :param number2: - :type number2: int - :rtype: int + :param number: + :type number: int + :rtype: IntSet """ - return int() + return IntSet() def dump(self): """ @@ -2005,15 +2401,49 @@ def dump(self): """ return None - def linearDistance(self, number1, number2): + def getBuilding(self, number): + """ + :param number: + :type number: int + :rtype: building + """ + return building() + + def getField(self, number): + """ + :param number: + :type number: int + :rtype: field + """ + return field() + + def getFleet(self, number): + """ + :param number: + :type number: int + :rtype: fleet + """ + return fleet() + + def getGenericShipDesign(self, string): + """ + Returns the ship design (ShipDesign) with the indicated name (string). + + :param string: + :type string: str + :rtype: shipDesign + """ + return shipDesign() + + def getImmediateNeighbors(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: float + :rtype: IntVec """ - return float() + return IntVec() def getObject(self, number): """ @@ -2039,17 +2469,33 @@ def getShip(self, number): """ return ship() - def systemsConnected(self, number1, number2, number3): + def getSystem(self, number): + """ + :param number: + :type number: int + :rtype: system + """ + return system() + + def getSystemNeighborsMap(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :param number3: - :type number3: int - :rtype: bool + :rtype: IntDblMap """ - return bool() + return IntDblMap() + + def getVisibility(self, number1, number2): + """ + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: visibility + """ + return visibility() def getVisibilityTurnsMap(self, number1, number2): """ @@ -2057,10 +2503,22 @@ def getVisibilityTurnsMap(self, number1, number2): :type number1: int :param number2: :type number2: int - :rtype: VisibilityIntMap + :rtype: dict[int, int] """ return dict() + def jumpDistance(self, number1, number2): + """ + If two system ids are passed or both objects are within a system, return the jump distance between the two systems. If one object (e.g. a fleet) is on a starlane, then calculate the jump distance from both ends of the starlane to the target system and return the smaller one. + + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: int + """ + return int() + def leastJumpsPath(self, number1, number2, number3): """ :param number1: @@ -2073,20 +2531,26 @@ def leastJumpsPath(self, number1, number2, number3): """ return IntVec() - def getFleet(self, number): + def linearDistance(self, number1, number2): """ - :param number: - :type number: int - :rtype: fleet + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: float """ - return fleet() + return float() - def getImmediateNeighbors(self, number1, number2): + def shortestNonHostilePath(self, number1, number2, number3): """ + Shortest sequence of System ids and distance from System (number1) to System (number2) with no hostile Fleets as determined by visibility of Empire (number3). (number3) must be a valid empire. + :param number1: :type number1: int :param number2: :type number2: int + :param number3: + :type number3: int :rtype: IntVec """ return IntVec() @@ -2103,39 +2567,15 @@ def shortestPath(self, number1, number2, number3): """ return IntVec() - def updateMeterEstimates(self, item_list): - """ - :param item_list: - :type item_list: list - :rtype: None - """ - return None - - def getField(self, number): - """ - :param number: - :type number: int - :rtype: field - """ - return field() - - def destroyedObjectIDs(self, number): - """ - :param number: - :type number: int - :rtype: IntSet - """ - return IntSet() - - def getSystemNeighborsMap(self, number1, number2): + def shortestPathDistance(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: IntDblMap + :rtype: float """ - return IntDblMap() + return float() def systemHasStarlane(self, number1, number2): """ @@ -2147,82 +2587,66 @@ def systemHasStarlane(self, number1, number2): """ return bool() - def getGenericShipDesign(self, string): - """ - Returns the ship design (ShipDesign) with the indicated name (string). - - :param string: - :type string: str - :rtype: shipDesign - """ - return shipDesign() - - def getVisibility(self, number1, number2): + def systemsConnected(self, number1, number2, number3): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: visibility - """ - return visibility() - - def getSystem(self, number): - """ - :param number: - :type number: int - :rtype: system + :param number3: + :type number3: int + :rtype: bool """ - return system() + return bool() - def getBuilding(self, number): + def updateMeterEstimates(self, item_list): """ - :param number: - :type number: int - :rtype: building + :param item_list: + :type item_list: list + :rtype: None """ - return building() + return None class universeObject(object): @property - def dump(self): + def ageInTurns(self): pass @property - def unowned(self): + def containedObjects(self): pass @property - def meters(self): + def containerObject(self): pass @property - def owner(self): + def creationTurn(self): pass @property - def id(self): + def dump(self): pass @property - def containerObject(self): + def id(self): pass @property - def creationTurn(self): + def meters(self): pass @property - def containedObjects(self): + def name(self): pass @property - def tags(self): + def owner(self): pass @property - def ageInTurns(self): + def specials(self): pass @property @@ -2230,22 +2654,22 @@ def systemID(self): pass @property - def name(self): + def tags(self): pass @property - def specials(self): + def unowned(self): pass @property - def y(self): + def x(self): pass @property - def x(self): + def y(self): pass - def contains(self, number): + def containedBy(self, number): """ :param number: :type number: int @@ -2253,15 +2677,15 @@ def contains(self, number): """ return bool() - def hasTag(self, string): + def contains(self, number): """ - :param string: - :type string: str + :param number: + :type number: int :rtype: bool """ return bool() - def nextTurnCurrentMeterValue(self, meter_type): + def currentMeterValue(self, meter_type): """ :param meter_type: :type meter_type: meterType @@ -2269,23 +2693,31 @@ def nextTurnCurrentMeterValue(self, meter_type): """ return float() - def initialMeterValue(self, meter_type): + def getMeter(self, meter_type): """ :param meter_type: :type meter_type: meterType - :rtype: float + :rtype: meter """ - return float() + return meter() - def containedBy(self, number): + def hasSpecial(self, string): """ - :param number: - :type number: int + :param string: + :type string: str :rtype: bool """ return bool() - def currentMeterValue(self, meter_type): + def hasTag(self, string): + """ + :param string: + :type string: str + :rtype: bool + """ + return bool() + + def initialMeterValue(self, meter_type): """ :param meter_type: :type meter_type: meterType @@ -2293,14 +2725,6 @@ def currentMeterValue(self, meter_type): """ return float() - def specialAddedOnTurn(self, string): - """ - :param string: - :type string: str - :rtype: int - """ - return int() - def ownedBy(self, number): """ :param number: @@ -2309,21 +2733,27 @@ def ownedBy(self, number): """ return bool() - def getMeter(self, meter_type): - """ - :param meter_type: - :type meter_type: meterType - :rtype: meter - """ - return meter() - - def hasSpecial(self, string): + def specialAddedOnTurn(self, string): """ :param string: :type string: str - :rtype: bool + :rtype: int """ - return bool() + return int() + + +class AccountingInfo(EffectCause): + @property + def meterChange(self): + pass + + @property + def meterRunningTotal(self): + pass + + @property + def sourceID(self): + pass class building(universeObject): @@ -2332,16 +2762,16 @@ def buildingTypeName(self): return str() @property - def producedByEmpireID(self): - return int() + def orderedScrapped(self): + return bool() @property def planetID(self): return int() @property - def orderedScrapped(self): - return bool() + def producedByEmpireID(self): + return int() class field(universeObject): @@ -2360,59 +2790,43 @@ def inField(self, base_object): class fleet(universeObject): @property - def hasOutpostShips(self): + def aggressive(self): return bool() @property - def shipIDs(self): - return IntSet() - - @property - def hasFighterShips(self): + def canChangeDirectionEnRoute(self): return bool() @property - def speed(self): - return float() + def empty(self): + return bool() @property - def previousSystemID(self): + def finalDestinationID(self): return int() @property - def numShips(self): - return int() + def fuel(self): + return float() @property - def hasColonyShips(self): + def hasArmedShips(self): return bool() @property - def canChangeDirectionEnRoute(self): + def hasColonyShips(self): return bool() @property - def nextSystemID(self): - return int() - - @property - def finalDestinationID(self): - return int() - - @property - def hasMonsters(self): + def hasFighterShips(self): return bool() @property - def hasArmedShips(self): + def hasMonsters(self): return bool() @property - def fuel(self): - return float() - - @property - def aggressive(self): + def hasOutpostShips(self): return bool() @property @@ -2424,152 +2838,110 @@ def maxFuel(self): return float() @property - def empty(self): - return bool() - - -class planet(universeObject, popCenter, resourceCenter): - @property - def originalType(self): - return planetType() - - @property - def nextLargerPlanetSize(self): - return planetSize() - - @property - def distanceFromOriginalType(self): + def nextSystemID(self): return int() @property - def clockwiseNextPlanetType(self): - return planetType() - - @property - def nextSmallerPlanetSize(self): - return planetSize() - - @property - def buildingIDs(self): - return IntSet() - - @property - def OrbitalPeriod(self): - return float() - - @property - def counterClockwiseNextPlanetType(self): - return planetType() - - @property - def RotationalPeriod(self): - return float() - - @property - def type(self): - return planetType() - - @property - def InitialOrbitalPosition(self): - return float() + def numShips(self): + return int() @property - def size(self): - return planetSize() - - def nextBetterPlanetTypeForSpecies(self, string): - """ - :param string: - :type string: str - :rtype: planetType - """ - return planetType() + def previousSystemID(self): + return int() - def environmentForSpecies(self, string): - """ - :param string: - :type string: str - :rtype: planetEnvironment - """ - return planetEnvironment() + @property + def shipIDs(self): + return IntSet() - def OrbitalPositionOnTurn(self, number): - """ - :param number: - :type number: int - :rtype: float - """ + @property + def speed(self): return float() class ship(universeObject): @property - def partMeters(self): - return ShipPartMeterMap() + def arrivedOnTurn(self): + return int() @property - def speciesName(self): - return str() + def canBombard(self): + return bool() @property - def orderedScrapped(self): + def canColonize(self): return bool() @property - def isArmed(self): + def canInvade(self): return bool() @property - def troopCapacity(self): + def colonyCapacity(self): return float() @property - def canColonize(self): - return bool() - - @property - def canInvade(self): - return bool() + def design(self): + return shipDesign() @property def designID(self): return int() @property - def producedByEmpireID(self): + def fleetID(self): return int() + @property + def hasFighters(self): + return bool() + + @property + def isArmed(self): + return bool() + @property def isMonster(self): return bool() @property - def design(self): - return shipDesign() + def lastResuppliedOnTurn(self): + return int() @property - def orderedInvadePlanet(self): + def lastTurnActiveInCombat(self): return int() @property - def colonyCapacity(self): - return float() + def orderedColonizePlanet(self): + return int() @property - def fleetID(self): + def orderedInvadePlanet(self): return int() @property - def canBombard(self): + def orderedScrapped(self): return bool() + @property + def partMeters(self): + return ShipPartMeterMap() + + @property + def producedByEmpireID(self): + return int() + + @property + def speciesName(self): + return str() + @property def speed(self): return float() @property - def orderedColonizePlanet(self): - return int() + def troopCapacity(self): + return float() def currentPartMeterValue(self, meter_type, string): """ @@ -2594,44 +2966,44 @@ def initialPartMeterValue(self, meter_type, string): class system(universeObject): @property - def numStarlanes(self): - return int() + def buildingIDs(self): + return IntSet() @property - def fleetIDs(self): + def fieldIDs(self): return IntSet() @property - def shipIDs(self): + def fleetIDs(self): return IntSet() @property - def starlanesWormholes(self): - return IntBoolMap() + def lastTurnBattleHere(self): + return int() @property - def fieldIDs(self): - return IntSet() + def numStarlanes(self): + return int() @property def numWormholes(self): return int() @property - def buildingIDs(self): + def planetIDs(self): return IntSet() @property - def starType(self): - return starType() + def shipIDs(self): + return IntSet() @property - def lastTurnBattleHere(self): - return int() + def starType(self): + return starType() @property - def planetIDs(self): - return IntSet() + def starlanesWormholes(self): + return IntBoolMap() def HasStarlaneToSystemID(self, number): """ @@ -2652,6 +3024,92 @@ def HasWormholeToSystemID(self, number): return bool() +class planet(universeObject, popCenter, resourceCenter): + @property + def InitialOrbitalPosition(self): + return float() + + @property + def LastTurnAttackedByShip(self): + return int() + + @property + def LastTurnConquered(self): + return int() + + @property + def OrbitalPeriod(self): + return float() + + @property + def RotationalPeriod(self): + return float() + + @property + def buildingIDs(self): + return IntSet() + + @property + def clockwiseNextPlanetType(self): + return planetType() + + @property + def counterClockwiseNextPlanetType(self): + return planetType() + + @property + def distanceFromOriginalType(self): + return int() + + @property + def habitableSize(self): + return int() + + @property + def nextLargerPlanetSize(self): + return planetSize() + + @property + def nextSmallerPlanetSize(self): + return planetSize() + + @property + def originalType(self): + return planetType() + + @property + def size(self): + return planetSize() + + @property + def type(self): + return planetType() + + def OrbitalPositionOnTurn(self, number): + """ + :param number: + :type number: int + :rtype: float + """ + return float() + + def environmentForSpecies(self, string): + """ + :param string: + :type string: str + :rtype: planetEnvironment + """ + return planetEnvironment() + + def nextBetterPlanetTypeForSpecies(self, string): + """ + :param string: + :type string: str + :rtype: planetType + """ + return planetType() + + class Enum(int): """Enum stub for docs, not really present in fo""" def __new__(cls, *args, **kwargs): @@ -2686,10 +3144,12 @@ def __init__(self, numerator, name): building = None # buildType(1, "building") ship = None # buildType(2, "ship") + stockpile = None # buildType(4, "stockpile") buildType.building = buildType(1, "building") buildType.ship = buildType(2, "ship") +buildType.stockpile = buildType(4, "stockpile") class captureResult(Enum): @@ -2713,15 +3173,23 @@ def __init__(self, numerator, name): noMessage = None # diplomaticMessageType(-1, "noMessage") warDeclaration = None # diplomaticMessageType(0, "warDeclaration") peaceProposal = None # diplomaticMessageType(1, "peaceProposal") - acceptProposal = None # diplomaticMessageType(2, "acceptProposal") - cancelProposal = None # diplomaticMessageType(3, "cancelProposal") + acceptPeaceProposal = None # diplomaticMessageType(2, "acceptPeaceProposal") + alliesProposal = None # diplomaticMessageType(3, "alliesProposal") + acceptAlliesProposal = None # diplomaticMessageType(4, "acceptAlliesProposal") + endAllies = None # diplomaticMessageType(5, "endAllies") + cancelProposal = None # diplomaticMessageType(6, "cancelProposal") + rejectProposal = None # diplomaticMessageType(7, "rejectProposal") diplomaticMessageType.noMessage = diplomaticMessageType(-1, "noMessage") diplomaticMessageType.warDeclaration = diplomaticMessageType(0, "warDeclaration") diplomaticMessageType.peaceProposal = diplomaticMessageType(1, "peaceProposal") -diplomaticMessageType.acceptProposal = diplomaticMessageType(2, "acceptProposal") -diplomaticMessageType.cancelProposal = diplomaticMessageType(3, "cancelProposal") +diplomaticMessageType.acceptPeaceProposal = diplomaticMessageType(2, "acceptPeaceProposal") +diplomaticMessageType.alliesProposal = diplomaticMessageType(3, "alliesProposal") +diplomaticMessageType.acceptAlliesProposal = diplomaticMessageType(4, "acceptAlliesProposal") +diplomaticMessageType.endAllies = diplomaticMessageType(5, "endAllies") +diplomaticMessageType.cancelProposal = diplomaticMessageType(6, "cancelProposal") +diplomaticMessageType.rejectProposal = diplomaticMessageType(7, "rejectProposal") class diplomaticStatus(Enum): @@ -2730,10 +3198,40 @@ def __init__(self, numerator, name): war = None # diplomaticStatus(0, "war") peace = None # diplomaticStatus(1, "peace") + allied = None # diplomaticStatus(2, "allied") diplomaticStatus.war = diplomaticStatus(0, "war") diplomaticStatus.peace = diplomaticStatus(1, "peace") +diplomaticStatus.allied = diplomaticStatus(2, "allied") + + +class effectsCauseType(Enum): + def __init__(self, numerator, name): + self.name = name + + invalid = None # effectsCauseType(-1, "invalid") + unknown = None # effectsCauseType(0, "unknown") + inherent = None # effectsCauseType(1, "inherent") + tech = None # effectsCauseType(2, "tech") + building = None # effectsCauseType(3, "building") + field = None # effectsCauseType(4, "field") + special = None # effectsCauseType(5, "special") + species = None # effectsCauseType(6, "species") + shipPart = None # effectsCauseType(7, "shipPart") + shipHull = None # effectsCauseType(8, "shipHull") + + +effectsCauseType.invalid = effectsCauseType(-1, "invalid") +effectsCauseType.unknown = effectsCauseType(0, "unknown") +effectsCauseType.inherent = effectsCauseType(1, "inherent") +effectsCauseType.tech = effectsCauseType(2, "tech") +effectsCauseType.building = effectsCauseType(3, "building") +effectsCauseType.field = effectsCauseType(4, "field") +effectsCauseType.special = effectsCauseType(5, "special") +effectsCauseType.species = effectsCauseType(6, "species") +effectsCauseType.shipPart = effectsCauseType(7, "shipPart") +effectsCauseType.shipHull = effectsCauseType(8, "shipHull") class galaxySetupOption(Enum): @@ -2803,26 +3301,28 @@ def __init__(self, numerator, name): maxStructure = None # meterType(10, "maxStructure") maxDefense = None # meterType(11, "maxDefense") maxSupply = None # meterType(12, "maxSupply") - maxTroops = None # meterType(13, "maxTroops") - population = None # meterType(14, "population") - industry = None # meterType(15, "industry") - research = None # meterType(16, "research") - trade = None # meterType(17, "trade") - construction = None # meterType(18, "construction") - happiness = None # meterType(19, "happiness") - capacity = None # meterType(20, "capacity") - secondaryStat = None # meterType(21, "secondaryStat") - fuel = None # meterType(22, "fuel") - shield = None # meterType(23, "shield") - structure = None # meterType(24, "structure") - defense = None # meterType(25, "defense") - supply = None # meterType(26, "supply") - troops = None # meterType(27, "troops") - rebels = None # meterType(28, "rebels") - size = None # meterType(29, "size") - stealth = None # meterType(30, "stealth") - detection = None # meterType(31, "detection") - speed = None # meterType(32, "speed") + maxStockpile = None # meterType(13, "maxStockpile") + maxTroops = None # meterType(14, "maxTroops") + population = None # meterType(15, "population") + industry = None # meterType(16, "industry") + research = None # meterType(17, "research") + trade = None # meterType(18, "trade") + construction = None # meterType(19, "construction") + happiness = None # meterType(20, "happiness") + capacity = None # meterType(21, "capacity") + secondaryStat = None # meterType(22, "secondaryStat") + fuel = None # meterType(23, "fuel") + shield = None # meterType(24, "shield") + structure = None # meterType(25, "structure") + defense = None # meterType(26, "defense") + supply = None # meterType(27, "supply") + stockpile = None # meterType(28, "stockpile") + troops = None # meterType(29, "troops") + rebels = None # meterType(30, "rebels") + size = None # meterType(31, "size") + stealth = None # meterType(32, "stealth") + detection = None # meterType(33, "detection") + speed = None # meterType(34, "speed") meterType.targetPopulation = meterType(0, "targetPopulation") @@ -2838,26 +3338,28 @@ def __init__(self, numerator, name): meterType.maxStructure = meterType(10, "maxStructure") meterType.maxDefense = meterType(11, "maxDefense") meterType.maxSupply = meterType(12, "maxSupply") -meterType.maxTroops = meterType(13, "maxTroops") -meterType.population = meterType(14, "population") -meterType.industry = meterType(15, "industry") -meterType.research = meterType(16, "research") -meterType.trade = meterType(17, "trade") -meterType.construction = meterType(18, "construction") -meterType.happiness = meterType(19, "happiness") -meterType.capacity = meterType(20, "capacity") -meterType.secondaryStat = meterType(21, "secondaryStat") -meterType.fuel = meterType(22, "fuel") -meterType.shield = meterType(23, "shield") -meterType.structure = meterType(24, "structure") -meterType.defense = meterType(25, "defense") -meterType.supply = meterType(26, "supply") -meterType.troops = meterType(27, "troops") -meterType.rebels = meterType(28, "rebels") -meterType.size = meterType(29, "size") -meterType.stealth = meterType(30, "stealth") -meterType.detection = meterType(31, "detection") -meterType.speed = meterType(32, "speed") +meterType.maxStockpile = meterType(13, "maxStockpile") +meterType.maxTroops = meterType(14, "maxTroops") +meterType.population = meterType(15, "population") +meterType.industry = meterType(16, "industry") +meterType.research = meterType(17, "research") +meterType.trade = meterType(18, "trade") +meterType.construction = meterType(19, "construction") +meterType.happiness = meterType(20, "happiness") +meterType.capacity = meterType(21, "capacity") +meterType.secondaryStat = meterType(22, "secondaryStat") +meterType.fuel = meterType(23, "fuel") +meterType.shield = meterType(24, "shield") +meterType.structure = meterType(25, "structure") +meterType.defense = meterType(26, "defense") +meterType.supply = meterType(27, "supply") +meterType.stockpile = meterType(28, "stockpile") +meterType.troops = meterType(29, "troops") +meterType.rebels = meterType(30, "rebels") +meterType.size = meterType(31, "size") +meterType.stealth = meterType(32, "stealth") +meterType.detection = meterType(33, "detection") +meterType.speed = meterType(34, "speed") class planetEnvironment(Enum): @@ -2943,11 +3445,49 @@ def __init__(self, numerator, name): industry = None # resourceType(0, "industry") trade = None # resourceType(1, "trade") research = None # resourceType(2, "research") + stockpile = None # resourceType(3, "stockpile") resourceType.industry = resourceType(0, "industry") resourceType.trade = resourceType(1, "trade") resourceType.research = resourceType(2, "research") +resourceType.stockpile = resourceType(3, "stockpile") + + +class roleType(Enum): + def __init__(self, numerator, name): + self.name = name + + host = None # roleType(0, "host") + clientTypeModerator = None # roleType(1, "clientTypeModerator") + clientTypePlayer = None # roleType(2, "clientTypePlayer") + clientTypeObserver = None # roleType(3, "clientTypeObserver") + galaxySetup = None # roleType(4, "galaxySetup") + + +roleType.host = roleType(0, "host") +roleType.clientTypeModerator = roleType(1, "clientTypeModerator") +roleType.clientTypePlayer = roleType(2, "clientTypePlayer") +roleType.clientTypeObserver = roleType(3, "clientTypeObserver") +roleType.galaxySetup = roleType(4, "galaxySetup") + + +class ruleType(Enum): + def __init__(self, numerator, name): + self.name = name + + invalid = None # ruleType(-1, "invalid") + toggle = None # ruleType(0, "toggle") + int = None # ruleType(1, "int") + double = None # ruleType(2, "double") + string = None # ruleType(3, "string") + + +ruleType.invalid = ruleType(-1, "invalid") +ruleType.toggle = ruleType(0, "toggle") +ruleType.int = ruleType(1, "int") +ruleType.double = ruleType(2, "double") +ruleType.string = ruleType(3, "string") class shipPartClass(Enum): @@ -3188,15 +3728,24 @@ def getGalaxySetupData(): return GalaxySetupData() -def getHullType(string): +def getGameRules(): """ - Returns the ship hull (HullType) with the indicated name (string). + Returns the game rules manager, which can be used to look up the names (string) of rules are defined with what type (boolean / toggle, int, double, string), and what values the rules have in the current game. + + :rtype: GameRules + """ + return GameRules() + + +def getShipHull(string): + """ + Returns the ship hull with the indicated name (string). :param string: :type string: str - :rtype: hullType + :rtype: shipHull """ - return hullType() + return shipHull() def getOptionsDBOptionBool(string): @@ -3243,15 +3792,24 @@ def getOptionsDBOptionStr(string): return object() -def getPartType(string): +def getOrders(): + """ + Returns the orders the client empire has issued (OrderSet). + + :rtype: OrderSet + """ + return OrderSet() + + +def getShipPart(string): """ - Returns the ship part (PartType) with the indicated name (string). + Returns the ShipPart with the indicated name (string). :param string: :type string: str - :rtype: partType + :rtype: shipPart """ - return partType() + return shipPart() def getSaveStateString(): @@ -3363,6 +3921,19 @@ def issueAggressionOrder(number, boolean): return int() +def issueAllowStockpileProductionOrder(number, boolean): + """ + Orders the item on the production queue at index queueIndex (int) to be enabled (or disabled) to use the imperial stockpile. Returns 1 (int) on success or 0 (int) on failure if the queue index is less than 0 or greater than the largest indexed item on the queue. + + :param number: + :type number: int + :param boolean: + :type boolean: bool + :rtype: int + """ + return int() + + def issueBombardOrder(number1, number2): """ :param number1: @@ -3547,7 +4118,7 @@ def issueInvadeOrder(number1, number2): def issueNewFleetOrder(string, number): """ - Orders a new fleet to be created with the indicated name (string) and containing the indicated shipIDs (IntVec). The ships must be located in the same system and must all be owned by this player. Returns 1 (int) on success or 0 (int) on failure due to one of the noted conditions not being met. + Orders a new fleet to be created with the indicated name (string) and containing the indicated shipIDs (IntVec). The ships must be located in the same system and must all be owned by this player. Returns the new fleets id (int) on success or 0 (int) on failure due to one of the noted conditions not being met. :param string: :type string: str @@ -3558,6 +4129,19 @@ def issueNewFleetOrder(string, number): return int() +def issuePauseProductionOrder(number, boolean): + """ + Orders the item on the production queue at index queueIndex (int) to be paused (or unpaused). Returns 1 (int) on success or 0 (int) on failure if the queue index is less than 0 or greater than the largest indexed item on the queue. + + :param number: + :type number: int + :param boolean: + :type boolean: bool + :rtype: int + """ + return int() + + def issueRenameOrder(number, string): """ Orders the renaming of the object with indicated objectID (int) to the new indicated name (string). Returns 1 (int) on success or 0 (int) on failure due to this AI player not being able to rename the indicated object (which this player must fully own, and which must be a fleet, ship or planet). diff --git a/default/python/AI/freeorion_tools/__init__.py b/default/python/AI/freeorion_tools/__init__.py index 38e116c6c8e..d96c7df40d3 100644 --- a/default/python/AI/freeorion_tools/__init__.py +++ b/default/python/AI/freeorion_tools/__init__.py @@ -1,5 +1,4 @@ -from _freeorion_tools import * -from extend_freeorion_AI_interface import patch_interface -from interactive_shell import handle_debug_chat -from timers import AITimer -from handlers import init_handlers +from ._freeorion_tools import * +from .extend_freeorion_AI_interface import patch_interface +from .interactive_shell import handle_debug_chat +from .timers import AITimer diff --git a/default/python/AI/freeorion_tools/_freeorion_tools.py b/default/python/AI/freeorion_tools/_freeorion_tools.py index fc6903a16cc..f6d4f550872 100644 --- a/default/python/AI/freeorion_tools/_freeorion_tools.py +++ b/default/python/AI/freeorion_tools/_freeorion_tools.py @@ -1,10 +1,17 @@ # This Python file uses the following encoding: utf-8 -import re -import logging +from collections.abc import Mapping +from io import StringIO -import freeOrionAIInterface as fo # pylint: disable=import-error +import cProfile +import logging +import pstats +import re +import traceback from functools import wraps +from logging import debug, error +from aistate_interface import get_aistate +import freeOrionAIInterface as fo # pylint: disable=import-error # color wrappers for chat: RED = '%s' @@ -16,6 +23,16 @@ def dict_from_map(thismap): return {el.key(): el.data() for el in thismap} +def dict_from_map_recursive(thismap): + retval = {} + try: + for el in thismap: + retval[el.key()] = dict_from_map_recursive(el.data()) + return retval + except Exception: + return dict_from_map(thismap) + + def get_ai_tag_grade(tag_list, tag_type): """ Accepts a list of string tags and a tag_type (like 'WEAPONS'). @@ -25,14 +42,15 @@ def get_ai_tag_grade(tag_list, tag_type): X is most commonly (but not necessarily) one of [NO, BAD, AVERAGE, GOOD, GREAT, ULTIMATE] If no matching tags, returns empty string (which for most types should be considered equivalent to AVERAGE) """ - for tag in [tag for tag in tag_list if tag.count("_") > 0]: + for tag in [tag_ for tag_ in tag_list if tag_.count("_") > 0]: parts = tag.split("_", 1) if parts[1] == tag_type.upper(): return parts[0] return "" -def UserString(label, default=None): # this name left with C naming style for compatibility with translation assistance procedures #pylint: disable=invalid-name +# this name left with C naming style for compatibility with translation assistance procedures +def UserString(label, default=None): # pylint: disable=invalid-name """ A translation assistance tool is intended to search for this method to identify translatable strings. @@ -49,7 +67,8 @@ def UserString(label, default=None): # this name left with C naming style for c return table_string -def UserStringList(label): # this name left with C naming style for compatibility with translation assistance procedures #pylint: disable=invalid-name +# this name left with C naming style for compatibility with translation assistance procedures +def UserStringList(label): # pylint: disable=invalid-name """ A translation assistance tool is intended to search for this method to identify translatable strings. @@ -64,7 +83,7 @@ def tech_is_complete(tech): """ Return if tech is complete. """ - return fo.getEmpire().getTechStatus(tech) == fo.techStatus.complete + return fo.getEmpire().techResearched(tech) def ppstring(foo): @@ -76,7 +95,7 @@ def ppstring(foo): if isinstance(foo, list): return "[" + ",".join(map(ppstring, foo)) + "]" elif isinstance(foo, dict): - return "{" + ",".join([ppstring(k) + ":" + ppstring(v) for k, v in foo.iteritems()]) + "}" + return "{" + ",".join([ppstring(k) + ":" + ppstring(v) for k, v in foo.items()]) + "}" elif isinstance(foo, tuple): return "(" + ",".join(map(ppstring, foo)) + ")" elif isinstance(foo, set) or isinstance(foo, frozenset): @@ -87,17 +106,6 @@ def ppstring(foo): return str(foo) -def chat_on_error(function): - @wraps(function) - def wrapper(*args, **kw): - try: - return function(*args, **kw) - except Exception as e: - logging.getLogger().exception(e) - raise - return wrapper - - class ConsoleLogHandler(logging.Handler): """A log handler to send errors to the console. """ def emit(self, record): @@ -115,9 +123,10 @@ def emit(self, record): except (KeyboardInterrupt, SystemExit): raise # Hide errors from within the ConsoleLogHandler - except: + except: # noqa: E722 self.handleError(record) + # Create the log handler, format it and attach it to the root logger console_handler = ConsoleLogHandler() @@ -132,7 +141,7 @@ def emit(self, record): def remove_tags(message): """Remove tags described in Font.h from message.""" - expr = '' + expr = r'' return re.sub(expr, '', message) @@ -142,51 +151,76 @@ def chat_human(message): Log message cleared form tags. """ human_id = [x for x in fo.allPlayerIDs() if fo.playerIsHost(x)][0] + message = str(message) fo.sendChatMessage(human_id, message) - print "\nChat Message to human: %s" % remove_tags(message) + debug("Chat Message to human: %s", remove_tags(message)) -def cache_by_session(function): +def cache_for_session(func): """ - Cache a function value by session. + Cache a function value for current session. + Wraps only functions with hashable arguments. + Use this only if the called function return value is constant throughout the game. """ _cache = {} - @wraps(function) + @wraps(func) def wrapper(*args, **kwargs): - key = (function, args, tuple(kwargs.items())) + key = (func, args, tuple(kwargs.items())) if key in _cache: return _cache[key] - res = function(*args, **kwargs) + res = func(*args, **kwargs) _cache[key] = res return res wrapper._cache = _cache return wrapper -def cache_by_turn(function): +def cache_for_current_turn(func): """ - Cache a function value by turn, stored in foAIstate so also provides a history that may be analysed. The cache - is keyed by the original function name. Wraps only functions without arguments. - Cache result is stored in savegame, will crash with picle error if result contains any boost object. + Cache a function value updated each turn. + + The cache is non-persistent through loading a game. + Wraps only functions with hashable arguments. """ - # avoid circular import - import FreeOrionAI as foAI + _cache = {} - @wraps(function) + @wraps(func) + def wrapper(*args, **kwargs): + key = (func, args, tuple(kwargs.items())) + this_turn = fo.currentTurn() + if key in _cache and _cache[key][0] == this_turn: + return _cache[key][1] + res = func(*args, **kwargs) + _cache[key] = (this_turn, res) + return res + wrapper._cache = _cache + return wrapper + + +def cache_by_turn_persistent(func): + """ + Cache a function value by turn, persistent through loading a game. + + It will also provides a history that may be analysed. + The cache is keyed by the original function name. It only wraps functions without arguments. + + As the result is stored in AIstate, its type must be trusted by the savegame_codec module. + """ + @wraps(func) def wrapper(): - if foAI.foAIstate is None: - return function() + if get_aistate() is None: + return func() else: - cache = foAI.foAIstate.misc.setdefault('caches', {}).setdefault(function.__name__, {}) + cache = get_aistate().misc.setdefault('caches', {}).setdefault(func.__name__, {}) this_turn = fo.currentTurn() - return cache[this_turn] if this_turn in cache else cache.setdefault(this_turn, function()) + return cache[this_turn] if this_turn in cache else cache.setdefault(this_turn, func()) return wrapper def dict_to_tuple(dic): - return tuple(dic.iteritems()) + return tuple(dic.items()) def tuple_to_dict(tup): @@ -195,6 +229,180 @@ def tuple_to_dict(tup): except TypeError: try: return {k: v for k, v in [tup]} - except: - print >> sys.stderr, "Can't convert tuple_list to dict: ", tup + except: # noqa: E722 + error("Can't convert tuple_list to dict: %s", tup) return {} + + +def profile(func): + + @wraps(func) + def wrapper(*args, **kwargs): + pr = cProfile.Profile() + pr.enable() + retval = func(*args, **kwargs) + pr.disable() + s = StringIO() + sortby = 'cumulative' + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + debug(s.getvalue()) + return retval + + return wrapper + + +@cache_for_current_turn +def get_partial_visibility_turn(obj_id): + """Return the last turn an object had at least partial visibility. + + :type obj_id: int + :return: Last turn an object had at least partial visibility, -9999 if never + :rtype: int + """ + visibility_turns_map = fo.getUniverse().getVisibilityTurnsMap(obj_id, fo.empireID()) + return visibility_turns_map.get(fo.visibility.partial, -9999) + + +class ReadOnlyDict(Mapping): + """A dict that offers only read access. + + Note that if the values of the ReadOnlyDict are mutable, + then those objects may actually be changed. + + It is strongly advised to store only immutable objects. + A slight protection is offered by checking for hashability of the values. + + Example usage: + my_dict = ReadOnlyDict({1:2, 3:4}) + print my_dict[1] + for k in my_dict: + print my_dict.get(k, -1) + for k in my_dict.keys(): + print my_dict[k] + for k, v in my_dict.iteritems(): + print k, v + my_dict[5] = 4 # throws TypeError + del my_dict[1] # throws TypeError + + Implementation note: + + The checks that values are hashable is the main difference from the built-in types.MappingProxyType. + MappingProxyType has slightly different signature and cannot be inherited. + """ + + def __init__(self, *args, **kwargs): + self._data = dict(*args, **kwargs) + for k, v in self._data.items(): + try: + hash(v) + except TypeError: + error("Tried to store a non-hashable value in ReadOnlyDict") + raise + + def __getitem__(self, item): + return self._data[item] + + def __iter__(self): + return iter(self._data) + + def __len__(self): + return len(self._data) + + def __str__(self): + return str(self._data) + + +def dump_universe(): + """Dump the universe but not more than once per turn.""" + cur_turn = fo.currentTurn() + + if (not hasattr(dump_universe, "last_dump") or + dump_universe.last_dump < cur_turn): + dump_universe.last_dump = cur_turn + fo.getUniverse().dump() # goes to debug logger + + +class LogLevelSwitcher: + """A context manager class which controls the log level within its scope. + + Example usage: + logging.getLogger().setLevel(logging.INFO) + + debug("Some message") # not printed because of log level + with LogLevelSwitcher(logging.DEBUG): + debug("foo") # printed because we set to DEBUG level + + debug("baz") # not printed, we are back to INFO level + """ + def __init__(self, log_level): + self.target_log_level = log_level + self.old_log_level = 0 + + def __enter__(self): + self.old_log_level = logging.getLogger().level + logging.getLogger().setLevel(self.target_log_level) + + def __exit__(self, exc_type, exc_val, exc_tb): + logging.getLogger().setLevel(self.old_log_level) + + +def with_log_level(log_level): + """A decorator to set a specific logging level for the function call. + + This decorator is useful to selectively activate debugging for a specific function + while the rest of the code base only logs at a higher level. + + If functions are called within the decorated function, then those will be + executed with the same logging level. However, if those functions use this + decorator as well, then that logging level will be respected for its scope. + + Example usage: + @log_with_specific_log_level(logging.DEBUG) + def foo(): + debug("debug stuff") + """ + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + with LogLevelSwitcher(log_level): + return func(*args, **kwargs) + + return wrapper + return decorator + + +def assertion_fails(cond, msg="", logger=logging.error): + """ + Check if condition fails and if so, log a traceback but raise no Exception. + + This is a useful functions for generic sanity checks and may be used to + replace manual error logging with more context provided by the traceback. + + :param bool cond: condition to be asserted + :param str msg: additional info to be logged + :param func logger: may be used to override default log level (error) + :return: True if assertion failed, otherwise false. + """ + if cond: + return False + + if msg: + header = "Assertion failed: %s." % msg + else: + header = "Assertion failed!" + stack = traceback.extract_stack()[:-1] # do not log this function + logger("%s Traceback (most recent call last): %s", header, + ''.join(traceback.format_list(stack))) + return True + + +@cache_for_session +def get_species_tag_grade(species_name, tag_type): + if not species_name: + return "" + species = fo.getSpecies(species_name) + if assertion_fails(species is not None): + return "" + + return get_ai_tag_grade(species.tags, tag_type) diff --git a/default/python/AI/freeorion_tools/charts_handler.py b/default/python/AI/freeorion_tools/charts_handler.py deleted file mode 100644 index fd5624fc0f2..00000000000 --- a/default/python/AI/freeorion_tools/charts_handler.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -This module contain code that used in charting. -Do not modify output because charting code is relay on it. -""" -import freeOrionAIInterface as fo -from PlanetUtilsAI import get_capital -from ResearchAI import get_research_index -from character.character_strings_module import get_trait_name_aggression - - -def charting_text(): - import FreeOrionAI as foAI # avoid circular imports - universe = fo.getUniverse() - empire = fo.getEmpire() - planet_id = get_capital() - planet = None - if planet_id is not None: - planet = universe.getPlanet(planet_id) - aggression_name_local = get_trait_name_aggression(foAI.foAIstate.character) - - print ("Generating Orders") - print ("EmpireID: {empire.empireID}" - " Name: {empire.name}_{empire.empireID}_pid:{p_id}_{p_name}RIdx_{res_idx}_{aggression}" - " Turn: {turn}").format(empire=empire, p_id=fo.playerID(), p_name=fo.playerName(), - res_idx=get_research_index(), turn=fo.currentTurn(), - aggression=aggression_name_local.capitalize()) - print "EmpireColors: {0.colour.r} {0.colour.g} {0.colour.b} {0.colour.a}".format(empire) - if planet: - print "CapitalID: " + str(planet_id) + " Name: " + planet.name + " Species: " + planet.speciesName - else: - print "CapitalID: None Currently Name: None Species: None " diff --git a/default/python/AI/freeorion_tools/extend_freeorion_AI_interface.py b/default/python/AI/freeorion_tools/extend_freeorion_AI_interface.py index 0b450ed8814..f0e093d5e91 100644 --- a/default/python/AI/freeorion_tools/extend_freeorion_AI_interface.py +++ b/default/python/AI/freeorion_tools/extend_freeorion_AI_interface.py @@ -40,11 +40,13 @@ def planet_repr(): """ import inspect import os +from itertools import zip_longest + from functools import wraps -from itertools import izip_longest +from logging import debug import freeOrionAIInterface as fo -from _freeorion_tools import dict_from_map +from ._freeorion_tools import dict_from_map, dict_from_map_recursive PLANET = 'P' @@ -61,6 +63,14 @@ def wrapper(*args): return wrapper +def to_dict_recursive(method): + @wraps(method) + def wrapper(*args): + return dict_from_map_recursive(method(*args)) + + return wrapper + + def to_str(prefix, id, name): return '{}_{}<{}>'.format(prefix, id, name) @@ -68,6 +78,8 @@ def to_str(prefix, id, name): def patch_interface(): fo.universe.getVisibilityTurnsMap = to_dict(fo.universe.getVisibilityTurnsMap) fo.empire.supplyProjections = to_dict(fo.empire.supplyProjections) + fo.GameRules.getRulesAsStrings = to_dict(fo.GameRules.getRulesAsStrings) + fo.universe.statRecords = to_dict_recursive(fo.universe.statRecords) fo.to_str = to_str @@ -124,10 +136,10 @@ def logger(callable_object, argument_wrappers=None): :return: """ def inner(*args, **kwargs): - arguments = [str(wrapper(arg) if wrapper else arg) for arg, wrapper in izip_longest(args, argument_wrappers)] + arguments = [str(wrapper(arg) if wrapper else arg) for arg, wrapper in zip_longest(args, argument_wrappers)] arguments.extend('%s=%s' % item for item in kwargs.items()) res = callable_object(*args, **kwargs) frame = inspect.currentframe().f_back - print "%s:%s %s(%s) -> %s" % (os.path.basename(frame.f_code.co_filename), frame.f_lineno, callable_object.__name__, ', '.join(arguments), res) + debug("%s:%s %s(%s) -> %s", os.path.basename(frame.f_code.co_filename), frame.f_lineno, callable_object.__name__, ', '.join(arguments), res) return res return inner diff --git a/default/python/AI/freeorion_tools/interactive_shell.py b/default/python/AI/freeorion_tools/interactive_shell.py index 2bdafbbe6e8..b6a12592466 100644 --- a/default/python/AI/freeorion_tools/interactive_shell.py +++ b/default/python/AI/freeorion_tools/interactive_shell.py @@ -1,9 +1,11 @@ +from io import StringIO + import freeOrionAIInterface as fo from freeorion_tools import chat_human from code import InteractiveInterpreter -from cStringIO import StringIO +import logging import sys - +from logging import error interpreter = InteractiveInterpreter({'fo': fo}) debug_mode = False @@ -29,7 +31,7 @@ def handle_debug_chat(sender, message): chat_human("exiting debug mode") debug_mode = False elif debug_mode: - print '>', message, + print('>', message, end='') is_debug_chat = True out, err = [x.strip('\n') for x in shell(message)] if out: @@ -41,19 +43,19 @@ def handle_debug_chat(sender, message): try: player_id = int(message[5:].strip()) except ValueError as e: - print e + error(e) chat_human(str(e)) return True if player_id == fo.playerID(): debug_mode = True initial_code = [ - 'import FreeOrionAI as foAI', + 'from aistate_interface import get_aistate', ] # add some variables to scope: (name, help text, value) scopes_variable = ( - ('ai', 'aistate', 'foAI.foAIstate'), + ('ai', 'aistate', 'get_aistate()'), ('u', 'universe', 'fo.getUniverse()'), ('e', 'empire', 'fo.getEmpire()'), ) @@ -88,8 +90,13 @@ def shell(msg): sys.stdout = StringIO() sys.stderr = StringIO() + handler = logging.StreamHandler(sys.stdout) + logging.getLogger().addHandler(handler) + interpreter.runsource(msg) + logging.getLogger().removeHandler(handler) + sys.stdout.seek(0) out = sys.stdout.read() sys.stderr.seek(0) diff --git a/default/python/AI/freeorion_tools/timers.py b/default/python/AI/freeorion_tools/timers.py index 77fda21b344..1e372b299d9 100644 --- a/default/python/AI/freeorion_tools/timers.py +++ b/default/python/AI/freeorion_tools/timers.py @@ -8,9 +8,12 @@ # setup module options = get_option_dict() -USE_TIMERS = check_bool(options[TIMERS_USE_TIMERS]) -DUMP_TO_FILE = check_bool(options[TIMERS_TO_FILE]) -TIMERS_DIR = os.path.join(fo.getUserDataDir(), DEFAULT_SUB_DIR, options[TIMERS_DUMP_FOLDER]) +try: + USE_TIMERS = check_bool(options[TIMERS_USE_TIMERS]) + DUMP_TO_FILE = check_bool(options[TIMERS_TO_FILE]) + TIMERS_DIR = os.path.join(fo.getUserDataDir(), DEFAULT_SUB_DIR, options[TIMERS_DUMP_FOLDER]) +except KeyError: + USE_TIMERS = False def make_header(*args): @@ -28,7 +31,7 @@ def _get_timers_dir(): return TIMERS_DIR -class DummyTimer(object): +class DummyTimer: """ Dummy timer to be used if timers are disabled. """ @@ -83,7 +86,7 @@ def _write(self, text): mode = 'w' # empty file else: mode = 'a' - with open(unicode(self.log_name, 'utf-8'), mode) as f: + with open(self.log_name, mode) as f: f.write(text) f.write('\n') diff --git a/default/python/AI/pathfinding.py b/default/python/AI/pathfinding.py new file mode 100644 index 00000000000..9d20f5f24b7 --- /dev/null +++ b/default/python/AI/pathfinding.py @@ -0,0 +1,271 @@ +from heapq import heappush, heappop +from collections import namedtuple +from logging import warning, error + +from aistate_interface import get_aistate +import freeOrionAIInterface as fo +import PlanetUtilsAI + +from AIDependencies import INVALID_ID +from EnumsAI import MissionType +from turn_state import state +from freeorion_tools import cache_for_current_turn, chat_human, get_partial_visibility_turn + +_DEBUG_CHAT = False +_ACCEPTABLE_DETOUR_LENGTH = 2000 +path_information = namedtuple('path_information', ['distance', 'fuel', 'path']) + + +# cache this so that boost python does not need to make a new copy every time this info is needed in a turn. +@cache_for_current_turn +def _get_unobstructed_systems(): + return fo.getEmpire().supplyUnobstructedSystems + + +def _info_string(path_info): + return "dist %.1f, path %s" % (path_info.distance, PlanetUtilsAI.sys_name_ids(path_info.path)) + + +# Note that this can cover departure from uncontested systems as well as from contested systems where our forces +# arrived first (and those forces may or may not be the forces for whom the pathfinding is being done) +def _more_careful_travel_starlane_func(c, d): + return c in _get_unobstructed_systems() + + +# In comparison to _more_careful_travel_starlane_func, this can also allow passage through contested +# systems where our forces already present there (which may or may not be the forces for whome the pathfinding +# is being done) arrived second but are currently preserving travel along the starlane of interest. +def _somewhat_careful_travel_starlane_func(c, d): + return any((c in _get_unobstructed_systems(), + fo.getEmpire().preservedLaneTravel(c, d))) + + +# For some activities like scouting, we may want to allow an extra bit of risk from routing through a system which +# has all its exits necessarily marked as restricted simply because it has never been partially visible +def _risky_travel_starlane_func(c, d): + return any((_somewhat_careful_travel_starlane_func(c, d), + get_partial_visibility_turn(c) <= 0)) + + +def _may_travel_anywhere(*args, **kwargs): + return True + + +_STARLANE_TRAVEL_FUNC_MAP = { + MissionType.EXPLORATION: _risky_travel_starlane_func, + MissionType.OUTPOST: _more_careful_travel_starlane_func, + MissionType.COLONISATION: _more_careful_travel_starlane_func, + MissionType.INVASION: _somewhat_careful_travel_starlane_func, + MissionType.MILITARY: _may_travel_anywhere, + MissionType.SECURE: _may_travel_anywhere, +} + + +def find_path_with_resupply(start, target, fleet_id, minimum_fuel_at_target=0, mission_type_override=None): + """ + :param start: start system id + :type start: int + :param target: target system id + :type target: int + :param fleet_id: fleet to find the path for + :type fleet_id: int + :param minimum_fuel_at_target: optional - if specified, only accept paths that leave the + fleet with at least this much fuel left at the target system + :type minimum_fuel_at_target: int + :param mission_type_override: optional - use the specified mission type, rather than the fleet's + current mission type, for pathfinding routing choices + :type mission_type_override: MissionType + :return: shortest possible path including resupply-detours in the form of system ids + including both start and target system + :rtype: path_information + """ + + universe = fo.getUniverse() + fleet = universe.getFleet(fleet_id) + if not fleet: + return None + empire = fo.getEmpire() + supplied_systems = set(empire.fleetSupplyableSystemIDs) + start_fuel = fleet.maxFuel if start in supplied_systems else fleet.fuel + + # We have 1 free jump from supplied system into unsupplied systems. + # Thus, the target system must be at most maxFuel + 1 jumps away + # in order to reach the system under standard conditions. + # In some edge cases, we may have more supply here than what the + # supply graph suggests. For example, we could have recently refueled + # in a system that is now blockaded by an enemy or we have found + # the refuel special. + target_distance_from_supply = -min(state.get_system_supply(target), 0) + if (fleet.maxFuel + 1 < (target_distance_from_supply + minimum_fuel_at_target) and + universe.jumpDistance(start, target) > (start_fuel - minimum_fuel_at_target)): + # can't possibly reach this system with the required fuel + return None + + mission_type = (mission_type_override if mission_type_override is not None else + get_aistate().get_fleet_mission(fleet_id)) + may_travel_starlane_func = _STARLANE_TRAVEL_FUNC_MAP.get(mission_type, _more_careful_travel_starlane_func) + + path_info = find_path_with_resupply_generic(start, target, start_fuel, fleet.maxFuel, + lambda s: s in supplied_systems, + minimum_fuel_at_target, + may_travel_starlane_func=may_travel_starlane_func) + + if not _DEBUG_CHAT: + return path_info + + if may_travel_starlane_func != _may_travel_anywhere: + risky_path = find_path_with_resupply_generic(start, target, start_fuel, fleet.maxFuel, + lambda s: s in supplied_systems, + minimum_fuel_at_target) + if path_info and may_travel_starlane_func == _risky_travel_starlane_func: + safest_path = find_path_with_resupply_generic(start, target, start_fuel, fleet.maxFuel, + lambda s: s in supplied_systems, + minimum_fuel_at_target, + may_travel_starlane_func=_more_careful_travel_starlane_func) + if safest_path and path_info.distance < safest_path.distance: + message = "(Scout?) Fleet %d chose somewhat risky path %s instead of safe path %s" % ( + fleet_id, _info_string(path_info), _info_string(risky_path)) + chat_human(message) + + if path_info and risky_path and risky_path.distance < path_info.distance: + + message = "Fleet %d chose safer path %s instead of risky path %s" % ( + fleet_id, _info_string(path_info), _info_string(risky_path)) + chat_human(message) + + return path_info + + +# TODO Add a list of systems as parameter that are forbidden for search (e.g. blocked by monster) +# TODO Allow short idling in system to use stationary refuel mechanics in unsupplied systems if we +# are very close to the target system but can't reach it due to fuel constraints +# TODO Consider additional optimizations: +# - Check if universe.shortestPath complies with fuel mechanics before running +# pathfinder (seemingly not worth it - we have exact heuristic in that case) +# - Cut off branches that can't reach target due to supply distance +# - For large graphs, check existence of a path on a simplified graph (use single node to represent supply clusters) +# - For large graphs, check if there are any must-visit nodes (e.g. only possible resupplying system), +# then try to find the shortest path between those and start/target. +def find_path_with_resupply_generic(start, target, start_fuel, max_fuel, system_suppliable_func, + minimum_fuel_at_target=0, + may_travel_system_func=None, + may_travel_starlane_func=None): + """Find the shortest possible path between two systems that complies with FreeOrion fuel mechanics. + + If the fleet can travel the shortest possible path between start and target system, then return that path. + Otherwise, find the shortest possible detour including refueling. + + The core algorithm is a modified A* with the universe.shortestPathDistance as heuristic. + While searching for a path, keep track of the fleet's fuel. Compared to standard A*/dijkstra, + nodes are locked only for a certain minimum level of fuel - if a longer path yields a higher fuel + level at a given system, then that path is considered as possible detour for refueling and added to the queue. + + :param start: start system id + :type start: int + :param target: target system id + :type target: int + :param start_fuel: starting fuel of the fleet + :type start_fuel: float + :param max_fuel: max fuel of the fleet + :type max_fuel: float + :param system_suppliable_func: boolean function with one int param s, specifying if a system s provides fleet supply + :type system_suppliable_func: (int) -> bool + :param minimum_fuel_at_target: optional - if specified, only accept paths that leave the + fleet with at least this much fuel left at the target system + :type minimum_fuel_at_target: int + :param may_travel_system_func: optional - boolean function with one int param, s, specifying if + a system s is OK to travel through + :type may_travel_system_func: (int) -> bool + :param may_travel_starlane_func: optional - boolean function with 2 int params c, d, specifying if + a starlane from c to d is OK to travel through + :type may_travel_starlane_func: (int, int) -> bool + :return: shortest possible path including resupply-detours in the form of system ids + including both start and target system + :rtype: path_information + """ + + universe = fo.getUniverse() + empire_id = fo.empireID() + + if start == INVALID_ID or target == INVALID_ID: + warning("Requested path between invalid systems.") + return None + + # make sure the minimum fuel at target is realistic + if minimum_fuel_at_target < 0: + error("Requested negative fuel at target.") + return None + + # make sure the minimum fuel at target is realistic + if max_fuel < minimum_fuel_at_target: + return None + + # make sure the target is connected to the start system + shortest_possible_path_distance = universe.shortestPathDistance(start, target) + if shortest_possible_path_distance == -1: + warning("Requested path between disconnected systems, doing nothing.") + return None + + if may_travel_system_func is None: + may_travel_system_func = _may_travel_anywhere + if may_travel_starlane_func is None: + may_travel_starlane_func = _may_travel_anywhere + + # initialize data structures + path_cache = {} + queue = [] + + # add starting system to queue + heappush(queue, (shortest_possible_path_distance, path_information(distance=0, fuel=start_fuel, path=(start,)))) + + while queue: + # get next system with path information + (_, path_info) = heappop(queue) + current = path_info.path[-1] + + # did we reach the target? + if current == target: + # do we satisfy fuel constraints? + if path_info.fuel < minimum_fuel_at_target: + continue + return path_info + + # add information about how we reached here to the cache + path_cache.setdefault(current, []).append(path_info) + + # check if we have enough fuel to travel to neighbors + if path_info.fuel < 1: + continue + + # add neighboring systems to the queue if the resulting path + # is either shorter or offers more fuel than the other paths + # which we already found to those systems + for neighbor in universe.getImmediateNeighbors(current, empire_id): + # A system we have never had partial vis for will count as fully blockaded for us, but perhaps if + # we are scouting we might want to be able to route a path through it anyway. + if any((not may_travel_starlane_func(current, neighbor), + neighbor != target and not may_travel_system_func(neighbor))): + continue + new_dist = path_info.distance + universe.linearDistance(current, neighbor) + new_fuel = (max_fuel if (system_suppliable_func(neighbor) or system_suppliable_func(current)) else + path_info.fuel - 1) + + # check if the node is already closed, i.e. a path was already found which both is shorter and offers + # more fuel. Priority queueing should ensure that all previously found paths here are shorter but check + # it anyway to be sure... + if any((dist <= new_dist and fuel >= new_fuel) for dist, fuel, _ in path_cache.get(neighbor, [])): + continue + + # calculate the new distance prediction, i.e. the A* heuristic. + predicted_distance = new_dist + universe.shortestPathDistance(neighbor, target) + + # Ignore paths that are much longer than the shortest possible path + if predicted_distance > max(2*shortest_possible_path_distance, + shortest_possible_path_distance + _ACCEPTABLE_DETOUR_LENGTH): + continue + + # All checks passed, consider this path for further pathfinding + heappush(queue, (predicted_distance, path_information(new_dist, new_fuel, path_info.path + (neighbor,)))) + + # no path exists, not even if we refuel on the way + return None diff --git a/default/python/AI/savegame_codec/__init__.py b/default/python/AI/savegame_codec/__init__.py new file mode 100644 index 00000000000..7e22c811919 --- /dev/null +++ b/default/python/AI/savegame_codec/__init__.py @@ -0,0 +1,46 @@ +"""This package provides json-based encoding and decoding for use in FreeOrion AI savegames. + +The encoding is json-based with custom prefixes to support some objects +and dictionary keys of types which are not supported in standard json. + +The decoder will only load trusted classes as defined in _definitions.py, +if an unknown/untrusted object is encountered, it will raise a InvalidSaveGameException. + +When class instances are loaded, the __setstate__ method will be invoked if available or +the __dict__ content will be set directly. It is the responsibility of each trusted class +to define a __setstate__ method to verify and possibly sanitize the passed data. +The __setstate__ method is expected to raise an Exception if invalid data was passed. + +In contrast to standard library json module, the type of dict keys (e.g. int) is preserved: + import json + json_string = json.dumps({1: 2}) ==> '{"1": 2}' + json.loads(json_string) ==> {"1": 2} + + import savegame_codec # load package + json_string = savegame_codec.encode({1: 2}) ==> '{"__INT__1": 2'}' + savegame_codec.decode(json_string) ==> {1: 2} + + +Public functions: + encode - encode a python object to an unambigious string representation + decode - decode and interpret a json string to retreive python objects + build_savegame_string - encode the AIstate and return the zlib-compressed result + load_savegame_string - decode a savegame string generated by build_savegame_string + +Defined Exceptions: + CanNotSaveGameException - raised when an object could not be encoded + InvalidSaveGameException - raised when a savegame string is not trusted or could not be decoded + +Example usage: + # saving the game + import savegame_codec # load package + savegame_string = savegame_codec.build_savegame_string() + + # loading the game + import savegame_codec + aistate = savegame_codec.load_savegame_string(savegame_string) +""" + +from ._decoder import decode, load_savegame_string +from ._definitions import CanNotSaveGameException, InvalidSaveGameException +from ._encoder import encode, build_savegame_string diff --git a/default/python/AI/savegame_codec/_decoder.py b/default/python/AI/savegame_codec/_decoder.py new file mode 100644 index 00000000000..aa02a348835 --- /dev/null +++ b/default/python/AI/savegame_codec/_decoder.py @@ -0,0 +1,208 @@ +"""This module defines the decoding for the FreeOrion AI savegames. + +The decoder is subclassed from the standard library json decoder and +uses its string parsing. However, the resulting objects will be interpreted +differently according to the encoding used in FreeOrion AI savegames. + +The decoder will only load trusted classes as defined in _definitions.py, +if an unknown/untrusted object is encountered, it will raise a InvalidSaveGameException. + +When classes are loaded, their __setstate__ method will be invoked if available or +the __dict__ content will be set directly. It is the responsiblity of the trusted classes +to provide a __setstate__ method to verify and possibly sanitize the content of the passed state. +""" +import binascii +import json +from typing import Union + +import EnumsAI +from AIstate import AIstate +from freeorion_tools import profile + +from ._definitions import (ENUM_PREFIX, FALSE, FLOAT_PREFIX, INT_PREFIX, InvalidSaveGameException, NONE, PLACEHOLDER, + SET_PREFIX, TRUE, TUPLE_PREFIX, trusted_classes, ) + + +class SaveDecompressException(Exception): + """ + Exception class for troubles with decompressing save game string. + """ + pass + + +@profile +def load_savegame_string(string: Union[str, bytes]) -> AIstate: + """ + :raises: SaveDecompressException, InvalidSaveGameException + """ + import base64 + import zlib + + try: + new_string = base64.b64decode(string) + except (binascii.Error, ValueError, TypeError) as e: + raise SaveDecompressException("Fail to decode base64 savestate %s" % e) from e + try: + new_string = zlib.decompress(new_string) + except zlib.error as e: + raise SaveDecompressException("Fail to decompress savestate %s" % e) from e + return decode(new_string.decode('utf-8')) + + +def decode(obj): + return _FreeOrionAISaveGameDecoder().decode(obj) + + +class _FreeOrionAISaveGameDecoder(json.JSONDecoder): + + def __init__(self, **kwargs): + # do not allow control characters + super(_FreeOrionAISaveGameDecoder, self).__init__(strict=True, **kwargs) + + def decode(self, s, _w=None): + # use the default JSONDecoder to parse the string into a dict + # then interpret the dict content according to our encoding + retval = super(_FreeOrionAISaveGameDecoder, self).decode(s) + return self.__interpret(retval) + + def __interpret_dict(self, obj): + # if the dict does not contain the class-encoding keys, + # then it is a standard dictionary. + if not all(key in obj for key in ('__class__', '__module__')): + return {self.__interpret(key): self.__interpret(value) + for key, value in obj.items()} + + # pop and verify class and module name, then parse the class content + class_name = obj.pop('__class__') + module_name = obj.pop('__module__') + full_name = '%s.%s' % (module_name, class_name) + cls = trusted_classes.get(full_name) + if cls is None: + raise InvalidSaveGameException("DANGER DANGER - %s not trusted" + % full_name) + + parsed_content = self.__interpret_dict(obj) + + # create a new instance without calling the actual __new__or __init__ + # function of the class (so we avoid any side-effects from those) + new_instance = object.__new__(cls) + + # Set the content trying to use the __setstate__ method if defined + # Otherwise, directly set the dict content. + try: + setstate = new_instance.__setstate__ + except AttributeError: + if not type(parsed_content) == dict: + raise InvalidSaveGameException("Could not set content for %s" + % new_instance) + new_instance.__dict__ = parsed_content + else: + # only call now to not catch exceptions in the setstate method + setstate(parsed_content) + return new_instance + + def __interpret(self, x): + """Interpret an object that was just decoded.""" + # primitive types do not have to be interpreted + if isinstance(x, (int, float)): + return x + + # special handling for dicts as they could encode our classes + if isinstance(x, dict): + return self.__interpret_dict(x) + + # for standard containers, interpret each element + if isinstance(x, list): + return list(self.__interpret(element) for element in x) + + # if it is a string, check if it encodes another data type + if isinstance(x, str): + + # does it encode an integer? + if x.startswith(INT_PREFIX): + x = x[len(INT_PREFIX):] + return int(x) + + # does it encode a float? + if x.startswith(FLOAT_PREFIX): + x = x[len(FLOAT_PREFIX):] + return float(x) + + # does it encode a tuple? + if x.startswith(TUPLE_PREFIX): + # ignore surrounding parentheses + content = x[len(TUPLE_PREFIX) + 1:-1] + content = _replace_quote_placeholders(content) + result = self.decode(content) + return tuple(result) + + # does it encode a set? + if x.startswith(SET_PREFIX): + # ignore surrounding parentheses + content = x[len(SET_PREFIX) + 1:-1] + content = _replace_quote_placeholders(content) + result = self.decode(content) + return set(result) + + # does it encode an enum? + if x.startswith(ENUM_PREFIX): + full_name = x[len(ENUM_PREFIX):] + partial_names = full_name.split('.') + if not len(partial_names) == 2: + raise InvalidSaveGameException("Could not decode Enum %s" + % x) + enum_name = partial_names[0] + enum = getattr(EnumsAI, enum_name, None) + if enum is None: + raise InvalidSaveGameException("Invalid enum %s" + % enum_name) + retval = getattr(enum, partial_names[1], None) + if retval is None: + raise InvalidSaveGameException("Invalid enum value %s" + % full_name) + return retval + + if x == TRUE: + return True + + if x == FALSE: + return False + + if x == NONE: + return None + + # no special cases apply at this point, should be a standard string + return x + + raise TypeError("Unexpected type %s (%s)" % (type(x), x)) + + +def _replace_quote_placeholders(s): + """Replace PLACEHOLDER with quotes if not nested within another encoded container. + + To be able to use tuples as dictionary keys, to use standard json decoder, + the entire tuple with its content must be encoded as a single string. + The inner objects may no longer be quoted as that would prematurely terminate + the strings. Inner quotes are therefore replaced with the PLACEHOLDER char. + + Example: + output = encode(tuple(["1", "string"])) + "__TUPLE__([$1$, $string$])" + + To be able to decode the inner content, the PLACEHOLDER must be converted + to quotes again. + """ + n = 0 # counts nesting level (i.e. number of opened but not closed parentheses) + start = 0 # starting point for string replacement + for i in range(len(s)): + if s[i] == '(': + # if this is an outer opening parenthesis, then replace placeholder from last parenthesis to here + if n == 0: + s = s[:start] + s[start:i].replace(PLACEHOLDER, '"') + s[i:] + n += 1 + elif s[i] == ')': + n -= 1 + if n == 0: + start = i + s = s[:start] + s[start:].replace(PLACEHOLDER, '"') + return s diff --git a/default/python/AI/savegame_codec/_definitions.py b/default/python/AI/savegame_codec/_definitions.py new file mode 100644 index 00000000000..df1e89a2d81 --- /dev/null +++ b/default/python/AI/savegame_codec/_definitions.py @@ -0,0 +1,52 @@ +# a list of trusted classes - other classes will not be loaded +import AIFleetMission +import fleet_orders +import character.character_module +import AIstate +import ColonisationAI +trusted_classes = {"%s.%s" % (cls.__module__, cls.__name__): cls for cls in [ + AIFleetMission.AIFleetMission, + fleet_orders.AIFleetOrder, + fleet_orders.OrderMilitary, + fleet_orders.OrderDefend, + fleet_orders.OrderColonize, + fleet_orders.OrderOutpost, + fleet_orders.OrderPause, + fleet_orders.OrderInvade, + fleet_orders.OrderMove, + fleet_orders.OrderRepair, + fleet_orders.OrderResupply, + character.character_module.Trait, + character.character_module.Aggression, + character.character_module.EmpireIDTrait, + character.character_module.Character, + AIstate.AIstate, + ColonisationAI.OrbitalColonizationManager, + ColonisationAI.OrbitalColonizationPlan, +]} + +# prefixes to encode types not supported by json +# or not fully supported as dictionary key +ENUM_PREFIX = '__ENUM__' +INT_PREFIX = '__INT__' +FLOAT_PREFIX = '__FLOAT__' +TRUE = '__TRUE__' +FALSE = '__FALSE__' +NONE = '__NONE__' +SET_PREFIX = '__SET__' +TUPLE_PREFIX = '__TUPLE__' + + +# placeholder char to represent quotes in nested containers +# which would break json decoding if present. +PLACEHOLDER = '$' + + +class CanNotSaveGameException(Exception): + """Exception raised when constructing the savegame string failed.""" + pass + + +class InvalidSaveGameException(Exception): + """Exception raised if the savegame could not be loaded.""" + pass diff --git a/default/python/AI/savegame_codec/_encoder.py b/default/python/AI/savegame_codec/_encoder.py new file mode 100644 index 00000000000..0ff1a028316 --- /dev/null +++ b/default/python/AI/savegame_codec/_encoder.py @@ -0,0 +1,143 @@ +"""This module defines the encoding for the FreeOrion AI savegames. + +The encoding is json-based with custom prefixes to support some objects +and dictionary keys of types which are not supported in standard json. + +This module encodes the integer as string with prefix so that the information +about its integer key will be kept throughout json decoding. + +The encoding implementation is recursive + 1) Identify the type of object to encode and call the correct encoder + 2) If it is a non-trivial type, first encode all its content + 3) Finally, encode the object itself + +For class instances, the __getstate__ method is invoked to get its content. +If not defined, its __dict__ will be encoded instead. + +If an object could not be encoded, raise a CanNotSaveGameException. +""" +import base64 +import collections +import zlib + +import EnumsAI +from freeorion_tools import profile + +from ._definitions import (CanNotSaveGameException, ENUM_PREFIX, FALSE, FLOAT_PREFIX, INT_PREFIX, NONE, PLACEHOLDER, + SET_PREFIX, TRUE, TUPLE_PREFIX, trusted_classes, ) + + +@profile +def build_savegame_string() -> bytes: + """Encode the AIstate and compress the resulting string with zlib. + + To decode the string, first call zlib.decompress() on it. + + :return: compressed savegame string + :rtype: str + """ + from aistate_interface import get_aistate + savegame_string = encode(get_aistate()) + return base64.b64encode(zlib.compress(savegame_string.encode('utf-8'))) + + +def encode(o): + """Encode the passed object as json-based string. + + :param o: object to be encoded + :return: String representation of the object state + :rtype: str + """ + o_type = type(o) + + # Find and call the correct encoder based + # on the type of the object to encode + try: + encoder = _encoder_table[o_type] + except KeyError: + if issubclass(o_type, EnumsAI.EnumItem): + return '"%s%s"' % (ENUM_PREFIX, str(o)) + else: + return _encode_object(o) + else: + # only call now to not catch KeyError withing the encoder call + return encoder(o) + + +def _encode_str(o): + return '"%s"' % o + + +def _encode_none(o): + return '"%s"' % NONE + + +def _encode_int(o): + return '"%s%s"' % (INT_PREFIX, str(o)) + + +def _encode_float(o): + return '"%s%s"' % (FLOAT_PREFIX, repr(o)) + + +def _encode_bool(o): + return '"%s"' % (TRUE if o else FALSE) + + +def _encode_object(obj): + """Get a string representation of state of an object which is not handled by a specialized encoder.""" + try: + class_name = "%s.%s" % (obj.__class__.__module__, obj.__class__.__name__) + if class_name not in trusted_classes: + raise CanNotSaveGameException("Class %s is not trusted" % class_name) + except AttributeError: + # obj does not have a class or class has no module + raise CanNotSaveGameException("Encountered unsupported object %s (%s)" % (obj, type(obj))) + + # if possible, use getstate method to query the state, otherwise use the object's __dict__ + try: + getstate = obj.__getstate__ + except AttributeError: + value = obj.__dict__ + else: + # only call now to avoid catching exceptions raised during the getstate call + value = getstate() + + # encode information about class + value.update({'__class__': obj.__class__.__name__, + '__module__': obj.__class__.__module__}) + return _encode_dict(value) + + +def _encode_list(o): + """Get a string representation of a list with its encoded content.""" + return "[%s]" % (', '.join([encode(v) for v in o])) + + +def _encode_tuple(o): + """Get a string representation of a tuple with its encoded content.""" + return '"%s(%s)"' % (TUPLE_PREFIX, _encode_list(list(o)).replace('"', PLACEHOLDER)) + + +def _encode_set(o): + """Get a string representation of a set with its encoded content.""" + return '"%s(%s)"' % (SET_PREFIX, _encode_list(list(o)).replace('"', PLACEHOLDER)) + + +def _encode_dict(o): + """Get a string representation of a dict with its encoded content.""" + return "{%s}" % (', '.join(['%s: %s' % (encode(k), encode(v)) for k, v in o.items()])) + + +_encoder_table = { + str: _encode_str, + bool: _encode_bool, + int: _encode_int, + float: _encode_float, + dict: _encode_dict, + collections.OrderedDict: _encode_dict, + list: _encode_list, + set: _encode_set, + tuple: _encode_tuple, + type(None): _encode_none, +} diff --git a/default/python/AI/universe_object.py b/default/python/AI/target.py similarity index 62% rename from default/python/AI/universe_object.py rename to default/python/AI/target.py index 337b89b9cc7..b2c8e1fabb6 100644 --- a/default/python/AI/universe_object.py +++ b/default/python/AI/target.py @@ -1,24 +1,26 @@ -import sys +from logging import warning import freeOrionAIInterface as fo # pylint: disable=import-error from AIDependencies import INVALID_ID -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) -class UniverseObject(object): +class Target: """ Stores information about AI target - its id and type. :type id: int """ + object_name = 'target' def __init__(self, target_id): self.id = target_id if not self: - print >> sys.stderr, "Target is invalid %s" % self + warning("Target is invalid %s" % self) - def __cmp__(self, other): - return type(self) == type(other) and cmp(self.id, other.id) + def __eq__(self, other): + return type(self) == type(other) and self.id == other.id + + def __hash__(self): + return hash(self.id) def __str__(self): target = self.get_object() @@ -28,32 +30,34 @@ def __str__(self): def get_object(self): """ - Returns fo.universeObject or None. + :rtype: fo.universeObject | None """ return None - def __nonzero__(self): + def __bool__(self): return self.id is not None and self.id >= 0 + __nonzero__ = __bool__ + def get_system(self): """ Returns the system that contains this object, or None. - :rtype: System | None + :rtype: TargetSystem | None """ raise NotImplementedError() -class Planet(UniverseObject): +class TargetPlanet(Target): object_name = 'planet' def get_system(self): """ Returns the system that contains this object, or None. - :rtype: System | None + :rtype: TargetSystem | None """ universe = fo.getUniverse() planet = universe.getPlanet(self.id) - return System(planet.systemID) + return TargetSystem(planet.systemID) def get_object(self): """ @@ -62,13 +66,13 @@ def get_object(self): return fo.getUniverse().getPlanet(self.id) -class System(UniverseObject): +class TargetSystem(Target): object_name = 'system' def get_system(self): """ Returns this object. - :rtype: System + :rtype: TargetSystem """ return self @@ -76,20 +80,34 @@ def get_object(self): return fo.getUniverse().getSystem(self.id) -class Fleet(UniverseObject): +class TargetFleet(Target): object_name = 'fleet' + def get_current_system_id(self): + """ + Get current systemID (or INVALID_ID if on a starlane). + + :rtype: int + """ + universe = fo.getUniverse() + fleet = universe.getFleet(self.id) + # will also return INVALID_ID if somehow the fleet cannot be retrieved + return fleet.systemID if fleet else INVALID_ID + def get_system(self): """ Get current fleet location or target system if currently on starlane. - :rtype: System | None + :rtype: TargetSystem | None """ universe = fo.getUniverse() fleet = universe.getFleet(self.id) system_id = fleet.nextSystemID if system_id == INVALID_ID: # fleet is not moving system_id = fleet.systemID - return System(system_id) + return TargetSystem(system_id) def get_object(self): + """ + :rtype fo.fleet: + """ return fo.getUniverse().getFleet(self.id) diff --git a/default/python/AI/turn_state.py b/default/python/AI/turn_state.py index 93eca23e132..612a53b487f 100644 --- a/default/python/AI/turn_state.py +++ b/default/python/AI/turn_state.py @@ -1,13 +1,17 @@ from collections import namedtuple +from logging import warning, error +from freeorion_tools import ReadOnlyDict, cache_for_current_turn import freeOrionAIInterface as fo -from common.configure_logging import convenience_function_references_for_logger -(debug, info, warn, error, fatal) = convenience_function_references_for_logger(__name__) + +import AIDependencies +from AIDependencies import INVALID_ID +from EnumsAI import FocusType PlanetInfo = namedtuple('PlanetInfo', ['pid', 'species_name', 'owner', 'system_id']) -class State(object): +class State: """ This class represent state for current turn. It contains variables that renewed each turn. @@ -25,9 +29,21 @@ def __init__(self): self.__have_ruins = False self.__have_nest = False self.__have_computronium = False + self.__have_panopticon = False self.__best_pilot_rating = 1e-8 self.__medium_pilot_rating = 1e-8 self.__planet_info = {} # map from planet_id to PlanetInfo + self.__num_researchers = 0 # population with research focus + self.__num_industrialists = 0 # population with industry focus + + # supply info - negative values indicate jumps away from supply + self.__system_supply = {} # map from system_id to supply + self.__distance_to_enemy_supply = {} # map from system_id to closest enemy supply + self.__systems_by_jumps_to_supply = {} # map from supply to list of system_ids + self.__empire_planets_by_system = {} + + # building info + self.__drydock_locations = ReadOnlyDict() # map from system id to planet id where empire has a drydock def update(self): """ @@ -35,30 +51,126 @@ def update(self): """ self.__init__() self.__update_planets() + self.__update_supply() + self.__update_buildings() def __update_planets(self): """ Update information about planets. """ universe = fo.getUniverse() + empire_id = fo.empireID() for pid in universe.planetIDs: planet = universe.getPlanet(pid) self.__planet_info[pid] = PlanetInfo(pid, planet.speciesName, planet.owner, planet.systemID) - def get_empire_inhabited_planets_by_system(self): + if planet.ownedBy(empire_id): + population = planet.currentMeterValue(fo.meterType.population) + if AIDependencies.ANCIENT_RUINS_SPECIAL in planet.specials: + self.__have_ruins = True + if AIDependencies.PANOPTICON_SPECIAL in planet.specials: + self.__have_panopticon = True + if population > 0 and AIDependencies.COMPUTRONIUM_SPECIAL in planet.specials: + self.__have_computronium = True # TODO: Check if species can set research focus + + if planet.focus == FocusType.FOCUS_INDUSTRY: + self.__num_industrialists += population + elif planet.focus == FocusType.FOCUS_RESEARCH: + self.__num_researchers += population + + def __update_buildings(self): + universe = fo.getUniverse() + empire_id = fo.empireID() + drydocks = {} + for building_id in universe.buildingIDs: + building = universe.getBuilding(building_id) + if not building: + continue + if building.buildingTypeName == AIDependencies.BLD_SHIPYARD_ORBITAL_DRYDOCK and building.ownedBy(empire_id): + drydocks.setdefault(building.systemID, []).append(building.planetID) + self.__drydock_locations = ReadOnlyDict({k: tuple(v) for k, v in drydocks.items()}) + + def __update_supply(self): """ - Return dict from system id to planet ids of empire with species. + Update information about supply. + """ + self.__system_supply.update(fo.getEmpire().supplyProjections()) + for sys_id, supply_val in self.__system_supply.items(): + self.__systems_by_jumps_to_supply.setdefault(min(0, supply_val), []).append(sys_id) + + # By converting the lists to immutable tuples now, we don't have to return copies when queried. + for key in self.__systems_by_jumps_to_supply: + self.__systems_by_jumps_to_supply[key] = tuple(self.__systems_by_jumps_to_supply[key]) - :rtype: dict[int, list[int]] + enemies = [fo.getEmpire(_id) for _id in fo.allEmpireIDs() if _id != fo.empireID()] + for enemy in enemies: + if enemy is None: + error('Got None for enemy empire!') + continue + + for sys_id, supply_val in enemy.supplyProjections().items(): + self.__distance_to_enemy_supply[sys_id] = min( + self.get_distance_to_enemy_supply(sys_id), -supply_val) + + def get_system_supply(self, sys_id): + """Get the supply level of a system. + + Negative values indicate jumps away from supply. + + :type sys_id: int + :return: Supply value of a system or -99 if system is not connected + :rtype: int """ - # TODO: as currently used, is duplicative with combo of foAI.foAIstate.popCtrSystemIDs and foAI.foAIstate.colonizedSystems - empire_id = fo.empireID() - empire_planets_with_species = (x for x in self.__planet_info.itervalues() if x.owner == empire_id and x.species_name) - result = {} - for x in empire_planets_with_species: - result.setdefault(x.system_id, []).append(x.pid) - return result + retval = self.__system_supply.get(sys_id, None) + if retval is None: + # This is only expected to happen if a system has no path to any supplied system. + # As the current code should not allow such queries, this is logged as warning. + # If future code breaks this assumption, feel free to adjust logging. + warning("Queried supply value of a system not mapped in empire.supplyProjections(): %d" % sys_id) + return -99 # pretend it is very far away from supply + return retval + + def get_systems_by_supply_tier(self, supply_tier): + """Get systems with supply tier. + + The current implementation does not distinguish between positive supply levels and caps at 0. + Negative values indicate jumps away from supply. + + :type supply_tier: int + :return: system_ids in specified supply tier + :rtype: tuple[int] + """ + if supply_tier > 0: + warning("The current implementation does not distinguish between positive supply levels. " + "Interpreting the query as supply_tier=0 (indicating system in supply).") + supply_tier = 0 + return self.__systems_by_jumps_to_supply.get(supply_tier, tuple()) + def get_distance_to_enemy_supply(self, sys_id): + return self.__distance_to_enemy_supply.get(sys_id, 999) + + def get_empire_planets_by_system(self, sys_id=None, include_outposts=True): + """ + Return dict from system id to planet ids of empire with species. + + :rtype: ReadOnlyDict[int, list[int]] + """ + # TODO: as currently used, is duplicative with combo of get_aistate().popCtrSystemIDs + if include_outposts not in self.__empire_planets_by_system: + empire_id = fo.empireID() + empire_planets = (x for x in self.__planet_info.values() + if x.owner == empire_id and (x.species_name or include_outposts)) + result = {} + for x in empire_planets: + result.setdefault(x.system_id, []).append(x.pid) + self.__empire_planets_by_system[include_outposts] = ReadOnlyDict( + {k: tuple(v) for k, v in result.items()} + ) + if sys_id is not None: + return self.__empire_planets_by_system[include_outposts].get(sys_id, tuple()) + return self.__empire_planets_by_system[include_outposts] + + @cache_for_current_turn def get_inhabited_planets(self): """ Return frozenset of empire planet ids with species. @@ -66,7 +178,15 @@ def get_inhabited_planets(self): :rtype: frozenset[int] """ empire_id = fo.empireID() - return frozenset(x.pid for x in self.__planet_info.itervalues() if x.owner == empire_id and x.species_name) + return frozenset(x.pid for x in self.__planet_info.values() if x.owner == empire_id and x.species_name) + + def get_empire_outposts(self): + empire_id = fo.empireID() + return tuple(x.pid for x in self.__planet_info.values() if x.owner == empire_id and not x.species_name) + + def get_all_empire_planets(self): + empire_id = fo.empireID() + return tuple(x.pid for x in self.__planet_info.values() if x.owner == empire_id) def get_empire_planets_with_species(self, species_name): """ @@ -88,10 +208,30 @@ def get_empire_planets_by_species(self): """ empire_id = fo.empireID() result = {} - for x in (x for x in self.__planet_info.itervalues() if x.owner == empire_id and x.species_name): + for x in (x for x in self.__planet_info.values() if x.owner == empire_id and x.species_name): result.setdefault(x.species_name, []).append(x.pid) return result + def get_unowned_empty_planets(self): + """Return the set of planets that are not owned by any player and have no natives. + + :rtype: set[int] + """ + return {x.pid for x in self.__planet_info.values() if x.owner == INVALID_ID and not x.species_name} + + def get_number_of_colonies(self): + return len(self.get_inhabited_planets()) + + def population_with_research_focus(self): + return self.__num_researchers + + def population_with_industry_focus(self): + return self.__num_industrialists + + def get_empire_drydocks(self): + """Return a map from system ids to planet ids where empire drydocks are located""" + return self.__drydock_locations + @property def have_gas_giant(self): return self.__have_gas_giant @@ -110,8 +250,9 @@ def set_have_asteroids(self): def have_ruins(self): return self.__have_ruins - def set_have_ruins(self): - self.__have_ruins = True + @property + def have_panopticon(self): + return self.__have_panopticon @property def have_nest(self): @@ -124,9 +265,6 @@ def set_have_nest(self): def have_computronium(self): return self.__have_computronium - def set_have_computronium(self): - self.__have_computronium = True - @property def best_pilot_rating(self): return self.__best_pilot_rating diff --git a/default/python/README.md b/default/python/README.md index 2edc550ea90..46bd1ce5977 100644 --- a/default/python/README.md +++ b/default/python/README.md @@ -9,6 +9,10 @@ an execute_turn_events() function returning a boolean (successful completion). * AI/ - Python code which controls the computer players. This is a sub-module of the resource directory and can be changed with the --ai-path flag. +* auth/ - Python code which manages auth information stored either in a file +or in a database. +* chat/ - Python code which manages chat history stored either in a file or in +a database. * common/ - Common files for code utilized by both the AI and the server. * handlers/ - see handlers/README.md * turn_events/ - Python scripts that run at the beginning of every turn, and @@ -19,20 +23,40 @@ editing options.py and universe_tables.py, both of which have more information in comments over there. The latter, specifically, controls which star types, planet types, planet sizes, and also other content get placed. - # Code style check -Code style checks are done with [pycodestyle](https://pypi.python.org/pypi/pycodestyle) +Each PR will be checked automatically, but you still can run checks manually. +Fixing all issues are mandatory before merging PR. + +We use [flake8-putty](https://pypi.python.org/pypi/flake8-putty) +which include [flake8](https://pypi.python.org/pypi/flake8) +which include [pycodestyle](https://pypi.python.org/pypi/pycodestyle) and other tools. + +`flake8-putty` is `flake8` plugin that allows one to disable one or more rules for a certain file, +see `putty-ignore` section in `tox.ini`. This allows ignoring certain +warnings only for specified files, like bare excepts, that are hard to fix, +or special files with tables. + +## TODO section + +See TODO section in tox.ini + +## Install dependencies -This part is in progress. +```sh +pip install flake8-putty +``` -Plan is to fix all easy things (code formatting) and ignore hard things (`E722 do not use bare except`). -Final stage is to setup automatic check to avoid adding new cases. +## Run checks +This script should be run from directory where `tox.ini` located -## Why we ignore -- `E241 multiple spaces after ':'` this is ok only for `AIDependencies.py` because it is used as config file -- `E501 line too long` it is ok to ignore it for `AIDependencies.py` +```sh +cd default/python +flake8 +``` -## List of check that should pass: -`pycodestyle python/AI/AIDependencies.py --ignore=E501,E241` -`pycodestyle python/AI/AIFleetMission.py --max-line-length=120` \ No newline at end of file +This script should be run from directory where `mypi.ini` located +```sh +cd default/python +mypy --config-file mypy.ini +``` diff --git a/default/python/auth/auth.py b/default/python/auth/auth.py new file mode 100644 index 00000000000..1ed8934cb1b --- /dev/null +++ b/default/python/auth/auth.py @@ -0,0 +1,84 @@ +from logging import warning, info + +from common.configure_logging import redirect_logging_to_freeorion_logger + +# Logging is redirected before other imports so that import errors appear in log files. +redirect_logging_to_freeorion_logger() + +import sys + +import freeorion as fo + +# Constants defined by the C++ game engine +NO_TEAM_ID = -1 + + +class AuthProvider: + def __init__(self): + self.logins = {} + self.roles_symbols = { + 'h': fo.roleType.host, 'm': fo.roleType.clientTypeModerator, + 'p': fo.roleType.clientTypePlayer, 'o': fo.roleType.clientTypeObserver, + 'g': fo.roleType.galaxySetup + } + try: + with open(fo.get_user_config_dir() + "/auth.txt") as f: + first_line = True + for line in f: + if first_line: + first_line = False + self.default_roles = self.__parse_roles(line.strip()) + else: + login, roles, password = line.rsplit(':', 2) + self.logins[login] = (password.strip(), self.__parse_roles(roles.strip())) + except IOError: + exctype, value = sys.exc_info()[:2] + warning("Cann't read auth file %s: %s %s" % (fo.get_user_config_dir() + "/auth.txt", exctype, value)) + self.default_roles = [ + fo.roleType.clientTypeModerator, fo.roleType.clientTypePlayer, + fo.roleType.clientTypeObserver, fo.roleType.galaxySetup + ] + info("Auth initialized") + + def __parse_roles(self, roles_str): + roles = [] + for c in roles_str: + r = self.roles_symbols.get(c) + if r is None: + warning("unknown role symbol '%c'" % c) + else: + roles.append(r) + return roles + + def is_require_auth_or_return_roles(self, player_name): + """Returns True if player should be authenticated or list of roles for anonymous players""" + known_login = player_name in self.logins + if not known_login: + # default list of roles + return self.default_roles + return True + + def is_success_auth_and_return_roles(self, player_name, auth): + """Return False if passowrd doesn't match or list of roles for authenticated player""" + auth_data = self.logins.get(player_name) + if auth_data[0] == auth: + return auth_data[1] + else: + return False + + def list_players(self): + """Returns list of PlayerSetupData to use in quickstart""" + players = [] + for player_name, auth_data in self.logins.items(): + if fo.roleType.clientTypePlayer in auth_data[1]: + psd = fo.PlayerSetupData() + psd.player_name = player_name + psd.empire_name = player_name + psd.starting_species = "RANDOM" + psd.starting_team = NO_TEAM_ID + players.append(psd) + return players + + def get_player_delegation(self, player_name): + """Returns list of players delegated by this player""" + return [] diff --git a/default/python/chat/chat.py b/default/python/chat/chat.py new file mode 100644 index 00000000000..30a50400b04 --- /dev/null +++ b/default/python/chat/chat.py @@ -0,0 +1,42 @@ +from logging import info + +from common.configure_logging import redirect_logging_to_freeorion_logger + +# Logging is redirected before other imports so that import errors appear in log files. +redirect_logging_to_freeorion_logger() + + +class ChatHistoryProvider: + def __init__(self): + """ + Initializes ChatProvider. Doesn't accept arguments. + """ + info("Chat initialized") + + def load_history(self): + """ + Loads chat history from external sources. + + Returns list of tuples: + (unixtime timestamp, player name, text, text color of type freeorion.GGColor) + """ + info("Loading history...") + # c = fo.GGColor(255, 128, 128, 255) + # e = (123456789012, "P1", "Test1", c) + # return [e] + return [] + + def put_history_entity(self, timestamp, player_name, text, text_color): + """ + Put chat into external storage. + + Return True if successfully stored. False otherwise. + + Arguments: + timestamp -- unixtime, number of seconds from 1970-01-01 00:00:00 UTC + player_name -- player name + text -- chat text + text_color -- freeorion.GGColor + """ + info("Chat %s: %s %s" % (player_name, text, text_color)) + return True diff --git a/default/python/common/charting/charts.py b/default/python/common/charting/charts.py index 74667caeac6..2049b6d4e1c 100755 --- a/default/python/common/charting/charts.py +++ b/default/python/common/charting/charts.py @@ -33,62 +33,66 @@ def show_only_some(x, pos): def parse_file(file_name, ai=True): - print "processing file ", file_name - sys.stdout.flush() - got_colors = False - got_species = False - got_name = False - data = {"PP": [], "RP": [], "RP_Ratio": [], "ShipCount": [], "turnsP": [], "turnPP": [], "PP + 2RP": []} - details = {'color': {1, 1, 1, 1}, 'name': "", 'species': ""} - with open(unicode(file_name, 'utf-8'), 'r') as lf: - while True: - line = lf.readline() - if not line: - break - if not got_colors and "EmpireColors:" in line: - colors = line.split("EmpireColors:")[1].split() - if len(colors) == 4: - got_colors = True - if isinstance(colors[0], str): - details['color'] = tuple(map(lambda x: float(x) / 255.0, colors)) - else: - details['color'] = tuple(map(lambda x: float(ord(x[0])) / 255.0, colors)) - if ai and not got_species and "CapitalID:" in line: - got_species = True - details['species'] = line.split("Species:")[1].strip() - if ai and not got_name and "EmpireID:" in line: - got_name = True - details['name'] = line.split("Name:")[1].split("Turn:")[0].strip() - if "Current Output (turn" in line: - info = line.split("Current Output (turn")[1] - parts = info.split(')') - data['turnsP'].append((int(parts[0]))) - data['turnPP'].append((int(parts[0]), float(parts[1].split('/')[-1]))) - rppp = parts[1].split('(')[-1].split('/') - data['PP'].append(float(rppp[1])) - data['RP'].append(float(rppp[0])) - data['PP + 2RP'].append(float(rppp[1]) + 2 * float(rppp[0])) - data['RP_Ratio'].append(float(rppp[0]) / (float(rppp[1]) + 0.001)) - if "Empire Ship Count:" in line: - data['ShipCount'].append(int(line.split("Empire Ship Count:")[1])) - return data, details + print("processing file ", file_name) + sys.stdout.flush() + got_colors = False + got_species = False + got_name = False + data = {"PP": [], "RP": [], "RP_Ratio": [], "ShipCount": [], "turnsP": [], "turnPP": [], "PP + 2RP": []} + details = {'color': {1, 1, 1, 1}, 'name': "", 'species': ""} + with open(file_name, 'r') as lf: + while True: + line = lf.readline() + if not line: + break + if not got_colors and "EmpireColors:" in line: + colors = line.split("EmpireColors:")[1].split() + if len(colors) == 4: + got_colors = True + if isinstance(colors[0], str): + details['color'] = tuple(map(lambda x: float(x) / 255.0, colors)) + else: + details['color'] = tuple(map(lambda x: float(ord(x[0])) / 255.0, colors)) + if ai and not got_species and "CapitalID:" in line: + got_species = True + details['species'] = line.split("Species:")[1].strip() + if ai and not got_name and "EmpireID:" in line: + got_name = True + details['name'] = line.split("Name:")[1].split("Turn:")[0].strip() + if "Current Output (turn" in line: + info = line.split("Current Output (turn")[1] + parts = info.split(')') + data['turnsP'].append((int(parts[0]))) + data['turnPP'].append((int(parts[0]), float(parts[1].split('/')[-1]))) + rppp = parts[1].split('(')[-1].split('/') + data['PP'].append(float(rppp[1])) + data['RP'].append(float(rppp[0])) + data['PP + 2RP'].append(float(rppp[1]) + 2 * float(rppp[0])) + data['RP_Ratio'].append(float(rppp[0]) / (float(rppp[1]) + 0.001)) + if "Empire Ship Count:" in line: + data['ShipCount'].append(int(line.split("Empire Ship Count:")[1])) + return data, details def main(): - dataDir = (os.environ.get('HOME', "") + "/.freeorion") if os.name != 'posix' else (os.environ.get('XDG_DATA_HOME', os.environ.get('HOME', "") + "/.local/share") + "/freeorion") + if os.name == 'nt': + dataDir = os.path.expanduser('~') + '\\Appdata\\Roaming\\Freeorion' + elif os.name == 'posix': + dataDir = os.environ.get('XDG_DATA_HOME', os.environ.get('HOME', "") + "/.local/share") + "/freeorion" + else: + dataDir = os.environ.get('HOME', "") + "/.freeorion" + graphDir = dataDir fileRoot = "game1" saveFile = True turnsP = None - turnsAI = None rankings = [] doPlotTypes = ["PP + 2RP"] + ["PP"] + ["RP"] + ["RP_Ratio"] # +[ "ShipCount"] allData = {} species = {} - empires = [] empireColors = {} playerName = "Player" @@ -96,9 +100,9 @@ def main(): A1log = glob(dataDir + os.sep + "AI_1.log") if not os.path.exists(dataDir + os.sep + "freeorion.log"): - print "can't find freeorion.log" + print("can't find freeorion.log") elif A1log and (A1log[0] in logfiles) and (os.path.getmtime(dataDir + os.sep + "freeorion.log") < os.path.getmtime(A1log[0]) - 300): - print "freeorion.log file is stale ( more than 5 minutes older than AI_1.log) and will be skipped" + print("freeorion.log file is stale ( more than 5 minutes older than AI_1.log) and will be skipped") else: data, details = parse_file(dataDir + os.sep + "freeorion.log", False) if len(data.get('PP', [])) > 0: @@ -114,21 +118,21 @@ def main(): # print "path ", path, "logtime diff: %.1f"%(A1Time -logtime) if logtime < A1Time - 300: del logfiles[logfiles.index(path)] - print "skipping stale logfile ", path + print("skipping stale logfile ", path) for lfile in logfiles: try: data, details = parse_file(lfile, True) allData[details['name']] = data empireColors[details['name']] = details['color'] species[details['name']] = details['species'] - except: - print "error processing %s" % lfile - print "Error: exception triggered and caught: ", traceback.format_exc() + except: # noqa: E722 + print("error processing %s" % lfile) + print("Error: exception triggered and caught: ", traceback.format_exc()) - print + print() for plotType in doPlotTypes: - print "--------------------" + print("--------------------") if plotType == "PP": caption = "Production" elif plotType == "RP": @@ -177,25 +181,27 @@ def main(): ax.set_yscale('log', basey=10) if playerName not in allData: - print "\t\t\tcan't find playerData in allData\n" + print("\t\t\tcan't find playerData in allData\n") else: if playerName in empireColors: turnsP = allData[playerName].get("turnsP", []) thisData = allData.get(playerName, {}).get(plotType, []) - print "plotting with color for player: ", playerName, "data min/max: ", min(allData[playerName].get(plotType, [])), ' | ', max(allData[playerName].get(plotType, [])) - plot(turnsP, thisData, 'o-', color=empireColors[playerName], label="%s - %.1f" % (playerName, sum(thisData)), linewidth=2.0) + print("plotting with color for player: ", playerName, "data min/max: ", + min(allData[playerName].get(plotType, [])), ' | ', max(allData[playerName].get(plotType, []))) + pylab.plot(turnsP, thisData, 'o-', color=empireColors[playerName], label="%s - %.1f" % (playerName, sum(thisData)), linewidth=2.0) else: - print "plotting withOUT color for player: ", playerName, "data min/max: ", min(allData[playerName].get(plotType, [])), ' | ', max(allData[playerName].get(plotType, [])) - plot(turnsP, allData[playerName].get(plotType, []), 'bx-', label=playerName, linewidth=2.0) - print "Ranked by ", plotType + print("plotting withOUT color for player: ", playerName, "data min/max: ", + min(allData[playerName].get(plotType, [])), ' | ', max(allData[playerName].get(plotType, []))) + pylab.plot(turnsP, allData[playerName].get(plotType, []), 'bx-', label=playerName, linewidth=2.0) + print("Ranked by ", plotType) for rank, name in rankings[::-1]: - print name + print(name) if name in empireColors: adata = allData[name].get(plotType, []) pylab.plot(range(turns[0], turns[0] + len(adata)), adata, color=empireColors[name], label="%s: %s - %.1f" % (name, species[name], sum(adata)), linewidth=2.0) else: - print "can't find empire color for ", name - # plot(range(turns[0], turns[0]+len(allData[name])), allData[name].get(plotType, []), label="(%d) "%(empires.index(name)+1)+name+" : "+species[name], linewidth=2.0) + print("can't find empire color for ", name) + # pylab.plot(range(turns[0], turns[0]+len(allData[name])), allData[name].get(plotType, []), label="(%d) "%(empires.index(name)+1)+name+" : "+species[name], linewidth=2.0) # legend(loc='upper left',prop={"size":'medium'}) pylab.legend(loc='upper left', prop={"size": 9}, labelspacing=0.2) pylab.xlabel('Turn') @@ -208,7 +214,7 @@ def main(): if 1.05 * ymax < yi * y2 / 10: newY2 = yi * y2 / 10 break - print "y1: %.1f ; ymin: %.1f ; newY2/100: %.1f" % (y1, ymin, newY2 / 100) + print("y1: %.1f ; ymin: %.1f ; newY2/100: %.1f" % (y1, ymin, newY2 / 100)) y1 = max(y1, 4, ymin, newY2 / 100) pylab.axis((x1, x2, y1, newY2)) pylab.grid(b=True, which='major', color='0.25', linestyle='-') @@ -222,5 +228,6 @@ def main(): pylab.savefig(graphDir + os.sep + plotType + "_" + fileRoot + ".png") pylab.show() + if __name__ == "__main__": main() diff --git a/default/python/common/configure_logging.py b/default/python/common/configure_logging.py index 0f5a420bdfa..67c406ef99d 100644 --- a/default/python/common/configure_logging.py +++ b/default/python/common/configure_logging.py @@ -7,9 +7,8 @@ Usage: -from common.configure_logging import redirect_logging_to_freeorion_logger, convenience_function_references_for_logger +from common.configure_logging import redirect_logging_to_freeorion_logger redirect_logging_to_freeorion_logger(log_level) -(debug, info, warn, error, fatal) = convenience_function_references_for_logger() Then use python logging or print and have it re-directed appropriately. @@ -21,7 +20,7 @@ * Using the root logger directly is the simplest way to log: logging.debug(msg) logging.info(msg) -logging.warn(msg) +logging.warning(msg) logging.error(msg) logging.fatal(msg) @@ -36,7 +35,7 @@ * One strength of the python standard logging library is the ability to create arbitrary hierarchical loggers. Loggers are created globally when getLogger() - is called with any string. The hierarchical levels are dot seperated. The + is called with any string. The hierarchical levels are dot separated. The root logger is the empty string. The following creates a logger: logging.getLogger("toplevel.2ndlevel") @@ -72,49 +71,43 @@ For more information about the python standard library logger see https://docs.python.org/2/howto/logging.html - -The function convenience_function_references_for_logger(name) returns the -specific logging functions from the logger ''name'' which can be bound to -convenient local functions, to avoid calling name.error() to produce an error -log. - """ import sys import logging -import inspect import os +import traceback try: import freeorion_logger # FreeOrion logger interface pylint: disable=import-error -except ImportError as e: +except ImportError: # Create an alternative logger for use in testing when the server is unavailable - class _FreeOrionLoggerForTest(object): + class _FreeOrionLoggerForTest: """A stub freeorion_logger for testing""" @staticmethod - def debug(msg): - print msg + def debug(msg, *args): + print(msg) @staticmethod - def info(msg): - print msg + def info(msg, *args): + print(msg) @staticmethod - def warn(msg): - print msg + def warn(msg, *args): + print(msg) @staticmethod - def error(msg): - print >> sys.stderr, msg + def error(msg, *args): + print(msg, file=sys.stderr) @staticmethod - def fatal(msg): - print >> sys.stderr, msg + def fatal(msg, *args): + print(msg, file=sys.stderr) freeorion_logger = _FreeOrionLoggerForTest() -class _stdXLikeStream(object): +class _stdXLikeStream: """A stream-like object to redirect stdout or stderr to the C++ process.""" def __init__(self, level): self.logger = { @@ -128,11 +121,18 @@ def __init__(self, level): def write(self, msg): # Grab the caller's call frame info - stack = inspect.stack() - if len(stack) > 1: - (_, filename, line_number, function_name, _, _) = inspect.stack()[1] + if hasattr(sys, '_getframe'): + frame = sys._getframe(1) + try: + line_number = frame.f_lineno + function_name = frame.f_code.co_name + filename = frame.f_code.co_filename + except: # noqa: E722 + (filename, line_number, function_name) = ("", "", "") + finally: + # Explicitly del references to the caller's frame to avoid persistent reference cycles + del frame else: - # No available call frame info (filename, line_number, function_name) = ("", "", "") try: @@ -140,7 +140,6 @@ def write(self, msg): finally: # Explicitly del references to the caller's frame to avoid persistent reference cycles - del stack del filename del line_number del function_name @@ -160,7 +159,16 @@ def __init__(self, level): }[level] def emit(self, record): - self.logger(str(record.msg) + "\n", str(record.name), str(record.filename), + record.message = record.getMessage() + msg = str(record.message) + "\n" + if record.exc_info: + if not isinstance(record.exc_info, tuple): + # record.exc_info is not a local variable and will be garbage collected when record + # is garbage collected by the logging library + record.exc_info = sys.exc_info() + traceback_msg = "".join(traceback.format_exception(*record.exc_info)) + msg += traceback_msg + self.logger(msg, "python", str(record.filename), str(record.funcName), str(record.lineno)) @@ -183,13 +191,25 @@ def _create_narrow_handler(level): return h +def _unhandled_exception_hook(*exc_info): + traceback_msg = "Uncaught exception: {0}".format( + "".join(traceback.format_exception(*exc_info))) + logging.getLogger().error(traceback_msg) + + def redirect_logging_to_freeorion_logger(initial_log_level=logging.DEBUG): """Redirect stdout, stderr and the logging.logger to hosting process' freeorion_logger.""" if not hasattr(redirect_logging_to_freeorion_logger, "only_redirect_once"): sys.stdout = _stdXLikeStream(logging.DEBUG) sys.stderr = _stdXLikeStream(logging.ERROR) - print 'Python stdout and stderr are redirected to ai process.' + print('Python stdout and stderr are redirected to ai process.') + + # thread and process information is already provided by boost logging framework + # we can avoid some logging call overheads by turning this off + logging.logThreads = 0 + logging.logProcesses = 0 + logging.logMultiprocessing = 0 logger = logging.getLogger() logger.addHandler(_create_narrow_handler(logging.DEBUG)) @@ -199,14 +219,11 @@ def redirect_logging_to_freeorion_logger(initial_log_level=logging.DEBUG): logger.addHandler(_create_narrow_handler(logging.FATAL)) logger.addHandler(_create_narrow_handler(logging.NOTSET)) + # Replace the system unhandled exception handler + sys.excepthook = _unhandled_exception_hook + logger.setLevel(initial_log_level) logger.info("The python logger is initialized with a log level of %s" % logging.getLevelName(logger.getEffectiveLevel())) redirect_logging_to_freeorion_logger.only_redirect_once = True - - -def convenience_function_references_for_logger(name=""): - """Return a tuple (debug, info, warn, error, fatal) of the ''name'' logger's convenience functions.""" - logger = logging.getLogger(name) - return (logger.debug, logger.info, logger.warning, logger.error, logger.critical) diff --git a/default/python/AI/freeorion_tools/handlers.py b/default/python/common/handlers.py similarity index 73% rename from default/python/AI/freeorion_tools/handlers.py rename to default/python/common/handlers.py index b0e6dea39eb..34d2fe5de39 100644 --- a/default/python/AI/freeorion_tools/handlers.py +++ b/default/python/common/handlers.py @@ -1,15 +1,18 @@ import sys -from freeorion_tools.charts_handler import charting_text from traceback import print_exc from shlex import split import os +from logging import error from common.option_tools import get_option_dict, HANDLERS -from common.listeners import register_pre_handler def init_handlers(config_str, search_dir): - handlers = split(get_option_dict()[HANDLERS]) + try: + handlers = split(get_option_dict()[HANDLERS]) + except KeyError: + error("Missing key in option dict: %s", HANDLERS) + handlers = [] for handler in handlers: module = os.path.basename(handler)[:-3] @@ -29,8 +32,7 @@ def init_handlers(config_str, search_dir): __import__(module) except Exception as e: for p in sys.path: - print p - print >> sys.stderr, "Fail to import handler %s with error %s" % (handler, e) + error(p) + error("Fail to import handler %s with error %s" % (handler, e)) print_exc() exit(1) - register_pre_handler('generateOrders', charting_text) diff --git a/default/python/common/listeners.py b/default/python/common/listeners.py index 51d06c58527..2031dc938e8 100644 --- a/default/python/common/listeners.py +++ b/default/python/common/listeners.py @@ -10,11 +10,11 @@ def _register(function_name, handler, is_post_handler): handlers.setdefault(function_name, [[], []])[is_post_handler].append(handler) - print 'Register "%s" %s "%s" execution' % ( + print('Register "%s" %s "%s" execution' % ( handler.__name__, 'after' if is_post_handler else 'before', function_name - ) + )) def register_pre_handler(function_name, handler): diff --git a/default/python/common/option_tools.py b/default/python/common/option_tools.py index 9c98c5661e4..0ca47476680 100644 --- a/default/python/common/option_tools.py +++ b/default/python/common/option_tools.py @@ -1,7 +1,7 @@ import os import sys -from ConfigParser import SafeConfigParser from collections import OrderedDict as odict +from configparser import ConfigParser AI_SUB_DIR = 'AI' DEFAULT_SUB_DIR = os.path.join(AI_SUB_DIR, 'default') @@ -65,16 +65,19 @@ def _create_default_config_file(path): """ Writes default config to file. """ - config = SafeConfigParser() + config = ConfigParser() presets = _get_preset_default_ai_options() - for section, entries in presets.iteritems(): + for section, entries in presets.items(): config.add_section(section) - for k, v in entries.iteritems(): + for k, v in entries.items(): config.set(section, k, str(v)) if path: - with open(unicode(path, 'utf-8'), 'w') as configfile: - config.write(configfile) - print "default config is dumped to %s" % path + try: + with open(path, 'w') as configfile: + config.write(configfile) + print("default config is dumped to %s" % path) + except IOError: + sys.stderr.write("AI Config Error: could not write default config %s\n" % path) return config @@ -99,10 +102,16 @@ def get_sectioned_option_dict(): def parse_config(option_string, config_dir): + + if option_string is not None and not isinstance(option_string, str): + # probably called by unit test + print("Specified option string is not a string: ", option_string, file=sys.stderr) + return + # get defaults; check if don't already exist and can write default_file = _get_option_file(config_dir) if os.path.exists(default_file): - config = SafeConfigParser() + config = ConfigParser() config.read([default_file]) else: try: @@ -115,7 +124,7 @@ def parse_config(option_string, config_dir): if option_string: config_files = [option_string] configs_read = config.read(config_files) - print "AI Config read config file(s): %s" % configs_read + print("AI Config read config file(s): %s" % configs_read) if len(configs_read) != len(config_files): sys.stderr.write("AI Config Error; could NOT read config file(s): %s\n" % list(set(config_files).difference(configs_read))) diff --git a/default/python/common/print_utils.py b/default/python/common/print_utils.py index 5d503c947de..8e82ff62dd9 100644 --- a/default/python/common/print_utils.py +++ b/default/python/common/print_utils.py @@ -4,12 +4,12 @@ For test proposes this module is not import any freeorion runtime libraries. If you improve it somehow, add usage example to __main__ section. """ +from itertools import zip_longest -from itertools import izip_longest from math import ceil -def print_in_columns(items, columns=2): +def print_in_columns(items, columns=2, printer=print): """ Prints collection as columns. @@ -21,18 +21,20 @@ def print_in_columns(items, columns=2): :type items: list|tuple :param columns: number of columns :type columns: int + :param printer: function to print result + :type printer: (str) -> None :return None: """ - row_count = int(ceil((float(len(items)) / columns))) - text_columns = list(izip_longest(*[iter(items)] * row_count, fillvalue='')) + row_count = int(ceil((len(items) / columns))) + text_columns = list(zip_longest(*[iter(items)] * row_count, fillvalue='')) column_widths = (max(len(x) for x in word) for word in text_columns) template = ' '.join('%%-%ss' % w for w in column_widths) for row in zip(*text_columns): - print template % row + printer(template % row) -class Base(object): +class Base: header_fmt = '' fmt = None @@ -78,13 +80,6 @@ class Text(Base): def __init__(self, name, description=None, align='<'): super(Text, self).__init__(name, align=align, description=description) - def to_unicode(self, val): - if not isinstance(val, unicode): - if not isinstance(val, str): - val = str(val) - val = val.decode('utf-8') - return self.fmt.format(val, **self.kwargs) - class Float(Base): fmt = u'{: .{precession}f}' @@ -102,7 +97,7 @@ def __init__(self, name, no_yes='-+', description=None): super(Bool, self).__init__(name, description=description) def to_unicode(self, val): - return self.no_yes[val].decode('utf-8') + return self.no_yes[val] class Sequence(Text): @@ -113,7 +108,7 @@ def to_unicode(self, vals): return self.fmt.format(', '.join(vals), **self.kwargs) -class Table(object): +class Table: def __init__(self, headers, vertical_sep='|', header_sep='=', bottom_sep='-', table_name=None): """ @@ -141,6 +136,9 @@ def __init__(self, headers, vertical_sep='|', header_sep='=', bottom_sep='-', ta self.__rows = [] self.__headers = headers + def __str__(self): + return self.get_table() + def add_row(self, row): self.__rows.append(tuple(h.to_unicode(cell) for h, cell in zip(self.__headers, row))) @@ -151,13 +149,10 @@ def __get_row_separator(self, char, column_widthes): 2 ) - def print_table(self): - print self.get_table() - def get_table(self): columns = [[len(y) for y in x] for x in zip(*self.__rows)] # join size of headers and columns, since columns can be empty - header_and_columns = [[h] + x for h, x in izip_longest([len(x.name) for x in self.__headers], columns, fillvalue=[])] + header_and_columns = [[h] + x for h, x in zip_longest([len(x.name) for x in self.__headers], columns, fillvalue=[])] column_widths = [max(x) for x in header_and_columns] result = [] @@ -190,4 +185,5 @@ def get_table(self): name_width = max(len(x.name) for x in legend) for header in legend: result.append(('*%-*s %s' % (name_width, header.name[:-1], header.description))) - return '\n'.join(x.encode('utf-8') for x in result) + + return '\n'.join(result) diff --git a/default/python/common/profiling.py b/default/python/common/profiling.py index be1ef02f27b..9afde88b6bc 100644 --- a/default/python/common/profiling.py +++ b/default/python/common/profiling.py @@ -2,7 +2,7 @@ import os import pstats import time -import StringIO +from io import StringIO def profile(save_path, sort_by='cumulative'): @@ -14,7 +14,7 @@ def profile(save_path, sort_by='cumulative'): Result stored in user directory in profile catalog. See lne in logs for exact position. :param save_path: path to save profile, for example: - os.path.join(fo.getUserDataDir(), 'profiling', base_path, foAI.foAIstate.uid) + os.path.join(fo.getUserDataDir(), 'profiling', base_path, get_aistate().uid) :type save_path: str :param sort_by: sort stats https://docs.python.org/2/library/profile.html#pstats.Stats.sort_stats :type sort_by: str @@ -28,15 +28,15 @@ def wrapper(*args, **kwargs): result = function(*args, **kwargs) end = time.clock() pr.disable() - print "Profile %s tooks %f s, saved to %s" % (function.__name__, end - start, save_path) - s = StringIO.StringIO() + print("Profile %s tooks %f s, saved to %s" % (function.__name__, end - start, save_path)) + s = StringIO() ps = pstats.Stats(pr, stream=s).strip_dirs().sort_stats(sort_by) ps.print_stats() base_path = os.path.dirname(save_path) if not os.path.exists(base_path): os.makedirs(base_path) - with open(unicode(save_path, 'utf-8'), 'a') as f: + with open(save_path, 'a') as f: f.write(s.getvalue()) f.write('\n') diff --git a/default/python/common/timers.py b/default/python/common/timers.py index 9955180de4c..4ed725677ec 100644 --- a/default/python/common/timers.py +++ b/default/python/common/timers.py @@ -1,7 +1,7 @@ from time import time -class Timer(object): +class Timer: def __init__(self, timer_name): """ Creates timer. Timer name is name that will be in logs. @@ -38,19 +38,19 @@ def _print_timer_table(self, time_table): # TODO: Convert to use table code from here /python/common/print_utils.py#L116 if not time_table: - print "NOT Time Table" + print("NOT Time Table") return max_header = max(len(x[0]) for x in time_table) line_max_size = max_header + 14 - print - print 'Timing for %s:' % self.timer_name - print '=' * line_max_size + print() + print('Timing for %s:' % self.timer_name) + print('=' * line_max_size) for name, val in time_table: - print "%-*s %8d msec" % (max_header, name, val) + print("%-*s %8d msec" % (max_header, name, val)) - print '-' * line_max_size - print ("Total: %8d msec" % sum(x[1] for x in time_table)).rjust(line_max_size) + print('-' * line_max_size) + print(("Total: %8d msec" % sum(x[1] for x in time_table)).rjust(line_max_size)) def print_flat(self): """ @@ -102,15 +102,15 @@ def print_statistics(self): max_header = max(len(x) for x in ordered_names) line_max_size = max_header + 70 - print - print 'Timing statistics for %s:' % self.timer_name - print '=' * line_max_size + print() + print('Timing statistics for %s:' % self.timer_name) + print('=' * line_max_size) for name in ordered_names: - print (("{:<{name_width}} num: {:>6d}, mean: {:>6.2f} msec, std: {:>6.2f} msec, max: {:>6.2f} msec"). - format(name, number_samples[name], means[name], stds[name], maxes[name], name_width=max_header)) + print(("{:<{name_width}} num: {:>6d}, mean: {:>6.2f} msec, std: {:>6.2f} msec, max: {:>6.2f} msec"). + format(name, number_samples[name], means[name], stds[name], maxes[name], name_width=max_header)) - print '=' * line_max_size + print('=' * line_max_size) def stop_and_print(self): """ diff --git a/default/python/interface_mock/result/freeorion.py b/default/python/freeorion.pyi similarity index 81% rename from default/python/interface_mock/result/freeorion.py rename to default/python/freeorion.pyi index 97eed978eab..62e0f87b905 100644 --- a/default/python/interface_mock/result/freeorion.py +++ b/default/python/freeorion.pyi @@ -1,16 +1,103 @@ -class FleetPlan(object): - def ship_designs(self): +# Autogenerated do not modify manually! +# This is a type-hinting python stub file, used by python IDEs to provide type hints. For more information +# about stub files, see https://www.python.org/dev/peps/pep-0484/#stub-files +# During execution, the actual freeOrionAIInterface module is made available via +# a C++ Boost-python process as part of the launch. + +# type: ignore + + +class AccountingInfoVec(object): + def __contains__(self, obj): """ - :rtype: list + :param obj: + :type obj: object + :rtype: bool """ - return list() + return bool() + + def __delitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + + def __iter__(self): + """ + :rtype: object + """ + return object() + + def __len__(self): + """ + :rtype: int + """ + return int() + + def __setitem__(self, obj1, obj2): + """ + :param obj1: + :type obj1: object + :param obj2: + :type obj2: object + :rtype: None + """ + return None + + def append(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def extend(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + +class EffectCause(object): + @property + def causeType(self): + pass + + @property + def customLabel(self): + pass + + @property + def specificCause(self): + pass + +class FleetPlan(object): def name(self): """ :rtype: object """ return object() + def ship_designs(self): + """ + :rtype: list + """ + return list() + class GGColor(object): @property @@ -18,29 +105,33 @@ def a(self): pass @property - def r(self): + def b(self): pass @property - def b(self): + def g(self): pass @property - def g(self): + def r(self): pass class GalaxySetupData(object): @property - def specialsFrequency(self): + def age(self): return galaxySetupOption() @property - def age(self): - return galaxySetupOption() + def gameUID(self): + return str() @property - def starlaneFrequency(self): + def maxAIAggression(self): + return aggression() + + @property + def monsterFrequency(self): return galaxySetupOption() @property @@ -51,76 +142,104 @@ def nativeFrequency(self): def planetDensity(self): return galaxySetupOption() + @property + def seed(self): + return str() + @property def shape(self): return galaxyShape() @property - def seed(self): - return str() + def size(self): + return int() @property - def monsterFrequency(self): + def specialsFrequency(self): return galaxySetupOption() @property - def size(self): - return int() + def starlaneFrequency(self): + return galaxySetupOption() + +class GameRules(object): @property - def maxAIAggression(self): - return aggression() + def empty(self): + return bool() + @property + def getRulesAsStrings(self): + return RuleValueStringsVec() -class IntBoolMap(object): - def __delitem__(self, obj): + def getDescription(self, string): """ - :param obj: - :type obj: object - :rtype: None + :param string: + :type string: str + :rtype: str """ - return None + return str() - def __getitem__(self, obj): + def getDouble(self, string): """ - :param obj: - :type obj: object - :rtype: object + :param string: + :type string: str + :rtype: float """ - return object() + return float() - def __contains__(self, obj): + def getInt(self, string): """ - :param obj: - :type obj: object + :param string: + :type string: str + :rtype: int + """ + return int() + + def getString(self, string): + """ + :param string: + :type string: str + :rtype: str + """ + return str() + + def getToggle(self, string): + """ + :param string: + :type string: str :rtype: bool """ return bool() - def __iter__(self): + def ruleExists(self, string): """ - :rtype: object + :param string: + :type string: str + :rtype: bool """ - return object() + return bool() - def __setitem__(self, obj1, obj2): + def ruleExistsWithType(self, string, rule_type): """ - :param obj1: - :type obj1: object - :param obj2: - :type obj2: object - :rtype: None + :param string: + :type string: str + :param rule_type: + :type rule_type: ruleType + :rtype: bool """ - return None + return bool() - def __len__(self): + +class IntBoolMap(object): + def __contains__(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: bool """ - return int() - + return bool() -class IntDblMap(object): def __delitem__(self, obj): """ :param obj: @@ -137,20 +256,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -161,14 +278,16 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): + +class IntDblMap(object): + def __contains__(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: bool """ - return int() - + return bool() -class IntFltMap(object): def __delitem__(self, obj): """ :param obj: @@ -185,20 +304,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -209,14 +326,16 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): + +class IntFltMap(object): + def __contains__(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: bool """ - return int() - + return bool() -class IntIntMap(object): def __delitem__(self, obj): """ :param obj: @@ -233,20 +352,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -257,23 +374,17 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): - """ - :rtype: int - """ - return int() - -class IntPairVec(object): - def __delitem__(self, obj): +class IntIntMap(object): + def __contains__(self, obj): """ :param obj: :type obj: object - :rtype: None + :rtype: bool """ - return None + return bool() - def extend(self, obj): + def __delitem__(self, obj): """ :param obj: :type obj: object @@ -289,6 +400,30 @@ def __getitem__(self, obj): """ return object() + def __iter__(self): + """ + :rtype: object + """ + return object() + + def __len__(self): + """ + :rtype: int + """ + return int() + + def __setitem__(self, obj1, obj2): + """ + :param obj1: + :type obj1: object + :param obj2: + :type obj2: object + :rtype: None + """ + return None + + +class IntPairVec(object): def __contains__(self, obj): """ :param obj: @@ -297,12 +432,34 @@ def __contains__(self, obj): """ return bool() + def __delitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -321,22 +478,16 @@ def append(self, obj): """ return None - def __len__(self): + def extend(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: None """ - return int() + return None class IntSet(object): - def count(self, number): - """ - :param number: - :type number: int - :rtype: int - """ - return int() - def __contains__(self, number): """ :param number: @@ -357,6 +508,14 @@ def __len__(self): """ return int() + def count(self, number): + """ + :param number: + :type number: int + :rtype: int + """ + return int() + def empty(self): """ :rtype: bool @@ -371,14 +530,6 @@ def size(self): class IntSetSet(object): - def count(self, int_set): - """ - :param int_set: - :type int_set: IntSet - :rtype: int - """ - return int() - def __contains__(self, int_set): """ :param int_set: @@ -399,6 +550,14 @@ def __len__(self): """ return int() + def count(self, int_set): + """ + :param int_set: + :type int_set: IntSet + :rtype: int + """ + return int() + def empty(self): """ :rtype: bool @@ -413,15 +572,15 @@ def size(self): class IntVec(object): - def __delitem__(self, obj): + def __contains__(self, obj): """ :param obj: :type obj: object - :rtype: None + :rtype: bool """ - return None + return bool() - def extend(self, obj): + def __delitem__(self, obj): """ :param obj: :type obj: object @@ -437,19 +596,17 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): + def __iter__(self): """ - :param obj: - :type obj: object - :rtype: bool + :rtype: object """ - return bool() + return object() - def __iter__(self): + def __len__(self): """ - :rtype: iter + :rtype: int """ - return iter() + return int() def __setitem__(self, obj1, obj2): """ @@ -469,33 +626,35 @@ def append(self, obj): """ return None - def __len__(self): + def extend(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: None """ - return int() + return None -class ItemSpec(object): +class UnlockableItem(object): @property - def type(self): + def name(self): pass @property - def name(self): + def type(self): pass -class ItemSpecVec(object): - def __delitem__(self, obj): +class UnlockableItemVec(object): + def __contains__(self, obj): """ :param obj: :type obj: object - :rtype: None + :rtype: bool """ - return None + return bool() - def extend(self, obj): + def __delitem__(self, obj): """ :param obj: :type obj: object @@ -511,20 +670,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -543,14 +700,24 @@ def append(self, obj): """ return None - def __len__(self): + def extend(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: None """ - return int() + return None -class MeterTypeMeterMap(object): +class MeterTypeAccountingInfoVecMap(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() + def __delitem__(self, obj): """ :param obj: @@ -567,6 +734,30 @@ def __getitem__(self, obj): """ return object() + def __iter__(self): + """ + :rtype: object + """ + return object() + + def __len__(self): + """ + :rtype: int + """ + return int() + + def __setitem__(self, obj1, obj2): + """ + :param obj1: + :type obj1: object + :param obj2: + :type obj2: object + :rtype: None + """ + return None + + +class MeterTypeMeterMap(object): def __contains__(self, obj): """ :param obj: @@ -575,12 +766,34 @@ def __contains__(self, obj): """ return bool() + def __delitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -591,12 +804,6 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): - """ - :rtype: int - """ - return int() - class MeterTypeStringPair(object): @property @@ -609,11 +816,19 @@ def string(self): class MonsterFleetPlan(object): - def spawn_rate(self): + def locations(self, item_list): """ - :rtype: float + :param item_list: + :type item_list: list + :rtype: list """ - return float() + return list() + + def name(self): + """ + :rtype: object + """ + return object() def ship_designs(self): """ @@ -627,45 +842,37 @@ def spawn_limit(self): """ return int() - def locations(self, item_list): - """ - :param item_list: - :type item_list: list - :rtype: list - """ - return list() - - def name(self): + def spawn_rate(self): """ - :rtype: object + :rtype: float """ - return object() + return float() class PairIntInt_IntMap(object): - def __delitem__(self, obj): + def __contains__(self, obj): """ :param obj: :type obj: object - :rtype: None + :rtype: bool """ - return None + return bool() - def __getitem__(self, obj): + def __delitem__(self, obj): """ :param obj: :type obj: object - :rtype: object + :rtype: None """ - return object() + return None - def __contains__(self, obj): + def __getitem__(self, obj): """ :param obj: :type obj: object - :rtype: bool + :rtype: object """ - return bool() + return object() def __iter__(self): """ @@ -673,6 +880,12 @@ def __iter__(self): """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -683,12 +896,6 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): - """ - :rtype: int - """ - return int() - class PlayerSetupData(object): @property @@ -696,11 +903,11 @@ def empire_color(self): pass @property - def player_name(self): + def empire_name(self): pass @property - def empire_name(self): + def player_name(self): pass @property @@ -708,7 +915,25 @@ def starting_species(self): pass -class ShipPartMeterMap(object): +class RuleValueStringStringPair(object): + @property + def name(self): + pass + + @property + def value(self): + pass + + +class RuleValueStringsVec(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() + def __delitem__(self, obj): """ :param obj: @@ -725,20 +950,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -749,15 +972,15 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): + def append(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: None """ - return int() - + return None -class ShipSlotVec(object): - def __delitem__(self, obj): + def extend(self, obj): """ :param obj: :type obj: object @@ -765,7 +988,17 @@ def __delitem__(self, obj): """ return None - def extend(self, obj): + +class ShipPartMeterMap(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() + + def __delitem__(self, obj): """ :param obj: :type obj: object @@ -781,6 +1014,30 @@ def __getitem__(self, obj): """ return object() + def __iter__(self): + """ + :rtype: object + """ + return object() + + def __len__(self): + """ + :rtype: int + """ + return int() + + def __setitem__(self, obj1, obj2): + """ + :param obj1: + :type obj1: object + :param obj2: + :type obj2: object + :rtype: None + """ + return None + + +class ShipSlotVec(object): def __contains__(self, obj): """ :param obj: @@ -789,12 +1046,34 @@ def __contains__(self, obj): """ return bool() + def __delitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -813,22 +1092,16 @@ def append(self, obj): """ return None - def __len__(self): + def extend(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: None """ - return int() + return None class StringSet(object): - def count(self, string): - """ - :param string: - :type string: str - :rtype: int - """ - return int() - def __contains__(self, string): """ :param string: @@ -849,6 +1122,14 @@ def __len__(self): """ return int() + def count(self, string): + """ + :param string: + :type string: str + :rtype: int + """ + return int() + def empty(self): """ :rtype: bool @@ -863,15 +1144,15 @@ def size(self): class StringVec(object): - def __delitem__(self, obj): + def __contains__(self, obj): """ :param obj: :type obj: object - :rtype: None + :rtype: bool """ - return None + return bool() - def extend(self, obj): + def __delitem__(self, obj): """ :param obj: :type obj: object @@ -887,20 +1168,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: iter """ return iter() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -919,14 +1198,24 @@ def append(self, obj): """ return None - def __len__(self): + def extend(self, obj): """ - :rtype: int + :param obj: + :type obj: object + :rtype: None """ - return int() + return None -class VisibilityIntMap(object): +class TargetIDAccountingMapMap(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() + def __delitem__(self, obj): """ :param obj: @@ -943,6 +1232,30 @@ def __getitem__(self, obj): """ return object() + def __iter__(self): + """ + :rtype: object + """ + return object() + + def __len__(self): + """ + :rtype: int + """ + return int() + + def __setitem__(self, obj1, obj2): + """ + :param obj1: + :type obj1: object + :param obj2: + :type obj2: object + :rtype: None + """ + return None + + +class VisibilityIntMap(object): def __contains__(self, obj): """ :param obj: @@ -951,12 +1264,34 @@ def __contains__(self, obj): """ return bool() + def __delitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: None + """ + return None + + def __getitem__(self, obj): + """ + :param obj: + :type obj: object + :rtype: object + """ + return object() + def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -967,12 +1302,6 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): - """ - :rtype: int - """ - return int() - class buildingType(object): @property @@ -985,13 +1314,13 @@ def description(self): @property def dump(self): - return str() + pass @property def name(self): return str() - def canBeProduced(self, number1, number2): + def canBeEnqueued(self, number1, number2): """ :param number1: :type number1: int @@ -1001,25 +1330,15 @@ def canBeProduced(self, number1, number2): """ return bool() - def productionTime(self, number1, number2): + def canBeProduced(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: int + :rtype: bool """ - return int() - - def perTurnCost(self, number1, number2): - """ - :param number1: - :type number1: int - :param number2: - :type number2: int - :rtype: float - """ - return float() + return bool() def captureResult(self, number1, number2, number3, boolean): """ @@ -1035,6 +1354,16 @@ def captureResult(self, number1, number2, number3, boolean): """ return captureResult() + def perTurnCost(self, number1, number2): + """ + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: float + """ + return float() + def productionCost(self, number1, number2): """ :param number1: @@ -1045,34 +1374,34 @@ def productionCost(self, number1, number2): """ return float() - def canBeEnqueued(self, number1, number2): + def productionTime(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: bool + :rtype: int """ - return bool() + return int() class diplomaticMessage(object): - @property - def type(self): - pass - @property def recipient(self): - pass + return int() @property def sender(self): - pass + return int() + + @property + def type(self): + return diplomaticMessageType() class diplomaticStatusUpdate(object): @property - def status(self): + def empire1(self): pass @property @@ -1080,136 +1409,158 @@ def empire2(self): pass @property - def empire1(self): + def status(self): pass class empire(object): @property - def capitalID(self): - return int() + def allShipDesigns(self): + pass @property - def productionPoints(self): - return float() + def availableBuildingTypes(self): + pass @property - def productionQueue(self): - return productionQueue() + def availableShipDesigns(self): + pass @property - def exploredSystemIDs(self): - return IntSet() + def availableShipHulls(self): + pass @property - def planetsWithWastedPP(self): - return IntSetSet() + def availableShipParts(self): + pass @property - def playerName(self): - return str() + def availableTechs(self): + pass @property - def fleetSupplyableSystemIDs(self): - return IntSet() + def capitalID(self): + pass @property - def planetsWithAllocatedPP(self): - return resPoolMap() + def colour(self): + pass @property - def won(self): - return bool() + def eliminated(self): + pass @property - def availableShipParts(self): - return StringSet() + def empireID(self): + pass @property - def availableBuildingTypes(self): - return StringSet() + def exploredSystemIDs(self): + pass @property - def systemSupplyRanges(self): - return IntFltMap() + def fleetSupplyableSystemIDs(self): + pass @property - def allShipDesigns(self): - return IntSet() + def name(self): + pass @property - def planetsWithAvailablePP(self): - return resPoolMap() + def planetsWithAllocatedPP(self): + pass @property - def researchQueue(self): - return researchQueue() + def planetsWithAvailablePP(self): + pass @property - def eliminated(self): - return bool() + def planetsWithWastedPP(self): + pass @property - def empireID(self): - return int() + def playerName(self): + pass @property - def name(self): - return str() + def productionPoints(self): + pass @property - def colour(self): - return GGColor() + def productionQueue(self): + pass @property - def availableShipHulls(self): - return StringSet() + def researchQueue(self): + pass @property - def availableShipDesigns(self): - return IntSet() + def supplyUnobstructedSystems(self): + pass @property - def supplyUnobstructedSystems(self): - return IntSet() + def systemSupplyRanges(self): + pass @property - def availableTechs(self): - return StringSet() + def won(self): + pass - def resourceAvailable(self, resource_type): + def buildingTypeAvailable(self, string): """ - :param resource_type: - :type resource_type: resourceType - :rtype: float + :param string: + :type string: str + :rtype: bool """ - return float() + return bool() - def techResearched(self, string): + def canBuild(self, build_type, string, number): """ + :param build_type: + :type build_type: buildType :param string: :type string: str + :param number: + :type number: int :rtype: bool """ return bool() - def resourceProduction(self, resource_type): + def getMeter(self, string): + """ + Returns the empire meter with the indicated name (string). + + :param string: + :type string: str + :rtype: meter + """ + return meter() + + def getResourcePool(self, resource_type): """ :param resource_type: :type resource_type: resourceType - :rtype: float + :rtype: resPool """ - return float() + return resPool() - def productionCostAndTime(self, production_queue_element): + def getSitRep(self, number): """ - :param production_queue_element: - :type production_queue_element: productionQueueElement - :rtype: object + :param number: + :type number: int + :rtype: sitrep """ - return object() + return sitrep() - def shipDesignAvailable(self, number): + def getTechStatus(self, string): + """ + :param string: + :type string: str + :rtype: techStatus + """ + return techStatus() + + def hasExploredSystem(self, number): """ :param number: :type number: int @@ -1225,91 +1576,89 @@ def numSitReps(self, number): """ return int() - def getResourcePool(self, resource_type): + def obstructedStarlanes(self): """ - :param resource_type: - :type resource_type: resourceType - :rtype: resPool + :rtype: IntPairVec """ - return resPool() + return IntPairVec() - def resourceStockpile(self, resource_type): + def population(self): """ - :param resource_type: - :type resource_type: resourceType :rtype: float """ return float() - def hasExploredSystem(self, number): + def preservedLaneTravel(self, number1, number2): """ - :param number: - :type number: int + :param number1: + :type number1: int + :param number2: + :type number2: int :rtype: bool """ return bool() - def buildingTypeAvailable(self, string): + def productionCostAndTime(self, production_queue_element): + """ + :param production_queue_element: + :type production_queue_element: productionQueueElement + :rtype: object + """ + return object() + + def researchProgress(self, string): """ :param string: :type string: str - :rtype: bool + :rtype: float """ - return bool() + return float() - def obstructedStarlanes(self): + def resourceAvailable(self, resource_type): """ - :rtype: IntPairVec + :param resource_type: + :type resource_type: resourceType + :rtype: float """ - return IntPairVec() + return float() - def population(self): + def resourceProduction(self, resource_type): """ + :param resource_type: + :type resource_type: resourceType :rtype: float """ return float() - def supplyProjections(self): + def resourceStockpile(self, resource_type): """ - :rtype: IntIntMap + :param resource_type: + :type resource_type: resourceType + :rtype: float """ - return IntIntMap() + return float() - def canBuild(self, build_type, string, number): + def shipDesignAvailable(self, number): """ - :param build_type: - :type build_type: buildType - :param string: - :type string: str :param number: :type number: int :rtype: bool """ return bool() - def getSitRep(self, number): - """ - :param number: - :type number: int - :rtype: sitrep - """ - return sitrep() - - def getTechStatus(self, string): + def supplyProjections(self): """ - :param string: - :type string: str - :rtype: techStatus + :rtype: dict[int, int] """ - return techStatus() + return dict() - def researchProgress(self, string): + def techResearched(self, string): """ :param string: :type string: str - :rtype: float + :rtype: bool """ - return float() + return bool() class fieldType(object): @@ -1319,69 +1668,49 @@ def description(self): @property def dump(self): - return str() + pass @property def name(self): return str() -class hullType(object): +class shipHull(object): @property def costTimeLocationInvariant(self): - pass - - @property - def name(self): - pass - - @property - def numSlots(self): - pass + return bool() @property - def structure(self): - pass + def fuel(self): + return float() @property - def stealth(self): - pass + def name(self): + return str() @property - def fuel(self): - pass + def numSlots(self): + return int() @property def slots(self): - pass + return ShipSlotVec() @property def speed(self): - pass + return float() @property def starlaneSpeed(self): - pass + return float() - def productionCost(self, number1, number2): - """ - :param number1: - :type number1: int - :param number2: - :type number2: int - :rtype: float - """ + @property + def stealth(self): return float() - def productionTime(self, number1, number2): - """ - :param number1: - :type number1: int - :param number2: - :type number2: int - :rtype: int - """ - return int() + @property + def structure(self): + return float() def hasTag(self, string): """ @@ -1399,6 +1728,26 @@ def numSlotsOfSlotType(self, ship_slot_type): """ return int() + def productionCost(self, number1, number2): + """ + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: float + """ + return float() + + def productionTime(self, number1, number2): + """ + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: int + """ + return int() + class meter(object): @property @@ -1406,48 +1755,46 @@ def current(self): pass @property - def initial(self): + def dump(self): pass @property - def dump(self): + def initial(self): pass -class partType(object): +class shipPart(object): @property - def mountableSlotTypes(self): - pass + def capacity(self): + return float() @property def costTimeLocationInvariant(self): - pass + return bool() @property - def capacity(self): - pass + def mountableSlotTypes(self): + return ShipSlotVec() @property def name(self): - pass + return str() @property def partClass(self): - pass + return shipPartClass() @property def secondaryStat(self): - pass + return float() - def productionTime(self, number1, number2): + def canMountInSlotType(self, ship_slot_type): """ - :param number1: - :type number1: int - :param number2: - :type number2: int - :rtype: int + :param ship_slot_type: + :type ship_slot_type: shipSlotType + :rtype: bool """ - return int() + return bool() def productionCost(self, number1, number2): """ @@ -1459,20 +1806,18 @@ def productionCost(self, number1, number2): """ return float() - def canMountInSlotType(self, ship_slot_type): + def productionTime(self, number1, number2): """ - :param ship_slot_type: - :type ship_slot_type: shipSlotType - :rtype: bool + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: int """ - return bool() + return int() class popCenter(object): - @property - def nextTurnPopGrowth(self): - pass - @property def speciesName(self): pass @@ -1484,19 +1829,19 @@ def allocatedPP(self): pass @property - def empty(self): + def empireID(self): pass @property - def totalSpent(self): + def empty(self): pass @property - def empireID(self): + def size(self): pass @property - def size(self): + def totalSpent(self): pass def __getitem__(self, number): @@ -1513,13 +1858,11 @@ def __iter__(self): """ return object() - def objectsWithWastedPP(self, res_pool): + def __len__(self): """ - :param res_pool: - :type res_pool: resPool - :rtype: IntSetSet + :rtype: int """ - return IntSetSet() + return int() def availablePP(self, res_pool): """ @@ -1529,20 +1872,22 @@ def availablePP(self, res_pool): """ return resPoolMap() - def __len__(self): + def objectsWithWastedPP(self, res_pool): """ - :rtype: int + :param res_pool: + :type res_pool: resPool + :rtype: IntSetSet """ - return int() + return IntSetSet() class productionQueueElement(object): @property - def buildType(self): + def allocation(self): pass @property - def name(self): + def allowedStockpile(self): pass @property @@ -1550,19 +1895,23 @@ def blocksize(self): pass @property - def turnsLeft(self): + def buildType(self): pass @property - def allocation(self): + def designID(self): pass @property - def designID(self): + def locationID(self): pass @property - def locationID(self): + def name(self): + pass + + @property + def paused(self): pass @property @@ -1573,12 +1922,24 @@ def progress(self): def remaining(self): pass + @property + def turnsLeft(self): + pass + class resPool(object): pass class resPoolMap(object): + def __contains__(self, obj): + """ + :param obj: + :type obj: object + :rtype: bool + """ + return bool() + def __delitem__(self, obj): """ :param obj: @@ -1595,20 +1956,18 @@ def __getitem__(self, obj): """ return object() - def __contains__(self, obj): - """ - :param obj: - :type obj: object - :rtype: bool - """ - return bool() - def __iter__(self): """ :rtype: object """ return object() + def __len__(self): + """ + :rtype: int + """ + return int() + def __setitem__(self, obj1, obj2): """ :param obj1: @@ -1619,38 +1978,24 @@ def __setitem__(self, obj1, obj2): """ return None - def __len__(self): - """ - :rtype: int - """ - return int() - class researchQueue(object): @property - def size(self): + def empireID(self): pass @property - def totalSpent(self): + def empty(self): pass @property - def empireID(self): + def size(self): pass @property - def empty(self): + def totalSpent(self): pass - def __getitem__(self, number): - """ - :param number: - :type number: int - :rtype: researchQueueElement - """ - return researchQueueElement() - def __contains__(self, research_queue_element): """ :param research_queue_element: @@ -1659,6 +2004,14 @@ def __contains__(self, research_queue_element): """ return bool() + def __getitem__(self, number): + """ + :param number: + :type number: int + :rtype: researchQueueElement + """ + return researchQueueElement() + def __iter__(self): """ :rtype: object @@ -1696,136 +2049,140 @@ def turnsLeft(self): class resourceCenter(object): @property - def turnsSinceFocusChange(self): + def availableFoci(self): pass @property - def availableFoci(self): + def focus(self): pass @property - def focus(self): + def turnsSinceFocusChange(self): pass class shipDesign(object): @property - def costTimeLocationInvariant(self): - return bool() + def attack(self): + pass @property - def dump(self): - return str() + def attackStats(self): + pass @property def canColonize(self): - return bool() + pass @property - def hull_type(self): - return hullType() + def canInvade(self): + pass @property - def tradeGeneration(self): - return float() + def colonyCapacity(self): + pass @property - def detection(self): - return float() + def costTimeLocationInvariant(self): + pass @property def defense(self): - return float() + pass @property - def speed(self): - return float() + def description(self): + pass @property - def id(self): - return int() + def designedOnTurn(self): + pass @property - def isMonster(self): - return bool() + def detection(self): + pass @property - def stealth(self): - return float() + def dump(self): + pass @property - def attack(self): - return float() + def fuel(self): + pass @property - def parts(self): - return StringVec() + def hasFighters(self): + pass @property - def fuel(self): - return float() + def hull(self): + pass @property - def shields(self): - return float() + def ship_hull(self): + pass @property - def description(self): - return str() + def id(self): + pass @property - def isArmed(self): - return bool() + def industryGeneration(self): + pass @property - def hull(self): - return str() + def isArmed(self): + pass @property - def designedOnTurn(self): - return int() + def isMonster(self): + pass @property - def colonyCapacity(self): - return float() + def name(self): + pass @property - def structure(self): - return float() + def parts(self): + pass @property def researchGeneration(self): - return float() + pass @property - def canInvade(self): - return bool() + def shields(self): + pass @property - def name(self): - return str() + def speed(self): + pass @property - def attackStats(self): - return IntVec() + def stealth(self): + pass @property - def troopCapacity(self): - return float() + def structure(self): + pass @property - def industryGeneration(self): - return float() + def tradeGeneration(self): + pass - def productionTime(self, number1, number2): + @property + def troopCapacity(self): + pass + + def perTurnCost(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: int + :rtype: float """ - return int() + return float() def productionCost(self, number1, number2): """ @@ -1847,15 +2204,15 @@ def productionLocationForEmpire(self, number1, number2): """ return bool() - def perTurnCost(self, number1, number2): + def productionTime(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: float + :rtype: int """ - return float() + return int() class sitrep(object): @@ -1864,50 +2221,50 @@ def getTags(self): pass @property - def typeString(self): + def getTurn(self): pass @property - def getTurn(self): + def typeString(self): pass - def getDataString(self, string): + def getDataIDNumber(self, string): """ :param string: :type string: str - :rtype: str + :rtype: int """ - return str() + return int() - def getDataIDNumber(self, string): + def getDataString(self, string): """ :param string: :type string: str - :rtype: int + :rtype: str """ - return int() + return str() class special(object): @property - def name(self): + def description(self): return str() @property def dump(self): - return str() + pass @property - def spawnrate(self): - return float() + def name(self): + return str() @property def spawnlimit(self): return int() @property - def description(self): - return str() + def spawnrate(self): + return float() def initialCapacity(self, number): """ @@ -1920,40 +2277,40 @@ def initialCapacity(self, number): class species(object): @property - def description(self): - return str() + def canColonize(self): + return bool() @property - def dump(self): - return str() + def canProduceShips(self): + return bool() @property - def tags(self): - return StringSet() + def description(self): + return str() @property - def canColonize(self): - return bool() + def dump(self): + pass @property def foci(self): return StringVec() @property - def canProduceShips(self): - return bool() + def homeworlds(self): + return IntSet() @property - def preferredFocus(self): + def name(self): return str() @property - def homeworlds(self): - return IntSet() + def preferredFocus(self): + return str() @property - def name(self): - return str() + def tags(self): + return StringSet() def getPlanetEnvironment(self, planet_type): """ @@ -1969,17 +2326,13 @@ class tech(object): def category(self): return str() - @property - def unlockedItems(self): - return ItemSpecVec() - @property def description(self): return str() @property - def unlockedTechs(self): - return StringSet() + def name(self): + return str() @property def prerequisites(self): @@ -1990,10 +2343,14 @@ def shortDescription(self): return str() @property - def name(self): - return str() + def unlockedItems(self): + return UnlockableItemVec() - def researchCost(self, number): + @property + def unlockedTechs(self): + return StringSet() + + def perTurnCost(self, number): """ :param number: :type number: int @@ -2009,7 +2366,7 @@ def recursivePrerequisites(self, number): """ return StringVec() - def perTurnCost(self, number): + def researchCost(self, number): """ :param number: :type number: int @@ -2028,42 +2385,44 @@ def researchTime(self, number): class universe(object): @property - def shipIDs(self): + def allObjectIDs(self): return IntVec() @property - def fieldIDs(self): + def buildingIDs(self): return IntVec() @property - def fleetIDs(self): + def effectAccounting(self): + return TargetIDAccountingMapMap() + + @property + def fieldIDs(self): return IntVec() @property - def planetIDs(self): + def fleetIDs(self): return IntVec() @property - def buildingIDs(self): + def planetIDs(self): return IntVec() @property - def allObjectIDs(self): + def shipIDs(self): return IntVec() @property def systemIDs(self): return IntVec() - def jumpDistance(self, number1, number2): + def destroyedObjectIDs(self, number): """ - :param number1: - :type number1: int - :param number2: - :type number2: int - :rtype: int + :param number: + :type number: int + :rtype: IntSet """ - return int() + return IntSet() def dump(self): """ @@ -2071,15 +2430,49 @@ def dump(self): """ return None - def linearDistance(self, number1, number2): + def getBuilding(self, number): + """ + :param number: + :type number: int + :rtype: building + """ + return building() + + def getField(self, number): + """ + :param number: + :type number: int + :rtype: field + """ + return field() + + def getFleet(self, number): + """ + :param number: + :type number: int + :rtype: fleet + """ + return fleet() + + def getGenericShipDesign(self, string): + """ + Returns the ship design (ShipDesign) with the indicated name (string). + + :param string: + :type string: str + :rtype: shipDesign + """ + return shipDesign() + + def getImmediateNeighbors(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: float + :rtype: IntVec """ - return float() + return IntVec() def getObject(self, number): """ @@ -2105,17 +2498,33 @@ def getShip(self, number): """ return ship() - def systemsConnected(self, number1, number2, number3): + def getSystem(self, number): + """ + :param number: + :type number: int + :rtype: system + """ + return system() + + def getSystemNeighborsMap(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :param number3: - :type number3: int - :rtype: bool + :rtype: IntDblMap """ - return bool() + return IntDblMap() + + def getVisibility(self, number1, number2): + """ + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: visibility + """ + return visibility() def getVisibilityTurnsMap(self, number1, number2): """ @@ -2123,10 +2532,22 @@ def getVisibilityTurnsMap(self, number1, number2): :type number1: int :param number2: :type number2: int - :rtype: VisibilityIntMap + :rtype: dict[int, int] """ return dict() + def jumpDistance(self, number1, number2): + """ + If two system ids are passed or both objects are within a system, return the jump distance between the two systems. If one object (e.g. a fleet) is on a starlane, then calculate the jump distance from both ends of the starlane to the target system and return the smaller one. + + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: int + """ + return int() + def leastJumpsPath(self, number1, number2, number3): """ :param number1: @@ -2139,20 +2560,26 @@ def leastJumpsPath(self, number1, number2, number3): """ return IntVec() - def getFleet(self, number): + def linearDistance(self, number1, number2): """ - :param number: - :type number: int - :rtype: fleet + :param number1: + :type number1: int + :param number2: + :type number2: int + :rtype: float """ - return fleet() + return float() - def getImmediateNeighbors(self, number1, number2): + def shortestNonHostilePath(self, number1, number2, number3): """ + Shortest sequence of System ids and distance from System (number1) to System (number2) with no hostile Fleets as determined by visibility of Empire (number3). (number3) must be a valid empire. + :param number1: :type number1: int :param number2: :type number2: int + :param number3: + :type number3: int :rtype: IntVec """ return IntVec() @@ -2169,39 +2596,15 @@ def shortestPath(self, number1, number2, number3): """ return IntVec() - def updateMeterEstimates(self, item_list): - """ - :param item_list: - :type item_list: list - :rtype: None - """ - return None - - def getField(self, number): - """ - :param number: - :type number: int - :rtype: field - """ - return field() - - def destroyedObjectIDs(self, number): - """ - :param number: - :type number: int - :rtype: IntSet - """ - return IntSet() - - def getSystemNeighborsMap(self, number1, number2): + def shortestPathDistance(self, number1, number2): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: IntDblMap + :rtype: float """ - return IntDblMap() + return float() def systemHasStarlane(self, number1, number2): """ @@ -2213,82 +2616,66 @@ def systemHasStarlane(self, number1, number2): """ return bool() - def getGenericShipDesign(self, string): - """ - Returns the ship design (ShipDesign) with the indicated name (string). - - :param string: - :type string: str - :rtype: shipDesign - """ - return shipDesign() - - def getVisibility(self, number1, number2): + def systemsConnected(self, number1, number2, number3): """ :param number1: :type number1: int :param number2: :type number2: int - :rtype: visibility - """ - return visibility() - - def getSystem(self, number): - """ - :param number: - :type number: int - :rtype: system + :param number3: + :type number3: int + :rtype: bool """ - return system() + return bool() - def getBuilding(self, number): + def updateMeterEstimates(self, item_list): """ - :param number: - :type number: int - :rtype: building + :param item_list: + :type item_list: list + :rtype: None """ - return building() + return None class universeObject(object): @property - def dump(self): + def ageInTurns(self): pass @property - def unowned(self): + def containedObjects(self): pass @property - def meters(self): + def containerObject(self): pass @property - def owner(self): + def creationTurn(self): pass @property - def id(self): + def dump(self): pass @property - def containerObject(self): + def id(self): pass @property - def creationTurn(self): + def meters(self): pass @property - def containedObjects(self): + def name(self): pass @property - def tags(self): + def owner(self): pass @property - def ageInTurns(self): + def specials(self): pass @property @@ -2296,22 +2683,22 @@ def systemID(self): pass @property - def name(self): + def tags(self): pass @property - def specials(self): + def unowned(self): pass @property - def y(self): + def x(self): pass @property - def x(self): + def y(self): pass - def contains(self, number): + def containedBy(self, number): """ :param number: :type number: int @@ -2319,15 +2706,15 @@ def contains(self, number): """ return bool() - def hasTag(self, string): + def contains(self, number): """ - :param string: - :type string: str + :param number: + :type number: int :rtype: bool """ return bool() - def nextTurnCurrentMeterValue(self, meter_type): + def currentMeterValue(self, meter_type): """ :param meter_type: :type meter_type: meterType @@ -2335,23 +2722,31 @@ def nextTurnCurrentMeterValue(self, meter_type): """ return float() - def initialMeterValue(self, meter_type): + def getMeter(self, meter_type): """ :param meter_type: :type meter_type: meterType - :rtype: float + :rtype: meter """ - return float() + return meter() - def containedBy(self, number): + def hasSpecial(self, string): """ - :param number: - :type number: int + :param string: + :type string: str :rtype: bool """ return bool() - def currentMeterValue(self, meter_type): + def hasTag(self, string): + """ + :param string: + :type string: str + :rtype: bool + """ + return bool() + + def initialMeterValue(self, meter_type): """ :param meter_type: :type meter_type: meterType @@ -2359,14 +2754,6 @@ def currentMeterValue(self, meter_type): """ return float() - def specialAddedOnTurn(self, string): - """ - :param string: - :type string: str - :rtype: int - """ - return int() - def ownedBy(self, number): """ :param number: @@ -2375,45 +2762,51 @@ def ownedBy(self, number): """ return bool() - def getMeter(self, meter_type): - """ - :param meter_type: - :type meter_type: meterType - :rtype: meter - """ - return meter() - - def hasSpecial(self, string): + def specialAddedOnTurn(self, string): """ :param string: :type string: str - :rtype: bool + :rtype: int """ - return bool() + return int() + + +class AccountingInfo(EffectCause): + @property + def meterChange(self): + pass + + @property + def meterRunningTotal(self): + pass + + @property + def sourceID(self): + pass class building(universeObject): @property def buildingTypeName(self): - return str() + pass @property - def producedByEmpireID(self): - return int() + def orderedScrapped(self): + pass @property def planetID(self): - return int() + pass @property - def orderedScrapped(self): - return bool() + def producedByEmpireID(self): + pass class field(universeObject): @property def fieldTypeName(self): - return str() + pass def inField(self, base_object): """ @@ -2426,216 +2819,158 @@ def inField(self, base_object): class fleet(universeObject): @property - def hasOutpostShips(self): - return bool() + def aggressive(self): + pass @property - def shipIDs(self): - return IntSet() + def canChangeDirectionEnRoute(self): + pass @property - def hasFighterShips(self): - return bool() + def empty(self): + pass @property - def speed(self): - return float() + def finalDestinationID(self): + pass @property - def previousSystemID(self): - return int() + def fuel(self): + pass @property - def numShips(self): - return int() + def hasArmedShips(self): + pass @property def hasColonyShips(self): - return bool() - - @property - def canChangeDirectionEnRoute(self): - return bool() - - @property - def nextSystemID(self): - return int() + pass @property - def finalDestinationID(self): - return int() + def hasFighterShips(self): + pass @property def hasMonsters(self): - return bool() - - @property - def hasArmedShips(self): - return bool() - - @property - def fuel(self): - return float() + pass @property - def aggressive(self): - return bool() + def hasOutpostShips(self): + pass @property def hasTroopShips(self): - return bool() + pass @property def maxFuel(self): - return float() - - @property - def empty(self): - return bool() - - -class planet(universeObject, popCenter, resourceCenter): - @property - def originalType(self): - return planetType() - - @property - def nextLargerPlanetSize(self): - return planetSize() - - @property - def distanceFromOriginalType(self): - return int() + pass @property - def clockwiseNextPlanetType(self): - return planetType() + def nextSystemID(self): + pass @property - def nextSmallerPlanetSize(self): - return planetSize() + def numShips(self): + pass @property - def buildingIDs(self): - return IntSet() + def previousSystemID(self): + pass @property - def OrbitalPeriod(self): - return float() + def shipIDs(self): + pass @property - def counterClockwiseNextPlanetType(self): - return planetType() + def speed(self): + pass - @property - def RotationalPeriod(self): - return float() +class ship(universeObject): @property - def type(self): - return planetType() + def arrivedOnTurn(self): + pass @property - def InitialOrbitalPosition(self): - return float() + def canBombard(self): + pass @property - def size(self): - return planetSize() - - def nextBetterPlanetTypeForSpecies(self, string): - """ - :param string: - :type string: str - :rtype: planetType - """ - return planetType() - - def environmentForSpecies(self, string): - """ - :param string: - :type string: str - :rtype: planetEnvironment - """ - return planetEnvironment() - - def OrbitalPositionOnTurn(self, number): - """ - :param number: - :type number: int - :rtype: float - """ - return float() - + def canColonize(self): + pass -class ship(universeObject): @property - def partMeters(self): - return ShipPartMeterMap() + def canInvade(self): + pass @property - def speciesName(self): - return str() + def colonyCapacity(self): + pass @property - def orderedScrapped(self): - return bool() + def design(self): + pass @property - def isArmed(self): - return bool() + def designID(self): + pass @property - def troopCapacity(self): - return float() + def fleetID(self): + pass @property - def canColonize(self): - return bool() + def hasFighters(self): + pass @property - def canInvade(self): - return bool() + def isArmed(self): + pass @property - def designID(self): - return int() + def isMonster(self): + pass @property - def producedByEmpireID(self): - return int() + def lastResuppliedOnTurn(self): + pass @property - def isMonster(self): - return bool() + def lastTurnActiveInCombat(self): + pass @property - def design(self): - return shipDesign() + def orderedColonizePlanet(self): + pass @property def orderedInvadePlanet(self): - return int() + pass @property - def colonyCapacity(self): - return float() + def orderedScrapped(self): + pass @property - def fleetID(self): - return int() + def partMeters(self): + pass @property - def canBombard(self): - return bool() + def producedByEmpireID(self): + pass + + @property + def speciesName(self): + pass @property def speed(self): - return float() + pass @property - def orderedColonizePlanet(self): - return int() + def troopCapacity(self): + pass def currentPartMeterValue(self, meter_type, string): """ @@ -2660,44 +2995,44 @@ def initialPartMeterValue(self, meter_type, string): class system(universeObject): @property - def numStarlanes(self): - return int() + def buildingIDs(self): + pass @property - def fleetIDs(self): - return IntSet() + def fieldIDs(self): + pass @property - def shipIDs(self): - return IntSet() + def fleetIDs(self): + pass @property - def starlanesWormholes(self): - return IntBoolMap() + def lastTurnBattleHere(self): + pass @property - def fieldIDs(self): - return IntSet() + def numStarlanes(self): + pass @property def numWormholes(self): - return int() + pass @property - def buildingIDs(self): - return IntSet() + def planetIDs(self): + pass @property - def starType(self): - return starType() + def shipIDs(self): + pass @property - def lastTurnBattleHere(self): - return int() + def starType(self): + pass @property - def planetIDs(self): - return IntSet() + def starlanesWormholes(self): + pass def HasStarlaneToSystemID(self, number): """ @@ -2718,6 +3053,92 @@ def HasWormholeToSystemID(self, number): return bool() +class planet(universeObject, popCenter, resourceCenter): + @property + def InitialOrbitalPosition(self): + pass + + @property + def LastTurnAttackedByShip(self): + pass + + @property + def LastTurnConquered(self): + pass + + @property + def OrbitalPeriod(self): + pass + + @property + def RotationalPeriod(self): + pass + + @property + def buildingIDs(self): + pass + + @property + def clockwiseNextPlanetType(self): + pass + + @property + def counterClockwiseNextPlanetType(self): + pass + + @property + def distanceFromOriginalType(self): + pass + + @property + def habitableSize(self): + pass + + @property + def nextLargerPlanetSize(self): + pass + + @property + def nextSmallerPlanetSize(self): + pass + + @property + def originalType(self): + pass + + @property + def size(self): + pass + + @property + def type(self): + pass + + def OrbitalPositionOnTurn(self, number): + """ + :param number: + :type number: int + :rtype: float + """ + return float() + + def environmentForSpecies(self, string): + """ + :param string: + :type string: str + :rtype: planetEnvironment + """ + return planetEnvironment() + + def nextBetterPlanetTypeForSpecies(self, string): + """ + :param string: + :type string: str + :rtype: planetType + """ + return planetType() + + class Enum(int): """Enum stub for docs, not really present in fo""" def __new__(cls, *args, **kwargs): @@ -2752,10 +3173,12 @@ def __init__(self, numerator, name): building = None # buildType(1, "building") ship = None # buildType(2, "ship") + stockpile = None # buildType(4, "stockpile") buildType.building = buildType(1, "building") buildType.ship = buildType(2, "ship") +buildType.stockpile = buildType(4, "stockpile") class captureResult(Enum): @@ -2779,15 +3202,23 @@ def __init__(self, numerator, name): noMessage = None # diplomaticMessageType(-1, "noMessage") warDeclaration = None # diplomaticMessageType(0, "warDeclaration") peaceProposal = None # diplomaticMessageType(1, "peaceProposal") - acceptProposal = None # diplomaticMessageType(2, "acceptProposal") - cancelProposal = None # diplomaticMessageType(3, "cancelProposal") + acceptPeaceProposal = None # diplomaticMessageType(2, "acceptPeaceProposal") + alliesProposal = None # diplomaticMessageType(3, "alliesProposal") + acceptAlliesProposal = None # diplomaticMessageType(4, "acceptAlliesProposal") + endAllies = None # diplomaticMessageType(5, "endAllies") + cancelProposal = None # diplomaticMessageType(6, "cancelProposal") + rejectProposal = None # diplomaticMessageType(7, "rejectProposal") diplomaticMessageType.noMessage = diplomaticMessageType(-1, "noMessage") diplomaticMessageType.warDeclaration = diplomaticMessageType(0, "warDeclaration") diplomaticMessageType.peaceProposal = diplomaticMessageType(1, "peaceProposal") -diplomaticMessageType.acceptProposal = diplomaticMessageType(2, "acceptProposal") -diplomaticMessageType.cancelProposal = diplomaticMessageType(3, "cancelProposal") +diplomaticMessageType.acceptPeaceProposal = diplomaticMessageType(2, "acceptPeaceProposal") +diplomaticMessageType.alliesProposal = diplomaticMessageType(3, "alliesProposal") +diplomaticMessageType.acceptAlliesProposal = diplomaticMessageType(4, "acceptAlliesProposal") +diplomaticMessageType.endAllies = diplomaticMessageType(5, "endAllies") +diplomaticMessageType.cancelProposal = diplomaticMessageType(6, "cancelProposal") +diplomaticMessageType.rejectProposal = diplomaticMessageType(7, "rejectProposal") class diplomaticStatus(Enum): @@ -2796,10 +3227,40 @@ def __init__(self, numerator, name): war = None # diplomaticStatus(0, "war") peace = None # diplomaticStatus(1, "peace") + allied = None # diplomaticStatus(2, "allied") diplomaticStatus.war = diplomaticStatus(0, "war") diplomaticStatus.peace = diplomaticStatus(1, "peace") +diplomaticStatus.allied = diplomaticStatus(2, "allied") + + +class effectsCauseType(Enum): + def __init__(self, numerator, name): + self.name = name + + invalid = None # effectsCauseType(-1, "invalid") + unknown = None # effectsCauseType(0, "unknown") + inherent = None # effectsCauseType(1, "inherent") + tech = None # effectsCauseType(2, "tech") + building = None # effectsCauseType(3, "building") + field = None # effectsCauseType(4, "field") + special = None # effectsCauseType(5, "special") + species = None # effectsCauseType(6, "species") + shipPart = None # effectsCauseType(7, "shipPart") + shipHull = None # effectsCauseType(8, "shipHull") + + +effectsCauseType.invalid = effectsCauseType(-1, "invalid") +effectsCauseType.unknown = effectsCauseType(0, "unknown") +effectsCauseType.inherent = effectsCauseType(1, "inherent") +effectsCauseType.tech = effectsCauseType(2, "tech") +effectsCauseType.building = effectsCauseType(3, "building") +effectsCauseType.field = effectsCauseType(4, "field") +effectsCauseType.special = effectsCauseType(5, "special") +effectsCauseType.species = effectsCauseType(6, "species") +effectsCauseType.shipPart = effectsCauseType(7, "shipPart") +effectsCauseType.shipHull = effectsCauseType(8, "shipHull") class galaxySetupOption(Enum): @@ -2869,26 +3330,28 @@ def __init__(self, numerator, name): maxStructure = None # meterType(10, "maxStructure") maxDefense = None # meterType(11, "maxDefense") maxSupply = None # meterType(12, "maxSupply") - maxTroops = None # meterType(13, "maxTroops") - population = None # meterType(14, "population") - industry = None # meterType(15, "industry") - research = None # meterType(16, "research") - trade = None # meterType(17, "trade") - construction = None # meterType(18, "construction") - happiness = None # meterType(19, "happiness") - capacity = None # meterType(20, "capacity") - secondaryStat = None # meterType(21, "secondaryStat") - fuel = None # meterType(22, "fuel") - shield = None # meterType(23, "shield") - structure = None # meterType(24, "structure") - defense = None # meterType(25, "defense") - supply = None # meterType(26, "supply") - troops = None # meterType(27, "troops") - rebels = None # meterType(28, "rebels") - size = None # meterType(29, "size") - stealth = None # meterType(30, "stealth") - detection = None # meterType(31, "detection") - speed = None # meterType(32, "speed") + maxStockpile = None # meterType(13, "maxStockpile") + maxTroops = None # meterType(14, "maxTroops") + population = None # meterType(15, "population") + industry = None # meterType(16, "industry") + research = None # meterType(17, "research") + trade = None # meterType(18, "trade") + construction = None # meterType(19, "construction") + happiness = None # meterType(20, "happiness") + capacity = None # meterType(21, "capacity") + secondaryStat = None # meterType(22, "secondaryStat") + fuel = None # meterType(23, "fuel") + shield = None # meterType(24, "shield") + structure = None # meterType(25, "structure") + defense = None # meterType(26, "defense") + supply = None # meterType(27, "supply") + stockpile = None # meterType(28, "stockpile") + troops = None # meterType(29, "troops") + rebels = None # meterType(30, "rebels") + size = None # meterType(31, "size") + stealth = None # meterType(32, "stealth") + detection = None # meterType(33, "detection") + speed = None # meterType(34, "speed") meterType.targetPopulation = meterType(0, "targetPopulation") @@ -2904,26 +3367,28 @@ def __init__(self, numerator, name): meterType.maxStructure = meterType(10, "maxStructure") meterType.maxDefense = meterType(11, "maxDefense") meterType.maxSupply = meterType(12, "maxSupply") -meterType.maxTroops = meterType(13, "maxTroops") -meterType.population = meterType(14, "population") -meterType.industry = meterType(15, "industry") -meterType.research = meterType(16, "research") -meterType.trade = meterType(17, "trade") -meterType.construction = meterType(18, "construction") -meterType.happiness = meterType(19, "happiness") -meterType.capacity = meterType(20, "capacity") -meterType.secondaryStat = meterType(21, "secondaryStat") -meterType.fuel = meterType(22, "fuel") -meterType.shield = meterType(23, "shield") -meterType.structure = meterType(24, "structure") -meterType.defense = meterType(25, "defense") -meterType.supply = meterType(26, "supply") -meterType.troops = meterType(27, "troops") -meterType.rebels = meterType(28, "rebels") -meterType.size = meterType(29, "size") -meterType.stealth = meterType(30, "stealth") -meterType.detection = meterType(31, "detection") -meterType.speed = meterType(32, "speed") +meterType.maxStockpile = meterType(13, "maxStockpile") +meterType.maxTroops = meterType(14, "maxTroops") +meterType.population = meterType(15, "population") +meterType.industry = meterType(16, "industry") +meterType.research = meterType(17, "research") +meterType.trade = meterType(18, "trade") +meterType.construction = meterType(19, "construction") +meterType.happiness = meterType(20, "happiness") +meterType.capacity = meterType(21, "capacity") +meterType.secondaryStat = meterType(22, "secondaryStat") +meterType.fuel = meterType(23, "fuel") +meterType.shield = meterType(24, "shield") +meterType.structure = meterType(25, "structure") +meterType.defense = meterType(26, "defense") +meterType.supply = meterType(27, "supply") +meterType.stockpile = meterType(28, "stockpile") +meterType.troops = meterType(29, "troops") +meterType.rebels = meterType(30, "rebels") +meterType.size = meterType(31, "size") +meterType.stealth = meterType(32, "stealth") +meterType.detection = meterType(33, "detection") +meterType.speed = meterType(34, "speed") class planetEnvironment(Enum): @@ -3009,11 +3474,49 @@ def __init__(self, numerator, name): industry = None # resourceType(0, "industry") trade = None # resourceType(1, "trade") research = None # resourceType(2, "research") + stockpile = None # resourceType(3, "stockpile") resourceType.industry = resourceType(0, "industry") resourceType.trade = resourceType(1, "trade") resourceType.research = resourceType(2, "research") +resourceType.stockpile = resourceType(3, "stockpile") + + +class roleType(Enum): + def __init__(self, numerator, name): + self.name = name + + host = None # roleType(0, "host") + clientTypeModerator = None # roleType(1, "clientTypeModerator") + clientTypePlayer = None # roleType(2, "clientTypePlayer") + clientTypeObserver = None # roleType(3, "clientTypeObserver") + galaxySetup = None # roleType(4, "galaxySetup") + + +roleType.host = roleType(0, "host") +roleType.clientTypeModerator = roleType(1, "clientTypeModerator") +roleType.clientTypePlayer = roleType(2, "clientTypePlayer") +roleType.clientTypeObserver = roleType(3, "clientTypeObserver") +roleType.galaxySetup = roleType(4, "galaxySetup") + + +class ruleType(Enum): + def __init__(self, numerator, name): + self.name = name + + invalid = None # ruleType(-1, "invalid") + toggle = None # ruleType(0, "toggle") + int = None # ruleType(1, "int") + double = None # ruleType(2, "double") + string = None # ruleType(3, "string") + + +ruleType.invalid = ruleType(-1, "invalid") +ruleType.toggle = ruleType(0, "toggle") +ruleType.int = ruleType(1, "int") +ruleType.double = ruleType(2, "double") +ruleType.string = ruleType(3, "string") class shipPartClass(Enum): @@ -3427,26 +3930,35 @@ def getFieldType(string): return fieldType() -def getHullType(string): +def getGameRules(): + """ + Returns the game rules manager, which can be used to look up the names (string) of rules are defined with what type (boolean / toggle, int, double, string), and what values the rules have in the current game. + + :rtype: GameRules + """ + return GameRules() + + +def getShipHull(string): """ - Returns the ship hull (HullType) with the indicated name (string). + Returns the ship hull with the indicated name (string). :param string: :type string: str - :rtype: hullType + :rtype: shipHull """ - return hullType() + return shipHull() -def getPartType(string): +def getShipPart(string): """ - Returns the ship part (PartType) with the indicated name (string). + Returns the ShipPart with the indicated name (string). :param string: :type string: str - :rtype: partType + :rtype: shipPart """ - return partType() + return shipPart() def getShipDesign(number): @@ -3564,6 +4076,50 @@ def get_native_species(): return list() +def get_options_db_option_bool(string): + """ + Returns the bool value of option in OptionsDB or None if the option does not exist. + + :param string: + :type string: str + :rtype: object + """ + return object() + + +def get_options_db_option_double(string): + """ + Returns the double value of option in OptionsDB or None if the option does not exist. + + :param string: + :type string: str + :rtype: object + """ + return object() + + +def get_options_db_option_int(string): + """ + Returns the integer value of option in OptionsDB or None if the option does not exist. + + :param string: + :type string: str + :rtype: object + """ + return object() + + +def get_options_db_option_str(string): + """ + Returns the string value of option in OptionsDB or None if the option does not exist. + + :param string: + :type string: str + :rtype: object + """ + return object() + + def get_owner(number): """ :param number: @@ -3617,6 +4173,24 @@ def get_universe_width(): return float() +def get_user_config_dir(): + """ + Returns path to directory where FreeOrion stores user specific configuration. + + :rtype: object + """ + return object() + + +def get_user_data_dir(): + """ + Returns path to directory where FreeOrion stores user specific data (saves, etc.). + + :rtype: str + """ + return str() + + def get_x(number): """ :param number: @@ -3685,7 +4259,7 @@ def load_fleet_plan_list(): return list() -def load_item_spec_list(): +def load_unlockable_item_list(): """ :rtype: list """ @@ -3699,6 +4273,13 @@ def load_monster_fleet_plan_list(): return list() +def load_starting_buildings(): + """ + :rtype: list + """ + return list() + + def objs_get_systems(item_list): """ :param item_list: diff --git a/default/python/handlers/README.md b/default/python/handlers/README.md index b512705b719..066bcdb9017 100644 --- a/default/python/handlers/README.md +++ b/default/python/handlers/README.md @@ -16,8 +16,8 @@ Debug prints required for charts. Started by default - `python\handlers\inspect_freeOrionAIInterface.py`: Code that create stub for `freeOrionAIInterface`. Must be launched with single AI player. + - `python\handlers\inspect_universe_generation.py` Code that create stub for `freeorion.pyi` ## Implementation Only AI handlers implementation is present now (`python\AI\freeorion_tools\handlers.py`), need to add universe generation and events implementation too. - diff --git a/default/python/handlers/inspect_freeOrionAIInterface.py b/default/python/handlers/inspect_freeOrionAIInterface.py index dae848f2d52..448bf65557d 100644 --- a/default/python/handlers/inspect_freeOrionAIInterface.py +++ b/default/python/handlers/inspect_freeOrionAIInterface.py @@ -1,11 +1,12 @@ -import PlanetUtilsAI -import freeOrionAIInterface as fo - from common.listeners import register_pre_handler -from interface_mock import inspect +from stub_generator import inspect def inspect_ai_interface(): + # Put all related imports inside + import PlanetUtilsAI + import freeOrionAIInterface as fo + capital_id = PlanetUtilsAI.get_capital() universe = fo.getUniverse() fleets_int_vector = universe.fleetIDs @@ -18,7 +19,7 @@ def inspect_ai_interface(): tech_spec = list(tech.unlockedItems)[0] part_id = list(empire.availableShipParts)[0] - part_type = fo.getPartType(part_id) + ship_part = fo.getShipPart(part_id) prod_queue = empire.productionQueue fo.issueEnqueueShipProductionOrder(list(empire.availableShipDesigns)[0], capital_id) @@ -39,7 +40,7 @@ def inspect_ai_interface(): inspect( fo, - [ + instances=[ meter, part_meters, color, @@ -55,14 +56,14 @@ def inspect_ai_interface(): fo.getFieldType('FLD_ION_STORM'), fo.getBuildingType('BLD_SHIPYARD_BASE'), fo.getGalaxySetupData(), - fo.getHullType('SH_XENTRONIUM'), - fo.getPartType('SR_WEAPON_1_1'), + fo.getShipHull('SH_XENTRONIUM'), + fo.getShipPart('SR_WEAPON_1_1'), fo.getSpecial('MODERATE_TECH_NATIVES_SPECIAL'), fo.getSpecies('SP_ABADDONI'), fo.getTech('SHP_WEAPON_4_1'), fo.diplomaticMessage(1, 2, fo.diplomaticMessageType.acceptPeaceProposal), fleets_int_vector, - part_type, + ship_part, prod_queue, prod_queue.allocatedPP, prod_queue[0], @@ -73,12 +74,14 @@ def inspect_ai_interface(): ], classes_to_ignore=( 'IntSet', 'StringSet', 'IntIntMap', 'ShipSlotVec', 'VisibilityIntMap', 'IntDblMap', - 'IntBoolMap', 'ItemSpecVec', 'PairIntInt_IntMap', 'IntSetSet', 'StringVec', + 'IntBoolMap', 'UnlockableItemVec', 'PairIntInt_IntMap', 'IntSetSet', 'StringVec', 'IntPairVec', 'IntFltMap', 'MeterTypeStringPair', 'MeterTypeMeterMap', 'universeObject', # this item cannot be get from generate orders 'diplomaticStatusUpdate', - ) + ), + path='AI' ) exit(1) # exit game to main menu no need to play anymore. + register_pre_handler('generateOrders', inspect_ai_interface) diff --git a/default/python/handlers/inspect_interface_config.ini b/default/python/handlers/inspect_interface_config.ini index 33a71aafb76..d19d2c33e69 100644 --- a/default/python/handlers/inspect_interface_config.ini +++ b/default/python/handlers/inspect_interface_config.ini @@ -1,2 +1,2 @@ [main] -handlers=inspect_freeOrionAIInterface.py +handlers=inspect_freeOrionAIInterface.py inspect_universe_generation.py diff --git a/default/python/handlers/inspect_universe_generation.py b/default/python/handlers/inspect_universe_generation.py new file mode 100644 index 00000000000..7dfa7003a6a --- /dev/null +++ b/default/python/handlers/inspect_universe_generation.py @@ -0,0 +1,47 @@ +from common.listeners import register_post_handler +from stub_generator import inspect + + +def inspect_universe_generation_interface(*args, **kwargs): + import freeorion as fo + tech = fo.getTech('LRN_ARTIF_MINDS') + universe = fo.get_universe() + empire = fo.get_empire(1) + rules = fo.getGameRules() + ship_hull = fo.getShipHull('SH_XENTRONIUM') + species = fo.getSpecies('SP_ABADDONI') + inspect( + fo, + instances=[ + fo.getFieldType('FLD_ION_STORM'), + fo.getBuildingType('BLD_SHIPYARD_BASE'), + ship_hull, + ship_hull.slots, + fo.getShipPart('SR_WEAPON_1_1'), + fo.getSpecial('MODERATE_TECH_NATIVES_SPECIAL'), + species, + fo.diplomaticMessage(1, 2, fo.diplomaticMessageType.acceptPeaceProposal), + rules, + tech, + tech.unlockedItems, + rules.getRulesAsStrings, + universe, + universe.effectAccounting, + universe.buildingIDs, + fo.get_galaxy_setup_data(), + empire, + empire.colour, + empire.productionQueue, + empire.researchQueue, + ], + classes_to_ignore=( + 'IntBoolMap', 'IntDblMap', 'IntFltMap', 'IntIntMap', 'IntPairVec', 'IntSetSet', + 'MeterTypeAccountingInfoVecMap', 'MeterTypeMeterMap', 'MeterTypeStringPair', 'MonsterFleetPlan', + 'PairIntInt_IntMap', 'RuleValueStringStringPair', 'ShipPartMeterMap', 'VisibilityIntMap', + 'AccountingInfoVec', 'IntSet', 'StringSet', 'StringVec', + ), + path=".", + ) + + +register_post_handler("create_universe", inspect_universe_generation_interface) diff --git a/default/python/interface_mock/__init__.py b/default/python/stub_generator/__init__.py similarity index 100% rename from default/python/interface_mock/__init__.py rename to default/python/stub_generator/__init__.py diff --git a/default/python/interface_mock/generate_mock.py b/default/python/stub_generator/generate_stub.py similarity index 72% rename from default/python/interface_mock/generate_mock.py rename to default/python/stub_generator/generate_stub.py index e720461d42b..2c2f8940c51 100644 --- a/default/python/interface_mock/generate_mock.py +++ b/default/python/stub_generator/generate_stub.py @@ -1,5 +1,6 @@ -from itertools import groupby +from logging import warning, error from operator import itemgetter + from parse_docs import Docs @@ -15,13 +16,13 @@ def handle_class(info): properties = [] instance_methods = [] - for attr_name, attr in attrs.items(): + for attr_name, attr in sorted(attrs.items()): if attr['type'] == "": properties.append((attr_name, attr.get('rtype', ''))) elif attr['type'] == "": - instance_methods.append(attr['rutine']) + instance_methods.append(attr['routine']) else: - print "!!!", name, attr + warning("Skipping '%s': %s" % (name, attr)) for property_name, rtype in properties: if not rtype: @@ -42,13 +43,11 @@ def handle_class(info): result.append(' %s' % return_text) result.append('') - for rutine_name, rutine_docs in instance_methods: - if rutine_name == 'error_stub': - continue - - docs = Docs(rutine_docs, 2, is_class=True) - - if docs.rtype == 'VisibilityIntMap': + for routine_name, routine_docs in instance_methods: + docs = Docs(routine_docs, 2, is_class=True) + # TODO: Subclass map-like classes from dict (or custom class) rather than this hack + if docs.rtype in ('VisibilityIntMap', 'IntIntMap'): + docs.rtype = 'dict[int, int]' return_string = 'return dict()' elif docs.rtype == 'None': return_string = 'return None' @@ -56,7 +55,7 @@ def handle_class(info): return_string = 'return %s()' % docs.rtype doc_string = docs.get_doc_string() - result.append(' def %s(%s):' % (rutine_name, docs.get_argument_string())) + result.append(' def %s(%s):' % (routine_name, docs.get_argument_string())) result.append(doc_string) result.append(' %s' % return_string) result.append('') @@ -107,13 +106,13 @@ def handle_enum(info): 'instance'} -def make_mock(data, result_path, classes_to_ignore): +def make_stub(data, result_path, classes_to_ignore): groups = {} for info in data: if info['type'] in known_types: groups.setdefault(info['type'], []).append(info) else: - print 'Unknown type %s in %s' % (info['type'], info) + error('Unknown type "%s" in "%s' % (info['type'], info)) classes = [x for x in groups['boost_class'] if not x['name'].startswith('map_indexing_suite_')] clases_map = {x['name']: x for x in classes} instance_names = {instance['class_name'] for instance in groups.get('instance', [])} @@ -122,12 +121,16 @@ def make_mock(data, result_path, classes_to_ignore): enums_names = [x['name'] for x in enums] missed_instances = instance_names.symmetric_difference(clases_map).difference(classes_to_ignore) - print "Classes without instances (%s):" % len(missed_instances), ', '.join(sorted(missed_instances)) + warning( + "Classes without instances (%s): %s", + len(missed_instances), + ', '.join(sorted(missed_instances, key=str.lower)) + ) for instance in groups.get('instance', []): class_name = instance['class_name'] if class_name in enums_names: - print "skipping enum instance: %s" % class_name + warning("skipping enum instance: %s" % class_name) continue class_attrs = clases_map[class_name]['attrs'] @@ -143,24 +146,29 @@ def make_mock(data, result_path, classes_to_ignore): elif v['type'] == "": pass else: - print "Unknown class attribute type", v['type'] - - res = [] - classes = sorted(classes, key=lambda class_: len(class_['parents'])) # put classes with no parents on first place - class_groups = groupby(classes, key=lambda class_: class_['parents'] and class_['parents'][0] or '') - - for name, group in class_groups: - for cls in sorted(group, key=itemgetter('name')): - res.append(handle_class(cls)) + error("Unknown class attribute type: '%s'" % v['type']) + res = [ + '# Autogenerated do not modify manually!\n' + '# This is a type-hinting python stub file, used by python IDEs to provide type hints. For more information\n' + '# about stub files, see https://www.python.org/dev/peps/pep-0484/#stub-files\n' + '# During execution, the actual module is made available via\n' + '# a C++ Boost-python process as part of the launch.' + '' + '# type: ignore' + ] + classes = sorted(classes, key=lambda class_: (len(class_['parents']), class_['parents'] and class_['parents'][0] or '', class_['name'])) # put classes with no parents on first place + + for cls in classes: + res.append(handle_class(cls)) res.append(ENUM_STUB) - for enum in enums: + for enum in sorted(enums, key=itemgetter('name')): res.append(handle_enum(enum)) for function in sorted(groups['function'], key=itemgetter('name')): res.append(handle_function(function)) - with open(unicode(result_path, 'utf-8'), 'w') as f: + with open(result_path, 'w') as f: f.write('\n\n\n'.join(res)) f.write('\n') diff --git a/default/python/interface_mock/interface_inspector.py b/default/python/stub_generator/interface_inspector.py similarity index 62% rename from default/python/interface_mock/interface_inspector.py rename to default/python/stub_generator/interface_inspector.py index ef1cc24a292..7aa93f35add 100644 --- a/default/python/interface_mock/interface_inspector.py +++ b/default/python/stub_generator/interface_inspector.py @@ -1,9 +1,11 @@ import os from inspect import getdoc, isroutine -from generate_mock import make_mock +from logging import warning, error, debug +from generate_stub import make_stub -def get_member_info(member): + +def get_member_info(name, member): info = { 'type': str(type(member)), } @@ -11,7 +13,7 @@ def get_member_info(member): if isinstance(member, property): info['getter'] = (member.fget.__name__, getdoc(member.fget)) elif isroutine(member): - info['rutine'] = (member.__name__, getdoc(member)) + info['routine'] = (member.__name__, getdoc(member)) elif 'freeOrionAIInterface' in info['type']: info['value'] = str(member) elif isinstance(member, int): @@ -19,13 +21,14 @@ def get_member_info(member): info['value'] = member else: info['value'] = str(member) - elif isinstance(member, (str, long, bool, float)): + elif isinstance(member, (str, bool, float, int)): info['value'] = member elif isinstance(member, (list, tuple, dict, set, frozenset)): if not len(member): info['value'] = member else: - print '>>>', type(member), "of", member + # instance properties will be already resolved + warning('[%s] Unexpected member "%s"(%s)', name, type(member), member) return info @@ -39,7 +42,7 @@ def getmembers(obj, predicate=None): except AttributeError: continue except Exception as e: - print 'Error in "%s.%s": %s' % (obj.__class__.__name__, key, e) + error('Error in "%s.%s" with error: %s' % (obj.__class__.__name__, key, e)) continue if not predicate or predicate(value): results.append((key, value)) @@ -55,25 +58,25 @@ def inspect_instance(instance): type='instance', attrs={}, parents=[str(parent.__name__) for parent in parents]) - for name, member in getmembers(instance): - if name not in parent_attrs + ['__module__']: - info['attrs'][name] = get_member_info(member) + for attr_name, member in getmembers(instance): + if attr_name not in parent_attrs + ['__module__']: + info['attrs'][attr_name] = get_member_info('%s.%s' % (instance.__class__.__name__, attr_name), member) return info -def inspect_boost_class(name, obj): +def inspect_boost_class(class_name, obj): parents = obj.mro()[1:-2] parent_attrs = sum((dir(parent) for parent in obj.mro()[1:]), []) info = {'type': "boost_class", - 'name': name, + 'name': class_name, 'attrs': {}, 'doc': getdoc(obj), 'parents': [str(parent.__name__) for parent in parents] } - for name, member in getmembers(obj): - if name not in parent_attrs + ['__module__', '__instance_size__']: - info['attrs'][name] = get_member_info(member) + for attr_name, member in getmembers(obj): + if attr_name not in parent_attrs + ['__module__', '__instance_size__']: + info['attrs'][attr_name] = get_member_info('%s.%s' % (class_name, attr_name), member) return info @@ -112,34 +115,35 @@ def _inspect(obj, instances): function = switcher.get(str(type(member)), None) if function: data.append(function(name, member)) - elif name in ('__doc__', '__package__', '__name__'): + elif name in ('__doc__', '__package__', '__name__', 'INVALID_GAME_TURN', 'to_str'): pass else: - print "Unknown", name, type(member), member + warning("Unknown: '%s' of type '%s': %s" % (name, type(member), member)) for i, instance in enumerate(instances, start=2): - if isinstance(instance, (basestring, int, long, float)): - print "Argument number %s(1-based) is builtin python instance: (%s) %s" % (i, type(instance), instance) + if isinstance(instance, (str, float, int)): + warning("Argument number %s(1-based) is builtin python instance: (%s) %s", i, type(instance), instance) continue try: data.append(inspect_instance(instance)) except Exception as e: - print "Error inspecting:", type(instance), type(e), e - from traceback import print_exc + error("Error inspecting: '%s' with '%s': %s", type(instance), type(e), e, exc_info=True) return data -def inspect(obj, instances, classes_to_ignore=tuple()): +def inspect(obj, instances, classes_to_ignore, path): """ - Inspect interface and generate mock. writes its logs to freeoriond.log + Inspect interface and generate stub. Writes its logs to freeoriond.log. :param obj: main interface module (freeOrionAIInterface for AI) :param instances: list of instances, required to get more detailed information about them :param classes_to_ignore: classes that should not to be reported when check for missed instances done. this argument required because some classes present in interface but have no methods, to get their instances. + :param path: relative path from python folder """ - print "\n\nStart generating skeleton for %s\n\n" % obj.__name__ - folder_name = os.path.join(os.path.dirname(__file__), 'result') - result_path = os.path.join(folder_name, '%s.py' % obj.__name__) - make_mock(_inspect(obj, instances), result_path, classes_to_ignore) - print "Skeleton written to %s" % result_path + debug("\n\nStart generating skeleton for %s\n\n" % obj.__name__) + python_folder_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) + result_folder = os.path.join(python_folder_path, path) + result_path = os.path.join(result_folder, '%s.pyi' % obj.__name__) + make_stub(_inspect(obj, instances), result_path, classes_to_ignore) + debug("Skeleton written to %s" % result_path) diff --git a/default/python/interface_mock/parse_docs.py b/default/python/stub_generator/parse_docs.py similarity index 75% rename from default/python/interface_mock/parse_docs.py rename to default/python/stub_generator/parse_docs.py index a6697efe73c..4d60466803c 100644 --- a/default/python/interface_mock/parse_docs.py +++ b/default/python/stub_generator/parse_docs.py @@ -1,6 +1,6 @@ +from logging import error import re -import sys normalization_dict = { 'empire': 'empire_object', @@ -27,7 +27,7 @@ 'tech': 'tech_object', 'list': 'item_list', 'planet': 'planet_object', - 'partType': 'part_type', + 'shipPart': 'ship_part', 'resPool': 'res_pool', 'researchQueueElement': 'research_queue_element', 'shipSlotType': 'ship_slot_type', @@ -38,7 +38,7 @@ 'IntSetSet': 'int_set_set', 'IntVec': 'int_list', 'IntVisibilityMap': 'int_visibility_map', - 'ItemSpecVec': 'item_spec_vec', + 'UnlockableItemVec': 'unlockable_item_vec', 'StringSet': 'string_set', 'VisibilityIntMap': 'visibility_int_map', 'buildingType': 'buildingType', @@ -46,7 +46,7 @@ 'resourceType': 'resource_type', 'buildType': 'build_type', 'field': 'field', - 'hullType': 'hull_type', + 'shipHull': 'ship_hull', 'PlanetSize': 'planet_size', 'planetSize': 'planet_size', 'unlockableItemType': 'unlockable_item_type', @@ -61,7 +61,8 @@ 'ShipPartMeterMap': 'ship_part_meter_map', 'ShipSlotVec': 'ship_slot_vec', 'special': 'special', - 'IntFltMap': 'int_flt_map' + 'IntFltMap': 'int_flt_map', + 'ruleType': 'rule_type', } @@ -71,7 +72,7 @@ def normalize_name(tp): return provided_name if argument_type not in normalization_dict: - print sys.stderr.write("Can't find proper name for: %s\n" % argument_type) + error("Can't find proper name for: %s\n" % argument_type) normalization_dict[argument_type] = 'arg' return normalization_dict[argument_type] @@ -102,33 +103,55 @@ def get_argument_names(arguments, is_class): def parse_name(txt): - match = re.match('\w+\((.*)\) -> (.+) :', txt) + match = re.match(r'\w+\((.*)\) -> (.+) :', txt) args, return_type = match.group(1, 2) args = [x.strip(' (').split(')') for x in args.split(',') if x] return [x[0] for x in args], return_type -def merge_args(arg_types, is_class): +def merge_args(name, raw_arg_types, is_class): + """ + Merge multiple set of arguments together. + + Single argument set is used as is. + If we have two unique argument sets, and on of them is empty, use keywords. + In other cases log error and use first one. + + :param str name: + :param list[tuple] raw_arg_types: + :param bool is_class: + :rtype: (list[str], list[(str, str)]) + """ + # If wrapper define functions that have same name, and same arguments but different return types, + # it will come here with len(arg_types) >= 2, where all arguments set are the same. + size = len(raw_arg_types) + arg_types = sorted(set(raw_arg_types)) + if len(arg_types) != size: + error("[%s] Duplicated argument types", name) + if len(arg_types) == 1: names, types = get_argument_names(arg_types[0], is_class) use_keyword = False elif len(arg_types) == 2 and any(not x for x in arg_types): - names, types = get_argument_names(filter(None, arg_types)[0], is_class) + + names, types = get_argument_names(next(filter(None, arg_types)), is_class) # pylint: disable=filter-builtin-not-iterating use_keyword = True else: - sys.stderr.write('Cannot merge, use first arguments from:\n %s\n' % '\n '.join(', '.join('(%s)%s' % (tp, name) for tp, name in arg_set) for arg_set in arg_types)) - names, types = get_argument_names(arg_types[0], is_class) + error('[%s] Cannot merge, use first argument group from:\n %s\n', + name, + '\n '.join(', '.join('(%s)%s' % (tp, name) for tp, name in arg_set) for arg_set in raw_arg_types)) + names, types = get_argument_names(raw_arg_types[0], is_class) use_keyword = False - return ['%s=None' % name for name in names] if use_keyword else names, zip(names, types) + return ['%s=None' % arg_name for arg_name in names] if use_keyword else names, list(zip(names, types)) -def normilize_rtype(rtype): +def normalize_rtype(rtype): if rtype == 'iterator': return 'iter' return rtype -class Docs(object): +class Docs: def __init__(self, text, indent, is_class=False): self.indent = indent self.is_class = is_class @@ -144,9 +167,9 @@ def __init__(self, text, indent, is_class=False): lines = [x.strip() for x in self.text.split('\n')] def parse_signature(line): - expre = re.compile('(\w+)\((.*)\) -> (\w+)') + expre = re.compile(r'(\w+)\((.*)\) -> (\w+)') name, args, rtype = expre.match(line).group(1, 2, 3) - args = re.findall('\((\w+)\) *(\w+)', args) + args = tuple(re.findall(r'\((\w+)\) *(\w+)', args)) return name, args, rtype res = [] @@ -162,9 +185,9 @@ def parse_signature(line): self.resources = res args, rtypes, infos = zip(*res) - rtypes = set(rtypes) - assert len(rtypes) == 1, "Different rtypes for: %s in: %s" % (rtypes, text) - self.rtype = normilize_rtype(rtypes.pop()) + if len(set(rtypes)) != 1: + error("[%s] Different rtypes", name) + self.rtype = normalize_rtype(rtypes[0]) # cut of first and last string if they are empty # we cant cut off all empty lines, because it can be inside docstring @@ -185,7 +208,7 @@ def parse_signature(line): # if docs are equals show only one of them self.header = sorted(doc_lines) - argument_declaration, args = merge_args(args, self.is_class) + argument_declaration, args = merge_args(name, args, self.is_class) self.argument_declaration = argument_declaration self.args = args @@ -220,12 +243,12 @@ def get_doc_string(self): # example1 = """getUserDataDir() -> str :\n Returns path to directory where FreeOrion stores user specific data (config files, saves, etc.).""" info = Docs(example1, 1) - print "=" * 100 - print "Arg string:", info.get_argument_string() - print "=" * 100 - print "Doc string:\n", info.get_doc_string() + print("=" * 100) + print("Arg string:", info.get_argument_string()) + print("=" * 100) + print("Doc string:\n", info.get_doc_string()) - # double standarts + # double standards # canBuild ['empire', 'buildType', 'str', 'int'], ['empire', 'buildType', 'int', 'int'] # inField ['field', 'universeObject'], ['field', 'float', 'float'] # validShipDesign ['str', 'StringVec'] diff --git a/default/python/interface_mock/mock_functions.py b/default/python/stub_generator/stub_functions.py similarity index 73% rename from default/python/interface_mock/mock_functions.py rename to default/python/stub_generator/stub_functions.py index 77592e4b876..c0d4577b105 100644 --- a/default/python/interface_mock/mock_functions.py +++ b/default/python/stub_generator/stub_functions.py @@ -1,4 +1,4 @@ -from interface_mock import inspect +from stub_generator import inspect def inspect_universe_generation(): @@ -37,7 +37,7 @@ def inspect_universe_generation(): universe_object = universe.getObject(universe.systemIDs[0]) - # fo.getHullType ? + # fo.getShipHull ? species = None special = None @@ -52,38 +52,40 @@ def inspect_universe_generation(): inspect( fo, - [fo.get_galaxy_setup_data(), - universe, - planet_ids, - universe.getPlanet(planet_ids[0]), - system, - techs, - tech, - tech.unlockedTechs, - building, - fo.getBuildingType(building.buildingTypeName), - empire, - field, - fo.getFieldType(field.fieldTypeName), - fleet, - ship, - # meter, - design, - fleet.shipIDs, # int set wtf? - universe_object, - system.starlanesWormholes, - empire.systemSupplyRanges, - empire.supplyProjections(), - empire.obstructedStarlanes(), - empire.planetsWithWastedPP, - unlocked_items, - species, - special - ], + instances=[ + fo.get_galaxy_setup_data(), + universe, + planet_ids, + universe.getPlanet(planet_ids[0]), + system, + techs, + tech, + tech.unlockedTechs, + building, + fo.getBuildingType(building.buildingTypeName), + empire, + field, + fo.getFieldType(field.fieldTypeName), + fleet, + ship, + # meter, + design, + fleet.shipIDs, # int set wtf? + universe_object, + system.starlanesWormholes, + empire.systemSupplyRanges, + empire.supplyProjections(), + empire.obstructedStarlanes(), + empire.planetsWithWastedPP, + unlocked_items, + species, + special + ], classes_to_ignore=( 'FleetPlan', 'GGColor', 'MonsterFleetPlan', 'PlayerSetupData', 'ShipPartMeterMap', 'ShipSlotVec', 'VisibilityIntMap', 'diplomaticMessage', 'diplomaticStatusUpdate', 'meter', - ) + ), + path='' ) # fo.sys_get_star_type(system), # fo.planet_get_size(pid), @@ -93,8 +95,8 @@ def inspect_universe_generation(): # fo.getBuildingType(string) # fo.getFieldType(string) - # fo.getHullType(string) - # fo.getPartType(string) + # fo.getShipHull(string) + # fo.getShipPart(string) # fo.getShipDesign(number) # fo.getSpecial(string) # fo.getSpecies(string) @@ -111,7 +113,7 @@ def inspect_universe_generation(): # Traceback (most recent call last): # File "f:\projects\FreeOrion\default/python/universe_generation\universe_generator.py", line 163, in create_universe # inspect_universe_generation() -# File "f:\projects\FreeOrion\default/python\interface_mock\mock_functions.py", line 27, in inspect_universe_generation +# File "f:\projects\FreeOrion\default/python\interface_stub\stub_functions.py", line 27, in inspect_universe_generation # meter = ship.getMeter(fo.meterType.maxFuel) # Boost.Python.ArgumentError: Python argument types in # ship.getMeter(ship, meterType) diff --git a/default/python/tests/AI/character/freeOrionAIInterface.py b/default/python/tests/AI/character/freeOrionAIInterface.py deleted file mode 100644 index d9e0f0c39ce..00000000000 --- a/default/python/tests/AI/character/freeOrionAIInterface.py +++ /dev/null @@ -1,28 +0,0 @@ -# Fake fo for testing Trait purposes - -# TODO Find a better way to import the static portions of fo without the test chaos, overhead and random state of fo. - -# The following line can replace the contents of this file if -# the __init__.py in AI/freeorion_debug is an empty file -# and an empty __init__.py file is added to freeorion_debug/ide_tools/result/ -# from freeorion_debug.ide_tools.result.freeOrionAIInterface import aggression, userString, userStringList - - -class aggression(object): - """Aggression enumeration.""" - beginner = 0 - turtle = 1 - cautious = 2 - typical = 3 - aggressive = 4 - maniacal = 5 - - -def userString(x): - """userString mock""" - return "UserString %s" % x - - -def userStringList(x): - """userStringList mock""" - return "UserStringList %s" % x diff --git a/default/python/tests/AI/character/character_strings_test.py b/default/python/tests/AI/character_test/character_strings_test.py similarity index 100% rename from default/python/tests/AI/character/character_strings_test.py rename to default/python/tests/AI/character_test/character_strings_test.py diff --git a/default/python/tests/AI/character/character_test.py b/default/python/tests/AI/character_test/character_test.py similarity index 96% rename from default/python/tests/AI/character/character_test.py rename to default/python/tests/AI/character_test/character_test.py index 845367a76d8..99b6c4496f7 100644 --- a/default/python/tests/AI/character/character_test.py +++ b/default/python/tests/AI/character_test/character_test.py @@ -75,6 +75,6 @@ def test_preference_combiner(self): assert rejection_character.preferred_research_cutoff([10, 11, 12]) == 11 def test_character_must_be_composed_of_traits(self): - with pytest.raises(TypeError): + with pytest.raises(TypeError, match='All traits must be sub-classes of Trait'): not_a_trait = int(1) Character([LeftTrait, not_a_trait]) diff --git a/default/python/tests/AI/test_assertion_fails.py b/default/python/tests/AI/test_assertion_fails.py new file mode 100644 index 00000000000..e1c06b655a2 --- /dev/null +++ b/default/python/tests/AI/test_assertion_fails.py @@ -0,0 +1,25 @@ +from freeorion_tools import assertion_fails + + +def test_does_fail(): + assert assertion_fails(False) + + +def test_does_not_fail(): + assert not assertion_fails(True) + + +def test_message_argument(): + assert assertion_fails(False, "") + assert assertion_fails(False, "Some message") + assert not assertion_fails(True, "") + assert not assertion_fails(True, "Some message") + + +def test_logger_argument(): + import logging + loggers = [logging.debug, logging.info, logging.warning, logging.error] + for logger in loggers: + assert assertion_fails(False, logger=logger) + assert assertion_fails(False, "Some message", logger=logger) + assert not assertion_fails(True, logger=logger) diff --git a/default/python/tests/AI/test_read_only_dict.py b/default/python/tests/AI/test_read_only_dict.py new file mode 100644 index 00000000000..7d619c26265 --- /dev/null +++ b/default/python/tests/AI/test_read_only_dict.py @@ -0,0 +1,78 @@ +import pytest +from freeorion_tools import ReadOnlyDict + +dict_content = { + 1: -1, + 1.09: 1.1, + 'abc': 'xyz', + (1, 2, 3): ('a', 1, (1, 2)), +} + + +def test_dict_content(): + test_dict = ReadOnlyDict(dict_content) + assert set(test_dict.keys()) == set(dict_content.keys()) + assert set(test_dict.values()) == set(dict_content.values()) + assert set(test_dict.items()) == set(dict_content.items()) + assert len(test_dict) == len(dict_content) + + +def test_membership(): + test_dict = ReadOnlyDict(dict_content) + # check for membership checks and retrieval + for key, value in dict_content.items(): + assert key in test_dict + assert test_dict[key] == value + assert test_dict.get(key, -99999) == value + + +def test_non_existing_keys(): + test_dict = ReadOnlyDict(dict_content) + # check correct functionality if keys not in dict + assert 'INVALID_KEY' not in test_dict + assert test_dict.get('INVALID_KEY', -99999) == -99999 + with pytest.raises(KeyError, match="'INVALID_KEY'"): + # noinspection PyStatementEffect + test_dict['INVALID_KEY'] + pytest.fail("Invalid key lookup didn't raise a KeyError") + + +def test_bool(): + test_dict = ReadOnlyDict(dict_content) + assert bool(test_dict) + assert test_dict + empty_dict = ReadOnlyDict() + assert not len(empty_dict) + assert not empty_dict + + +def test_conversion_to_dict(): + read_only_dict = ReadOnlyDict(dict_content) + normal_dict = dict(read_only_dict) + assert len(normal_dict) == len(dict_content) + assert set(normal_dict.items()) == set(dict_content.items()) + + +def test_deletion(): + test_dict = ReadOnlyDict(dict_content) + # check that dict can not be modified + try: + del test_dict[1] + raise AssertionError("Can delete items from the dict.") + except TypeError: + pass + + +def test_setitem(): + test_dict = ReadOnlyDict(dict_content) + with pytest.raises(TypeError, match="'ReadOnlyDict' object does not support item assignment"): + test_dict['INVALID_KEY'] = 1 + pytest.fail('Can add items to the dict.') + assert 'INVALID_KEY' not in test_dict + + +def test_nonhashable_values(): + # make sure can't store unhashable items (as heuristic for mutable items) + with pytest.raises(TypeError, match="unhashable type: 'list'"): + ReadOnlyDict({1: [1]}) + pytest.fail('Can store mutable items in dict') diff --git a/default/python/tests/AI/test_save_game_decoding.py b/default/python/tests/AI/test_save_game_decoding.py new file mode 100644 index 00000000000..ab0025e90d7 --- /dev/null +++ b/default/python/tests/AI/test_save_game_decoding.py @@ -0,0 +1,44 @@ +import pytest +from pytest import fixture +from savegame_codec import build_savegame_string, load_savegame_string +import aistate_interface +from savegame_codec._decoder import SaveDecompressException + + +@fixture +def state(): + return {1: "2"} + + +@fixture +def save_string(monkeypatch, state): + def get_aistate(): + return state + + monkeypatch.setattr(aistate_interface, "get_aistate", get_aistate) + return build_savegame_string().decode('utf-8') + + +def test_save_load_game_from_string(save_string, state): + result = load_savegame_string(save_string) + assert result == state + + +def test_save_load_game_from_bytes(save_string, state): + result = load_savegame_string(save_string.encode("utf-8")) + assert result == state + + +def test_decode_not_base64(): + with pytest.raises(SaveDecompressException, match="Fail to decode base64 savestate"): + load_savegame_string("///") + + +def test_decode_not_gziped(): + with pytest.raises(SaveDecompressException, match="Fail to decompress savestate"): + load_savegame_string("aaaa") + + +def test_decode_not_ascii(): + with pytest.raises(SaveDecompressException, match="Fail to decode base64 savestate"): + load_savegame_string("шишка") diff --git a/default/python/tests/AI/test_savegame_manager.py b/default/python/tests/AI/test_savegame_manager.py new file mode 100644 index 00000000000..44de62febba --- /dev/null +++ b/default/python/tests/AI/test_savegame_manager.py @@ -0,0 +1,152 @@ +from importlib import reload + +import pytest +import savegame_codec +from pytest import fixture + + +class DummyTestClass(object): + def __init__(self): + self.some_int = 812 + self.some_negative_int = -712 + self.some_float = 1.2 + self.some_other_float = 1.248e3 + self.some_string = "TestString" + self._some_private_var = "_Test" + self.__some_more_private_var = "__Test" + self.some_dict = {'ABC': 123.1, (1, 2.3, 'ABC', "zz"): (1, (2, (3, 4)))} + self.some_list = [self.some_int, self.some_float, self.some_other_float, self.some_string] + self.some_set = set(self.some_list) + self.some_tuple = tuple(self.some_list) + + def dummy_class_function(self): + pass + + +class TrustedScope(object): + + def __enter__(self): + reload(savegame_codec) # just to be sure + savegame_codec._definitions.trusted_classes.update({ + __name__ + ".DummyTestClass": DummyTestClass, + __name__ + ".GetStateTester": GetStateTester, + __name__ + ".SetStateTester": SetStateTester, + }) + + def __exit__(self, exc_type, exc_val, exc_tb): + savegame_codec._definitions.trusted_classes.clear() + + def __del__(self): + savegame_codec._definitions.trusted_classes.clear() + + +class Success(Exception): + pass + + +class GetStateTester(object): + def __getstate__(self): + raise Success + + +class SetStateTester(object): + def __setstate__(self, state): + raise Success + + +class OldStyleClass(): + pass + + +@fixture( + params=[ + int(), float(), str(), bool(), + 1, 1.2, 1.2e4, "TestString", True, False, None, + tuple(), set(), list(), dict(), + (1, 2, 3), [1, 2, 3], {1, 2, 3}, + {'a': 1, True: False, (1, 2): {1, 2, 3}, (1, 2, (3, (4,))): [1, 2, (3, 4), {1, 2, 3}]} + ], + ids=str) +def simple_object(request, ): + return request.param + + +def check_encoding(obj): + retval = savegame_codec.encode(obj) + assert retval + assert isinstance(retval, str) + + restored_obj = savegame_codec.decode(retval) + assert type(restored_obj) == type(obj) + assert restored_obj == obj + + +def test_encoding_simple_object(simple_object): + check_encoding(simple_object) + + +def test_encoding_function(): + match = "Class builtins.function is not trusted" + + with pytest.raises(savegame_codec.CanNotSaveGameException, match=match): + check_encoding(lambda: 0) + pytest.fail("Could save function") + + +def test_encoding_type(): + match = "Class builtins.type is not trusted$" + + with pytest.raises(savegame_codec.CanNotSaveGameException, + match=match): + check_encoding(list) + pytest.fail("Could save untrusted class") + + +def test_class_encoding(): + obj = DummyTestClass() + with pytest.raises(savegame_codec.CanNotSaveGameException, + match="Class test_savegame_manager.DummyTestClass is not trusted"): + savegame_codec.encode(obj) + pytest.fail("Could save game even though test module should not be trusted") + + with TrustedScope(): + retval = savegame_codec.encode(obj) + + assert retval + assert isinstance(retval, str) + + loaded_obj = savegame_codec.decode(retval) + assert isinstance(loaded_obj, DummyTestClass) + assert type(loaded_obj) == type(obj) + + original_dict = loaded_obj.__dict__ + loaded_dict = loaded_obj.__dict__ + + assert loaded_dict == original_dict + + with pytest.raises(savegame_codec.InvalidSaveGameException, + match="DANGER DANGER - test_savegame_manager.DummyTestClass not trusted"): + savegame_codec.decode(retval) + pytest.fail("Could load object from untrusted module") + + +def test_getstate_call(): + with TrustedScope(): + with pytest.raises(Success): + savegame_codec.encode(GetStateTester()) + pytest.fail("__getstate__ was not called during encoding.") + + +def test_setstate_call(): + with TrustedScope(): + with pytest.raises(Success): + savegame_codec.decode(savegame_codec.encode(SetStateTester())) + pytest.fail("__setstate__ was not called during decoding.") + + +def test_enums(): + import EnumsAI + + test_obj = {EnumsAI.MissionType.COLONISATION: EnumsAI.MissionType.INVASION} + restored_obj = savegame_codec.decode(savegame_codec.encode(test_obj)) + assert test_obj == restored_obj diff --git a/default/python/tests/README.md b/default/python/tests/README.md index 1a451c433f8..6432ab9f771 100644 --- a/default/python/tests/README.md +++ b/default/python/tests/README.md @@ -1,17 +1,20 @@ # Test runner -This test are supposed to be run outside the game engine. +These tests are supposed to be run outside the game engine. +Tests run automatically on Travis for each PR +and can be executed on the local machine. -## Requirements - - install [pytest](http://pytest.org/latest/getting-started.html) +## Requirements for local run + - Install [pytest](http://pytest.org/latest/getting-started.html), + use the same version as mentioned in [.travis.yml](/.travis.yml#L1). ## Run test - open console (`cmd` or `PowerShell` for win) - - change dir to `freeorion\default\python\tests` - - execute `run_tests.py` with python - - `python run_tests.py` for windows - - `./run_tests.py` for linux and mac + - execute `pytest` - `run_tests.py` accepts same commandline arguments as pytest. I prefer to add `-v --tb long` - - `run_tests.py` adds required directories to python path and runs pytest. +If you want to run specific tests see +[pytest documentation](https://docs.pytest.org/en/latest/usage.html#specifying-tests-selecting-tests) + +If you need to test a module that is not in the python path, +add it to [conftest.py](conftest.py#L1), +this file is executed before import of the test files. diff --git a/default/python/tests/conftest.py b/default/python/tests/conftest.py new file mode 100644 index 00000000000..4db4471a376 --- /dev/null +++ b/default/python/tests/conftest.py @@ -0,0 +1,22 @@ +""" +Config for tests inside this directory. +https://docs.pytest.org/en/2.7.3/plugins.html?highlight=re#working-with-plugins-and-conftest-files +""" + +import os +import sys + +this_dir = os.path.dirname(__file__) + +sys.path.append(this_dir) +sys.path.append(os.path.join(this_dir, '..', '..', 'python', 'AI/')) +sys.path.append(os.path.join(this_dir, '..', '..', 'python')) + +# Since the 'true' freeOrionAIInterface is not available during testing, this import loads the 'mock' freeOrionInterface +# module into memory. For testing purposes the mock interface provides values for certain constants which various +# AI modules rely upon. Since any later imports of the freeOrionInterface module will simply refer back the first- +# loaded module of the same name, code from the AI modules can then be imported and tested, provided that such +# AI code only relies upon constants from the freeOrionAIInterface (and specifically does not depend on any of the +# various dynamic galaxy-state queries provided by the 'true' freeOrionAIInterface). +# noinspection PyUnresolvedReferences +import freeOrionAIInterface diff --git a/default/python/tests/freeOrionAIInterface.py b/default/python/tests/freeOrionAIInterface.py new file mode 100644 index 00000000000..f75400cb2eb --- /dev/null +++ b/default/python/tests/freeOrionAIInterface.py @@ -0,0 +1,268 @@ +# Mock module for tests + + +class aggression(object): + """Aggression enumeration.""" + beginner = 0 + turtle = 1 + cautious = 2 + typical = 3 + aggressive = 4 + maniacal = 5 + + +class planetSize(object): + """PlanetSize enumeration.""" + tiny = 1 + small = 2 + medium = 3 + large = 4 + huge = 5 + asteroids = 6 + gasGiant = 7 + noWorld = 0 + unknown = -1 + + +class starType(object): + """StarType enumeration.""" + blue = 0 + white = 1 + yellow = 2 + orange = 3 + red = 4 + neutron = 5 + blackHole = 6 + noStar = 7 + unknown = -1 + + +class visibility(object): + """Visibility enumeration.""" + invalid = -1 + none = 0 + basic = 1 + partial = 2 + full = 3 + + +class planetType(object): + """PlanetType enumeration.""" + # this is the listing order in EnumWrapper.cpp, so keeping it the same here + swamp = 0 + radiated = 3 + toxic = 1 + inferno = 2 + barren = 4 + tundra = 5 + desert = 6 + terran = 7 + ocean = 8 + asteroids = 9 + gasGiant = 10 + unknown = -1 + + +class planetEnvironment(object): + """PlanetEnvironment enumeration.""" + uninhabitable = 0 + hostile = 1 + poor = 2 + adequate = 3 + good = 4 + + +class techStatus(object): + """TechStatus enumeration.""" + unresearchable = 0 + partiallyUnlocked = 1 + researchable = 2 + complete = 3 + + +class buildType(object): + """BuildType enumeration.""" + building = 0 + ship = 1 + stockpile = 2 + + +class resourceType(object): + """ResourceType enumeration.""" + industry = 0 + trade = 1 + research = 2 + stockpile = 3 + + +class meterType(object): + """MeterType enumeration.""" + targetPopulation = 0 + targetIndustry = 1 + targetResearch = 2 + targetTrade = 3 + targetConstruction = 4 + targetHappiness = 5 + maxDamage = 6 + maxCapacity = 6 + maxSecondaryStat = 7 + maxFuel = 8 + maxShield = 9 + maxStructure = 10 + maxDefense = 11 + maxSupply = 12 + maxStockpile = 13 + maxTroops = 14 + population = 15 + industry = 16 + research = 17 + trade = 18 + construction = 19 + happiness = 20 + damage = 21 + capacity = 21 + secondaryStat = 22 + fuel = 23 + shield = 24 + structure = 25 + defense = 26 + supply = 27 + stockpile = 28 + troops = 29 + rebels = 30 + size = 31 + stealth = 32 + detection = 33 + speed = 34 + + +class diplomaticStatus(object): + """DiplomaticStatus enumeration.""" + war = 0 + peace = 1 + allied = 2 + + +class diplomaticMessageType(object): + """DiplomaticMessageType enumeration.""" + noMessage = -1 + warDeclaration = 0 + peaceProposal = 1 + acceptPeaceProposal = 2 + alliesProposal = 3 + acceptAlliesProposal = 4 + endAllies = 5 + cancelProposal = 6 + rejectProposal = 7 + + +class captureResult(object): + """CaptureResult enumeration.""" + capture = 0 + destroy = 1 + retain = 2 + + +class effectsCauseType(object): + """EffectsCauseType enumeration.""" + invalid = -1 + unknown = 0 + inherent = 1 + tech = 2 + building = 3 + field = 4 + special = 5 + species = 6 + shipPart = 7 + shipHull = 8 + + +class shipSlotType(object): + """ShipSlotType enumeration.""" + external = 0 + internal = 1 + core = 2 + + +class shipPartClass(object): + """ShipPartClass enumeration.""" + shortRange = 0 + fighterBay = 1 + fighterHangar = 2 + shields = 3 + armour = 4 + troops = 5 + detection = 6 + stealth = 7 + fuel = 8 + colony = 9 + speed = 10 + general = 11 + bombard = 12 + industry = 13 + research = 14 + trade = 15 + productionLocation = 16 + + +class unlockableItemType(object): + """UnlockableItemType enumeration.""" + invalid = -1 + building = 0 + shipPart = 1 + shipHull = 2 + shipDesign = 3 + tech = 4 + + +class galaxySetupOption(object): + """GalaxySetupOption enumeration.""" + invalid = -1 + none = 0 + low = 1 + medium = 2 + high = 3 + random = 4 + + +class galaxyShape(object): + """GalaxyShape enumeration.""" + invalid = -1 + spiral2 = 0 + spiral3 = 1 + spiral4 = 2 + cluster = 3 + elliptical = 4 + disc = 5 + box = 6 + irregular = 7 + ring = 8 + random = 9 + + +class ruleType(object): + """RuleType enumeration.""" + invalid = -1 + toggle = 0 + int = 1 + double = 2 + string = 3 + + +class roleType(object): + """RoleType enumeration.""" + host = 0 + clientTypeModerator = 1 + clientTypePlayer = 2 + clientTypeObserver = 3 + galaxySetup = 4 + + +def userString(x): + """userString mock""" + return "UserString %s" % x + + +def userStringList(x): + """userStringList mock""" + return "UserStringList %s" % x diff --git a/default/python/tests/run_tests.py b/default/python/tests/run_tests.py deleted file mode 100755 index feb42dc23ab..00000000000 --- a/default/python/tests/run_tests.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python2.7 - -import os -import sys -import pytest - -this_dir = os.path.dirname(__file__) - -# add path to AI -sys.path.append(os.path.join(this_dir, '..', '..', 'python', 'AI/')) -# path to freeOrionAIInterface mock -sys.path.append(os.path.join(this_dir, '..', '..', 'python', 'AI/', 'freeorion_debug', 'ide_tools', 'result')) -# add path to common tools -sys.path.append(os.path.join(this_dir, '..', '..', 'python')) - -pytest.main(sys.argv[1:]) diff --git a/default/python/tox.ini b/default/python/tox.ini new file mode 100644 index 00000000000..15a3423ee22 --- /dev/null +++ b/default/python/tox.ini @@ -0,0 +1,24 @@ +[flake8] +max-line-length=120 + +ignore = E121,E123,E126,E226,E24,E704,W503 # default ignore list +# TODO: fix these warnings +extend-ignore = + E501, # Long lines + W504 # Line break occurred after a binary operator + +per-file-ignores = + AI/FreeOrionAI.py:E402 + AI/AIDependencies.py: E241 + AI/savegame_codec/__init__.py: F401 + AI/freeorion_tools/__init__.py: F401,F403 + auth/auth.py: E402 + chat/chat.py: E402 + turn_events/turn_events.py: E402 + universe_generation/universe_generator.py: E402 + universe_generation/universe_tables.py: E201,E122,E231 + stub_generator/__init__.py: F401 + tests/conftest.py: E402,F401 + +exclude = + *_venv/ diff --git a/default/python/turn_events/turn_events.py b/default/python/turn_events/turn_events.py index 7448c26a8dc..c9b7dd333be 100644 --- a/default/python/turn_events/turn_events.py +++ b/default/python/turn_events/turn_events.py @@ -1,8 +1,7 @@ -from common.configure_logging import redirect_logging_to_freeorion_logger, convenience_function_references_for_logger +from common.configure_logging import redirect_logging_to_freeorion_logger # Logging is redirected before other imports so that import errors appear in log files. redirect_logging_to_freeorion_logger() -(debug, info, warn, error, fatal) = convenience_function_references_for_logger() import sys from random import random, uniform, choice @@ -13,7 +12,7 @@ def execute_turn_events(): - print "Executing turn events for turn", fo.current_turn() + print("Executing turn events for turn", fo.current_turn()) # creating fields systems = fo.get_systems() @@ -34,9 +33,9 @@ def execute_turn_events(): x = radius + (dist_from_center * sin(angle)) y = radius + (dist_from_center * cos(angle)) - print "...creating new", field_type, "field, at distance", dist_from_center, "from center" + print("...creating new", field_type, "field, at distance", dist_from_center, "from center") if fo.create_field(field_type, x, y, size) == fo.invalid_object(): - print >> sys.stderr, "Turn events: couldn't create new field" + print("Turn events: couldn't create new field", file=sys.stderr) # creating monsters gsd = fo.get_galaxy_setup_data() @@ -44,7 +43,7 @@ def execute_turn_events(): # monster freq ranges from 1/30 (= one monster per 30 systems) to 1/3 (= one monster per 3 systems) # (example: low monsters and 150 Systems results in 150 / 30 * 0.01 = 0.05) if monster_freq > 0 and random() < len(systems) * monster_freq * 0.01: - #only spawn Krill at the moment, other monsters can follow in the future + # only spawn Krill at the moment, other monsters can follow in the future if random() < 1: monster_type = "SM_KRILL_1" else: @@ -53,20 +52,20 @@ def execute_turn_events(): # search for systems without planets or fleets candidates = [s for s in systems if len(fo.sys_get_planets(s)) <= 0 and len(fo.sys_get_fleets(s)) <= 0] if not candidates: - print >> sys.stderr, "Turn events: unable to find system for monster spawn" + print("Turn events: unable to find system for monster spawn", file=sys.stderr) else: system = choice(candidates) - print "...creating new", monster_type, "at", fo.get_name(system) + print("...creating new", monster_type, "at", fo.get_name(system)) # create monster fleet monster_fleet = fo.create_monster_fleet(system) # if fleet creation fails, report an error if monster_fleet == fo.invalid_object(): - print >> sys.stderr, "Turn events: unable to create new monster fleet" + print("Turn events: unable to create new monster fleet", file=sys.stderr) else: # create monster, if creation fails, report an error monster = fo.create_monster(monster_type, monster_fleet) if monster == fo.invalid_object(): - print >> sys.stderr, "Turn events: unable to create monster in fleet" + print("Turn events: unable to create monster in fleet", file=sys.stderr) return True diff --git a/default/python/universe_generation/empires.py b/default/python/universe_generation/empires.py index 82418b8b767..813dd164d2a 100644 --- a/default/python/universe_generation/empires.py +++ b/default/python/universe_generation/empires.py @@ -1,17 +1,15 @@ -import os.path import random import freeorion as fo -from starsystems import star_types_real, pick_star_type -from planets import calc_planet_size, calc_planet_type, planet_sizes_real, planet_types_real +import universe_statistics from names import get_name_list, random_name -from options import (HS_ACCEPTABLE_PLANET_TYPES, HS_MIN_PLANETS_IN_VICINITY_TOTAL, - HS_MIN_PLANETS_IN_VICINITY_PER_SYSTEM, HS_MIN_DISTANCE_PRIORITY_LIMIT, HS_MAX_JUMP_DISTANCE_LIMIT, - HS_VICINITY_RANGE, HS_MIN_SYSTEMS_IN_VICINITY, HS_ACCEPTABLE_PLANET_SIZES) - +from options import (HS_ACCEPTABLE_PLANET_SIZES, HS_ACCEPTABLE_PLANET_TYPES, HS_MAX_JUMP_DISTANCE_LIMIT, + HS_MIN_DISTANCE_PRIORITY_LIMIT, HS_MIN_PLANETS_IN_VICINITY_PER_SYSTEM, + HS_MIN_PLANETS_IN_VICINITY_TOTAL, HS_MIN_SYSTEMS_IN_VICINITY, HS_VICINITY_RANGE) +from planets import calc_planet_size, calc_planet_type, planet_sizes_real, planet_types_real +from starsystems import pick_star_type, star_types_real from util import report_error -import universe_statistics def get_empire_name_generator(): @@ -84,7 +82,7 @@ def min_planets_in_vicinity_limit(num_systems): return min(HS_MIN_PLANETS_IN_VICINITY_TOTAL, num_systems * HS_MIN_PLANETS_IN_VICINITY_PER_SYSTEM) -class HomeSystemFinder(object): +class HomeSystemFinder: """Finds a set of home systems with a least ''num_home_systems'' systems.""" def __init__(self, _num_home_systems): # cache of sytem merits @@ -147,10 +145,10 @@ def find_home_systems_for_min_jump_distance(self, systems_pool, min_jumps): if len(local_pool) < self.num_home_systems: if not best_candidate: - print ("Failing in find_home_systems_for_min_jump_distance because " - "current_merit_lower_bound = {} trims local pool to {} systems " - "which is less than num_home_systems {}.".format( - current_merit_lower_bound, len(local_pool), self.num_home_systems)) + print("Failing in find_home_systems_for_min_jump_distance because " + "current_merit_lower_bound = {} trims local pool to {} systems " + "which is less than num_home_systems {}.".format( + current_merit_lower_bound, len(local_pool), self.num_home_systems)) break attempts = min(attempts - 1, len(local_pool)) @@ -179,13 +177,14 @@ def find_home_systems_for_min_jump_distance(self, systems_pool, min_jumps): # If we have a better candidate, set the new lower bound and try for a better candidate. if merit > current_merit_lower_bound: - print ("Home system set merit lower bound improved from {} to " - "{}".format(current_merit_lower_bound, merit)) + print("Home system set merit lower bound improved from {} to " + "{}".format(current_merit_lower_bound, merit)) current_merit_lower_bound = merit best_candidate = [s for (_, s) in merit_system] - # Quit sucessfully if the lowest merit system meets the minimum threshold - if merit >= min_planets_in_vicinity_limit(fo.systems_within_jumps_unordered(HS_VICINITY_RANGE, [system])): + # Quit successfully if the lowest merit system meets the minimum threshold + if merit >= min_planets_in_vicinity_limit( + len(fo.systems_within_jumps_unordered(HS_VICINITY_RANGE, [system]))): break return best_candidate @@ -205,16 +204,16 @@ def find_home_systems(num_home_systems, pool_list, jump_distance, min_jump_dista # try to find home systems, decrease the min jumps until enough systems can be found, or the jump distance drops # below the specified minimum jump distance (which means failure) while jump_distance >= min_jump_distance: - print "Trying to find", num_home_systems, "home systems that are at least", jump_distance, "jumps apart..." + print("Trying to find", num_home_systems, "home systems that are at least", jump_distance, "jumps apart...") # try to pick our home systems by iterating over the pools we got for pool, pool_label in pool_list: - print "...use", pool_label + print("...use", pool_label) # check if the pool has enough systems to pick from if len(pool) <= num_home_systems: # no, the pool has less systems than home systems requested, so just skip trying using that pool - print "...pool only has", len(pool), "systems, skip trying to use it" + print("...pool only has", len(pool), "systems, skip trying to use it") continue # try to pick home systems @@ -222,11 +221,11 @@ def find_home_systems(num_home_systems, pool_list, jump_distance, min_jump_dista # check if we got enough if len(home_systems) >= num_home_systems: # yes, we got what we need, return the home systems we found - print "...", len(home_systems), "systems found" + print("...", len(home_systems), "systems found") return home_systems else: # no, try next pool - print "...only", len(home_systems), "systems found" + print("...only", len(home_systems), "systems found") # we did not find enough home systems with the current jump distance requirement, # so decrease the jump distance and try again @@ -240,7 +239,7 @@ def add_planets_to_vicinity(vicinity, num_planets, gsd): """ Adds the specified number of planets to the specified systems. """ - print "Adding", num_planets, "planets to the following systems:", vicinity + print("Adding", num_planets, "planets to the following systems:", vicinity) # first, compile a list containing all the free orbits in the specified systems # begin with adding the free orbits of all systems that have a real star (that is, no neutron star, black hole, @@ -280,7 +279,7 @@ def add_planets_to_vicinity(vicinity, num_planets, gsd): if len(free_orbits_map) < num_planets: report_error("Python add_planets_to_vicinity: less free orbits than planets to add - cancelled") - print "...free orbits available:", free_orbits_map + print("...free orbits available:", free_orbits_map) # as we will pop the free orbits from this list afterwards, shuffle it to randomize the order of the orbits random.shuffle(free_orbits_map) @@ -295,10 +294,10 @@ def add_planets_to_vicinity(vicinity, num_planets, gsd): # if it is a black hole or has no star, change the star type # pick a star type, continue until we get a real star # don't accept neutron, black hole or no star - print "...system picked to add a planet has star type", star_type + print("...system picked to add a planet has star type", star_type) while star_type not in star_types_real: star_type = pick_star_type(gsd.age) - print "...change that to", star_type + print("...change that to", star_type) fo.sys_set_star_type(system, star_type) # pick a planet size, continue until we get a size that matches the HS_ACCEPTABLE_PLANET_SIZES option @@ -310,7 +309,7 @@ def add_planets_to_vicinity(vicinity, num_planets, gsd): planet_type = calc_planet_type(star_type, orbit, planet_size) # finally, create the planet in the system and orbit we got - print "...adding", planet_size, planet_type, "planet to system", system + print("...adding", planet_size, planet_type, "planet to system", system) if fo.create_planet(planet_size, planet_type, system, orbit, "") == fo.invalid_object(): report_error("Python add_planets_to_vicinity: create planet in system %d failed" % system) @@ -322,7 +321,7 @@ def compile_home_system_list(num_home_systems, systems, gsd): """ Compiles a list with a requested number of home systems. """ - print "Compile home system list:", num_home_systems, "systems requested" + print("Compile home system list:", num_home_systems, "systems requested") # if the list of systems to choose home systems from is empty, report an error and return empty list if not systems: @@ -336,7 +335,7 @@ def compile_home_system_list(num_home_systems, systems, gsd): # and with large galaxies an excessive amount of time can be used in failed attempts # b.) lower than the minimum jump distance limit that should be considered high priority (see options.py), # otherwise no attempt at all would be made to enforce the other requirements for home systems (see below) - min_jumps = min(HS_MAX_JUMP_DISTANCE_LIMIT, max(int(float(len(systems)) / float(num_home_systems * 2)), + min_jumps = min(HS_MAX_JUMP_DISTANCE_LIMIT, max(int(len(systems) / (num_home_systems * 2)), HS_MIN_DISTANCE_PRIORITY_LIMIT)) # home systems must have a certain minimum of systems and planets in their near vicinity @@ -356,9 +355,9 @@ def compile_home_system_list(num_home_systems, systems, gsd): pool_matching_sys_limit.append(system) if count_planets_in_systems(systems_in_vicinity) >= min_planets_in_vicinity_limit(len(systems_in_vicinity)): pool_matching_sys_and_planet_limit.append(system) - print (len(pool_matching_sys_and_planet_limit), - "systems meet the min systems and planets in the near vicinity limit") - print len(pool_matching_sys_limit), "systems meet the min systems in the near vicinity limit" + print(len(pool_matching_sys_and_planet_limit), + "systems meet the min systems and planets in the near vicinity limit") + print(len(pool_matching_sys_limit), "systems meet the min systems in the near vicinity limit") # now try to pick the requested number of home systems # we will do this by calling find_home_systems, which takes a list of tuples defining the pools from which to pick @@ -368,7 +367,7 @@ def compile_home_system_list(num_home_systems, systems, gsd): # to start with the min_jumps jumps distance we calculated above, but not to go lower than # HS_MIN_DISTANCE_PRIORITY_LIMIT - print "First attempt: trying to pick home systems from the filtered pools of preferred systems" + print("First attempt: trying to pick home systems from the filtered pools of preferred systems") pool_list = [ # the better pool is of course the one where all systems meet BOTH the min systems and planets limit (pool_matching_sys_and_planet_limit, "pool of systems that meet both the min systems and planets limit"), @@ -383,7 +382,7 @@ def compile_home_system_list(num_home_systems, systems, gsd): # again with the min_jumps jump distance limit and specifying 0 as number of required home systems to pick as much # systems as possible if len(home_systems) < num_home_systems: - print "Second attempt: trying to pick home systems from all systems" + print("Second attempt: trying to pick home systems from all systems") home_systems = find_home_systems(num_home_systems, [(systems, "complete pool")], min_jumps, 1) # check if the selection process delivered a list with enough home systems @@ -408,14 +407,14 @@ def compile_home_system_list(num_home_systems, systems, gsd): # if this home system has no "real" star, change star type to a randomly selected "real" star if fo.sys_get_star_type(home_system) not in star_types_real: star_type = random.choice(star_types_real) - print "Home system", home_system, "has star type", fo.sys_get_star_type(home_system),\ - ", changing that to", star_type + print("Home system", home_system, "has star type", fo.sys_get_star_type(home_system), ", changing that to", + star_type) fo.sys_set_star_type(home_system, star_type) # if this home system has no planets, create one in a random orbit # we take random values for type and size, as these will be set to suitable values later if not fo.sys_get_planets(home_system): - print "Home system", home_system, "has no planets, adding one" + print("Home system", home_system, "has no planets, adding one") planet = fo.create_planet(random.choice(planet_sizes_real), random.choice(planet_types_real), home_system, random.randint(0, fo.sys_get_num_orbits(home_system) - 1), "") @@ -426,15 +425,15 @@ def compile_home_system_list(num_home_systems, systems, gsd): # finally, check again if all home systems meet the criteria of having the required minimum number of planets # within their near vicinity, if not, add the missing number of planets - print "Checking if home systems have the required minimum of planets within the near vicinity..." + print("Checking if home systems have the required minimum of planets within the near vicinity...") for home_system in home_systems: # calculate the number of missing planets, and add them if this number is > 0 systems_in_vicinity = fo.systems_within_jumps_unordered(HS_VICINITY_RANGE, [home_system]) num_systems_in_vicinity = len(systems_in_vicinity) num_planets_in_vicinity = count_planets_in_systems(systems_in_vicinity) num_planets_to_add = min_planets_in_vicinity_limit(num_systems_in_vicinity) - num_planets_in_vicinity - print "Home system", home_system, "has", num_systems_in_vicinity, "systems and", num_planets_in_vicinity,\ - "planets in the near vicinity, required minimum:", min_planets_in_vicinity_limit(num_systems_in_vicinity) + print("Home system", home_system, "has", num_systems_in_vicinity, "systems and", num_planets_in_vicinity, + "planets in the near vicinity, required minimum:", min_planets_in_vicinity_limit(num_systems_in_vicinity)) if num_planets_to_add > 0: systems_in_vicinity.remove(home_system) # don't add planets to the home system, so remove it from the list # sort the systems_in_vicinity before adding, since the C++ engine doesn't guarrantee the same @@ -453,16 +452,16 @@ def setup_empire(empire, empire_name, home_system, starting_species, player_name # set empire name, if no one is given, pick one randomly if not empire_name: - print "No empire name set for player", player_name, ", picking one randomly" + print("No empire name set for player", player_name, ", picking one randomly") empire_name = next(empire_name_generator) fo.empire_set_name(empire, empire_name) - print "Empire name for player", player_name, "is", empire_name + print("Empire name for player", player_name, "is", empire_name) # check starting species, if no one is given, pick one randomly if starting_species == "RANDOM" or not starting_species: - print "Picking random starting species for player", player_name + print("Picking random starting species for player", player_name) starting_species = next(starting_species_pool) - print "Starting species for player", player_name, "is", starting_species + print("Starting species for player", player_name, "is", starting_species) universe_statistics.empire_species[starting_species] += 1 # pick a planet from the specified home system as homeworld @@ -483,54 +482,66 @@ def setup_empire(empire, empire_name, home_system, starting_species, player_name preferred_focus = fo.species_preferred_focus(starting_species) if preferred_focus in available_foci: # if yes, set the homeworld focus to the preferred focus - print "Player", player_name, ": setting preferred focus", preferred_focus, "on homeworld" + print("Player", player_name, ": setting preferred focus", preferred_focus, "on homeworld") fo.planet_set_focus(homeworld, preferred_focus) elif len(available_foci) > 0: # if no, and there is at least one available focus, # just take the first of the list if preferred_focus == "": - print "Player", player_name, ": starting species", starting_species, "has no preferred focus, using",\ - available_foci[0], "instead" + print("Player", player_name, ": starting species", starting_species, "has no preferred focus, using", + available_foci[0], "instead") else: - print "Player", player_name, ": preferred focus", preferred_focus, "for starting species",\ - starting_species, "not available on homeworld, using", available_foci[0], "instead" + print("Player", player_name, ": preferred focus", preferred_focus, "for starting species", starting_species, + "not available on homeworld, using", available_foci[0], "instead") fo.planet_set_focus(homeworld, available_foci[0]) else: # if no focus is available on the homeworld, don't set any focus - print "Player", player_name, ": no available foci on homeworld for starting species", starting_species + print("Player", player_name, ": no available foci on homeworld for starting species", starting_species) # give homeworld starting buildings - print "Player", player_name, ": add starting buildings to homeworld" + print("Player", player_name, ": add starting buildings to homeworld") for item in fo.load_starting_buildings(): fo.create_building(item.name, homeworld, empire) # unlock starting techs, buildings, hulls, ship parts, etc. # use default content file - print "Player", player_name, ": add unlocked items" - for item in fo.load_item_spec_list(): + print("Player", player_name, ": add unlocked items") + for item in fo.load_unlockable_item_list(): fo.empire_unlock_item(empire, item.type, item.name) # add premade ship designs to empire - print "Player", player_name, ": add premade ship designs" + print("Player", player_name, ": add premade ship designs") for ship_design in fo.design_get_premade_list(): fo.empire_add_ship_design(empire, ship_design) # add starting fleets to empire # use default content file - print "Player", player_name, ": add starting fleets" + print("Player", player_name, ": add starting fleets") fleet_plans = fo.load_fleet_plan_list() for fleet_plan in fleet_plans: + # should fleet be aggressive? check if any ships are armed. + should_be_aggressive = False + for ship_design in fleet_plan.ship_designs(): + design = fo.getPredefinedShipDesign(ship_design) + if design is None: + report_error("Looked up null design with name %s", ship_design) + elif design.isArmed: + should_be_aggressive = True + break + # first, create the fleet - fleet = fo.create_fleet(fleet_plan.name(), home_system, empire) + fleet = fo.create_fleet(fleet_plan.name(), home_system, empire, should_be_aggressive) # if the fleet couldn't be created, report an error and try to continue with the next fleet plan if fleet == fo.invalid_object(): report_error("Python setup empire: couldn't create fleet %s" % fleet_plan.name()) continue + # second, iterate over the list of ship design names in the fleet plan for ship_design in fleet_plan.ship_designs(): # create a ship in the fleet # if the ship couldn't be created, report an error and try to continue with the next ship design if fo.create_ship("", ship_design, starting_species, fleet) == fo.invalid_object(): - report_error("Python setup empire: couldn't create ship %s for fleet %s" + report_error("Python setup empire: couldn't create ship of design %s for fleet %s" % (ship_design, fleet_plan.name())) + return True diff --git a/default/python/universe_generation/fields.py b/default/python/universe_generation/fields.py index e896422413c..2c9600acbcf 100644 --- a/default/python/universe_generation/fields.py +++ b/default/python/universe_generation/fields.py @@ -1,6 +1,7 @@ -from random import randint, uniform, choice, sample +from random import choice, sample, uniform import freeorion as fo + from util import report_error @@ -12,7 +13,7 @@ def generate_fields(systems): candidates = [s for s in systems if (fo.sys_get_star_type(s) == fo.starType.noStar) and (not fo.sys_get_planets(s))] # make sure we have at least one empty no star system, otherwise return without creating any fields if not candidates: - print "...no empty no star systems found, no fields created" + print("...no empty no star systems found, no fields created") return # pick 10-20% of all empty no star systems to create stationary fields in them, but at least one accepted = sample(candidates, max(int(len(candidates) * uniform(0.1, 0.2)), 1)) @@ -23,4 +24,4 @@ def generate_fields(systems): if fo.create_field_in_system(field_type, uniform(40, 120), system) == fo.invalid_object(): # create field failed, report an error report_error("Python generate_fields: create field %s in system %d failed" % (field_type, system)) - print "...fields created in %d systems out of %d empty no star systems" % (len(accepted), len(candidates)) + print("...fields created in %d systems out of %d empty no star systems" % (len(accepted), len(candidates))) diff --git a/default/python/universe_generation/galaxy.py b/default/python/universe_generation/galaxy.py index 31658b24641..0a6a3a41bf1 100644 --- a/default/python/universe_generation/galaxy.py +++ b/default/python/universe_generation/galaxy.py @@ -1,22 +1,26 @@ import sys -from random import random, uniform, randint, gauss -from math import pi, sin, cos, acos, sqrt, ceil, floor from collections import defaultdict +from math import acos, ceil, cos, floor, pi, sin, sqrt +from random import gauss, randint, random, uniform import freeorion as fo -import util + import universe_tables +import util -class AdjacencyGrid(object): +class AdjacencyGrid: def __init__(self, universe_width): + """ + :param float universe_width: + """ assert universe_tables.MIN_SYSTEM_SEPARATION < universe_tables.MAX_STARLANE_LENGTH self.min_dist = universe_tables.MIN_SYSTEM_SEPARATION self.max_dist = universe_tables.MAX_STARLANE_LENGTH self.cell_size = min(max(universe_width / 50, self.min_dist), self.max_dist / sqrt(2)) self.width = int(universe_width / self.cell_size) + 1 self.grid = defaultdict(set) - print "Adjacency Grid: width {}, cell size {}".format(self.width, self.cell_size) + print("Adjacency Grid: width {}, cell size {}".format(self.width, self.cell_size)) def cell(self, pos): """Returns cell index.""" @@ -30,8 +34,9 @@ def remove_pos(self, pos): """Removes pos.""" self.grid[self.cell(pos)].discard(pos) - def _square_indices_containing_cell(self, (cell_x, cell_y), radius): + def _square_indices_containing_cell(self, cell, radius): """Return a square of indices containing cell.""" + cell_x, cell_y = cell upper_left_x = max(0, cell_x - radius) upper_left_y = max(0, cell_y - radius) lower_right_x = min(self.width - 1, cell_x + radius) @@ -84,7 +89,7 @@ def too_close_to_other_positions(self, pos): for p2 in self.grid[p2_cell]) -class DSet(object): +class DSet: """ A set of pos that is disjoint (not connected) to other sets. @@ -113,7 +118,7 @@ def inc_rank(self): self.rank += 1 -class DisjointSets(object): +class DisjointSets: """ A set of disjoint sets. @@ -201,10 +206,10 @@ def complete_sets(self): ret = defaultdict(list) for pos in self.dsets.keys(): ret[self.root(pos)].append(pos) - return ret.values() + return list(ret.values()) -class Clusterer(object): +class Clusterer: """ Computes all clusters of positions separated by more than MAX_STARLANE_LENGTH @@ -332,7 +337,7 @@ def enforce_max_distance(positions, adjacency_grid): clusterer = Clusterer(positions, adjacency_grid) if len(clusterer) == 1: - print "All systems positioned in a single connected cluster" + print("All systems positioned in a single connected cluster") else: print("{} clusters separated by more than the MAX_STARLANE_LENGTH." " Starting to fill gaps.".format(len(clusterer))) @@ -361,7 +366,7 @@ def enforce_max_distance(positions, adjacency_grid): clusterer.stitch_clusters(p1, p2, extra_positions) if len(clusterer) == 1: - print "All systems now positioned in a single connected cluster" + print("All systems now positioned in a single connected cluster") else: print("{} clusters separated by more the MAX_STARLANE_LENGTH. " "Continuing to fill gaps.".format(len(clusterer))) @@ -517,9 +522,13 @@ def disc_galaxy_calc_positions(positions, adjacency_grid, size, width): def cluster_galaxy_calc_positions(positions, adjacency_grid, size, width): """ Calculate positions for the cluster galaxy shape. + :type positions: list + :type adjacency_grid: AdjacencyGrid + :type size: int + :type width: float """ if size < 1: - print >> sys.stderr, "Cluster galaxy requested for less than 1 star" + print("Cluster galaxy requested for less than 1 star", file=sys.stderr) return if size == 1: @@ -637,6 +646,8 @@ def ring_galaxy_calc_positions(positions, adjacency_grid, size, width): def box_galaxy_calc_positions(positions, adjacency_grid, size, width): """ Calculate positions for the box galaxy shape. + + :param float width: """ for i in range(size): attempts = 100 @@ -669,7 +680,7 @@ def irregular_galaxy_calc_positions(positions, adjacency_grid, size, width): Calculate positions for the irregular galaxy shape. """ max_delta = max(min(float(universe_tables.MAX_STARLANE_LENGTH), width / 10.0), adjacency_grid.min_dist * 2.0) - print "Irregular galaxy shape: max delta distance = {}".format(max_delta) + print("Irregular galaxy shape: max delta distance = {}".format(max_delta)) origin_x, origin_y = width / 2.0, width / 2.0 prev_x, prev_y = origin_x, origin_y reset_to_origin = 0 @@ -692,7 +703,7 @@ def irregular_galaxy_calc_positions(positions, adjacency_grid, size, width): adjacency_grid.insert_pos(pos) positions.append(pos) prev_x, prev_y = x, y - print "Reset to origin {} times".format(reset_to_origin) + print("Reset to origin {} times".format(reset_to_origin)) def recalc_universe_width(positions): @@ -703,7 +714,7 @@ def recalc_universe_width(positions): Returns the new universe width and the recalculated positions. """ - print "Recalculating universe width..." + print("Recalculating universe width...") # first, get the uppermost, lowermost, leftmost and rightmost positions # (these are those with their x or y coordinate closest to or farthest away from the x or y axis) min_x = min(positions, key=lambda p: p[0])[0] @@ -725,13 +736,13 @@ def recalc_universe_width(positions): width = max_x - min_x height = max_y - min_y actual_width = max(width, height) + 20.0 - print "...recalculated universe width: {}".format(actual_width) + print("...recalculated universe width: {}".format(actual_width)) # shift all positions so the entire map is centered in a quadratic box of the width we just calculated # this box defines the extends of our universe delta_x = ((actual_width - width) / 2) - min_x delta_y = ((actual_width - height) / 2) - min_y - print "...shifting all system positions by {}/{}".format(delta_x, delta_y) + print("...shifting all system positions by {}/{}".format(delta_x, delta_y)) new_positions = [(p[0] + delta_x, p[1] + delta_y) for p in positions] print("...the leftmost system position is now at x coordinate {}" @@ -755,13 +766,13 @@ def calc_star_system_positions(gsd): # calculate typical width for universe based on number of systems width = calc_universe_width(gsd.shape, gsd.size) - print "Set universe width to {}".format(width) + print("Set universe width to {}".format(width)) fo.set_universe_width(width) positions = [] adjacency_grid = AdjacencyGrid(width) - print "Creating {} galaxy shape".format(gsd.shape) + print("Creating {} galaxy shape".format(gsd.shape)) if gsd.shape == fo.galaxyShape.spiral2: spiral_galaxy_calc_positions(positions, adjacency_grid, 2, gsd.size, width) elif gsd.shape == fo.galaxyShape.spiral3: @@ -789,7 +800,7 @@ def calc_star_system_positions(gsd): # to avoid having too much "extra space" around the system positions of our galaxy map, recalculate the universe # width and shift all positions accordingly width, positions = recalc_universe_width(positions) - print "Set universe width to {}".format(width) + print("Set universe width to {}".format(width)) fo.set_universe_width(width) return positions diff --git a/default/python/universe_generation/monsters.py b/default/python/universe_generation/monsters.py index 150c7b83989..010283db79f 100644 --- a/default/python/universe_generation/monsters.py +++ b/default/python/universe_generation/monsters.py @@ -1,12 +1,14 @@ import random + import freeorion as fo -from util import MapGenerationError, report_error + import universe_statistics import universe_tables from galaxy import DisjointSets +from util import MapGenerationError, report_error -class StarlaneAlteringMonsters(object): +class StarlaneAlteringMonsters: def __init__(self, systems): self.systems = systems self.placed = set() @@ -78,7 +80,7 @@ def populate_monster_fleet(fleet_plan, system): if fo.create_monster(design, monster_fleet) == fo.invalid_object(): raise MapGenerationError("Python generate_monsters: unable to create monster %s" % design) - print "Spawn", fleet_plan.name(), "at", fo.get_name(system) + print("Spawn", fleet_plan.name(), "at", fo.get_name(system)) def generate_monsters(monster_freq, systems): @@ -92,7 +94,7 @@ def generate_monsters(monster_freq, systems): # a value of 0 means no monsters, in this case return immediately if basic_chance <= 0: return - print "Default monster spawn chance:", basic_chance + print("Default monster spawn chance:", basic_chance) expectation_tally = 0.0 actual_tally = 0 @@ -122,21 +124,22 @@ def generate_monsters(monster_freq, systems): universe = fo.get_universe() # Fleet plans that include ships capable of altering starlanes. - # @content_tag{CAN_ALTER_STARLANES} universe_generator special handling for fleets containing a hull design with this tag. + # @content_tag{CAN_ALTER_STARLANES} universe_generator special handling + # for fleets containing a hull design with this tag. fleet_can_alter_starlanes = {fp for fp in fleet_plans - if any([universe.getGenericShipDesign(design).hull_type.hasTag("CAN_ALTER_STARLANES") + if any([universe.getGenericShipDesign(design).ship_hull.hasTag("CAN_ALTER_STARLANES") for design in fp.ship_designs()])} # dump a list of all monster fleets meeting these conditions and their properties to the log - print "Monster fleets available for generation at game start:" + print("Monster fleets available for generation at game start:") fp_location_cache = {} for fleet_plan in fleet_plans: - print "...", fleet_plan.name(), ": spawn rate", fleet_plan.spawn_rate(), - print "/ spawn limit", fleet_plan.spawn_limit(), - print "/ effective chance", basic_chance * fleet_plan.spawn_rate(), + print("...", fleet_plan.name(), ": spawn rate", fleet_plan.spawn_rate(), end=' ') + print("/ spawn limit", fleet_plan.spawn_limit(), end=' ') + print("/ effective chance", basic_chance * fleet_plan.spawn_rate(), end=' ') fp_location_cache[fleet_plan] = set(fleet_plan.locations(systems)) - print ("/ can be spawned at", len(fp_location_cache[fleet_plan]), - "of", len(systems), "systems") + print("/ can be spawned at", len(fp_location_cache[fleet_plan]), + "of", len(systems), "systems") if fleet_plan.name() in nest_name_map.values(): universe_statistics.tracked_monsters_chance[fleet_plan.name()] = basic_chance * fleet_plan.spawn_rate() @@ -162,7 +165,7 @@ def generate_monsters(monster_freq, systems): # filter out all monster fleets whose location condition allows this system and whose counter hasn't reached 0. suitable_fleet_plans = [fp for fp in fleet_plans if system in fp_location_cache[fp] - and spawn_limits[fp] + and spawn_limits.get(fp, 0) and (fp not in fleet_can_alter_starlanes or starlane_altering_monsters.can_place_at(system, fp))] # if there are no suitable monster fleets for this system, continue with the next @@ -199,12 +202,14 @@ def generate_monsters(monster_freq, systems): report_error(str(err)) continue - print "Actual # monster fleets placed: %d; Total Placement Expectation: %.1f" % (actual_tally, expectation_tally) + print("Actual # monster fleets placed: %d; Total Placement Expectation: %.1f" % (actual_tally, expectation_tally)) # finally, compile some statistics to be dumped to the log later - universe_statistics.monsters_summary = [(fp.name(), fp.spawn_limit() - counter) for fp, counter in spawn_limits.iteritems()] + universe_statistics.monsters_summary = [ + (fp.name(), fp.spawn_limit() - counter) for fp, counter in spawn_limits.items() + ] universe_statistics.tracked_monsters_tries.update(tracked_plan_tries) universe_statistics.tracked_monsters_summary.update(tracked_plan_counts) - universe_statistics.tracked_monsters_location_summary.update([(fp.name(), count) - for fp, count in tracked_plan_valid_locations.iteritems()]) - universe_statistics.tracked_nest_location_summary.update([(nest_name_map[nest], count) - for nest, count in tracked_nest_valid_locations.items()]) + universe_statistics.tracked_monsters_location_summary.update( + (fp.name(), count) for fp, count in tracked_plan_valid_locations.items()) + universe_statistics.tracked_nest_location_summary.update( + (nest_name_map[nest], count) for nest, count in tracked_nest_valid_locations.items()) diff --git a/default/python/universe_generation/names.py b/default/python/universe_generation/names.py index 4b5e892d000..7e12fb6133f 100644 --- a/default/python/universe_generation/names.py +++ b/default/python/universe_generation/names.py @@ -19,7 +19,7 @@ def random_name(size): It rotates between consonants and vowels. Rotation is global first letter depends on prev calls. """ - return ''.join(next(_random_letter_generator) for _ in xrange(size)).capitalize() + return ''.join(next(_random_letter_generator) for _ in range(size)).capitalize() def get_name_list(name_list): diff --git a/default/python/universe_generation/natives.py b/default/python/universe_generation/natives.py index 6fc2ec9b1ba..a4a140611ec 100644 --- a/default/python/universe_generation/natives.py +++ b/default/python/universe_generation/natives.py @@ -1,5 +1,5 @@ -import random import itertools +import random import freeorion as fo @@ -7,6 +7,7 @@ import universe_statistics import universe_tables + natives_for_planet_type = {} planet_types_for_natives = {} @@ -33,15 +34,15 @@ def generate_natives(native_freq, systems, empire_home_systems): native_safe_planets = set(itertools.chain.from_iterable( [fo.sys_get_planets(s) for s in systems if s not in empire_exclusions])) - print "Number of planets far enough from players for natives to be allowed:", len(native_safe_planets) + print("Number of planets far enough from players for natives to be allowed:", len(native_safe_planets)) # if there are no "native safe" planets at all, we can stop here if not native_safe_planets: return # get all native species native_species = fo.get_native_species() - print "Species that can be added as natives:" - print "... " + "\n... ".join(native_species) + print("Species that can be added as natives:") + print("... " + "\n... ".join(native_species)) # create a map with a list for each planet type containing the species # for which this planet type is a good environment @@ -93,7 +94,7 @@ def generate_natives(native_freq, systems, empire_home_systems): # if no, and there is at least one available focus, just take the first of the list # otherwise don't set any focus fo.planet_set_focus(candidate, available_foci[0]) - print "Added native", natives, "to planet", fo.get_name(candidate) + print("Added native", natives, "to planet", fo.get_name(candidate)) # increase the statistics counter for this native species, so a species summary can be dumped to the log later universe_statistics.species_summary[natives] += 1 diff --git a/default/python/universe_generation/options.py b/default/python/universe_generation/options.py index c1f5290cf6a..647cf9dd97d 100644 --- a/default/python/universe_generation/options.py +++ b/default/python/universe_generation/options.py @@ -1,10 +1,11 @@ import freeorion as fo -from planets import planet_types_real, planet_sizes_real +from planets import planet_sizes_real, planet_types_real -############################### -## STAR GROUP NAMING OPTIONS ## -############################### + +############################# +# STAR GROUP NAMING OPTIONS # +############################# # if star_groups_use_chars is true use entries from the stringtable entry STAR_GROUP_CHARS (single characters), # otherwise use words like 'Alpha' from the stringtable entry STAR_GROUP_WORDS. @@ -22,9 +23,9 @@ NAMING_LARGE_GALAXY_SIZE = 200 # a galaxy with 200+ star systems is considered as large, below that value as small -################################### -## HOME SYSTEM SELECTION OPTIONS ## -################################### +################################# +# HOME SYSTEM SELECTION OPTIONS # +################################# # These options are needed in the home system selection/placement process. They determine the minimum number of # systems and planets that a home system must have in its near vicinity, define the extend of this "near vicinity", etc. diff --git a/default/python/universe_generation/planets.py b/default/python/universe_generation/planets.py index d6fd460e788..8b369277ea3 100644 --- a/default/python/universe_generation/planets.py +++ b/default/python/universe_generation/planets.py @@ -1,8 +1,10 @@ -import sys import random +import sys + import freeorion as fo -import util + import universe_tables as tables +import util # tuple of all valid planet sizes (with "no world") @@ -64,7 +66,7 @@ def calc_planet_size(star_type, orbit, planet_density, galaxy_shape): if max_roll < roll: max_roll = roll planet_size = size - except: + except: # noqa: E722 # in case of an error play it safe and set planet size to invalid planet_size = fo.planetSize.unknown util.report_error("Python calc_planet_size: Pick planet size failed" + str(sys.exc_info()[1])) diff --git a/default/python/universe_generation/specials.py b/default/python/universe_generation/specials.py index 310f5a157e0..4c412948d15 100644 --- a/default/python/universe_generation/specials.py +++ b/default/python/universe_generation/specials.py @@ -2,9 +2,11 @@ from collections import defaultdict import freeorion as fo + import universe_statistics import universe_tables + # REPEAT_RATE along with calculate_number_of_specials_to_place determines if there are multiple # specials in a single location. There can only be at most 4 specials in a single location. # The probabilites break down as follows: @@ -45,7 +47,7 @@ def place_special(specials, obj): continue fo.add_special(obj, special) - print "Special", special, "added to", fo.get_name(obj) + print("Special", special, "added to", fo.get_name(obj)) universe_statistics.specials_summary[special] += 1 return 1 @@ -72,7 +74,7 @@ def distribute_specials(specials_freq, universe_objects): return # dump a list of all specials meeting that conditions and their properties to the log - print "Specials available for distribution at game start:" + print("Specials available for distribution at game start:") for special in specials: print("... {:30}: spawn rate {:2.3f} / spawn limit {}". format(special, fo.special_spawn_rate(special), fo.special_spawn_limit(special))) @@ -104,12 +106,12 @@ def distribute_specials(specials_freq, universe_objects): for (obj, system, specials_count) in obj_tuple_needing_specials: systems_needing_specials[system].add((obj, system, specials_count)) - print " Placing in {} locations remaining.".format(len(systems_needing_specials)) + print(" Placing in {} locations remaining.".format(len(systems_needing_specials))) # Find a list of candidates all spaced GALAXY_DECOUPLING_DISTANCE apart candidates = [] while systems_needing_specials: - random_sys = random.choice(systems_needing_specials.values()) + random_sys = random.choice(list(systems_needing_specials.values())) member = random.choice(list(random_sys)) obj, system, specials_count = member candidates.append(obj) diff --git a/default/python/universe_generation/starnames.py b/default/python/universe_generation/starnames.py index 674e4aefcf3..fbb010b0c3c 100644 --- a/default/python/universe_generation/starnames.py +++ b/default/python/universe_generation/starnames.py @@ -1,7 +1,9 @@ import random + import freeorion as fo -import options + import names +import options # for starname modifiers @@ -70,7 +72,7 @@ def cluster_stars(positions, num_star_groups): old_c = 1 - old_c else: if loop > 0: # if here at loop 0, then didn't try for convergence - print "falling through system clustering iteration loop without convergence" + print("falling through system clustering iteration loop without convergence") return clusters[1 - old_c] @@ -174,7 +176,7 @@ def name_star_systems(system_list): indiv_systems = [] # remove groups with only one non-deep-system - for groupindex, group_list in star_groups.items(): + for groupindex, group_list in list(star_groups.items()): max_can_transfer = len(potential_group_names) - len(star_groups) + len(individual_names) - len(indiv_systems) if max_can_transfer <= 0: break @@ -218,16 +220,16 @@ def name_star_systems(system_list): random.shuffle(potential_group_names) random.shuffle(individual_names) random.shuffle(group_names) - num_for_indiv = min(max(len(potential_group_names) / 2, num_individual_stars + 1 - len(individual_names)), + num_for_indiv = min(max(len(potential_group_names) // 2, num_individual_stars + 1 - len(individual_names)), len(potential_group_names)) individual_names.extend(potential_group_names[:num_for_indiv]) group_names.extend(potential_group_names[num_for_indiv:]) - # print "sampling for %d indiv names from list of %d total indiv names"%(num_individual_stars, len(individual_names)) + # print "sampling for %d indiv names from list of %d total indiv names" % ( + # num_individual_stars, len(individual_names)) indiv_name_sample = random.sample(individual_names, num_individual_stars) # indiv_name_assignments = zip([(pos.x, pos.y) for pos in position_list[:num_individual_stars]], indiv_name_sample) - indiv_name_assignments = zip(indiv_systems, indiv_name_sample) - star_name_map.update(indiv_name_assignments) + star_name_map.update(zip(indiv_systems, indiv_name_sample)) # print "sampling for %d group names from list of %d total group names"%(num_star_groups, len(group_names)) if len(group_names) < num_star_groups: group_names.extend([names.random_name(6) for _ in range(num_star_groups - len(group_names))]) diff --git a/default/python/universe_generation/starsystems.py b/default/python/universe_generation/starsystems.py index 8c720a7d3b7..4ff6478e116 100644 --- a/default/python/universe_generation/starsystems.py +++ b/default/python/universe_generation/starsystems.py @@ -1,10 +1,13 @@ -import sys import random +import sys +from itertools import product + import freeorion as fo + import planets -import util import universe_tables -from itertools import product +import util + # tuple of available star types star_types = (fo.starType.blue, fo.starType.white, fo.starType.yellow, fo.starType.orange, @@ -32,7 +35,7 @@ def pick_star_type(galaxy_age): if max_roll < roll: max_roll = roll star_type = candidate - except: + except: # noqa: E722 # in case of an error play save and set star type to invalid star_type = fo.starType.unknown util.report_error("Python pick_star_type: Pick star type failed\n" + sys.exc_info()[1]) @@ -87,7 +90,7 @@ def generate_systems(pos_list, gsd): continue sys_list.append(system) - orbits = range(fo.sys_get_num_orbits(system)) + orbits = list(range(fo.sys_get_num_orbits(system))) if not planets.can_have_planets(star_type, orbits, gsd.planet_density, gsd.shape): continue @@ -112,7 +115,7 @@ def generate_systems(pos_list, gsd): break else: # Intentionally non-modal. Should be a warning. - print >> sys.stderr, ("Python generate_systems: place planets in system %d at position (%.2f, %.2f) failed" - % (system, position[0], position[1])) + print(("Python generate_systems: place planets in system %d at position (%.2f, %.2f) failed" + % (system, position[0], position[1])), file=sys.stderr) return sys_list diff --git a/default/python/universe_generation/universe_generator.py b/default/python/universe_generation/universe_generator.py index fb5166e47df..256272ad813 100755 --- a/default/python/universe_generation/universe_generator.py +++ b/default/python/universe_generation/universe_generator.py @@ -1,8 +1,7 @@ -from common.configure_logging import redirect_logging_to_freeorion_logger, convenience_function_references_for_logger +from common.configure_logging import redirect_logging_to_freeorion_logger # Logging is redirected before other imports so that import errors appear in log files. redirect_logging_to_freeorion_logger() -(debug, info, warn, error, fatal) = convenience_function_references_for_logger() import random @@ -20,6 +19,12 @@ from universe_tables import MAX_JUMPS_BETWEEN_SYSTEMS, MAX_STARLANE_LENGTH import universe_statistics +from common.handlers import init_handlers +from common.listeners import listener +from common.option_tools import parse_config +parse_config(fo.get_options_db_option_str("ai-config"), fo.get_user_config_dir()) +init_handlers(fo.get_options_db_option_str("ai-config"), None) + class PyGalaxySetupData: """ @@ -38,19 +43,21 @@ def __init__(self, galaxy_setup_data): self.monster_frequency = galaxy_setup_data.monsterFrequency self.native_frequency = galaxy_setup_data.nativeFrequency self.max_ai_aggression = galaxy_setup_data.maxAIAggression + self.game_uid = galaxy_setup_data.gameUID def dump(self): - print "Galaxy Setup Data:" - print "...Seed:", self.seed - print "...Size:", self.size - print "...Shape:", self.shape - print "...Age:", self.age - print "...Starlane Frequency:", self.starlane_frequency - print "...Planet Density:", self.planet_density - print "...Specials Frequency:", self.specials_frequency - print "...Monster Frequency:", self.monster_frequency - print "...Native Frequency:", self.native_frequency - print "...Max AI Aggression:", self.max_ai_aggression + print("Galaxy Setup Data:") + print("...Seed:", self.seed) + print("...Size:", self.size) + print("...Shape:", self.shape) + print("...Age:", self.age) + print("...Starlane Frequency:", self.starlane_frequency) + print("...Planet Density:", self.planet_density) + print("...Specials Frequency:", self.specials_frequency) + print("...Monster Frequency:", self.monster_frequency) + print("...Native Frequency:", self.native_frequency) + print("...Max AI Aggression:", self.max_ai_aggression) + print("...Game UID:", self.game_uid) def error_report(): @@ -60,11 +67,12 @@ def error_report(): return error_list +@listener def create_universe(psd_map): """ Main universe generation function invoked from C++ code. """ - print "Python Universe Generator" + print("Python Universe Generator") # fetch universe and player setup data gsd = PyGalaxySetupData(fo.get_galaxy_setup_data()) @@ -72,44 +80,44 @@ def create_universe(psd_map): total_players = len(psd_map) # initialize RNG - h = int_hash(gsd.seed) - print "Using hashed seed", h + h = int_hash(gsd.seed.encode('utf-8')) + print("Using hashed seed", h) seed_rng(h) seed_pool = [random.random() for _ in range(100)] - print "Seed pool:", seed_pool + print("Seed pool:", seed_pool) # make sure there are enough systems for the given number of players - print "Universe creation requested with %d systems for %d players" % (gsd.size, total_players) + print("Universe creation requested with %d systems for %d players" % (gsd.size, total_players)) min_size = total_players * 3 if min_size > gsd.size: gsd.size = min_size - print "Too few systems for the requested number of players, number of systems adjusted accordingly" - print "Creating universe with %d systems for %d players" % (gsd.size, total_players) + print("Too few systems for the requested number of players, number of systems adjusted accordingly") + print("Creating universe with %d systems for %d players" % (gsd.size, total_players)) # calculate star system positions seed_rng(seed_pool.pop()) system_positions = calc_star_system_positions(gsd) size = len(system_positions) - print gsd.shape, "Star system positions calculated, final number of systems:", size + print(gsd.shape, "Star system positions calculated, final number of systems:", size) # generate and populate systems seed_rng(seed_pool.pop()) systems = generate_systems(system_positions, gsd) - print len(systems), "systems generated and populated" + print(len(systems), "systems generated and populated") # generate Starlanes seed_rng(seed_pool.pop()) fo.generate_starlanes(MAX_JUMPS_BETWEEN_SYSTEMS[gsd.starlane_frequency], MAX_STARLANE_LENGTH) - print "Starlanes generated" + print("Starlanes generated") - print "Compile list of home systems..." + print("Compile list of home systems...") seed_rng(seed_pool.pop()) home_systems = compile_home_system_list(total_players, systems, gsd) if not home_systems: err_msg = "Python create_universe: couldn't get any home systems, ABORTING!" report_error(err_msg) raise Exception(err_msg) - print "Home systems:", home_systems + print("Home systems:", home_systems) # set up empires for each player seed_rng(seed_pool.pop()) @@ -121,49 +129,49 @@ def create_universe(psd_map): # this needs to be done after all systems have been generated and empire home systems have been set, as # only after all that is finished star types as well as planet sizes and types are fixed, and the naming # process depends on that - print "Assign star system names" + print("Assign star system names") seed_rng(seed_pool.pop()) name_star_systems(systems) - print "Set planet names" + print("Set planet names") for system in systems: name_planets(system) - print "Generating stationary fields in systems" + print("Generating stationary fields in systems") seed_rng(seed_pool.pop()) generate_fields(systems) - print "Generating Natives" + print("Generating Natives") seed_rng(seed_pool.pop()) generate_natives(gsd.native_frequency, systems, home_systems) - print "Generating Space Monsters" + print("Generating Space Monsters") seed_rng(seed_pool.pop()) generate_monsters(gsd.monster_frequency, systems) - print "Distributing Starting Specials" + print("Distributing Starting Specials") seed_rng(seed_pool.pop()) distribute_specials(gsd.specials_frequency, fo.get_all_objects()) # finally, write some statistics to the log file - print "############################################################" - print "## Universe generation statistics ##" - print "############################################################" + print("############################################################") + print("## Universe generation statistics ##") + print("############################################################") universe_statistics.log_planet_count_dist(systems) - print "############################################################" + print("############################################################") universe_statistics.log_planet_type_summary(systems) - print "############################################################" + print("############################################################") universe_statistics.log_species_summary(gsd.native_frequency) - print "############################################################" + print("############################################################") universe_statistics.log_monsters_summary(gsd.monster_frequency) - print "############################################################" + print("############################################################") universe_statistics.log_specials_summary() - print "############################################################" + print("############################################################") universe_statistics.log_systems() universe_statistics.log_planets() if error_list: - print "Python Universe Generator completed with errors" + print("Python Universe Generator completed with errors") return False else: - print "Python Universe Generator completed successfully" + print("Python Universe Generator completed successfully") return True diff --git a/default/python/universe_generation/universe_statistics.py b/default/python/universe_generation/universe_statistics.py index 10727724c21..85fc130cee7 100644 --- a/default/python/universe_generation/universe_statistics.py +++ b/default/python/universe_generation/universe_statistics.py @@ -1,8 +1,9 @@ import freeorion as fo -from common.print_utils import Table, Text, Float, Sequence +from common.print_utils import Float, Sequence, Table, Text + -import planets import natives +import planets import universe_tables species_summary = {species: 0 for species in fo.get_native_species()} @@ -39,8 +40,8 @@ def log_planet_count_dist(sys_list): ) for planet_count, sys_count in planet_count_dist.items(): count_distribution_table.add_row((planet_count, sys_count[0], 100.0 * sys_count[0] / len(sys_list))) - count_distribution_table.print_table() - print + print(count_distribution_table) + print() size_distribution = Table( [Text('size'), Text('count'), Float('% of planets', precession=1)], @@ -48,8 +49,8 @@ def log_planet_count_dist(sys_list): ) for planet_size, planet_count in sorted(planet_size_dist.items()): size_distribution.add_row((planet_size, planet_count, 100.0 * planet_count / planet_tally)) - size_distribution.print_table() - print + print(size_distribution) + print() def log_planet_type_summary(sys_list): @@ -66,8 +67,8 @@ def log_planet_type_summary(sys_list): for planet_type, planet_count in sorted(planet_type_summary_table.items()): type_summary_table.add_row((planet_type.name, 100.0 * planet_count / planet_total)) - type_summary_table.print_table() - print + print(type_summary_table) + print() def log_species_summary(native_freq): @@ -85,8 +86,8 @@ def log_species_summary(native_freq): for species, count in sorted(empire_species.items()): species_summary_table.add_row((species, count, 100.0 * count / num_empires)) - species_summary_table.print_table() - print + print(species_summary_table) + print() native_chance = universe_tables.NATIVE_FREQUENCY[native_freq] # as the value in the universe table is higher for a lower frequency, we have to invert it @@ -112,7 +113,12 @@ def log_species_summary(native_freq): expectation_tally = 0.0 for p_type in natives.planet_types_for_natives[species]: settleable_planets += potential_native_planet_summary[p_type] - expectation_tally += native_chance * 100.0 * potential_native_planet_summary[p_type] / (1E-10 + len(natives.natives_for_planet_type[p_type])) + expectation_tally += ( + native_chance * + 100.0 * + potential_native_planet_summary[p_type] / + (1E-10 + len(natives.natives_for_planet_type[p_type])) + ) expectation = expectation_tally / (1E-10 + settleable_planets) native_table.add_row( [species, @@ -122,12 +128,13 @@ def log_species_summary(native_freq): [str(p_t) for p_t in natives.planet_types_for_natives[species]]] ) - native_table.print_table() - print + print(native_table) + print() native_settled_planet_total = sum(settled_native_planet_summary.values()) type_summary_table = Table( - [Text('planet type'), Float('potential (% of tot)', precession=1), Float('settled (% of potential)', precession=1)], + [Text('planet type'), Float('potential (% of tot)', precession=1), + Float('settled (% of potential)', precession=1)], table_name=("Planet Type Summary for Native Planets (native frequency: %.1f%%)\n" "Totals: native_potential_planet_total: %s; native_settled_planet_total %s" ) % (100 * native_chance, native_potential_planet_total, native_settled_planet_total) @@ -138,16 +145,16 @@ def log_species_summary(native_freq): potential_percent = 100.0 * planet_count / (1E-10 + native_potential_planet_total) settled_percent = 100.0 * settled_planet_count / (1E-10 + planet_count) type_summary_table.add_row((planet_type.name, potential_percent, settled_percent)) - type_summary_table.print_table() - print + print(type_summary_table) + print() def log_monsters_summary(monster_freq): monster_place_table = Table([Text('monster'), Text('count')], table_name='Monster placement') for monster, counter in sorted(monsters_summary): monster_place_table.add_row([monster, counter]) - monster_place_table.print_table() - print + print(monster_place_table) + print() monster_chance = universe_tables.MONSTER_FREQUENCY[monster_freq] monster_table = Table( @@ -162,8 +169,8 @@ def log_monsters_summary(monster_freq): tracked_monsters_tries[monster], tracked_monsters_summary[monster], tracked_monsters_location_summary[monster], tracked_nest_location_summary[monster]) ) - monster_table.print_table() - print + print(monster_table) + print() def log_specials_summary(): @@ -173,8 +180,8 @@ def log_specials_summary(): ) for special in sorted(specials_summary): special_placement_count_table.add_row([special, specials_summary[special]]) - special_placement_count_table.print_table() - print + print(special_placement_count_table) + print() special_placement = Table( [Text('count'), Text('tally'), Float('% of objects', precession=1)], @@ -183,8 +190,8 @@ def log_specials_summary(): objects_tally = sum(specials_repeat_dist.values()) for number, tally in specials_repeat_dist.items(): special_placement.add_row((number, tally, 100.0 * tally / (1E-10 + objects_tally))) - special_placement.print_table() - print + print(special_placement) + print() def log_systems(): @@ -201,14 +208,15 @@ def log_systems(): # Printing too much info at once will lead to truncation of text for line in systems_table.get_table().split('\n'): - print line + print(line) def log_planets(): universe = fo.get_universe() planets_table = Table( - [Text('id'), Text('name'), Text('system'), Text('type'), Sequence('specials'), Text('species'), Sequence('buildings')], + [Text('id'), Text('name'), Text('system'), Text('type'), + Sequence('specials'), Text('species'), Sequence('buildings')], table_name='Planets summary') # group planets by system for sid in fo.get_systems(): @@ -227,4 +235,4 @@ def log_planets(): # Printing too much info at once will lead to truncation of text for line in planets_table.get_table().split('\n'): - print line + print(line) diff --git a/default/python/universe_generation/util.py b/default/python/universe_generation/util.py index b3d845cf7a0..c542fac3d7b 100644 --- a/default/python/universe_generation/util.py +++ b/default/python/universe_generation/util.py @@ -1,9 +1,8 @@ -import sys import math import random +import sys from hashlib import md5 - error_list = [] @@ -27,10 +26,12 @@ def seed_rng(seed): # random.jumpahead(999999) -def distance((x1, y1), (x2, y2)): +def distance(start, end): """ Calculates linear distance between two coordinates. """ + x1, y1 = start + x2, y2 = end return math.hypot(float(x1) - float(x2), float(y1) - float(y2)) @@ -39,7 +40,7 @@ def report_error(msg): Handles error messages. """ error_list.append(msg) - print >> sys.stderr, msg + print(msg, file=sys.stderr) class MapGenerationError(RuntimeError): diff --git a/default/scripting/buildings/ART_BLACK_HOLE.focs.txt b/default/scripting/buildings/ART_BLACK_HOLE.focs.txt index 166c5ca1d64..0e706e54afa 100644 --- a/default/scripting/buildings/ART_BLACK_HOLE.focs.txt +++ b/default/scripting/buildings/ART_BLACK_HOLE.focs.txt @@ -29,7 +29,6 @@ BuildingType EffectsGroup scope = Source - activation = Source effects = Destroy ] icon = "icons/building/blackhole.png" diff --git a/default/scripting/buildings/ART_FACTORY_PLANET.focs.txt b/default/scripting/buildings/ART_FACTORY_PLANET.focs.txt index 1eae6e6050f..3e8b406a1b0 100644 --- a/default/scripting/buildings/ART_FACTORY_PLANET.focs.txt +++ b/default/scripting/buildings/ART_FACTORY_PLANET.focs.txt @@ -1,7 +1,7 @@ BuildingType name = "BLD_ART_FACTORY_PLANET" description = "BLD_ART_FACTORY_PLANET_DESC" - buildcost = 200 * Target.SizeAsDouble + ( 70 * [[COLONY_UPKEEP_MULTIPLICATOR]] * [[BUILDING_COST_MULTIPLIER]]) + buildcost = 200 * Target.HabitableSize + ( 70 * [[COLONY_UPKEEP_MULTIPLICATOR]] * [[BUILDING_COST_MULTIPLIER]]) buildtime = 12 location = And [ Planet @@ -20,7 +20,7 @@ BuildingType Object id = Source.PlanetID Planet ] - activation = Source + priority = [[EARLY_POPULATION_OVERRIDE_PRIORITY]] effects = [ SetPlanetType type = Barren SetSpecies name = "SP_EXOBOT" @@ -35,11 +35,11 @@ BuildingType EffectsGroup scope = Source - activation = Source effects = Destroy ] icon = "icons/species/robotic-01.png" #include "/scripting/common/enqueue.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" \ No newline at end of file diff --git a/default/scripting/buildings/ART_MOON.focs.txt b/default/scripting/buildings/ART_MOON.focs.txt index f58a8a7cdc8..c3cadf6ad69 100644 --- a/default/scripting/buildings/ART_MOON.focs.txt +++ b/default/scripting/buildings/ART_MOON.focs.txt @@ -17,7 +17,6 @@ BuildingType Object id = Source.PlanetID Planet ] - activation = Source effects = AddSpecial name = "RESONANT_MOON_SPECIAL" icon = "icons/specials_huge/resonant_moon.png" diff --git a/default/scripting/buildings/ART_PARADISE_PLANET.focs.txt b/default/scripting/buildings/ART_PARADISE_PLANET.focs.txt index fb4a65d13e3..3280ac5d444 100644 --- a/default/scripting/buildings/ART_PARADISE_PLANET.focs.txt +++ b/default/scripting/buildings/ART_PARADISE_PLANET.focs.txt @@ -1,7 +1,7 @@ BuildingType name = "BLD_ART_PARADISE_PLANET" description = "BLD_ART_PARADISE_PLANET_DESC" - buildcost = (200 * Target.SizeAsDouble + 300) * [[BUILDING_COST_MULTIPLIER]] + buildcost = (200 * Target.HabitableSize + 300) * [[BUILDING_COST_MULTIPLIER]] buildtime = 10 location = And [ Planet @@ -20,7 +20,6 @@ BuildingType Object id = Source.PlanetID Planet ] - activation = Source effects = [ SetPlanetType type = Barren AddSpecial name = "GAIA_SPECIAL" @@ -34,10 +33,9 @@ BuildingType EffectsGroup scope = Source - activation = Source effects = Destroy ] icon = "icons/building/paradise_planet.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/ART_PLANET.focs.txt b/default/scripting/buildings/ART_PLANET.focs.txt index f0d8291941b..e7f02bf3297 100644 --- a/default/scripting/buildings/ART_PLANET.focs.txt +++ b/default/scripting/buildings/ART_PLANET.focs.txt @@ -1,7 +1,7 @@ BuildingType name = "BLD_ART_PLANET" description = "BLD_ART_PLANET_DESC" - buildcost = 200 * Target.SizeAsDouble * [[BUILDING_COST_MULTIPLIER]] + buildcost = 200 * Target.HabitableSize * [[BUILDING_COST_MULTIPLIER]] buildtime = 8 location = And [ Planet @@ -19,7 +19,6 @@ BuildingType Object id = Source.PlanetID Planet ] - activation = Source effects = [ SetPlanetType type = Barren GenerateSitRepMessage @@ -32,7 +31,6 @@ BuildingType EffectsGroup scope = Source - activation = Source effects = Destroy ] icon = "icons/specials_huge/tidal_lock.png" diff --git a/default/scripting/buildings/COLONY_BASE.focs.txt b/default/scripting/buildings/COLONY_BASE.focs.txt index 5b7b8659a08..94f8c7c1384 100644 --- a/default/scripting/buildings/COLONY_BASE.focs.txt +++ b/default/scripting/buildings/COLONY_BASE.focs.txt @@ -13,12 +13,10 @@ BuildingType effectsgroups = [ EffectsGroup scope = Source - activation = Source effects = CreateShip designname = "SD_COLONY_BASE" empire = Source.Owner species = Source.Planet.Species EffectsGroup scope = Source - activation = Source effects = Destroy ] icon = "icons/ship_hulls/colony_base_hull_small.png" diff --git a/default/scripting/buildings/CONC_CAMP.focs.txt b/default/scripting/buildings/CONC_CAMP.focs.txt index 3f74629aa25..b0aa26fa1ef 100644 --- a/default/scripting/buildings/CONC_CAMP.focs.txt +++ b/default/scripting/buildings/CONC_CAMP.focs.txt @@ -58,9 +58,9 @@ BuildingType OwnedBy empire = Source.Owner HasSpecial name = "CONC_CAMP_MASTER_SPECIAL" ] - priority = 150 //after everything else is resolved + priority = [[CONCENTRATION_CAMP_PRIORITY]] effects = [ - [[NEXT_TURN_POPULATION_REDUCE_BY(3)]] + SetPopulation value = min(Value, Target.Population - 3) SetIndustry value = Target.TargetIndustry ] EffectsGroup diff --git a/default/scripting/buildings/ENCLAVE_VOID.focs.txt b/default/scripting/buildings/ENCLAVE_VOID.focs.txt index be1ab39c424..24a41dd94d9 100644 --- a/default/scripting/buildings/ENCLAVE_VOID.focs.txt +++ b/default/scripting/buildings/ENCLAVE_VOID.focs.txt @@ -15,7 +15,6 @@ BuildingType OwnedBy empire = Source.Owner Focus type = "FOCUS_RESEARCH" ] - activation = Source stackinggroup = "BLD_ENCLAVE_VOID_STACK" priority = [[EARLY_PRIORITY]] effects = SetTargetResearch value = Value + Target.Population * 3.75 * [[RESEARCH_PER_POP]] diff --git a/default/scripting/buildings/EVACUATION.focs.txt b/default/scripting/buildings/EVACUATION.focs.txt index d433437103f..cf3a32765e1 100644 --- a/default/scripting/buildings/EVACUATION.focs.txt +++ b/default/scripting/buildings/EVACUATION.focs.txt @@ -40,6 +40,7 @@ BuildingType ResourceSupplyConnected empire = Source.Owner condition = Source Population low = 1 high = LocalCandidate.TargetPopulation - 1 ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value + 2 GenerateSitRepMessage @@ -59,13 +60,14 @@ BuildingType PopulationCenter OwnedBy empire = Source.Owner ] + priority = [[CONCENTRATION_CAMP_PRIORITY]] effects = [ - [[NEXT_TURN_POPULATION_REDUCE_BY(2)]] + SetPopulation value = min(Value, Target.Population - 2) SetIndustry value = 0 SetTargetIndustry value = 0 SetResearch value = 0 SetTargetResearch value = 0 - ] + ] EffectsGroup // remove evacuation when planet is depopulated or no longer owned by empire that produced this building scope = Source @@ -80,9 +82,10 @@ BuildingType HasSpecial name = "CONC_CAMP_SLAVE_SPECIAL" ] ] + priority = [[END_CLEANUP_PRIORITY]] effects = Destroy ] icon = "icons/building/evacuation.png" -#include "/scripting/common/pop_reduce.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/priorities.macros" +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/EXPERIMENTOR_OUTPOST.focs.txt b/default/scripting/buildings/EXPERIMENTOR_OUTPOST.focs.txt index 5f8be306ec9..c7531ddf964 100644 --- a/default/scripting/buildings/EXPERIMENTOR_OUTPOST.focs.txt +++ b/default/scripting/buildings/EXPERIMENTOR_OUTPOST.focs.txt @@ -78,7 +78,7 @@ BuildingType Not OwnedBy affiliation = AnyEmpire ] activation = Or [ - Turn high = [[EXPERIMENTOR_SPAWN_START_TURN]] // always remove lanes before spawn start turn + Turn high = ([[EXPERIMENTOR_SPAWN_START_TURN]]) // always remove lanes before spawn start turn Not ContainedBy Contains And [ Monster Unowned Not Design name = "SM_EXP_OUTPOST"] ] effects = RemoveStarlanes endpoint = WithinStarlaneJumps jumps = 1 condition = Source @@ -86,7 +86,7 @@ BuildingType EffectsGroup scope = Source activation = And [ - Turn low = [[EXPERIMENTOR_SPAWN_START_TURN]] high = ( [[EXPERIMENTOR_SPAWN_START_TURN]] + 35) + Turn low = ([[EXPERIMENTOR_SPAWN_START_TURN]]) high = ( [[EXPERIMENTOR_SPAWN_START_TURN]] + 35) Random probability = (0.2 * [[EXPERIMENTOR_MONSTER_FREQ_FACTOR]] ) ] effects = [ @@ -254,12 +254,12 @@ EXPERIMENTOR_SPAWN_AI_AGGRESSION_CHECK // the following intermediate value is turn 200 for Manaical Max AI Aggression, and 50 turns later for each aggression tier less, // for AI aggressions above Typical, otherwise adding 1000 turns EXPERIMENTOR_SPAWN_CALC_A -'''( [[EXPERIMENTOR_SPAWN_BASE_TURN]] + (50 * (5 - GalaxyMaxAIAggression)) + (1000 * [[EXPERIMENTOR_SPAWN_AI_AGGRESSION_CHECK]]))''' +'''( [[EXPERIMENTOR_SPAWN_BASE_TURN]] + 50 * (5 - GalaxyMaxAIAggression) + 1000 * [[EXPERIMENTOR_SPAWN_AI_AGGRESSION_CHECK]])''' // the following modifies Experimentor spawn start by a factor of 0.1 up for low planet density, and 0.1 down for high planet density // for galaxy sizes above 200 increases it somewhat as the galaxy size grows EXPERIMENTOR_SPAWN_START_TURN -'''( [[EXPERIMENTOR_SPAWN_CALC_A]] * ((1.2 - ( GalaxyPlanetDensity / 10 ))) * ((Max(1, GalaxySize / 200))^0.4))''' +'''[[EXPERIMENTOR_SPAWN_CALC_A]] * (1.2 - ( GalaxyPlanetDensity / 10 )) * ((Max(1, GalaxySize / 200))^0.4)''' EXPERIMENTOR_ADD_STARLANE '''AddStarlanes endpoint = NumberOf number = 1 diff --git a/default/scripting/buildings/GAIA_TRANS.focs.txt b/default/scripting/buildings/GAIA_TRANS.focs.txt index 301c04fc470..5f728c20017 100644 --- a/default/scripting/buildings/GAIA_TRANS.focs.txt +++ b/default/scripting/buildings/GAIA_TRANS.focs.txt @@ -1,7 +1,7 @@ BuildingType name = "BLD_GAIA_TRANS" description = "BLD_GAIA_TRANS_DESC" - buildcost = 200 * Target.SizeAsDouble * [[BUILDING_COST_MULTIPLIER]] + buildcost = 200 * Target.HabitableSize * [[BUILDING_COST_MULTIPLIER]] buildtime = 12 location = And [ Planet @@ -28,7 +28,6 @@ BuildingType Object id = Source.PlanetID Planet ] - activation = Source effects = [ AddSpecial name = "GAIA_SPECIAL" GenerateSitRepMessage @@ -41,7 +40,6 @@ BuildingType EffectsGroup scope = Source - activation = Source effects = Destroy ] icon = "icons/specials_huge/gaia.png" diff --git a/default/scripting/buildings/GAS_GIANT_GEN.focs.txt b/default/scripting/buildings/GAS_GIANT_GEN.focs.txt index 4c1797387c6..45a48a48398 100644 --- a/default/scripting/buildings/GAS_GIANT_GEN.focs.txt +++ b/default/scripting/buildings/GAS_GIANT_GEN.focs.txt @@ -20,9 +20,25 @@ BuildingType NOT Population high = 0 OwnedBy empire = Source.Owner ] + activation = ContainedBy Population high = 0 stackinggroup = "GAS_GIANT_GEN_STACK" priority = [[VERY_LATE_PRIORITY]] effects = SetTargetIndustry value = Value + 10 + + EffectsGroup + scope = And [ + Planet + InSystem id = Source.SystemID + Focus type = "FOCUS_INDUSTRY" + NOT Population high = 0 + OwnedBy empire = Source.Owner + ] + activation = ContainedBy Not Population high = 0 + // unpopulated gas giant generator planet bonus applies first + stackinggroup = "GAS_GIANT_GEN_STACK" + priority = [[VERY_LATE_PRIORITY]] + effects = SetTargetIndustry value = Value + 5 + EffectsGroup scope = Source activation = Not Planet type = GasGiant diff --git a/default/scripting/buildings/GATEWAY_VOID.focs.txt b/default/scripting/buildings/GATEWAY_VOID.focs.txt index 017d017029b..bf878ad4883 100644 --- a/default/scripting/buildings/GATEWAY_VOID.focs.txt +++ b/default/scripting/buildings/GATEWAY_VOID.focs.txt @@ -14,7 +14,6 @@ BuildingType Fleet InSystem id = Source.SystemID ] - activation = Source effects = [ Destroy GenerateSitRepMessage @@ -27,19 +26,20 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = And [ PopulationCenter InSystem id = Source.SystemID ] activation = Turn low = Source.CreationTurn + 1 - priority = 200 + priority = [[EARLY_POPULATION_OVERRIDE_PRIORITY]] // Overrides both target and current population effects effects = [ SetTargetPopulation value = min(Value, 0) SetPopulation value = min(Value, 0) SetMaxSupply value = 0 SetSupply value = 0 - ] + ] EffectsGroup scope = InSystem id = Source.SystemID @@ -49,4 +49,5 @@ BuildingType icon = "icons/building/monument_to_exodus.png" #include "/scripting/common/enqueue.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" \ No newline at end of file diff --git a/default/scripting/buildings/HYPER_DAM.focs.txt b/default/scripting/buildings/HYPER_DAM.focs.txt index bc880afbd56..e7cc38f66bf 100644 --- a/default/scripting/buildings/HYPER_DAM.focs.txt +++ b/default/scripting/buildings/HYPER_DAM.focs.txt @@ -21,9 +21,8 @@ BuildingType Not ResourceSupplyConnected empire = Source.Owner Condition = And [ Building name = "BLD_BLACK_HOLE_POW_GEN" OwnedBy empire = Source.Owner - ] + ] ] - activation = Source stackinggroup = "BLD_HYPER_DAM_BONUS" priority = [[VERY_LATE_PRIORITY]] effects = SetTargetIndustry value = Value + Target.Population * 5 * [[INDUSTRY_PER_POP]] @@ -38,12 +37,11 @@ BuildingType Not ResourceSupplyConnected empire = Source.Owner Condition = And [ Building name = "BLD_BLACK_HOLE_POW_GEN" OwnedBy empire = Source.Owner - ] + ] ] - activation = Source stackinggroup = "BLD_HYPER_DAM_MALUS" - priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetPopulation value = Value - Target.SizeAsDouble + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value - Target.HabitableSize ] icon = "icons/building/blackhole.png" diff --git a/default/scripting/buildings/INTERSPECIES_ACADEMY.focs.txt b/default/scripting/buildings/INTERSPECIES_ACADEMY.focs.txt new file mode 100644 index 00000000000..0ed691014aa --- /dev/null +++ b/default/scripting/buildings/INTERSPECIES_ACADEMY.focs.txt @@ -0,0 +1,97 @@ +BuildingType + name = "BLD_INTERSPECIES_ACADEMY" + description = "BLD_INTERSPECIES_ACADEMY_DESC" + buildcost = 50 * [[BUILDING_COST_MULTIPLIER]] + buildtime = 5 + location = AND [ + // Homeworld + Species + Not Contains Building name = "BLD_INTERSPECIES_ACADEMY" + OwnedBy empire = Source.Owner + Or [ + Happiness low = 15 + // The preferred focus happiness bonus helps only a bit + And [ + Focus type = Source.PreferredFocus + Happiness low = 19 + ] + ] + Not WithinStarlaneJumps jumps = 3 condition = And [ + System + Contains And [ + Building name = "BLD_INTERSPECIES_ACADEMY" + OwnedBy empire = Source.Owner + ] + ] + Number low = 0 high = 0 condition = And [ + Building name = "BLD_INTERSPECIES_ACADEMY" + Species name = RootCandidate.Species + OwnedBy empire = RootCandidate.Owner + ] + Number low = 0 high = 0 condition = Described description = "CONDITION_INTERSPECIES_ACADEMY_SPECIES_ALREADY_EXISTS" condition = And [ + Planet + Enqueued type = Building name = "BLD_INTERSPECIES_ACADEMY" + Species name = RootCandidate.Species + OwnedBy empire = RootCandidate.Owner + Not Object id = RootCandidate.PlanetID + ] + Number low = 0 high = 6 condition = And [ + Building name = "BLD_INTERSPECIES_ACADEMY" + OwnedBy empire = Source.Owner + ] + + ] + EnqueueLocation = [[ENQUEUE_BUILD_ONE_PER_PLANET]] + effectsgroups = [ + // Destroy superfluous academies + EffectsGroup + scope = NumberOf number = 1 condition = And [ + Building name = "BLD_INTERSPECIES_ACADEMY" + OwnedBy empire = Source.Owner + Not Object id = Source.ID + Species name = Source.Planet.Species + ] + activation = Random probability = 0.3 + effects = [ + GenerateSitRepMessage + message = "EFFECT_INTERSPECIES_ACADEMY_DESTROY" + label = "EFFECT_INTERSPECIES_ACADEMY_DESTROY_LABEL" + icon = "icons/building/blackhole.png" + parameters = [ + tag = "planet" data = Target.PlanetID + ] + empire = Source.Owner + Destroy + ] + + // Apply stockpile effects + EffectsGroup + scope = And [ + Object id = Source.PlanetID + Planet + Focus type = "FOCUS_STOCKPILE" + OwnedBy empire = Source.Owner + Species + ] + accountinglabel = "INTERSPECIES_ACADEMY_LABEL" + priority = [[LATE_PRIORITY]] + effects = SetMaxStockpile value = Value + 10 + EffectsGroup + scope = And [ + Object id = Source.PlanetID + Planet + Focus type = "FOCUS_RESEARCH" + OwnedBy empire = Source.Owner + Species + ] + accountinglabel = "INTERSPECIES_ACADEMY_LABEL" + priority = [[LATE_PRIORITY]] + effects = SetTargetResearch value = Value + 5 + + ] + icon = "icons/building/science-institute.png" + +#include "/scripting/common/base_prod.macros" + +#include "/scripting/common/enqueue.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/buildings/MEGALITH.focs.txt b/default/scripting/buildings/MEGALITH.focs.txt index 01405443b23..5d3a2f6452f 100644 --- a/default/scripting/buildings/MEGALITH.focs.txt +++ b/default/scripting/buildings/MEGALITH.focs.txt @@ -17,7 +17,6 @@ BuildingType Object id = Source.PlanetID Planet ] - activation = Source stackinggroup = "BLD_MEGALITH_EFFECT" effects = [ SetTargetConstruction value = Value + 30 @@ -33,7 +32,6 @@ BuildingType OwnedBy empire = Source.Owner TargetPopulation low = 1 ] - activation = Source stackinggroup = "BLD_MEGALITH_EFFECT" effects = SetMaxSupply value = Value + 1 diff --git a/default/scripting/buildings/NEST_ERADICATOR.focs.txt b/default/scripting/buildings/NEST_ERADICATOR.focs.txt new file mode 100644 index 00000000000..08f7f1c613a --- /dev/null +++ b/default/scripting/buildings/NEST_ERADICATOR.focs.txt @@ -0,0 +1,50 @@ +BuildingType + name = "BLD_NEST_ERADICATOR" + description = "BLD_NEST_ERADICATOR_DESC" + buildcost = 240 + buildtime = 8 + location = And [ + Planet + OwnedBy empire = Source.Owner + Not Contains Building name = "BLD_NEST_ERADICATOR" + Or [ + HasSpecial name = "JUGGERNAUT_NEST_SPECIAL" + HasSpecial name = "KRAKEN_NEST_SPECIAL" + HasSpecial name = "SNOWFLAKE_NEST_SPECIAL" + ] + ] + EnqueueLocation = Not Enqueued type = Building name = "BLD_NEST_ERADICATOR" + effectsgroups = [ + [[EG_NEST_REMOVAL(JUGGERNAUT)]] + [[EG_NEST_REMOVAL(KRAKEN)]] + [[EG_NEST_REMOVAL(SNOWFLAKE)]] + + EffectsGroup + scope = Source + effects = Destroy + ] + icon = "icons/building/nest_eradicator.png" + +// @1@ Monster name +EG_NEST_REMOVAL +'''EffectsGroup + scope = And [ + Object id = Source.PlanetID + Planet + HasSpecial name = "@1@_NEST_SPECIAL" + ] + stackinggroup = "@1@_NEST_STACK" // groups with @1@_NEST_SPECIAL + priority = [[EARLY_PRIORITY]] + effects = [ + RemoveSpecial name = "@1@_NEST_SPECIAL" + GenerateSitRepMessage + message = "EFFECT_NEST_REMOVAL" + label = "EFFECT_NEST_REMOVAL_LABEL" + icon = "icons/building/nest_eradicator.png" + parameters = tag = "planet" data = Target.ID + empire = Source.Owner + ] +''' + +#include "/scripting/common/base_prod.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/buildings/SCRYING_SPHERE.focs.txt b/default/scripting/buildings/SCRYING_SPHERE.focs.txt index 1643c01ace5..0d4d54729e4 100644 --- a/default/scripting/buildings/SCRYING_SPHERE.focs.txt +++ b/default/scripting/buildings/SCRYING_SPHERE.focs.txt @@ -26,7 +26,9 @@ BuildingType Not Unowned ] ] - effects = SetVisibility visibility = Partial empire = Source.Planet.Owner + effects = SetVisibility empire = Source.Planet.Owner + visibility = max(Partial, Value) + icon = "icons/specials_huge/ancient_ruins_excavated.png" #include "/scripting/common/base_prod.macros" \ No newline at end of file diff --git a/default/scripting/buildings/SPACE_ELEVATOR.focs.txt b/default/scripting/buildings/SPACE_ELEVATOR.focs.txt index c4a1ee13b90..ef332fcf817 100644 --- a/default/scripting/buildings/SPACE_ELEVATOR.focs.txt +++ b/default/scripting/buildings/SPACE_ELEVATOR.focs.txt @@ -17,7 +17,6 @@ BuildingType Object id = Source.PlanetID Planet size = Tiny ] - activation = Source effects = SetMaxSupply value = Value + 1 EffectsGroup @@ -25,7 +24,6 @@ BuildingType Object id = Source.PlanetID Planet size = Small ] - activation = Source effects = SetMaxSupply value = Value + 2 EffectsGroup @@ -33,7 +31,6 @@ BuildingType Object id = Source.PlanetID Planet size = Medium ] - activation = Source effects = SetMaxSupply value = Value + 3 EffectsGroup @@ -41,7 +38,6 @@ BuildingType Object id = Source.PlanetID Planet size = Large ] - activation = Source effects = SetMaxSupply value = Value + 4 EffectsGroup @@ -49,7 +45,6 @@ BuildingType Object id = Source.PlanetID Planet size = Huge ] - activation = Source effects = SetMaxSupply value = Value + 5 EffectsGroup @@ -57,10 +52,9 @@ BuildingType Object id = Source.PlanetID Planet type = GasGiant ] - activation = Source - effects = SetMaxSupply value = Value + 4 + effects = SetMaxSupply value = Value + 3 ] icon = "icons/building/space-elevator.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/STOCKPILING_CENTER.focs.txt b/default/scripting/buildings/STOCKPILING_CENTER.focs.txt new file mode 100644 index 00000000000..63585ab3e90 --- /dev/null +++ b/default/scripting/buildings/STOCKPILING_CENTER.focs.txt @@ -0,0 +1,53 @@ +BuildingType + name = "BLD_STOCKPILING_CENTER" + description = "BLD_STOCKPILING_CENTER_DESC" + captureresult = destroy + buildcost = 100 * [[BUILDING_COST_MULTIPLIER]] + buildtime = 5 + location = AND [ + Capital + OwnedBy empire = Source.Owner + Number low = 0 high = 0 condition = And [ + Building name = "BLD_STOCKPILING_CENTER" + OwnedBy empire = Source.Owner + ] + ] + enqueuelocation = And [ + Capital + OwnedBy empire = Source.Owner + + Number low = 0 high = 0 condition = And [ + Building name = "BLD_STOCKPILING_CENTER" + OwnedBy empire = Source.Owner + ] + + // can't enqueue if already have an enqueued stockpiling_center anywhere + Number low = 0 high = 0 condition = And [ + Planet + Enqueued + type = Building + name = "BLD_STOCKPILING_CENTER" + empire = Source.Owner + low = 1 + ] + ] + effectsgroups = + EffectsGroup + scope = And [ + Object id = Source.PlanetID + Planet + ] + accountinglabel = "BLD_STOCKPILING_CENTER_LABEL" + effects = [ + SetMaxStockpile value = Value + (0.1 * Statistic Sum value = LocalCandidate.Industry condition = And [ + ProductionCenter + OwnedBy empire = Source.Owner + ]) + ] + + icon = "icons/building/stockpiling_center.png" + +#include "/scripting/common/base_prod.macros" + +#include "/scripting/common/enqueue.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/buildings/SUPER_TEST.focs.txt b/default/scripting/buildings/SUPER_TEST.focs.txt index 8e7c5bf5195..802b58a339c 100644 --- a/default/scripting/buildings/SUPER_TEST.focs.txt +++ b/default/scripting/buildings/SUPER_TEST.focs.txt @@ -3,13 +3,17 @@ BuildingType description = "BLD_SUPER_TEST_DESC" buildcost = 1 buildtime = 1 - location = Planet + location = And [ + Planet + ((GameRule name = "RULE_ENABLE_SUPER_TESTER") > 0) + ] effectsgroups = [ Effectsgroup scope = And [ Object id = Source.PlanetID Planet ] + priority = [[EARLY_POPULATION_OVERRIDE_PRIORITY]] effects = [ SetSpecies name = "SP_SUPER_TEST" SetPopulation value = max(Target.Population, 1) @@ -21,3 +25,5 @@ BuildingType effects = Destroy ] icon = "icons/species/other-04.png" + +#include "/scripting/common/priorities.macros" \ No newline at end of file diff --git a/default/scripting/buildings/TERRAFORM.focs.txt b/default/scripting/buildings/TERRAFORM.focs.txt index 35a5b1c2fd9..b9866f8ff6e 100644 --- a/default/scripting/buildings/TERRAFORM.focs.txt +++ b/default/scripting/buildings/TERRAFORM.focs.txt @@ -1,7 +1,7 @@ BuildingType name = "BLD_TERRAFORM" description = "BLD_TERRAFORM_DESC" - buildcost = 100 * (Target.SizeAsDouble * (1 + Target.DistanceFromOriginalType) ) * [[BUILDING_COST_MULTIPLIER]] + buildcost = 100 * (Target.HabitableSize * (1 + Target.DistanceFromOriginalType) ) * [[BUILDING_COST_MULTIPLIER]] buildtime = 8 location = And [ Planet @@ -15,6 +15,7 @@ BuildingType Species name = "SP_EXOBOT" Planet environment = Adequate ] + Not HasTag name = "NO_TERRAFORM" ] EnqueueLocation = And [ Planet diff --git a/default/scripting/buildings/colonies/SP_ABADDONI.focs.txt b/default/scripting/buildings/colonies/SP_ABADDONI.focs.txt index 9a431368095..bec932bc8dd 100644 --- a/default/scripting/buildings/colonies/SP_ABADDONI.focs.txt +++ b/default/scripting/buildings/colonies/SP_ABADDONI.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_ABADDONI" description = "BLD_COL_ABADDONI_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_ABADDONI" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_ABADDONI" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_ABADDONI")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_BANFORO.focs.txt b/default/scripting/buildings/colonies/SP_BANFORO.focs.txt index b28addd4a6f..b43a4574d0f 100644 --- a/default/scripting/buildings/colonies/SP_BANFORO.focs.txt +++ b/default/scripting/buildings/colonies/SP_BANFORO.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_BANFORO" description = "BLD_COL_BANFORO_DESC" @@ -30,12 +32,12 @@ BuildingType OwnerHasTech name = "SHP_ORG_HULL" OwnerHasTech name = "SHP_QUANT_ENRG_MAG" ]) - + 20 * (Statistic If Condition = OwnerHasTech Name = "SHP_QUANT_ENRG_MAG") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_IMPROVED_ENGINE_COUPLINGS") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_SINGULARITY_ENGINE_CORE") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_TRANSSPACE_DRIVE") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_INTSTEL_LOG") + + 20 * (Statistic If Condition = OwnerHasTech name = "SHP_QUANT_ENRG_MAG") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_IMPROVED_ENGINE_COUPLINGS") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_SINGULARITY_ENGINE_CORE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_TRANSSPACE_DRIVE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_INTSTEL_LOG") ) ) tags = [ "SP_BANFORO" "CTRL_EXTINCT" ] @@ -87,32 +89,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_BANFORO" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_BANFORO" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_BANFORO")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -130,6 +108,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -139,4 +118,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_CHATO.focs.txt b/default/scripting/buildings/colonies/SP_CHATO.focs.txt index a8787637b66..2541e91ab25 100644 --- a/default/scripting/buildings/colonies/SP_CHATO.focs.txt +++ b/default/scripting/buildings/colonies/SP_CHATO.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_CHATO" description = "BLD_COL_CHATO_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_CHATO" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_CHATO" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_CHATO")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_CRAY.focs.txt b/default/scripting/buildings/colonies/SP_CRAY.focs.txt index 5515ee81ad2..c52d74a3d77 100644 --- a/default/scripting/buildings/colonies/SP_CRAY.focs.txt +++ b/default/scripting/buildings/colonies/SP_CRAY.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_CRAY" description = "BLD_COL_CRAY_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_CRAY" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_CRAY" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_CRAY")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_DERTHREAN.focs.txt b/default/scripting/buildings/colonies/SP_DERTHREAN.focs.txt index 12a0ef401e7..fd25f8ca592 100644 --- a/default/scripting/buildings/colonies/SP_DERTHREAN.focs.txt +++ b/default/scripting/buildings/colonies/SP_DERTHREAN.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_DERTHREAN" description = "BLD_COL_DERTHREAN_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_DERTHREAN" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_DERTHREAN" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_DERTHREAN")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_EAXAW.focs.txt b/default/scripting/buildings/colonies/SP_EAXAW.focs.txt index e270e61c148..efa8486492b 100644 --- a/default/scripting/buildings/colonies/SP_EAXAW.focs.txt +++ b/default/scripting/buildings/colonies/SP_EAXAW.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_EAXAW" description = "BLD_COL_EAXAW_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_EAXAW" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_EAXAW" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_EAXAW")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_EGASSEM.focs.txt b/default/scripting/buildings/colonies/SP_EGASSEM.focs.txt index a4717654f55..ec7913d997b 100644 --- a/default/scripting/buildings/colonies/SP_EGASSEM.focs.txt +++ b/default/scripting/buildings/colonies/SP_EGASSEM.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_EGASSEM" description = "BLD_COL_EGASSEM_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_EGASSEM" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_EGASSEM" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_EGASSEM")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_ETTY.focs.txt b/default/scripting/buildings/colonies/SP_ETTY.focs.txt index 4ab8eee7d13..bf42fb8987b 100644 --- a/default/scripting/buildings/colonies/SP_ETTY.focs.txt +++ b/default/scripting/buildings/colonies/SP_ETTY.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_ETTY" description = "BLD_COL_ETTY_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_ETTY" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_ETTY" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_ETTY")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_EXOBOT.focs.txt b/default/scripting/buildings/colonies/SP_EXOBOT.focs.txt index 5fe696f03ae..7ae2a0b6712 100644 --- a/default/scripting/buildings/colonies/SP_EXOBOT.focs.txt +++ b/default/scripting/buildings/colonies/SP_EXOBOT.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_EXOBOT" description = "BLD_COL_EXOBOT_DESC" @@ -22,32 +24,8 @@ BuildingType // no existing Exobot colony required! ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_EXOBOT" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_EXOBOT" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_EXOBOT")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -65,6 +43,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -74,4 +53,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_FULVER.focs.txt b/default/scripting/buildings/colonies/SP_FULVER.focs.txt new file mode 100644 index 00000000000..e7a417bbcb8 --- /dev/null +++ b/default/scripting/buildings/colonies/SP_FULVER.focs.txt @@ -0,0 +1,97 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. +BuildingType + name = "BLD_COL_FULVER" + description = "BLD_COL_FULVER_DESC" + buildcost = 50 * [[COLONY_UPKEEP_MULTIPLICATOR]] * [[BUILDING_COST_MULTIPLIER]] + buildtime = 1.0 * max(5.0, 1.0 + + (Statistic Min Value = ShortestPath Object = Target.SystemID Object = LocalCandidate.SystemID + Condition = And [ + Planet + OwnedBy empire = Source.Owner + Species name = "SP_FULVER" + Population low = [[MIN_RECOLONIZING_SIZE]] + Happiness low = 5 + ResourceSupplyConnected empire = Source.Owner condition = Target + ] + ) / (60 + + 20 * (Statistic If Condition = Or [ + OwnerHasTech name = "SHP_MIL_ROBO_CONT" + OwnerHasTech name = "SHP_ORG_HULL" + OwnerHasTech name = "SHP_QUANT_ENRG_MAG" + ]) + + 20 * (Statistic If Condition = Or [ + OwnerHasTech name = "SHP_ORG_HULL" + OwnerHasTech name = "SHP_QUANT_ENRG_MAG" + ]) + + 20 * (Statistic If Condition = OwnerHasTech name = "SHP_QUANT_ENRG_MAG") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_IMPROVED_ENGINE_COUPLINGS") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_SINGULARITY_ENGINE_CORE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_TRANSSPACE_DRIVE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_INTSTEL_LOG") + ) + ) + tags = [ "SP_FULVER" ] + location = And [ + Planet + OwnedBy empire = Source.Owner + Population high = 0 + Not Planet environment = Uninhabitable species = "SP_FULVER" + Not Contains Building name = "BLD_COL_FULVER" + ResourceSupplyConnected empire = Source.Owner condition = And [ + Planet + OwnedBy empire = Source.Owner + Species name = "SP_FULVER" + Population low = [[MIN_RECOLONIZING_SIZE]] + Happiness low = 5 + ] + ] + EnqueueLocation = And [ + Planet + OwnedBy empire = Source.Owner + Population high = 0 + Not Planet environment = Uninhabitable species = "SP_FULVER" + Not Contains Building name = "BLD_COL_FULVER" + Not Enqueued type = Building name = "BLD_COL_FULVER" + ResourceSupplyConnected empire = Source.Owner condition = And [ + Planet + OwnedBy empire = Source.Owner + Species name = "SP_FULVER" + Population low = [[MIN_RECOLONIZING_SIZE]] + Happiness low = 5 + ] + ] + effectsgroups = [ + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_FULVER")]] + + EffectsGroup + scope = And [ + Object id = Source.PlanetID + Planet + ] + activation = Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 + effects = [ + GenerateSitRepMessage + message = "SITREP_NEW_COLONY_ESTABLISHED" + label = "SITREP_NEW_COLONY_ESTABLISHED_LABEL" + icon = "icons/species/insectoid-01.png" + parameters = [ + tag = "species" data = "SP_FULVER" + tag = "planet" data = Target.ID + ] + empire = Source.Owner + ] + + EffectsGroup + scope = Source + activation = Turn low = Source.CreationTurn + 2 + effects = Destroy + ] + icon = "icons/species/insectoid-01.png" + +#include "/scripting/common/misc.macros" +#include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" +#include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_FURTHEST.focs.txt b/default/scripting/buildings/colonies/SP_FURTHEST.focs.txt index 502c6acf13d..bdfe97beed0 100644 --- a/default/scripting/buildings/colonies/SP_FURTHEST.focs.txt +++ b/default/scripting/buildings/colonies/SP_FURTHEST.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_FURTHEST" description = "BLD_COL_FURTHEST_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_FURTHEST" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_FURTHEST" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_FURTHEST")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_GEORGE.focs.txt b/default/scripting/buildings/colonies/SP_GEORGE.focs.txt index 1fa16e48e05..f77d7b167c4 100644 --- a/default/scripting/buildings/colonies/SP_GEORGE.focs.txt +++ b/default/scripting/buildings/colonies/SP_GEORGE.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_GEORGE" description = "BLD_COL_GEORGE_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_GEORGE" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_GEORGE" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_GEORGE")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_GYSACHE.focs.txt b/default/scripting/buildings/colonies/SP_GYSACHE.focs.txt index 39a979bb993..ca246c980b0 100644 --- a/default/scripting/buildings/colonies/SP_GYSACHE.focs.txt +++ b/default/scripting/buildings/colonies/SP_GYSACHE.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_GYSACHE" description = "BLD_COL_GYSACHE_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_GYSACHE" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_GYSACHE" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_GYSACHE")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_HAPPY.focs.txt b/default/scripting/buildings/colonies/SP_HAPPY.focs.txt index 466ece70c81..83503c69f3a 100644 --- a/default/scripting/buildings/colonies/SP_HAPPY.focs.txt +++ b/default/scripting/buildings/colonies/SP_HAPPY.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_HAPPY" description = "BLD_COL_HAPPY_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_HAPPY" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_HAPPY" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_HAPPY")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_HHHOH.focs.txt b/default/scripting/buildings/colonies/SP_HHHOH.focs.txt index 261a657c27e..7978c79b4c8 100644 --- a/default/scripting/buildings/colonies/SP_HHHOH.focs.txt +++ b/default/scripting/buildings/colonies/SP_HHHOH.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_HHHOH" description = "BLD_COL_HHHOH_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_HHHOH" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_HHHOH" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_HHHOH")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_HUMAN.focs.txt b/default/scripting/buildings/colonies/SP_HUMAN.focs.txt index 6c4a0a18c53..fb1ef52962f 100644 --- a/default/scripting/buildings/colonies/SP_HUMAN.focs.txt +++ b/default/scripting/buildings/colonies/SP_HUMAN.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_HUMAN" description = "BLD_COL_HUMAN_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_HUMAN" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_HUMAN" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_HUMAN")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_KILANDOW.focs.txt b/default/scripting/buildings/colonies/SP_KILANDOW.focs.txt index c9874d6bdd1..8d0ce351c6a 100644 --- a/default/scripting/buildings/colonies/SP_KILANDOW.focs.txt +++ b/default/scripting/buildings/colonies/SP_KILANDOW.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_KILANDOW" description = "BLD_COL_KILANDOW_DESC" @@ -30,12 +32,12 @@ BuildingType OwnerHasTech name = "SHP_ORG_HULL" OwnerHasTech name = "SHP_QUANT_ENRG_MAG" ]) - + 20 * (Statistic If Condition = OwnerHasTech Name = "SHP_QUANT_ENRG_MAG") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_IMPROVED_ENGINE_COUPLINGS") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_SINGULARITY_ENGINE_CORE") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_TRANSSPACE_DRIVE") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_INTSTEL_LOG") + + 20 * (Statistic If Condition = OwnerHasTech name = "SHP_QUANT_ENRG_MAG") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_IMPROVED_ENGINE_COUPLINGS") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_SINGULARITY_ENGINE_CORE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_TRANSSPACE_DRIVE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_INTSTEL_LOG") ) ) tags = [ "SP_KILANDOW" "CTRL_EXTINCT" ] @@ -87,32 +89,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_KILANDOW" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_KILANDOW" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_KILANDOW")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -130,6 +108,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -139,4 +118,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_KOBUNTURA.focs.txt b/default/scripting/buildings/colonies/SP_KOBUNTURA.focs.txt index 218169ac540..8f1a16483db 100644 --- a/default/scripting/buildings/colonies/SP_KOBUNTURA.focs.txt +++ b/default/scripting/buildings/colonies/SP_KOBUNTURA.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_KOBUNTURA" description = "BLD_COL_KOBUNTURA_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_KOBUNTURA" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_KOBUNTURA" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_KOBUNTURA")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_LAENFA.focs.txt b/default/scripting/buildings/colonies/SP_LAENFA.focs.txt index 1952977f69b..309c4d28648 100644 --- a/default/scripting/buildings/colonies/SP_LAENFA.focs.txt +++ b/default/scripting/buildings/colonies/SP_LAENFA.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_LAENFA" description = "BLD_COL_LAENFA_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_LAENFA" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_LAENFA" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_LAENFA")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_MISIORLA.focs.txt b/default/scripting/buildings/colonies/SP_MISIORLA.focs.txt index 7675ae725dd..f94c4ca8183 100644 --- a/default/scripting/buildings/colonies/SP_MISIORLA.focs.txt +++ b/default/scripting/buildings/colonies/SP_MISIORLA.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_MISIORLA" description = "BLD_COL_MISIORLA_DESC" @@ -30,12 +32,12 @@ BuildingType OwnerHasTech name = "SHP_ORG_HULL" OwnerHasTech name = "SHP_QUANT_ENRG_MAG" ]) - + 20 * (Statistic If Condition = OwnerHasTech Name = "SHP_QUANT_ENRG_MAG") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_IMPROVED_ENGINE_COUPLINGS") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_SINGULARITY_ENGINE_CORE") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_TRANSSPACE_DRIVE") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_INTSTEL_LOG") + + 20 * (Statistic If Condition = OwnerHasTech name = "SHP_QUANT_ENRG_MAG") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_IMPROVED_ENGINE_COUPLINGS") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_SINGULARITY_ENGINE_CORE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_TRANSSPACE_DRIVE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_INTSTEL_LOG") ) ) tags = [ "SP_MISIORLA" "CTRL_EXTINCT" ] @@ -87,32 +89,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_MISIORLA" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_MISIORLA" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_MISIORLA")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -130,6 +108,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -139,4 +118,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_MUURSH.focs.txt b/default/scripting/buildings/colonies/SP_MUURSH.focs.txt index c7f0a7ecb42..9d7692ad47f 100644 --- a/default/scripting/buildings/colonies/SP_MUURSH.focs.txt +++ b/default/scripting/buildings/colonies/SP_MUURSH.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_MUURSH" description = "BLD_COL_MUURSH_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_MUURSH" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_MUURSH" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_MUURSH")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_PHINNERT.focs.txt b/default/scripting/buildings/colonies/SP_PHINNERT.focs.txt index ca1b07e5adf..09cc1fd8717 100644 --- a/default/scripting/buildings/colonies/SP_PHINNERT.focs.txt +++ b/default/scripting/buildings/colonies/SP_PHINNERT.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_PHINNERT" description = "BLD_COL_PHINNERT_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_PHINNERT" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_PHINNERT" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_PHINNERT")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_REPLICON.focs.txt b/default/scripting/buildings/colonies/SP_REPLICON.focs.txt index f378c2b357a..f56fd1dada5 100644 --- a/default/scripting/buildings/colonies/SP_REPLICON.focs.txt +++ b/default/scripting/buildings/colonies/SP_REPLICON.focs.txt @@ -1,7 +1,9 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_REPLICON" description = "BLD_COL_REPLICON_DESC" - buildcost = 50 * [[COLONY_UPKEEP_MULTIPLICATOR]] + buildcost = 50 * [[COLONY_UPKEEP_MULTIPLICATOR]] * [[BUILDING_COST_MULTIPLIER]] buildtime = 1.0 * max(5.0, 1.0 + (Statistic Min Value = ShortestPath Object = Target.SystemID Object = LocalCandidate.SystemID Condition = And [ @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_RADON" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_RADON" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_REPLICON")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -97,13 +75,14 @@ BuildingType GenerateSitRepMessage message = "SITREP_NEW_COLONY_ESTABLISHED" label = "SITREP_NEW_COLONY_ESTABLISHED_LABEL" - icon = "icons/species/robotic-05.png" + icon = "icons/species/replicon.png" parameters = [ tag = "species" data = "SP_REPLICON" tag = "planet" data = Target.ID ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -112,5 +91,7 @@ BuildingType icon = "icons/species/replicon.png" #include "/scripting/common/misc.macros" - #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" +#include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_SCYLIOR.focs.txt b/default/scripting/buildings/colonies/SP_SCYLIOR.focs.txt index 84a7d8b55d3..69402c49948 100644 --- a/default/scripting/buildings/colonies/SP_SCYLIOR.focs.txt +++ b/default/scripting/buildings/colonies/SP_SCYLIOR.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_SCYLIOR" description = "BLD_COL_SCYLIOR_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_SCYLIOR" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_SCYLIOR" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_SCYLIOR")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_SETINON.focs.txt b/default/scripting/buildings/colonies/SP_SETINON.focs.txt index f794f8fe386..c651544dba9 100644 --- a/default/scripting/buildings/colonies/SP_SETINON.focs.txt +++ b/default/scripting/buildings/colonies/SP_SETINON.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_SETINON" description = "BLD_COL_SETINON_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_SETINON" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_SETINON" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_SETINON")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_SILEXIAN.focs.txt b/default/scripting/buildings/colonies/SP_SILEXIAN.focs.txt index ab0e20aee1d..309c51d04b1 100644 --- a/default/scripting/buildings/colonies/SP_SILEXIAN.focs.txt +++ b/default/scripting/buildings/colonies/SP_SILEXIAN.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_SILEXIAN" description = "BLD_COL_SILEXIAN_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_SILEXIAN" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_SILEXIAN" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_SILEXIAN")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_SLY.focs.txt b/default/scripting/buildings/colonies/SP_SLY.focs.txt new file mode 100644 index 00000000000..2fd215f6c63 --- /dev/null +++ b/default/scripting/buildings/colonies/SP_SLY.focs.txt @@ -0,0 +1,97 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. +BuildingType + name = "BLD_COL_SLY" + description = "BLD_COL_SLY_DESC" + buildcost = 50 * [[COLONY_UPKEEP_MULTIPLICATOR]] * [[BUILDING_COST_MULTIPLIER]] + buildtime = 1.0 * max(5.0, 1.0 + + (Statistic Min Value = ShortestPath Object = Target.SystemID Object = LocalCandidate.SystemID + Condition = And [ + Planet + OwnedBy empire = Source.Owner + Species name = "SP_SLY" + Population low = [[MIN_RECOLONIZING_SIZE]] + Happiness low = 5 + ResourceSupplyConnected empire = Source.Owner condition = Target + ] + ) / (60 + + 20 * (Statistic If Condition = Or [ + OwnerHasTech name = "SHP_MIL_ROBO_CONT" + OwnerHasTech name = "SHP_ORG_HULL" + OwnerHasTech name = "SHP_QUANT_ENRG_MAG" + ]) + + 20 * (Statistic If Condition = Or [ + OwnerHasTech name = "SHP_ORG_HULL" + OwnerHasTech name = "SHP_QUANT_ENRG_MAG" + ]) + + 20 * (Statistic If Condition = OwnerHasTech name = "SHP_QUANT_ENRG_MAG") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_IMPROVED_ENGINE_COUPLINGS") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_SINGULARITY_ENGINE_CORE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_TRANSSPACE_DRIVE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_INTSTEL_LOG") + ) + ) + tags = [ "SP_SLY" ] + location = And [ + Planet + OwnedBy empire = Source.Owner + Population high = 0 + Not Planet environment = Uninhabitable species = "SP_SLY" + Not Contains Building name = "BLD_COL_SLY" + ResourceSupplyConnected empire = Source.Owner condition = And [ + Planet + OwnedBy empire = Source.Owner + Species name = "SP_SLY" + Population low = [[MIN_RECOLONIZING_SIZE]] + Happiness low = 5 + ] + ] + EnqueueLocation = And [ + Planet + OwnedBy empire = Source.Owner + Population high = 0 + Not Planet environment = Uninhabitable species = "SP_SLY" + Not Contains Building name = "BLD_COL_SLY" + Not Enqueued type = Building name = "BLD_COL_SLY" + ResourceSupplyConnected empire = Source.Owner condition = And [ + Planet + OwnedBy empire = Source.Owner + Species name = "SP_SLY" + Population low = [[MIN_RECOLONIZING_SIZE]] + Happiness low = 5 + ] + ] + effectsgroups = [ + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_SLY")]] + + EffectsGroup + scope = And [ + Object id = Source.PlanetID + Planet + ] + activation = Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 + effects = [ + GenerateSitRepMessage + message = "SITREP_NEW_COLONY_ESTABLISHED" + label = "SITREP_NEW_COLONY_ESTABLISHED_LABEL" + icon = "icons/species/amorphous-05.png" + parameters = [ + tag = "species" data = "SP_SLY" + tag = "planet" data = Target.ID + ] + empire = Source.Owner + ] + + EffectsGroup + scope = Source + activation = Turn low = Source.CreationTurn + 2 + effects = Destroy + ] + icon = "icons/species/amorphous-05.png" + +#include "/scripting/common/misc.macros" +#include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" +#include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_SSLITH.focs.txt b/default/scripting/buildings/colonies/SP_SSLITH.focs.txt index c306f5b05cc..2c981adf774 100644 --- a/default/scripting/buildings/colonies/SP_SSLITH.focs.txt +++ b/default/scripting/buildings/colonies/SP_SSLITH.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_SSLITH" description = "BLD_COL_SSLITH_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_SSLITH" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_SSLITH" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_SSLITH")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_SUPER_TEST.focs.txt b/default/scripting/buildings/colonies/SP_SUPER_TEST.focs.txt index 41434571942..b2e6e4f9488 100644 --- a/default/scripting/buildings/colonies/SP_SUPER_TEST.focs.txt +++ b/default/scripting/buildings/colonies/SP_SUPER_TEST.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_SUPER_TEST" description = "BLD_COL_SUPER_TEST_DESC" @@ -44,6 +46,7 @@ BuildingType Population low = [[MIN_RECOLONIZING_SIZE]] Happiness low = 5 ] + ((GameRule name = "RULE_ENABLE_SUPER_TESTER") > 0) ] EnqueueLocation = And [ Planet @@ -59,34 +62,11 @@ BuildingType Population low = [[MIN_RECOLONIZING_SIZE]] Happiness low = 5 ] + ((GameRule name = "RULE_ENABLE_SUPER_TESTER") > 0) ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_SUPER_TEST" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_SUPER_TEST" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_SUPER_TEST")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +84,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +94,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_TAEGHIRUS.focs.txt b/default/scripting/buildings/colonies/SP_TAEGHIRUS.focs.txt index b92df0b1002..88ecdc06654 100644 --- a/default/scripting/buildings/colonies/SP_TAEGHIRUS.focs.txt +++ b/default/scripting/buildings/colonies/SP_TAEGHIRUS.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_TAEGHIRUS" description = "BLD_COL_TAEGHIRUS_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_TAEGHIRUS" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_TAEGHIRUS" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_TAEGHIRUS")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_TRITH.focs.txt b/default/scripting/buildings/colonies/SP_TRITH.focs.txt index e789c7ce5fd..a57124fb88f 100644 --- a/default/scripting/buildings/colonies/SP_TRITH.focs.txt +++ b/default/scripting/buildings/colonies/SP_TRITH.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_TRITH" description = "BLD_COL_TRITH_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_TRITH" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_TRITH" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_TRITH")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/SP_UGMORS.focs.txt b/default/scripting/buildings/colonies/SP_UGMORS.focs.txt index 10e9ae4a800..cd87c12a2e5 100644 --- a/default/scripting/buildings/colonies/SP_UGMORS.focs.txt +++ b/default/scripting/buildings/colonies/SP_UGMORS.focs.txt @@ -1,3 +1,5 @@ +// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. BuildingType name = "BLD_COL_UGMORS" description = "BLD_COL_UGMORS_DESC" @@ -61,32 +63,8 @@ BuildingType ] ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_UGMORS" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "SP_UGMORS" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("SP_UGMORS")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -104,6 +82,7 @@ BuildingType ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -113,4 +92,6 @@ BuildingType #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" diff --git a/default/scripting/buildings/colonies/col_bld_gen.py b/default/scripting/buildings/colonies/col_bld_gen.py index 945c82150fd..6f661573a60 100644 --- a/default/scripting/buildings/colonies/col_bld_gen.py +++ b/default/scripting/buildings/colonies/col_bld_gen.py @@ -1,51 +1,89 @@ +"""col_bld_gen.py + +Utility script to generate consistent colony building definitions for each species. +This script is not utilized by the game. + +When executed, definition files will be generated in the current working directory. +""" + import os import os.path import string +# List of all species in game: definition key, graphic file(relative to default/data/art) species_list = [ - ("SP_SUPER_TEST", "Super Tester", "icons/species/other-04.png"), - ("SP_ABADDONI", "Abaddoni", "icons/species/abaddonnian.png"), - ("SP_BANFORO", "Banforo", "icons/species/banforo.png"), - ("SP_CHATO", "Chato", "icons/species/chato-matou-gormoshk.png"), - ("SP_CRAY", "Cray", "icons/species/cray.png"), - ("SP_DERTHREAN", "Derthrean", "icons/species/derthrean.png"), - ("SP_EAXAW", "Eaxaw", "icons/species/eaxaw.png"), - ("SP_EGASSEM", "Egassem", "icons/species/egassem.png"), - ("SP_ETTY", "Etty", "icons/species/etty.png"), - ("SP_FURTHEST", "Furthest", "icons/species/furthest.png"), - ("SP_GEORGE", "George", "icons/species/george.png"), - ("SP_GYSACHE", "Gysache", "icons/species/gysache.png"), - ("SP_HAPPY", "Happybirthday", "icons/species/ichthyoid-06.png"), - ("SP_HHHOH", "Hhhoh", "icons/species/hhhoh.png"), - ("SP_HUMAN", "Human", "icons/species/human.png"), - ("SP_KILANDOW", "Kilandow", "icons/species/kilandow.png"), - ("SP_KOBUNTURA", "Kobuntura", "icons/species/intangible-04.png"), - ("SP_LAENFA", "Laenfa", "icons/species/laenfa.png"), - ("SP_MISIORLA", "Misiorla", "icons/species/misiorla.png"), - ("SP_MUURSH", "Mu Ursh", "icons/species/muursh.png"), - ("SP_PHINNERT", "Phinnert", "icons/species/phinnert.png"), - ("SP_SCYLIOR", "Scylior", "icons/species/scylior.png"), - ("SP_SETINON", "Setinon", "icons/species/amorphous-02.png"), - ("SP_SILEXIAN", "Silexian", "icons/species/robotic-06.png"), - ("SP_SSLITH", "Sslith", "icons/species/sslith.png"), - ("SP_TAEGHIRUS", "Tae Ghirus", "icons/species/t-aeghirus.png"), - ("SP_TRITH", "Trith", "icons/species/trith.png"), - ("SP_REPLICON", "Replicons", "icons/species/replicon.png"), - ("SP_UGMORS", "Ugmors", "icons/species/amorphous-06.png"), - ("SP_EXOBOT", "Exobot", "icons/species/robotic-01.png") + ("SP_SUPER_TEST", "icons/species/other-04.png"), + ("SP_ABADDONI", "icons/species/abaddonnian.png"), + ("SP_BANFORO", "icons/species/banforo.png"), + ("SP_CHATO", "icons/species/chato-matou-gormoshk.png"), + ("SP_CRAY", "icons/species/cray.png"), + ("SP_DERTHREAN", "icons/species/derthrean.png"), + ("SP_EAXAW", "icons/species/eaxaw.png"), + ("SP_EGASSEM", "icons/species/egassem.png"), + ("SP_ETTY", "icons/species/etty.png"), + ("SP_FULVER", "icons/species/insectoid-01.png"), + ("SP_FURTHEST", "icons/species/furthest.png"), + ("SP_GEORGE", "icons/species/george.png"), + ("SP_GYSACHE", "icons/species/gysache.png"), + ("SP_HAPPY", "icons/species/ichthyoid-06.png"), + ("SP_HHHOH", "icons/species/hhhoh.png"), + ("SP_HUMAN", "icons/species/human.png"), + ("SP_KILANDOW", "icons/species/kilandow.png"), + ("SP_KOBUNTURA", "icons/species/intangible-04.png"), + ("SP_LAENFA", "icons/species/laenfa.png"), + ("SP_MISIORLA", "icons/species/misiorla.png"), + ("SP_MUURSH", "icons/species/muursh.png"), + ("SP_PHINNERT", "icons/species/phinnert.png"), + ("SP_SCYLIOR", "icons/species/scylior.png"), + ("SP_SETINON", "icons/species/amorphous-02.png"), + ("SP_SILEXIAN", "icons/species/robotic-06.png"), + ("SP_SLY", "icons/species/amorphous-05.png"), + ("SP_SSLITH", "icons/species/sslith.png"), + ("SP_TAEGHIRUS", "icons/species/t-aeghirus.png"), + ("SP_TRITH", "icons/species/trith.png"), + ("SP_REPLICON", "icons/species/replicon.png"), + ("SP_UGMORS", "icons/species/amorphous-06.png"), + ("SP_EXOBOT", "icons/species/robotic-01.png") ] -tech_dict = dict( - SP_BANFORO="TECH_COL_BANFORO", - SP_KILANDOW="TECH_COL_KILANDOW", - SP_MISIORLA="TECH_COL_MISIORLA" -) +# Species which start as extinct and tech enabling colonization without a suitable colony in supply range +species_extinct_techs = { + "SP_BANFORO": "TECH_COL_BANFORO", + "SP_KILANDOW": "TECH_COL_KILANDOW", + "SP_MISIORLA": "TECH_COL_MISIORLA" +} + +# Default gamerule to toggle availablity of colony buildings for a species (no rule) +colony_gamerule_default = "" + +# Species specific gamerule enabling colony building +species_colony_gamerules = { + "SP_SUPER_TEST": "RULE_ENABLE_SUPER_TESTER" +} + +# default base buildcost +buildcost_default = 50 + +# Species specific overrides to base buildcost +species_buildcost = { + "SP_EXOBOT": 70 +} + +# default buildtime factor +buildtime_factor_default = "1.0" -time_factor = {"SP_HAPPY": "1.2", "SP_PHINNERT": "0.75"} +# Species specific overrides to default buildtime factor +species_time_factor = { + "SP_HAPPY": "1.2", + "SP_PHINNERT": "0.75" +} -t_main = string.Template('''BuildingType +# Main template +t_main = string.Template('''// For long term changes - Do not modify this definition directly +// Instead modify and execute col_bld_gen.py and use the result. +BuildingType name = "BLD_COL_${name}" description = "BLD_COL_${name}_DESC" buildcost = ${cost} * [[COLONY_UPKEEP_MULTIPLICATOR]] * [[BUILDING_COST_MULTIPLIER]] @@ -69,32 +107,8 @@ ${species_condition} ] effectsgroups = [ - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "${id}" - SetPopulation value = 1 - ] - EffectsGroup - scope = And [ - Object id = Source.PlanetID - Planet - ] - activation = And [ - OwnerHasTech name = "GRO_LIFECYCLE_MAN" - Turn low = Source.CreationTurn + 1 high = Source.CreationTurn + 1 - ] - effects = [ - SetSpecies name = "${id}" - SetPopulation value = [[MIN_RECOLONIZING_SIZE]] - ] + [[LIFECYCLE_MANIP_POPULATION_EFFECTS("${id}")]] + EffectsGroup scope = And [ Object id = Source.PlanetID @@ -112,6 +126,7 @@ ] empire = Source.Owner ] + EffectsGroup scope = Source activation = Turn low = Source.CreationTurn + 2 @@ -121,10 +136,13 @@ #include "/scripting/common/misc.macros" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" +#include "/scripting/species/common/population.macros" ''') -t_species_condition = string.Template( +# Location and Enqueued condition template +t_species_cond = string.Template( '''ResourceSupplyConnected empire = Source.Owner condition = And [ Planet OwnedBy empire = Source.Owner @@ -133,7 +151,8 @@ Happiness low = 5 ]''') -t_species_condition_extinct = string.Template( +# Location and Enqueued condition template for extinct species +t_species_cond_extinct = string.Template( '''ResourceSupplyConnected empire = Source.Owner condition = And [ Planet OwnedBy empire = Source.Owner @@ -151,38 +170,20 @@ ] ]''') -t_buildtime = string.Template('''${t_factor} * max(5.0, 1.0 + - (Statistic Min Value = ShortestPath Object = Target.SystemID Object = LocalCandidate.SystemID - Condition = And [ +# buildtime statistic condition template +t_buildtime_stat_cond = string.Template( + '''Condition = And [ Planet OwnedBy empire = Source.Owner Species name = "${id}" Population low = [[MIN_RECOLONIZING_SIZE]] Happiness low = 5 ResourceSupplyConnected empire = Source.Owner condition = Target - ] - ) / (60 - + 20 * (Statistic If Condition = Or [ - OwnerHasTech name = "SHP_MIL_ROBO_CONT" - OwnerHasTech name = "SHP_ORG_HULL" - OwnerHasTech name = "SHP_QUANT_ENRG_MAG" - ]) - + 20 * (Statistic If Condition = Or [ - OwnerHasTech name = "SHP_ORG_HULL" - OwnerHasTech name = "SHP_QUANT_ENRG_MAG" - ]) - + 20 * (Statistic If Condition = OwnerHasTech name = "SHP_QUANT_ENRG_MAG") - + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_IMPROVED_ENGINE_COUPLINGS") - + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") - + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_SINGULARITY_ENGINE_CORE") - + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_TRANSSPACE_DRIVE") - + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_INTSTEL_LOG") - ) - )''') + ]''') -t_buildtime_extinct = string.Template('''${t_factor} * max(5.0, 1.0 + - (Statistic Min Value = ShortestPath Object = Target.SystemID Object = LocalCandidate.SystemID - Condition = And [ +# buildtime statistic condition template for extinct species +t_buildtime_stat_cond_extinct = string.Template( + '''Condition = And [ Planet OwnedBy empire = Source.Owner Or [ @@ -197,7 +198,12 @@ ] ] ResourceSupplyConnected empire = Source.Owner condition = Target - ] + ]''') + +# buildtime template +t_buildtime = string.Template('''${t_factor} * max(5.0, 1.0 + + (Statistic Min Value = ShortestPath Object = Target.SystemID Object = LocalCandidate.SystemID + ${stat_condition} ) / (60 + 20 * (Statistic If Condition = Or [ OwnerHasTech name = "SHP_MIL_ROBO_CONT" @@ -208,43 +214,54 @@ OwnerHasTech name = "SHP_ORG_HULL" OwnerHasTech name = "SHP_QUANT_ENRG_MAG" ]) - + 20 * (Statistic If Condition = OwnerHasTech Name = "SHP_QUANT_ENRG_MAG") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_IMPROVED_ENGINE_COUPLINGS") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_SINGULARITY_ENGINE_CORE") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_TRANSSPACE_DRIVE") - + 10 * (Statistic If Condition = OwnerHasTech Name = "SHP_INTSTEL_LOG") + + 20 * (Statistic If Condition = OwnerHasTech name = "SHP_QUANT_ENRG_MAG") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_IMPROVED_ENGINE_COUPLINGS") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_SINGULARITY_ENGINE_CORE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_TRANSSPACE_DRIVE") + + 10 * (Statistic If Condition = OwnerHasTech name = "SHP_INTSTEL_LOG") ) )''') outpath = os.getcwd() -print ("Output folder: %s" % outpath) +print("Output folder: %s" % outpath) -for sp_id, sp_desc_name, sp_graphic in species_list: +for sp_id, sp_graphic in species_list: sp_name = sp_id.split("_", 1)[1] sp_tags = '"' + sp_id + '"' sp_filename = sp_id + ".focs.txt" - sp_tech = tech_dict.get(sp_id, '') + sp_gamerule = species_colony_gamerules.get(sp_id, colony_gamerule_default) + extinct_tech = species_extinct_techs.get(sp_id, '') data = { 'id': sp_id, 'name': sp_name, 'tags': sp_tags, 'graphic': sp_graphic, - 'cost': 50, - 'time': t_buildtime.substitute(id=sp_id, t_factor=time_factor.get(sp_id, "1.0")), - 'species_condition': t_species_condition.substitute(id=sp_id) + 'cost': species_buildcost.get(sp_id, buildcost_default), + 'time': '', + 'species_condition': '' } if sp_id == "SP_EXOBOT": data['tags'] += ' "CTRL_ALWAYS_REPORT"' - data['cost'] = 70 data['time'] = 5 data['species_condition'] = r"// no existing Exobot colony required!" - elif sp_id in ("SP_BANFORO", "SP_KILANDOW", "SP_MISIORLA"): - data['tags'] += ' "CTRL_EXTINCT"' - data['species_condition'] = t_species_condition_extinct.substitute(tech_name=sp_tech, id=sp_id, name=sp_name) - data['time'] = t_buildtime_extinct.substitute(id=sp_id, name=sp_name, t_factor=time_factor.get(sp_id, "1.0")) + else: + if extinct_tech != '': + data['tags'] += ' "CTRL_EXTINCT"' + data['time'] = t_buildtime.substitute(t_factor=species_time_factor.get(sp_id, buildtime_factor_default), + stat_condition=t_buildtime_stat_cond_extinct.substitute(id=sp_id, + name=sp_name)) + data['species_condition'] = t_species_cond_extinct.substitute(tech_name=extinct_tech, id=sp_id, + name=sp_name) + else: + data['time'] = t_buildtime.substitute(t_factor=species_time_factor.get(sp_id, buildtime_factor_default), + stat_condition=t_buildtime_stat_cond.substitute(id=sp_id)) + data['species_condition'] = t_species_cond.substitute(id=sp_id) + + if sp_gamerule: + data['species_condition'] += ("\n ((GameRule name = \"%s\") > 0)" % sp_gamerule) with open(os.path.join(outpath, sp_filename), "w") as f: f.write(t_main.substitute(**data)) diff --git a/default/scripting/buildings/shipyards/ASTEROID.focs.txt b/default/scripting/buildings/shipyards/ASTEROID.focs.txt index 8c1d7bf585c..a7eb0b15163 100644 --- a/default/scripting/buildings/shipyards/ASTEROID.focs.txt +++ b/default/scripting/buildings/shipyards/ASTEROID.focs.txt @@ -8,15 +8,9 @@ BuildingType Not Contains Building name = "BLD_SHIPYARD_AST" Planet type = Asteroids OwnedBy empire = Source.Owner - - /* TODO: Check that one, seems to be weird */ - Not Contains And [ - Building name = "BLD_SHIPYARD_AST" - OwnedBy empire = Source.Owner - ] ] EnqueueLocation = [[ENQUEUE_BUILD_ONE_PER_PLANET]] icon = "icons/building/shipyard-5.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/shipyards/ASTEROID_REF.focs.txt b/default/scripting/buildings/shipyards/ASTEROID_REF.focs.txt index 61be1d47724..ccca345d2e8 100644 --- a/default/scripting/buildings/shipyards/ASTEROID_REF.focs.txt +++ b/default/scripting/buildings/shipyards/ASTEROID_REF.focs.txt @@ -8,20 +8,10 @@ BuildingType Not Contains Building name = "BLD_SHIPYARD_AST_REF" Planet type = Asteroids OwnedBy empire = Source.Owner - Or [ - Contains And [ - Building name = "BLD_SHIPYARD_AST" - OwnedBy empire = Source.Owner - ] - Enqueued type = Building name = "BLD_SHIPYARD_AST" - ] - Not Contains And [ - Building name = "BLD_SHIPYARD_AST_REF" - OwnedBy empire = Source.Owner - ] + [[LOCATION_ALLOW_BUILD_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_AST)]] ] EnqueueLocation = [[ENQUEUE_BUILD_ONE_PER_PLANET]] icon = "icons/building/shipyard-6.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/shipyards/CON_ADV_ENGINE.focs.txt b/default/scripting/buildings/shipyards/CON_ADV_ENGINE.focs.txt index 40e11ee5425..68d822f4ada 100644 --- a/default/scripting/buildings/shipyards/CON_ADV_ENGINE.focs.txt +++ b/default/scripting/buildings/shipyards/CON_ADV_ENGINE.focs.txt @@ -7,21 +7,12 @@ BuildingType location = And [ Planet Not Contains Building name = "BLD_SHIPYARD_CON_ADV_ENGINE" - Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] - Or [ - Contains And [ - Building name = "BLD_SHIPYARD_ORBITAL_DRYDOCK" - OwnedBy empire = Source.Owner - ] - Enqueued type = Building name = "BLD_SHIPYARD_ORBITAL_DRYDOCK" - ] + [[LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_BASE)]] + [[LOCATION_ALLOW_BUILD_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_ORBITAL_DRYDOCK)]] OwnedBy empire = Source.Owner ] EnqueueLocation = [[ENQUEUE_BUILD_ONE_PER_PLANET]] icon = "icons/building/shipyard-4.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/shipyards/CON_GEOINT.focs.txt b/default/scripting/buildings/shipyards/CON_GEOINT.focs.txt index fb8365ad7c9..08cbc9b07e1 100644 --- a/default/scripting/buildings/shipyards/CON_GEOINT.focs.txt +++ b/default/scripting/buildings/shipyards/CON_GEOINT.focs.txt @@ -7,21 +7,12 @@ BuildingType location = And [ Planet Not Contains Building name = "BLD_SHIPYARD_CON_GEOINT" - Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] - Or [ - Contains And [ - Building name = "BLD_SHIPYARD_ORBITAL_DRYDOCK" - OwnedBy empire = Source.Owner - ] - Enqueued type = Building name = "BLD_SHIPYARD_ORBITAL_DRYDOCK" - ] + [[LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_BASE)]] + [[LOCATION_ALLOW_BUILD_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_ORBITAL_DRYDOCK)]] OwnedBy empire = Source.Owner ] EnqueueLocation = [[ENQUEUE_BUILD_ONE_PER_PLANET]] icon = "icons/building/shipyard-3.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/shipyards/CON_NANOROBO.focs.txt b/default/scripting/buildings/shipyards/CON_NANOROBO.focs.txt index 944b67ed93b..16056af4dbe 100644 --- a/default/scripting/buildings/shipyards/CON_NANOROBO.focs.txt +++ b/default/scripting/buildings/shipyards/CON_NANOROBO.focs.txt @@ -7,21 +7,12 @@ BuildingType location = And [ Planet Not Contains Building name = "BLD_SHIPYARD_CON_NANOROBO" - Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] - Or [ - Contains And [ - Building name = "BLD_SHIPYARD_ORBITAL_DRYDOCK" - OwnedBy empire = Source.Owner - ] - Enqueued type = Building name = "BLD_SHIPYARD_ORBITAL_DRYDOCK" - ] + [[LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_BASE)]] + [[LOCATION_ALLOW_BUILD_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_ORBITAL_DRYDOCK)]] OwnedBy empire = Source.Owner ] EnqueueLocation = [[ENQUEUE_BUILD_ONE_PER_PLANET]] icon = "icons/building/shipyard-2.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/shipyards/ENERGY_COMP.focs.txt b/default/scripting/buildings/shipyards/ENERGY_COMP.focs.txt index f58bea50468..aaf2a9c24f2 100644 --- a/default/scripting/buildings/shipyards/ENERGY_COMP.focs.txt +++ b/default/scripting/buildings/shipyards/ENERGY_COMP.focs.txt @@ -7,10 +7,7 @@ BuildingType location = And [ Planet Not Contains Building name = "BLD_SHIPYARD_ENRG_COMP" - Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] + [[LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_BASE)]] Or [ Star type = [White Blue BlackHole] Enqueued type = Building name = "BLD_ART_BLACK_HOLE" @@ -29,4 +26,4 @@ BuildingType icon = "icons/building/shipyard-10.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/shipyards/ENERGY_SOLAR.focs.txt b/default/scripting/buildings/shipyards/ENERGY_SOLAR.focs.txt index 6f13a124e4b..8e0c679bc1b 100644 --- a/default/scripting/buildings/shipyards/ENERGY_SOLAR.focs.txt +++ b/default/scripting/buildings/shipyards/ENERGY_SOLAR.focs.txt @@ -7,17 +7,8 @@ BuildingType location = And [ Planet Not Contains Building name = "BLD_SHIPYARD_ENRG_SOLAR" - Or [ - Contains And [ - Building name = "BLD_SHIPYARD_ENRG_COMP" - OwnedBy empire = Source.Owner - ] - Enqueued type = Building name = "BLD_SHIPYARD_ENRG_COMP" - ] - Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] + [[LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_BASE)]] + [[LOCATION_ALLOW_BUILD_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_ENRG_COMP)]] Or [ Star type = BlackHole Enqueued type = Building name = "BLD_ART_BLACK_HOLE" @@ -36,4 +27,4 @@ BuildingType icon = "icons/building/shipyard-16.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/shipyards/ORBITAL_DRYDOCK.focs.txt b/default/scripting/buildings/shipyards/ORBITAL_DRYDOCK.focs.txt index 8ee6d06cc97..6ea9182a0ff 100644 --- a/default/scripting/buildings/shipyards/ORBITAL_DRYDOCK.focs.txt +++ b/default/scripting/buildings/shipyards/ORBITAL_DRYDOCK.focs.txt @@ -15,10 +15,7 @@ BuildingType location = And [ Planet Not Contains Building name = "BLD_SHIPYARD_ORBITAL_DRYDOCK" - Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] + [[LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_BASE)]] OwnedBy empire = Source.Owner ] EnqueueLocation = [[ENQUEUE_BUILD_ONE_PER_PLANET]] @@ -28,15 +25,18 @@ BuildingType scope = And [ Ship InSystem id = Source.SystemID - OwnedBy empire = Source.Owner + Or [ + OwnedBy empire = Source.Owner + And [ + ((GameRule name = "RULE_ENABLE_ALLIED_REPAIR") > 0) + OwnedBy affiliation = AllyOf empire = Source.Owner + ] + ] Structure high = LocalCandidate.MaxStructure - 0.001 ] - activation = And [ - Source - ContainedBy And [ - [[PLANET_OWNED_DRYDOCK_HIGHEST_HAPPINESS]] - Population high = 0 - ] + activation = ContainedBy And [ + [[PLANET_OWNED_DRYDOCK_HIGHEST_HAPPINESS]] + Population high = 0 ] stackinggroup = "SHIP_REPAIR" priority = 75 @@ -57,7 +57,13 @@ BuildingType scope = And [ Ship InSystem id = Source.SystemID - OwnedBy empire = Source.Owner + Or [ + OwnedBy empire = Source.Owner + And [ + ((GameRule name = "RULE_ENABLE_ALLIED_REPAIR") > 0) + OwnedBy affiliation = AllyOf empire = Source.Owner + ] + ] Structure low = LocalCandidate.MaxStructure - [[ORB_DRYDOCK_REPAIR_VAL(LocalCandidate.MaxStructure)]] - 0.001 high = LocalCandidate.MaxStructure - 0.001 @@ -88,7 +94,13 @@ BuildingType scope = And [ Ship InSystem id = Source.SystemID - OwnedBy empire = Source.Owner + Or [ + OwnedBy empire = Source.Owner + And [ + ((GameRule name = "RULE_ENABLE_ALLIED_REPAIR") > 0) + OwnedBy affiliation = AllyOf empire = Source.Owner + ] + ] Structure high = LocalCandidate.MaxStructure - [[ORB_DRYDOCK_REPAIR_VAL(LocalCandidate.MaxStructure)]] Turn low = LocalCandidate.ArrivedOnTurn + 1 @@ -118,7 +130,13 @@ BuildingType scope = And [ Ship InSystem id = Source.SystemID - OwnedBy empire = Source.Owner + Or [ + OwnedBy empire = Source.Owner + And [ + ((GameRule name = "RULE_ENABLE_ALLIED_REPAIR") > 0) + OwnedBy affiliation = AllyOf empire = Source.Owner + ] + ] Structure high = LocalCandidate.MaxStructure - 0.001 Turn low = LocalCandidate.ArrivedOnTurn + 1 (Source.Planet.Happiness < [[CONST_ORB_DRYDOCK_MIN_HAPPY]]) @@ -174,12 +192,18 @@ ORB_DRYDOCK_REPAIR_RATE ORB_DRYDOCK_REPAIR_VAL '''[[ORB_DRYDOCK_HAPPY_RATE]] * [[ORB_DRYDOCK_REPAIR_RATE(@1@)]]''' -+// Planet in system with highest happiness that is owned and has a drydock +// Planet in system with highest happiness that is owned and has a drydock PLANET_OWNED_DRYDOCK_HIGHEST_HAPPINESS '''MaximumNumberOf Number = 1 SortKey = LocalCandidate.Happiness Condition = And [ Planet InSystem id = Source.SystemID - OwnedBy empire = Source.Owner + Or [ + OwnedBy empire = Source.Owner + And [ + ((GameRule name = "RULE_ENABLE_ALLIED_REPAIR") > 0) + OwnedBy affiliation = AllyOf empire = Source.Owner + ] + ] Contains Building name = "BLD_SHIPYARD_ORBITAL_DRYDOCK" ] @@ -187,4 +211,4 @@ PLANET_OWNED_DRYDOCK_HIGHEST_HAPPINESS #include "/scripting/common/enqueue.macros" #include "/scripting/common/priorities.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/shipyards/ORGANIC_CEL_GRO.focs.txt b/default/scripting/buildings/shipyards/ORGANIC_CEL_GRO.focs.txt index 9ca8b4c8065..deb6750ee84 100644 --- a/default/scripting/buildings/shipyards/ORGANIC_CEL_GRO.focs.txt +++ b/default/scripting/buildings/shipyards/ORGANIC_CEL_GRO.focs.txt @@ -7,21 +7,12 @@ BuildingType location = And [ Planet Not Contains Building name = "BLD_SHIPYARD_ORG_CELL_GRO_CHAMB" - Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] - Or [ - Contains And [ - Building name = "BLD_SHIPYARD_ORG_ORB_INC" - OwnedBy empire = Source.Owner - ] - Enqueued type = Building name = "BLD_SHIPYARD_ORG_ORB_INC" - ] + [[LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_BASE)]] + [[LOCATION_ALLOW_BUILD_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_ORG_ORB_INC)]] OwnedBy empire = Source.Owner ] EnqueueLocation = [[ENQUEUE_BUILD_ONE_PER_PLANET]] icon = "icons/building/cellular-growth-chamber.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/shipyards/ORGANIC_ORB_INC.focs.txt b/default/scripting/buildings/shipyards/ORGANIC_ORB_INC.focs.txt index fe2b7683914..02647415b7c 100644 --- a/default/scripting/buildings/shipyards/ORGANIC_ORB_INC.focs.txt +++ b/default/scripting/buildings/shipyards/ORGANIC_ORB_INC.focs.txt @@ -7,14 +7,11 @@ BuildingType location = And [ Planet Not Contains Building name = "BLD_SHIPYARD_ORG_ORB_INC" - Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] + [[LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_BASE)]] OwnedBy empire = Source.Owner ] EnqueueLocation = [[ENQUEUE_BUILD_ONE_PER_PLANET]] icon = "icons/building/orbital-incubator.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/buildings/shipyards/ORGANIC_XENO_FAC.focs.txt b/default/scripting/buildings/shipyards/ORGANIC_XENO_FAC.focs.txt index 14c7f55b09f..141c01d7d89 100644 --- a/default/scripting/buildings/shipyards/ORGANIC_XENO_FAC.focs.txt +++ b/default/scripting/buildings/shipyards/ORGANIC_XENO_FAC.focs.txt @@ -7,21 +7,12 @@ BuildingType location = And [ Planet Not Contains Building name = "BLD_SHIPYARD_ORG_XENO_FAC" - Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] - Or [ - Contains And [ - Building name = "BLD_SHIPYARD_ORG_ORB_INC" - OwnedBy empire = Source.Owner - ] - Enqueued type = Building name = "BLD_SHIPYARD_ORG_ORB_INC" - ] + [[LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_BASE)]] + [[LOCATION_ALLOW_BUILD_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_ORG_ORB_INC)]] OwnedBy empire = Source.Owner ] EnqueueLocation = [[ENQUEUE_BUILD_ONE_PER_PLANET]] icon = "icons/building/xeno-coordination-facility.png" #include "/scripting/common/enqueue.macros" -#include "/scripting/common/base_prod.macros" \ No newline at end of file +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/common/base_prod.macros b/default/scripting/common/base_prod.macros index c56cbf91a92..24c934cfce6 100644 --- a/default/scripting/common/base_prod.macros +++ b/default/scripting/common/base_prod.macros @@ -3,6 +3,9 @@ INDUSTRY_PER_POP '''0.2''' +STOCKPILE_PER_POP +'''0.02''' + RESEARCH_PER_POP '''0.2''' diff --git a/default/scripting/common/enqueue.macros b/default/scripting/common/enqueue.macros index e689143e121..3dd69dca634 100644 --- a/default/scripting/common/enqueue.macros +++ b/default/scripting/common/enqueue.macros @@ -1,3 +1,33 @@ +/* Allows the current building to be enqueued if the given prerequisite is built or enqueued. + Takes the prerequisite name as parameter; Usage: + [[LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_BASE)]] +*/ +LOCATION_ALLOW_ENQUEUE_IF_PREREQ_ENQUEUED +'''Or [ + Contains And [ + Building name = "@1@" + OwnedBy empire = Source.Owner + ] + // Allows enqueue if this is not enqueued but prerequisite @1@ is + And [ + Enqueued type = Building name = "@1@" + Not Enqueued type = Building name = CurrentContent + ] + ]''' +/* Allows the current building to be build if the given prerequisite is built or enqueued. + Takes the prerequisite name as parameter; Usage: + [[LOCATION_ALLOW_BUILD_IF_PREREQ_ENQUEUED(BLD_SHIPYARD_ORBITAL_DRYDOCK)]] +*/ +LOCATION_ALLOW_BUILD_IF_PREREQ_ENQUEUED +'''Or [ + Contains And [ + Building name = "@1@" + OwnedBy empire = Source.Owner + ] + Enqueued type = Building name = "@1@" + ]''' + + ENQUEUE_BUILD_ONE_PER_PLANET '''And [ Not Contains Building name = CurrentContent diff --git a/default/scripting/common/pop_reduce.macros b/default/scripting/common/pop_reduce.macros deleted file mode 100644 index f2ae5974624..00000000000 --- a/default/scripting/common/pop_reduce.macros +++ /dev/null @@ -1,8 +0,0 @@ -/* Complicated population formula necessary to actually peg pop reduction at a specific value such as -2, not just 2 less growth than it would have been - the fixed amount for pop reduction per turn is 'k' towards the beginning and the end of the comment line immediately below - SetPopulation value = min(Value - k, Value + 0.5*(101+Target.TargetPopulation-2*Value - Max(0,(101+Target.TargetPopulation-2*Value)^2 -4*(Value*(Value-1-Target.TargetPopulation)-k*100))^0.5)) - This macro takes a parameter, i.e., use it like - [[NEXT_TURN_POPULATION_REDUCE_BY(2)]] -*/ -NEXT_TURN_POPULATION_REDUCE_BY -'''SetPopulation value = min(Value - @1@, Value + 0.5*(101+Target.TargetPopulation-2*Value - Max(0,(101+Target.TargetPopulation-2*Value)^2 -4*(Value*(Value-1-Target.TargetPopulation) - @1@*100))^0.5))''' diff --git a/default/scripting/common/priorities.macros b/default/scripting/common/priorities.macros index 29dc5983ab4..8e8246eeed5 100644 --- a/default/scripting/common/priorities.macros +++ b/default/scripting/common/priorities.macros @@ -1,5 +1,40 @@ // Default = 100 +TARGET_POPULATION_BEFORE_SCALING_PRIORITY +'''10''' + +// 11 reserved for future use + +TARGET_POPULATION_SCALING_PRIORITY +'''12''' + +// 13 reserved for future use + +TARGET_POPULATION_AFTER_SCALING_PRIORITY +'''14''' + +// 15 reserved for future use + +TARGET_POPULATION_LAST_BEFORE_OVERRIDE_PRIORITY +'''16''' + +TARGET_POPULATION_OVERRIDE_PRIORITY +'''17''' + + +// Current population effects should happen after target population effects +// But before industry/research effects which may depend on current population +EARLY_FIRST_NATURAL_POPULATION_PRIORITY +'''18''' + +EARLY_POPULATION_PRIORITY +'''20''' + +EARLY_POPULATION_OVERRIDE_PRIORITY +'''30''' + + + VERY_EARLY_PRIORITY '''80''' @@ -15,5 +50,23 @@ LATE_PRIORITY VERY_LATE_PRIORITY '''120''' -LATEST_GROWTH_PRIORITY -'''99999''' +VERY_VERY_LATE_PRIORITY +'''130''' + + +AFTER_ALL_TARGET_MAX_METERS_PRIORITY +'''1000''' + + +FOCUS_CHANGE_PENALTY_PRIORITY +'''1100''' + + +METER_OVERRIDE_PRIORITY +'''2000''' + +CONCENTRATION_CAMP_PRIORITY +'''2200''' + +END_CLEANUP_PRIORITY +'''5000''' diff --git a/default/scripting/common/upkeep.macros b/default/scripting/common/upkeep.macros index 2dd4e398b73..a98fd12dcf0 100644 --- a/default/scripting/common/upkeep.macros +++ b/default/scripting/common/upkeep.macros @@ -2,7 +2,17 @@ COLONY_UPKEEP_MULTIPLICATOR '''(1 + 0.06 * SpeciesColoniesOwned empire = Source.Owner)''' FLEET_UPKEEP_MULTIPLICATOR -'''(1 + 0.01 * ShipDesignsOwned empire = Source.Owner)''' +'''(1 + + (1 - (GameRule name = "RULE_SHIP_PART_BASED_UPKEEP")) * ( + (0.01 * ShipDesignsOwned empire = Source.Owner) + + (0.01 * ShipDesignsInProduction empire = Source.Owner)) + + (GameRule name = "RULE_SHIP_PART_BASED_UPKEEP") * ( + (0.002 * ShipPartsOwned empire = Source.Owner class = ShortRange) + + (0.002 * ShipPartsOwned empire = Source.Owner class = FighterHangar) + + (0.002 * ShipPartsOwned empire = Source.Owner class = Armour) + + (0.002 * ShipPartsOwned empire = Source.Owner class = Troops) + + (0.002 * ShipDesignsOwned empire = Source.Owner) + + (0.01 * ShipDesignsInProduction empire = Source.Owner)))''' SHIP_HULL_COST_MULTIPLIER '''(GameRule name = "RULE_SHIP_HULL_COST_FACTOR")''' diff --git a/default/scripting/empire_statistics/Empire.focs.txt b/default/scripting/empire_statistics/Empire.focs.txt index 858922dcfa6..b274315db52 100644 --- a/default/scripting/empire_statistics/Empire.focs.txt +++ b/default/scripting/empire_statistics/Empire.focs.txt @@ -24,3 +24,11 @@ Statistic name = "PLANETS_DEPOPULATED" value = Statistic name = "PLANETS_INVADED" value = SpeciesPlanetsInvaded empire = Source.Owner + +Statistic name = "TOTAL_POPULATION_STAT" + value = Statistic Sum + value = LocalCandidate.Population + condition = And [ + PopulationCenter + OwnedBy empire = Source.Owner + ] diff --git a/default/scripting/empire_statistics/STATISTICS_TEST.focs.txt b/default/scripting/empire_statistics/STATISTICS_TEST.focs.txt index bb78e9b56ba..0245b68829c 100644 --- a/default/scripting/empire_statistics/STATISTICS_TEST.focs.txt +++ b/default/scripting/empire_statistics/STATISTICS_TEST.focs.txt @@ -17,7 +17,8 @@ Statistic name = "STATISTICS_TEST_2" value = ] */ -// The following currently causes a crash upon parsing +// The following is not correctly parsed, and generates a warning in the log file +/* Statistic name = "STATISTICS_TEST_3" value = 1.0 + MAX value = JumpsBetween @@ -27,3 +28,4 @@ Statistic name = "STATISTICS_TEST_3" value = Planet OwnedBy empire = Source.Owner ] +*/ \ No newline at end of file diff --git a/default/scripting/encyclopedia/game_concepts/diplomacy/AI_LEVELS.focs.txt b/default/scripting/encyclopedia/game_concepts/AI_LEVELS.focs.txt similarity index 78% rename from default/scripting/encyclopedia/game_concepts/diplomacy/AI_LEVELS.focs.txt rename to default/scripting/encyclopedia/game_concepts/AI_LEVELS.focs.txt index 6a56e9409b6..3477381aeaa 100644 --- a/default/scripting/encyclopedia/game_concepts/diplomacy/AI_LEVELS.focs.txt +++ b/default/scripting/encyclopedia/game_concepts/AI_LEVELS.focs.txt @@ -1,6 +1,6 @@ Article name = "AI_LEVELS_TITLE" - category = "DIPLOMACY_TITLE" + category = "CATEGORY_GAME_CONCEPTS" short_description = "AI_LEVELS_TITLE" description = "AI_LEVELS_TEXT" icon = "icons/combat.png" diff --git a/default/scripting/encyclopedia/game_concepts/FUEL.focs.txt b/default/scripting/encyclopedia/game_concepts/FUEL.focs.txt new file mode 100644 index 00000000000..aeeff7a849a --- /dev/null +++ b/default/scripting/encyclopedia/game_concepts/FUEL.focs.txt @@ -0,0 +1,13 @@ +Article + name = "FUEL_TITLE" + category = "CATEGORY_GAME_CONCEPTS" + short_description = "METER_ARTICLE_SHORT_DESC" + description = "FUEL_REFUEL_EFFICIENCY_TEXT" + icon = "icons/meter/fuel.png" + +Article + name = "FUEL_EFFICIENCY_TITLE" + category = "CATEGORY_GAME_CONCEPTS" + short_description = "METER_ARTICLE_SHORT_DESC" + description = "FUEL_REFUEL_EFFICIENCY_TEXT" + icon = "icons/meter/fuel.png" diff --git a/default/scripting/encyclopedia/game_concepts/STOCKPILE_TRANSFER.focs.txt b/default/scripting/encyclopedia/game_concepts/STOCKPILE_TRANSFER.focs.txt new file mode 100644 index 00000000000..adb921beeb1 --- /dev/null +++ b/default/scripting/encyclopedia/game_concepts/STOCKPILE_TRANSFER.focs.txt @@ -0,0 +1,6 @@ +Article + name = "PROJECT_BT_STOCKPILE" + category = "CATEGORY_GAME_CONCEPTS" + short_description = "PROJECT_BT_STOCKPILE_SHORT_DESC" + description = "PROJECT_BT_STOCKPILE_DESC" + icon = "icons/meter/stockpile.png" diff --git a/default/scripting/encyclopedia/game_concepts/diplomacy/ALLIANCE.focs.txt b/default/scripting/encyclopedia/game_concepts/diplomacy/ALLIANCE.focs.txt new file mode 100644 index 00000000000..42df2151c72 --- /dev/null +++ b/default/scripting/encyclopedia/game_concepts/diplomacy/ALLIANCE.focs.txt @@ -0,0 +1,6 @@ +Article + name = "ALLIANCE_TITLE" + category = "DIPLOMACY_TITLE" + short_description = "ALLIANCE_TITLE" + description = "ALLIANCE_TEXT" + icon = "icons/allied.png" diff --git a/default/scripting/encyclopedia/game_concepts/diplomacy/GIFTING.focs.txt b/default/scripting/encyclopedia/game_concepts/diplomacy/GIFTING.focs.txt new file mode 100644 index 00000000000..0eaff6ccd94 --- /dev/null +++ b/default/scripting/encyclopedia/game_concepts/diplomacy/GIFTING.focs.txt @@ -0,0 +1,6 @@ +Article + name = "GIFTING_TITLE" + category = "DIPLOMACY_TITLE" + short_description = "GIFTING_TITLE" + description = "GIFTING_TEXT" + icon = "misc/gifting.png" diff --git a/default/scripting/encyclopedia/game_concepts/diplomacy/WAR.focs.txt b/default/scripting/encyclopedia/game_concepts/diplomacy/WAR.focs.txt new file mode 100644 index 00000000000..0bd42fd0463 --- /dev/null +++ b/default/scripting/encyclopedia/game_concepts/diplomacy/WAR.focs.txt @@ -0,0 +1,6 @@ +Article + name = "WAR_TITLE" + category = "DIPLOMACY_TITLE" + short_description = "WAR_TITLE" + description = "WAR_TEXT" + icon = "icons/war.png" \ No newline at end of file diff --git a/default/scripting/encyclopedia/game_concepts/metabolism/GASEOUS_SPECIES.focs.txt b/default/scripting/encyclopedia/game_concepts/metabolism/GASEOUS_SPECIES.focs.txt new file mode 100644 index 00000000000..f878b0d953a --- /dev/null +++ b/default/scripting/encyclopedia/game_concepts/metabolism/GASEOUS_SPECIES.focs.txt @@ -0,0 +1,6 @@ +Article + name = "GASEOUS_SPECIES_TITLE" + category = "METABOLISM_TITLE" + short_description = "SPECIES_ARTICLE_SHORT_DESC" + description = "GASEOUS_SPECIES_TEXT" + icon = "icons/species/amorphous-05.png" diff --git a/default/scripting/encyclopedia/game_concepts/planet_management/HOMEWORLDS.focs.txt b/default/scripting/encyclopedia/game_concepts/planet_management/HOMEWORLDS.focs.txt new file mode 100644 index 00000000000..5f01f71571b --- /dev/null +++ b/default/scripting/encyclopedia/game_concepts/planet_management/HOMEWORLDS.focs.txt @@ -0,0 +1,6 @@ +Article + name = "HOMEWORLDS_TITLE" + category = "PLANET_MANAGEMENT_TITLE" + short_description = "HOMEWORLDS_ARTICLE_SHORT_DESC" + description = "HOMEWORLDS_TEXT" + icon = "icons/specials_large/homeworld.png" diff --git a/default/scripting/encyclopedia/game_concepts/planetary_focus/STOCKPILE_FOCUS.focs.txt b/default/scripting/encyclopedia/game_concepts/planetary_focus/STOCKPILE_FOCUS.focs.txt new file mode 100644 index 00000000000..57db3de95c3 --- /dev/null +++ b/default/scripting/encyclopedia/game_concepts/planetary_focus/STOCKPILE_FOCUS.focs.txt @@ -0,0 +1,6 @@ +Article + name = "STOCKPILE_FOCUS_TITLE" + category = "PLANETARY_FOCUS_TITLE" + short_description = "STOCKPILE_ARTICLE_SHORT_DESC" + description = "STOCKPILE_FOCUS_TEXT" + icon = "icons/focus/growth.png" diff --git a/default/scripting/encyclopedia/guides/CONFIGURATION.focs.txt b/default/scripting/encyclopedia/guides/CONFIGURATION.focs.txt new file mode 100644 index 00000000000..09080448ca3 --- /dev/null +++ b/default/scripting/encyclopedia/guides/CONFIGURATION.focs.txt @@ -0,0 +1,6 @@ +Article + name = "CONFIG_GUIDE_TITLE" + category = "CATEGORY_GUIDES" + short_description = "CONFIG_GUIDE_TITLE" + description = "CONFIG_GUIDE_TEXT" + icon = "icons/FO_Icon_32x32.png" diff --git a/default/scripting/encyclopedia/species/GASEOUS_SPECIES_CLASS.focs.txt b/default/scripting/encyclopedia/species/GASEOUS_SPECIES_CLASS.focs.txt new file mode 100644 index 00000000000..65ddf2122ee --- /dev/null +++ b/default/scripting/encyclopedia/species/GASEOUS_SPECIES_CLASS.focs.txt @@ -0,0 +1,6 @@ +Article + name = "GASEOUS_SPECIES_CLASS" + category = "ENC_SPECIES" + short_description = "GASEOUS_SPECIES_CLASS" + description = "GASEOUS_SPECIES_CLASS_DESC" + icon = "icons/species/amorphous-05.png" diff --git a/default/scripting/fields/FLD_ACCRETION_DISC.focs.txt b/default/scripting/fields/FLD_ACCRETION_DISC.focs.txt index d23df9472a3..d8457cf88d0 100644 --- a/default/scripting/fields/FLD_ACCRETION_DISC.focs.txt +++ b/default/scripting/fields/FLD_ACCRETION_DISC.focs.txt @@ -2,4 +2,4 @@ FieldType name = "FLD_ACCRETION_DISC" description = "FLD_ACCRETION_DISC_DESC" stealth = 0.01 - graphic = "nebulae/nebula20.png" + graphic = "fields/accretion_disc.png" diff --git a/default/scripting/fields/FLD_ION_STORM.focs.txt b/default/scripting/fields/FLD_ION_STORM.focs.txt index 7d49ec75a41..45c524d2b8a 100644 --- a/default/scripting/fields/FLD_ION_STORM.focs.txt +++ b/default/scripting/fields/FLD_ION_STORM.focs.txt @@ -15,7 +15,7 @@ FieldType EffectsGroup // grow size when young scope = Source activation = And [ - (Source.Age <= max((UniverseWidth ^ 1.1) / 50, 20)) + (LocalCandidate.Age <= max((UniverseWidth ^ 1.1) / 50, 20)) Size high = 50 ] effects = SetSize value = Value + min(max(Value * RandomNumber(0.05, 0.1), 1.0), 3.0) @@ -47,10 +47,10 @@ FieldType EffectsGroup // after reaching a certain age, dissipate when small scope = Source activation = And [ - (Source.Age >= 10) + (LocalCandidate.Age >= 10) Size high = 10 ] effects = Destroy ] - graphic = "fields/rainbow_storm.png" + graphic = "fields/ion_storm.png" diff --git a/default/scripting/fields/FLD_MOLECULAR_CLOUD.focs.txt b/default/scripting/fields/FLD_MOLECULAR_CLOUD.focs.txt index f7dde8fd4ed..4a4bfcba161 100644 --- a/default/scripting/fields/FLD_MOLECULAR_CLOUD.focs.txt +++ b/default/scripting/fields/FLD_MOLECULAR_CLOUD.focs.txt @@ -15,19 +15,19 @@ FieldType EffectsGroup // grow size when young scope = Source activation = And [ - (Source.Age <= max((UniverseWidth ^ 1.1) / 50, 30)) + (LocalCandidate.Age <= max((UniverseWidth ^ 1.1) / 50, 30)) Size high = 120 - ] + ] effects = SetSize value = Value + min(max(Value * RandomNumber(0.05, 0.1), 1.0), 5.0) EffectsGroup // shrink size when old scope = Source - activation = (Source.Age >= max((UniverseWidth ^ 1.1) / 50, 30)) + activation = (LocalCandidate.Age >= max((UniverseWidth ^ 1.1) / 50, 30)) effects = SetSize value = Value - min(max(Value * RandomNumber(0.05, 0.1), 1.0), 5.0) /* EffectsGroup // after reaching a certain age, shrink size a bit each turn when near systems scope = Source - activation = (Source.Age >= 10) + activation = (LocalCandidate.Age >= 10) effects = SetSize value = Value - 0.1 * (Count condition = And [ System WithinDistance distance = 80 condition = Source @@ -44,9 +44,9 @@ FieldType EffectsGroup // after reaching a certain age, dissipate when small scope = Source activation = And [ - (Source.Age >= 10) + (LocalCandidate.Age >= 10) Size high = 10 ] effects = Destroy ] - graphic = "nebulae/nebula8.png" + graphic = "fields/molecular_cloud.png" diff --git a/default/scripting/fields/FLD_NEBULA_1.focs.txt b/default/scripting/fields/FLD_NEBULA_1.focs.txt index 8342c433a07..ac4a90e9631 100644 --- a/default/scripting/fields/FLD_NEBULA_1.focs.txt +++ b/default/scripting/fields/FLD_NEBULA_1.focs.txt @@ -53,6 +53,6 @@ FieldType AddSpecial name = "ACCRETION_DISC_SPECIAL" ] ] - graphic = "nebulae/nebula12.png" + graphic = "fields/star_forming_nebula_1.png" #include "fields.macros" diff --git a/default/scripting/fields/FLD_NEBULA_2.focs.txt b/default/scripting/fields/FLD_NEBULA_2.focs.txt index dbe96818085..2a33a6e58ba 100644 --- a/default/scripting/fields/FLD_NEBULA_2.focs.txt +++ b/default/scripting/fields/FLD_NEBULA_2.focs.txt @@ -53,6 +53,6 @@ FieldType AddSpecial name = "ACCRETION_DISC_SPECIAL" ] ] - graphic = "nebulae/nebula15.png" + graphic = "fields/star_forming_nebula_2.png" #include "fields.macros" diff --git a/default/scripting/game_rules.focs.txt b/default/scripting/game_rules.focs.txt index 8142e376a8a..b29bcf9d0ed 100644 --- a/default/scripting/game_rules.focs.txt +++ b/default/scripting/game_rules.focs.txt @@ -42,10 +42,94 @@ GameRule type = Toggle default = On +GameRule + name = "RULE_ENABLE_SUPER_TESTER" + description = "RULE_ENABLE_SUPER_TESTER_DESC" + category = "CONTENT" + type = Toggle + default = On + GameRule name = "RULE_TEST_STRING" description = "RULE_TEST_STRING_DESC" category = "TEST" type = String - default = "HUMAN_PLAYER" - allowed = ["MODERATOR" "OBSERVER" "HUMAN_PLAYER" "AI_PLAYER"] + default = "PLAYER" + allowed = ["MODERATOR" "OBSERVER" "PLAYER" "AI_PLAYER"] + +GameRule + name = "RULE_HABITABLE_SIZE_TINY" + description = "RULE_HABITABLE_SIZE_DESC" + category = "PLANET_SIZE" + type = Integer + default = 1 + min = 0 + max = 999 + +GameRule + name = "RULE_HABITABLE_SIZE_SMALL" + description = "RULE_HABITABLE_SIZE_DESC" + category = "PLANET_SIZE" + type = Integer + default = 2 + min = 0 + max = 999 + +GameRule + name = "RULE_HABITABLE_SIZE_MEDIUM" + description = "RULE_HABITABLE_SIZE_DESC" + category = "PLANET_SIZE" + type = Integer + default = 3 + min = 0 + max = 999 + +GameRule + name = "RULE_HABITABLE_SIZE_LARGE" + description = "RULE_HABITABLE_SIZE_DESC" + category = "PLANET_SIZE" + type = Integer + default = 4 + min = 0 + max = 999 + +GameRule + name = "RULE_HABITABLE_SIZE_HUGE" + description = "RULE_HABITABLE_SIZE_DESC" + category = "PLANET_SIZE" + type = Integer + default = 5 + min = 0 + max = 999 + +GameRule + name = "RULE_HABITABLE_SIZE_ASTEROIDS" + description = "RULE_HABITABLE_SIZE_DESC" + category = "PLANET_SIZE" + type = Integer + default = 3 + min = 0 + max = 999 + +GameRule + name = "RULE_HABITABLE_SIZE_GASGIANT" + description = "RULE_HABITABLE_SIZE_DESC" + category = "PLANET_SIZE" + type = Integer + default = 6 + min = 0 + max = 999 + +GameRule + name = "RULE_SHIP_PART_BASED_UPKEEP" + description = "RULE_SHIP_PART_BASED_UPKEEP_DESC" + category = "BALANCE" + type = Toggle + default = Off + +GameRule + name = "RULE_ENABLE_ALLIED_REPAIR" + description = "RULE_ENABLE_ALLIED_REPAIR_DESC" + category = "MULTIPLAYER" + type = Toggle + default = Off diff --git a/default/scripting/monster_designs/SM_KRILL_2.focs.txt b/default/scripting/monster_designs/SM_KRILL_2.focs.txt index 97a5a83ab73..1682e210ed3 100644 --- a/default/scripting/monster_designs/SM_KRILL_2.focs.txt +++ b/default/scripting/monster_designs/SM_KRILL_2.focs.txt @@ -4,7 +4,7 @@ ShipDesign description = "SM_KRILL_2_DESC" hull = "SH_KRILL_2_BODY" parts = [ - "FT_KRILL" + "FT_HANGAR_KRILL" "FT_BAY_KRILL" ] icon = "icons/monsters/krill-2.png" diff --git a/default/scripting/monster_designs/SM_KRILL_3.focs.txt b/default/scripting/monster_designs/SM_KRILL_3.focs.txt index 6f2f373b4a3..b360ec35858 100644 --- a/default/scripting/monster_designs/SM_KRILL_3.focs.txt +++ b/default/scripting/monster_designs/SM_KRILL_3.focs.txt @@ -4,7 +4,7 @@ ShipDesign description = "SM_KRILL_3_DESC" hull = "SH_KRILL_3_BODY" parts = [ - "FT_KRILL" + "FT_HANGAR_KRILL" "FT_BAY_KRILL" "FT_BAY_KRILL" "" diff --git a/default/scripting/monster_designs/SM_KRILL_4.focs.txt b/default/scripting/monster_designs/SM_KRILL_4.focs.txt index 13fc551c71b..b5ac5f7a3f0 100644 --- a/default/scripting/monster_designs/SM_KRILL_4.focs.txt +++ b/default/scripting/monster_designs/SM_KRILL_4.focs.txt @@ -4,7 +4,7 @@ ShipDesign description = "SM_KRILL_4_DESC" hull = "SH_KRILL_4_BODY" parts = [ - "FT_KRILL" + "FT_HANGAR_KRILL" "FT_BAY_KRILL" "FT_BAY_KRILL" "FT_BAY_KRILL" diff --git a/default/scripting/ship_designs/SD_BASE_DECOY.focs.txt b/default/scripting/ship_designs/SD_BASE_DECOY.focs.txt index 330302de73d..435eb3fd268 100644 --- a/default/scripting/ship_designs/SD_BASE_DECOY.focs.txt +++ b/default/scripting/ship_designs/SD_BASE_DECOY.focs.txt @@ -3,6 +3,10 @@ ShipDesign uuid = "08a58b08-0929-496d-84fc-faa91424ca10" description = "SD_BASE_DECOY_DESC" hull = "SH_COLONY_BASE" - parts = "" + parts = [ + "" + "" + "" + ] icon = "icons/ship_hulls/colony_base_hull_small.png" model = "some model" diff --git a/default/scripting/ship_designs/SD_CARRIER.focs.txt b/default/scripting/ship_designs/SD_CARRIER.focs.txt index a2fece636a3..55e75d690c0 100644 --- a/default/scripting/ship_designs/SD_CARRIER.focs.txt +++ b/default/scripting/ship_designs/SD_CARRIER.focs.txt @@ -2,7 +2,7 @@ ShipDesign name = "SD_CARRIER" uuid = "08a58b08-0929-496d-84fc-faa91424ca34" description = "SD_CARRIER_DESC" - hull = "SH_STANDARD" + hull = "SH_BASIC_LARGE" parts = [ "FT_BAY_1" "FT_BAY_1" diff --git a/default/scripting/ship_designs/SD_CARRIER_2.focs.txt b/default/scripting/ship_designs/SD_CARRIER_2.focs.txt index 856c157f6ac..81df52ef937 100644 --- a/default/scripting/ship_designs/SD_CARRIER_2.focs.txt +++ b/default/scripting/ship_designs/SD_CARRIER_2.focs.txt @@ -2,7 +2,7 @@ ShipDesign name = "SD_CARRIER_2" uuid = "08a58b08-0929-496d-84fc-faa91424ca05" description = "SD_CARRIER_2_DESC" - hull = "SH_STANDARD" + hull = "SH_BASIC_LARGE" parts = [ "FT_BAY_1" "SR_WEAPON_1_1" diff --git a/default/scripting/ship_designs/SD_GRAVITATING1.focs.txt b/default/scripting/ship_designs/SD_GRAVITATING1.focs.txt index f5b10f6dcbf..fb743376eb1 100644 --- a/default/scripting/ship_designs/SD_GRAVITATING1.focs.txt +++ b/default/scripting/ship_designs/SD_GRAVITATING1.focs.txt @@ -7,7 +7,6 @@ ShipDesign "SR_WEAPON_3_1" "SR_WEAPON_3_1" "SR_WEAPON_3_1" - "SR_WEAPON_3_1" "SR_WEAPON_0_1" "" "AR_DIAMOND_PLATE" diff --git a/default/scripting/ship_designs/SD_GRAVITATING2.focs.txt b/default/scripting/ship_designs/SD_GRAVITATING2.focs.txt index 2787eb18598..d5068211dea 100644 --- a/default/scripting/ship_designs/SD_GRAVITATING2.focs.txt +++ b/default/scripting/ship_designs/SD_GRAVITATING2.focs.txt @@ -7,11 +7,10 @@ ShipDesign "SR_WEAPON_4_1" "SR_WEAPON_4_1" "SR_WEAPON_4_1" - "SR_WEAPON_4_1" "SR_WEAPON_0_1" "FU_SINGULARITY_ENGINE_CORE" "AR_XENTRONIUM_PLATE" - "FU_N_DIMENSIONAL_ENGINE_MATRIX" + "FU_BASIC_TANK" "SH_BLACK" ] model = "mark1" diff --git a/default/scripting/ship_designs/SD_LARGE_MARK_3.focs.txt b/default/scripting/ship_designs/SD_LARGE_MARK_3.focs.txt index e50b423627b..d74c41a3e30 100644 --- a/default/scripting/ship_designs/SD_LARGE_MARK_3.focs.txt +++ b/default/scripting/ship_designs/SD_LARGE_MARK_3.focs.txt @@ -2,7 +2,7 @@ ShipDesign name = "SD_LARGE_MARK_3" uuid = "08a58b08-0929-496d-84fc-faa91424ca32" description = "SD_LARGE_MARK3_DESC" - hull = "SH_STANDARD" + hull = "SH_BASIC_LARGE" parts = [ "SR_WEAPON_0_1" "SR_WEAPON_1_1" diff --git a/default/scripting/ship_designs/SD_LARGE_MARK_4.focs.txt b/default/scripting/ship_designs/SD_LARGE_MARK_4.focs.txt index bb43b1e276d..0f0a14c1c38 100644 --- a/default/scripting/ship_designs/SD_LARGE_MARK_4.focs.txt +++ b/default/scripting/ship_designs/SD_LARGE_MARK_4.focs.txt @@ -2,7 +2,7 @@ ShipDesign name = "SD_LARGE_MARK_4" uuid = "08a58b08-0929-496d-84fc-faa91424ca03" description = "SD_LARGE_MARK4_DESC" - hull = "SH_STANDARD" + hull = "SH_BASIC_LARGE" parts = [ "SR_WEAPON_0_1" "SR_WEAPON_2_1" diff --git a/default/scripting/ship_designs/SD_ROBO_TITAN1.focs.txt b/default/scripting/ship_designs/SD_ROBO_TITAN1.focs.txt index 0cfa0b11076..5a0b75a90f2 100644 --- a/default/scripting/ship_designs/SD_ROBO_TITAN1.focs.txt +++ b/default/scripting/ship_designs/SD_ROBO_TITAN1.focs.txt @@ -11,18 +11,17 @@ ShipDesign "SR_WEAPON_4_1" "SR_WEAPON_4_1" "SR_WEAPON_4_1" - "SR_WEAPON_4_1" - "SR_WEAPON_4_1" - "SR_WEAPON_4_1" - "SR_WEAPON_4_1" - "SR_WEAPON_4_1" "AR_XENTRONIUM_PLATE" "AR_XENTRONIUM_PLATE" + "FT_BAY_1" + "SR_WEAPON_4_1" + "SR_WEAPON_4_1" + "FT_BAY_1" "AR_XENTRONIUM_PLATE" "AR_XENTRONIUM_PLATE" "SH_PLASMA" - "FU_N_DIMENSIONAL_ENGINE_MATRIX" - "FU_N_DIMENSIONAL_ENGINE_MATRIX" + "FT_HANGAR_1" + "FT_HANGAR_1" "FU_SINGULARITY_ENGINE_CORE" ] model = "mark1" diff --git a/default/scripting/ship_designs/ShipDesign-ac1e0345-119a-4408-9e42-2d92bf8823e0.focs.txt b/default/scripting/ship_designs/ShipDesign-ac1e0345-119a-4408-9e42-2d92bf8823e0.focs.txt index 2374776aaf8..29b8dabd07e 100644 --- a/default/scripting/ship_designs/ShipDesign-ac1e0345-119a-4408-9e42-2d92bf8823e0.focs.txt +++ b/default/scripting/ship_designs/ShipDesign-ac1e0345-119a-4408-9e42-2d92bf8823e0.focs.txt @@ -2,7 +2,7 @@ ShipDesign name = "SD_LARGE_MARK_2" uuid = "ac1e0345-119a-4408-9e42-2d92bf8823e0" description = "SD_LARGE_MARK2_DESC" - hull = "SH_STANDARD" + hull = "SH_BASIC_LARGE" parts = [ "SR_WEAPON_2_1" "SR_WEAPON_2_1" diff --git a/default/scripting/ship_designs/ShipDesign-cea092f3-e46e-41ad-b865-4188341536cd.focs.txt b/default/scripting/ship_designs/ShipDesign-cea092f3-e46e-41ad-b865-4188341536cd.focs.txt index 55cf73ab2a3..ba54d6ea96e 100644 --- a/default/scripting/ship_designs/ShipDesign-cea092f3-e46e-41ad-b865-4188341536cd.focs.txt +++ b/default/scripting/ship_designs/ShipDesign-cea092f3-e46e-41ad-b865-4188341536cd.focs.txt @@ -2,7 +2,7 @@ ShipDesign name = "SD_LARGE_MARK_1" uuid = "cea092f3-e46e-41ad-b865-4188341536cd" description = "SD_LARGE_MARK1_DESC" - hull = "SH_STANDARD" + hull = "SH_BASIC_LARGE" parts = [ "SR_WEAPON_1_1" "SR_WEAPON_1_1" diff --git a/default/scripting/ship_designs/required/SD_COLONY_BASE.focs.txt b/default/scripting/ship_designs/required/SD_COLONY_BASE.focs.txt index bd30ea932f1..50221292881 100644 --- a/default/scripting/ship_designs/required/SD_COLONY_BASE.focs.txt +++ b/default/scripting/ship_designs/required/SD_COLONY_BASE.focs.txt @@ -3,6 +3,10 @@ ShipDesign uuid = "08a58b08-0929-496d-84fc-faa91424ca11" description = "SD_COLONY_BASE_DESC" hull = "SH_COLONY_BASE" - parts = "CO_COLONY_POD" + parts = [ + "CO_COLONY_POD" + "" + "" + ] icon = "icons/ship_hulls/colony_base_hull_small.png" // These designs may specify a ship icon, but if not the hull's icon is used. model = "seed" diff --git a/default/scripting/ship_designs/required/SD_CRYONIC_COLONY_BASE.focs.txt b/default/scripting/ship_designs/required/SD_CRYONIC_COLONY_BASE.focs.txt index 5327518c5d7..e6ad68a3852 100644 --- a/default/scripting/ship_designs/required/SD_CRYONIC_COLONY_BASE.focs.txt +++ b/default/scripting/ship_designs/required/SD_CRYONIC_COLONY_BASE.focs.txt @@ -3,6 +3,10 @@ ShipDesign uuid = "08a58b08-0929-496d-84fc-faa91424ca15" description = "SD_CRYONIC_COLONY_BASE_DESC" hull = "SH_COLONY_BASE" - parts = "CO_SUSPEND_ANIM_POD" + parts = [ + "CO_SUSPEND_ANIM_POD" + "" + "" + ] icon = "icons/ship_hulls/colony_base_hull_small.png" model = "seed" diff --git a/default/scripting/ship_designs/SD_MARK_1.focs.txt b/default/scripting/ship_designs/required/SD_MARK_1.focs.txt similarity index 100% rename from default/scripting/ship_designs/SD_MARK_1.focs.txt rename to default/scripting/ship_designs/required/SD_MARK_1.focs.txt diff --git a/default/scripting/ship_designs/required/SD_OUTPOST_BASE.focs.txt b/default/scripting/ship_designs/required/SD_OUTPOST_BASE.focs.txt index 5714ff706bf..c033b075a9b 100644 --- a/default/scripting/ship_designs/required/SD_OUTPOST_BASE.focs.txt +++ b/default/scripting/ship_designs/required/SD_OUTPOST_BASE.focs.txt @@ -3,5 +3,9 @@ ShipDesign uuid = "08a58b08-0929-496d-84fc-faa91424ca16" description = "SD_OUTPOST_BASE_DESC" hull = "SH_COLONY_BASE" - parts = "CO_OUTPOST_POD" + parts = [ + "CO_OUTPOST_POD" + "" + "" + ] model = "seed" diff --git a/default/scripting/ship_designs/SD_SCOUT.focs.txt b/default/scripting/ship_designs/required/SD_SCOUT.focs.txt similarity index 100% rename from default/scripting/ship_designs/SD_SCOUT.focs.txt rename to default/scripting/ship_designs/required/SD_SCOUT.focs.txt diff --git a/default/scripting/ship_designs/required/SD_TROOP_DROP.focs.txt b/default/scripting/ship_designs/required/SD_TROOP_DROP.focs.txt index fdeb996493c..2bc70570c92 100644 --- a/default/scripting/ship_designs/required/SD_TROOP_DROP.focs.txt +++ b/default/scripting/ship_designs/required/SD_TROOP_DROP.focs.txt @@ -5,5 +5,7 @@ ShipDesign hull = "SH_COLONY_BASE" parts = [ "GT_TROOP_POD" + "GT_TROOP_POD" + "GT_TROOP_POD" ] model = "mark1" diff --git a/default/scripting/ship_designs/required/SD_TROOP_DROP_HEAVY.focs.txt b/default/scripting/ship_designs/required/SD_TROOP_DROP_HEAVY.focs.txt index 070503c9746..3c9b3acc774 100644 --- a/default/scripting/ship_designs/required/SD_TROOP_DROP_HEAVY.focs.txt +++ b/default/scripting/ship_designs/required/SD_TROOP_DROP_HEAVY.focs.txt @@ -5,5 +5,7 @@ ShipDesign hull = "SH_COLONY_BASE" parts = [ "GT_TROOP_POD_2" + "GT_TROOP_POD_2" + "GT_TROOP_POD_2" ] model = "mark1" diff --git a/default/scripting/ship_hulls/Basic.focs.txt b/default/scripting/ship_hulls/Basic.focs.txt deleted file mode 100644 index 870b8d6706a..00000000000 --- a/default/scripting/ship_hulls/Basic.focs.txt +++ /dev/null @@ -1,89 +0,0 @@ -Hull - name = "SH_BASIC_SMALL" - description = "SH_BASIC_SMALL_DESC" - speed = 75 - fuel = 4 - stealth = 5 - structure = 5 - slots = [ - Slot type = External position = (0.50, 0.45) - ] - buildCost = 10.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] - buildTime = 2 - tags = [ "PEDIA_HULL_LINE_GENERIC" ] - location = Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] - effectsgroups = [ - [[AVERAGE_BASE_FUEL_REGEN]] - [[REGULAR_HULL_DETECTION]] - [[SCAVANGE_FUEL_UNOWNED]] - [[UNOWNED_GOOD_VISION]] - [[UNOWNED_MOVE]] - ] - icon = "icons/ship_hulls/basic-small-hull_small.png" - graphic = "hulls_design/basic-small-hull.png" - -Hull - name = "SH_BASIC_MEDIUM" - description = "SH_BASIC_MEDIUM_DESC" - speed = 60 - fuel = 3 - stealth = 5 - structure = 10 - slots = [ - Slot type = External position = (0.65, 0.25) - Slot type = External position = (0.65, 0.55) - Slot type = Internal position = (0.35, 0.35) - ] - buildCost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] - buildTime = 2 - tags = [ "PEDIA_HULL_LINE_GENERIC" ] - location = Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] - effectsgroups = [ - [[AVERAGE_BASE_FUEL_REGEN]] - [[REGULAR_HULL_DETECTION]] - [[SCAVANGE_FUEL_UNOWNED]] - [[UNOWNED_GOOD_VISION]] - [[UNOWNED_MOVE]] - ] - icon = "icons/ship_hulls/basic-medium-hull_small.png" - graphic = "hulls_design/basic-medium-hull.png" - -Hull - name = "SH_STANDARD" - description = "SH_STANDARD_DESC" - speed = 60 - fuel = 3 - stealth = 5 - structure = 15 - slots = [ - Slot type = External position = (0.50, 0.35) - Slot type = External position = (0.50, 0.60) - Slot type = External position = (0.80, 0.45) - Slot type = Internal position = (0.30, 0.40) - ] - buildCost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] - buildTime = 2 - tags = [ "PEDIA_HULL_LINE_GENERIC" ] - location = Contains And [ - Building name = "BLD_SHIPYARD_BASE" - OwnedBy empire = Source.Owner - ] - effectsgroups = [ - [[AVERAGE_BASE_FUEL_REGEN]] - [[REGULAR_HULL_DETECTION]] - [[SCAVANGE_FUEL_UNOWNED]] - [[UNOWNED_GOOD_VISION]] - [[UNOWNED_MOVE]] - ] - icon = "icons/ship_hulls/basic-large-hull_small.png" - graphic = "hulls_design/basic-large-hull.png" - -#include "ship_hulls.macros" - -#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_hulls/SH_BASIC_LARGE.focs.txt b/default/scripting/ship_hulls/SH_BASIC_LARGE.focs.txt new file mode 100644 index 00000000000..9b25d1d35cf --- /dev/null +++ b/default/scripting/ship_hulls/SH_BASIC_LARGE.focs.txt @@ -0,0 +1,37 @@ +Hull + name = "SH_BASIC_LARGE" + description = "SH_BASIC_LARGE_DESC" + speed = 60 + fuel = 1.5 + NoDefaultFuelEffect + stealth = 5 + structure = 15 + slots = [ + Slot type = External position = (0.50, 0.35) + Slot type = External position = (0.50, 0.60) + Slot type = External position = (0.80, 0.45) + Slot type = Internal position = (0.30, 0.40) + ] + buildCost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] + buildTime = 2 + tags = [ "PEDIA_HULL_LINE_GENERIC" "AVERAGE_FUEL_EFFICIENCY" ] + location = Contains And [ + Building name = "BLD_SHIPYARD_BASE" + OwnedBy empire = Source.Owner + ] + effectsgroups = [ + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[AVERAGE_BASE_FUEL_REGEN]] + [[REGULAR_HULL_DETECTION]] + [[SCAVANGE_FUEL_UNOWNED]] + [[UNOWNED_GOOD_VISION]] + [[UNOWNED_MOVE]] + ] + icon = "icons/ship_hulls/basic-large-hull_small.png" + graphic = "hulls_design/basic-large-hull.png" + +#include "ship_hulls.macros" + +#include "/scripting/common/upkeep.macros" + diff --git a/default/scripting/ship_hulls/SH_BASIC_MEDIUM.focs.txt b/default/scripting/ship_hulls/SH_BASIC_MEDIUM.focs.txt new file mode 100644 index 00000000000..8213370cd56 --- /dev/null +++ b/default/scripting/ship_hulls/SH_BASIC_MEDIUM.focs.txt @@ -0,0 +1,36 @@ +Hull + name = "SH_BASIC_MEDIUM" + description = "SH_BASIC_MEDIUM_DESC" + speed = 60 + fuel = 3 + NoDefaultFuelEffect + stealth = 5 + structure = 10 + slots = [ + Slot type = External position = (0.65, 0.25) + Slot type = External position = (0.65, 0.55) + Slot type = Internal position = (0.35, 0.35) + ] + buildCost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] + buildTime = 2 + tags = [ "PEDIA_HULL_LINE_GENERIC" "GOOD_FUEL_EFFICIENCY" ] + location = Contains And [ + Building name = "BLD_SHIPYARD_BASE" + OwnedBy empire = Source.Owner + ] + effectsgroups = [ + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[AVERAGE_BASE_FUEL_REGEN]] + [[REGULAR_HULL_DETECTION]] + [[SCAVANGE_FUEL_UNOWNED]] + [[UNOWNED_GOOD_VISION]] + [[UNOWNED_MOVE]] + ] + icon = "icons/ship_hulls/basic-medium-hull_small.png" + graphic = "hulls_design/basic-medium-hull.png" + +#include "ship_hulls.macros" + +#include "/scripting/common/upkeep.macros" + diff --git a/default/scripting/ship_hulls/SH_BASIC_SMALL.focs.txt b/default/scripting/ship_hulls/SH_BASIC_SMALL.focs.txt new file mode 100644 index 00000000000..754139a264a --- /dev/null +++ b/default/scripting/ship_hulls/SH_BASIC_SMALL.focs.txt @@ -0,0 +1,33 @@ +Hull + name = "SH_BASIC_SMALL" + description = "SH_BASIC_SMALL_DESC" + speed = 75 + fuel = 8 + NoDefaultFuelEffect + stealth = 5 + structure = 5 + slots = [ + Slot type = External position = (0.50, 0.45) + ] + buildCost = 10.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] + buildTime = 2 + tags = [ "PEDIA_HULL_LINE_GENERIC" "GREAT_FUEL_EFFICIENCY" ] + location = Contains And [ + Building name = "BLD_SHIPYARD_BASE" + OwnedBy empire = Source.Owner + ] + effectsgroups = [ + [[GREAT_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[AVERAGE_BASE_FUEL_REGEN]] + [[REGULAR_HULL_DETECTION]] + [[SCAVANGE_FUEL_UNOWNED]] + [[UNOWNED_GOOD_VISION]] + [[UNOWNED_MOVE]] + ] + icon = "icons/ship_hulls/basic-small-hull_small.png" + graphic = "hulls_design/basic-small-hull.png" + +#include "ship_hulls.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_hulls/SH_COLONY_BASE.focs.txt b/default/scripting/ship_hulls/SH_COLONY_BASE.focs.txt index a6c7e335733..5dbd3fafd58 100644 --- a/default/scripting/ship_hulls/SH_COLONY_BASE.focs.txt +++ b/default/scripting/ship_hulls/SH_COLONY_BASE.focs.txt @@ -3,10 +3,15 @@ Hull description = "SH_COLONY_BASE_DESC" speed = 0 fuel = 0 + NoDefaultFuelEffect stealth = 5 structure = 2 - slots = Slot type = Internal position = (0.50, 0.50) - buildCost = 1 + slots = [ + Slot type = Internal position = (0.38, 0.50) + Slot type = Internal position = (0.50, 0.50) + Slot type = Internal position = (0.62, 0.50) + ] + buildCost = 6 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 3 tags = [ "PEDIA_HULL_LINE_GENERIC" ] location = OwnedBy empire = Source.Owner @@ -16,3 +21,5 @@ Hull graphic = "hulls_design/colony_base_hull.png" #include "ship_hulls.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_hulls/SH_XENTRONIUM.focs.txt b/default/scripting/ship_hulls/SH_XENTRONIUM.focs.txt index ca81a61d976..7bb754581f9 100644 --- a/default/scripting/ship_hulls/SH_XENTRONIUM.focs.txt +++ b/default/scripting/ship_hulls/SH_XENTRONIUM.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_XENTRONIUM_DESC" speed = 70 fuel = 3 + NoDefaultFuelEffect stealth = 5 structure = 50 slots = [ @@ -14,12 +15,14 @@ Hull ] buildCost = 50 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 5 - tags = [ "SHINY" "EXOTIC" ] + tags = [ "SHINY" "EXOTIC" "GOOD_FUEL_EFFICIENCY" ] location = Contains And [ Building name = "BLD_SHIPYARD_BASE" OwnedBy empire = Source.Owner ] effectsgroups = [ + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/asteroid/SH_AGREGATE_ASTEROID.disabled b/default/scripting/ship_hulls/asteroid/SH_AGREGATE_ASTEROID.disabled index 9c95f26bf28..0e22d5b8803 100644 --- a/default/scripting/ship_hulls/asteroid/SH_AGREGATE_ASTEROID.disabled +++ b/default/scripting/ship_hulls/asteroid/SH_AGREGATE_ASTEROID.disabled @@ -28,7 +28,7 @@ Hull ] buildCost = 60.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 3 - tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" ] + tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" "HUGE_HULL" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_BASE" @@ -58,6 +58,7 @@ Hull ] effects = SetStealth value = Value + 40 + [[HUGE_HULL_FUEL]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] [[UNOWNED_GOOD_VISION]] diff --git a/default/scripting/ship_hulls/asteroid/SH_ASTEROID.focs.txt b/default/scripting/ship_hulls/asteroid/SH_ASTEROID.focs.txt index 23833fc9eaa..72ad3ee789a 100644 --- a/default/scripting/ship_hulls/asteroid/SH_ASTEROID.focs.txt +++ b/default/scripting/ship_hulls/asteroid/SH_ASTEROID.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_ASTEROID" description = "SH_ASTEROID_DESC" speed = 60 - fuel = 2 + fuel = 1 + NoDefaultFuelEffect stealth = 5 structure = 30 slots = [ @@ -15,7 +16,7 @@ Hull ] buildCost = 20.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 2 - tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" ] + tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" "AVERAGE_FUEL_EFFICIENCY" ] location = And [ Planet Contains And [ @@ -31,6 +32,8 @@ Hull ] ] effectsgroups = [ + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[ASTEROID_FIELD_STEALTH_BONUS]] [[REGULAR_HULL_DETECTION]] diff --git a/default/scripting/ship_hulls/asteroid/SH_CAMOUFLAGE_ASTEROID.focs.txt b/default/scripting/ship_hulls/asteroid/SH_CAMOUFLAGE_ASTEROID.focs.txt index b87a411999f..0b555c11a4c 100644 --- a/default/scripting/ship_hulls/asteroid/SH_CAMOUFLAGE_ASTEROID.focs.txt +++ b/default/scripting/ship_hulls/asteroid/SH_CAMOUFLAGE_ASTEROID.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_CAMOUFLAGE_ASTEROID" description = "SH_CAMOUFLAGE_ASTEROID_DESC" speed = 60 - fuel = 4 + fuel = 2 + NoDefaultFuelEffect stealth = 25 structure = 40 slots = [ @@ -13,7 +14,7 @@ Hull ] buildCost = 16.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 2 - tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" ] + tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" "AVERAGE_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_BASE" @@ -37,6 +38,8 @@ Hull accountinglabel = "ASTEROID_FIELD_STEALTH" effects = SetStealth value = Value + 40 + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/asteroid/SH_CRYSTALLIZED_ASTEROID.focs.txt b/default/scripting/ship_hulls/asteroid/SH_CRYSTALLIZED_ASTEROID.focs.txt index ebd2255f56e..521f8525037 100644 --- a/default/scripting/ship_hulls/asteroid/SH_CRYSTALLIZED_ASTEROID.focs.txt +++ b/default/scripting/ship_hulls/asteroid/SH_CRYSTALLIZED_ASTEROID.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_CRYSTALLIZED_ASTEROID" description = "SH_CRYSTALLIZED_ASTEROID_DESC" speed = 60 - fuel = 2 + fuel = 1 + NoDefaultFuelEffect stealth = 5 structure = 55 slots = [ @@ -15,7 +16,7 @@ Hull ] buildCost = 20.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 3 - tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" ] + tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" "AVERAGE_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_BASE" @@ -37,6 +38,8 @@ Hull ] ] effectsgroups = [ + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[ASTEROID_FIELD_STEALTH_BONUS]] [[REGULAR_HULL_DETECTION]] diff --git a/default/scripting/ship_hulls/asteroid/SH_HEAVY_ASTEROID.focs.txt b/default/scripting/ship_hulls/asteroid/SH_HEAVY_ASTEROID.focs.txt index 21caf59ae47..0d52585de68 100644 --- a/default/scripting/ship_hulls/asteroid/SH_HEAVY_ASTEROID.focs.txt +++ b/default/scripting/ship_hulls/asteroid/SH_HEAVY_ASTEROID.focs.txt @@ -2,23 +2,23 @@ Hull name = "SH_HEAVY_ASTEROID" description = "SH_HEAVY_ASTEROID_DESC" speed = 60 - fuel = 2 + fuel = 1.2 + NoDefaultFuelEffect stealth = 5 structure = 50 slots = [ - Slot type = External position = (0.30, 0.25) Slot type = External position = (0.45, 0.25) Slot type = External position = (0.60, 0.25) + Slot type = External position = (0.30, 0.50) Slot type = External position = (0.75, 0.50) Slot type = External position = (0.52, 0.75) Slot type = External position = (0.68, 0.75) - Slot type = Internal position = (0.30, 0.50) Slot type = Internal position = (0.45, 0.50) Slot type = Internal position = (0.60, 0.50) ] buildCost = 40.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 3 - tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" ] + tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" "BAD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_BASE" @@ -33,6 +33,8 @@ Hull ] ] effectsgroups = [ + [[BAD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[ASTEROID_FIELD_STEALTH_BONUS]] [[REGULAR_HULL_DETECTION]] diff --git a/default/scripting/ship_hulls/asteroid/SH_MINIASTEROID_SWARM.focs.txt b/default/scripting/ship_hulls/asteroid/SH_MINIASTEROID_SWARM.focs.txt index 33d002d55a5..3203b3e8d1a 100644 --- a/default/scripting/ship_hulls/asteroid/SH_MINIASTEROID_SWARM.focs.txt +++ b/default/scripting/ship_hulls/asteroid/SH_MINIASTEROID_SWARM.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_MINIASTEROID_SWARM_DESC" speed = 60 fuel = 4 + NoDefaultFuelEffect stealth = 35 structure = 11 slots = [ @@ -11,7 +12,7 @@ Hull ] buildCost = 10.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 3 - tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" ] + tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" "GOOD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_BASE" @@ -33,10 +34,11 @@ Hull ] ] effectsgroups = [ + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[ASTEROID_FIELD_STEALTH_BONUS]] EffectsGroup scope = Source - activation = Source effects = SetMaxShield value = Value + 5 [[AVERAGE_BASE_FUEL_REGEN]] diff --git a/default/scripting/ship_hulls/asteroid/SH_SCATTERED_ASTEROID.focs.txt b/default/scripting/ship_hulls/asteroid/SH_SCATTERED_ASTEROID.focs.txt index ce6977bf7ed..6641f471120 100644 --- a/default/scripting/ship_hulls/asteroid/SH_SCATTERED_ASTEROID.focs.txt +++ b/default/scripting/ship_hulls/asteroid/SH_SCATTERED_ASTEROID.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_SCATTERED_ASTEROID" description = "SH_SCATTERED_ASTEROID_DESC" speed = 60 - fuel = 2 + fuel = 1.2 + NoDefaultFuelEffect stealth = 5 structure = 140 slots = [ @@ -12,7 +13,7 @@ Hull Slot type = External position = (0.50, 0.15) Slot type = External position = (0.60, 0.15) Slot type = External position = (0.70, 0.15) - Slot type = External position = (0.80, 0.15) + //Slot type = External position = (0.80, 0.15) Slot type = External position = (0.85, 0.50) Slot type = External position = (0.20, 0.85) Slot type = External position = (0.30, 0.85) @@ -20,7 +21,7 @@ Hull Slot type = External position = (0.50, 0.85) Slot type = External position = (0.60, 0.85) Slot type = External position = (0.70, 0.85) - Slot type = External position = (0.80, 0.85) + //Slot type = External position = (0.80, 0.85) Slot type = Internal position = (0.20, 0.50) Slot type = Internal position = (0.30, 0.50) Slot type = Internal position = (0.60, 0.50) @@ -29,7 +30,7 @@ Hull ] buildCost = 160.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 8 - tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" ] + tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" "BAD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_BASE" @@ -69,10 +70,11 @@ Hull OwnedBy affiliation = AllyOf empire = Source.Owner ] ] - activation = Source stackinggroup = "FLAGSHIP_EFFECT_SCATTERED_ASTEROID" effects = SetMaxShield value = Value + 3 + [[BAD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/asteroid/SH_SMALL_ASTEROID.focs.txt b/default/scripting/ship_hulls/asteroid/SH_SMALL_ASTEROID.focs.txt index 2041adc6787..a1d5ec33867 100644 --- a/default/scripting/ship_hulls/asteroid/SH_SMALL_ASTEROID.focs.txt +++ b/default/scripting/ship_hulls/asteroid/SH_SMALL_ASTEROID.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_SMALL_ASTEROID_DESC" speed = 60 fuel = 2 + NoDefaultFuelEffect stealth = 5 structure = 15 slots = [ @@ -11,7 +12,7 @@ Hull ] buildCost = 6.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 2 - tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" ] + tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" "GOOD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_BASE" @@ -26,6 +27,8 @@ Hull ] ] effectsgroups = [ + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[ASTEROID_FIELD_STEALTH_BONUS]] [[REGULAR_HULL_DETECTION]] diff --git a/default/scripting/ship_hulls/asteroid/SH_SMALL_CAMOUFLAGE_ASTEROID.focs.txt b/default/scripting/ship_hulls/asteroid/SH_SMALL_CAMOUFLAGE_ASTEROID.focs.txt index ae8062881a7..3dfa6aa5684 100644 --- a/default/scripting/ship_hulls/asteroid/SH_SMALL_CAMOUFLAGE_ASTEROID.focs.txt +++ b/default/scripting/ship_hulls/asteroid/SH_SMALL_CAMOUFLAGE_ASTEROID.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_SMALL_CAMOUFLAGE_ASTEROID_DESC" speed = 60 fuel = 4 + NoDefaultFuelEffect stealth = 35 structure = 15 slots = [ @@ -11,7 +12,7 @@ Hull ] buildCost = 9.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 2 - tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" ] + tags = [ "ASTEROID_HULL" "PEDIA_HULL_LINE_ASTEROIDS" "GOOD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_BASE" @@ -26,6 +27,8 @@ Hull ] ] effectsgroups = [ + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[ASTEROID_FIELD_STEALTH_BONUS]] [[REGULAR_HULL_DETECTION]] diff --git a/default/scripting/ship_hulls/energy/SH_COMPRESSED_ENERGY.focs.txt b/default/scripting/ship_hulls/energy/SH_COMPRESSED_ENERGY.focs.txt index 3ccc2852822..660b491efc4 100644 --- a/default/scripting/ship_hulls/energy/SH_COMPRESSED_ENERGY.focs.txt +++ b/default/scripting/ship_hulls/energy/SH_COMPRESSED_ENERGY.focs.txt @@ -3,12 +3,13 @@ Hull description = "SH_COMPRESSED_ENERGY_DESC" speed = 120 fuel = 5 + NoDefaultFuelEffect stealth = 45 structure = 10 slots = Slot type = External position = (0.65, 0.40) buildCost = 5 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 2 - tags = [ "ENERGY_HULL" "PEDIA_HULL_LINE_ENERGY" ] + tags = [ "ENERGY_HULL" "PEDIA_HULL_LINE_ENERGY" "GOOD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ENRG_COMP" @@ -22,6 +23,8 @@ Hull Star type = [White Blue BlackHole] ] effectsgroups = [ + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/energy/SH_ENERGY_FRIGATE.focs.txt b/default/scripting/ship_hulls/energy/SH_ENERGY_FRIGATE.focs.txt index 33d3237385b..561b069ebab 100644 --- a/default/scripting/ship_hulls/energy/SH_ENERGY_FRIGATE.focs.txt +++ b/default/scripting/ship_hulls/energy/SH_ENERGY_FRIGATE.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_ENERGY_FRIGATE_DESC" speed = 120 fuel = 4 + NoDefaultFuelEffect stealth = 5 structure = 15 slots = [ @@ -14,7 +15,7 @@ Hull ] buildCost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 3 - tags = [ "PEDIA_HULL_LINE_ENERGY" ] + tags = [ "PEDIA_HULL_LINE_ENERGY" "GOOD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ENRG_COMP" @@ -28,6 +29,8 @@ Hull Star type = [White Blue BlackHole] ] effectsgroups = [ + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/energy/SH_FRACTAL_ENERGY.focs.txt b/default/scripting/ship_hulls/energy/SH_FRACTAL_ENERGY.focs.txt index 77797ca6474..b601e220a9e 100644 --- a/default/scripting/ship_hulls/energy/SH_FRACTAL_ENERGY.focs.txt +++ b/default/scripting/ship_hulls/energy/SH_FRACTAL_ENERGY.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_FRACTAL_ENERGY" description = "SH_FRACTAL_ENERGY_DESC" speed = 120 - fuel = 5 + fuel = 2.5 + NoDefaultFuelEffect stealth = -5 structure = 40 slots = [ @@ -23,7 +24,7 @@ Hull ] buildCost = 80.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 7 - tags = [ "ENERGY_HULL" "PEDIA_HULL_LINE_ENERGY" ] + tags = [ "ENERGY_HULL" "PEDIA_HULL_LINE_ENERGY" "AVERAGE_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ENRG_COMP" @@ -37,6 +38,8 @@ Hull Star type = [Blue BlackHole] ] effectsgroups = [ + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/energy/SH_QUANTUM_ENERGY.focs.txt b/default/scripting/ship_hulls/energy/SH_QUANTUM_ENERGY.focs.txt index cae5c16957c..320c2bc777c 100644 --- a/default/scripting/ship_hulls/energy/SH_QUANTUM_ENERGY.focs.txt +++ b/default/scripting/ship_hulls/energy/SH_QUANTUM_ENERGY.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_QUANTUM_ENERGY" description = "SH_QUANTUM_ENERGY_DESC" speed = 120 - fuel = 5 + fuel = 2.5 + NoDefaultFuelEffect stealth = -15 structure = 50 slots = [ @@ -19,7 +20,7 @@ Hull ] buildCost = 60 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 5 - tags = [ "ENERGY_HULL" "PEDIA_HULL_LINE_ENERGY" ] + tags = [ "ENERGY_HULL" "PEDIA_HULL_LINE_ENERGY" "AVERAGE_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ENRG_COMP" @@ -33,6 +34,8 @@ Hull Star type = [Blue BlackHole] ] effectsgroups = [ + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/energy/SH_SOLAR.focs.txt b/default/scripting/ship_hulls/energy/SH_SOLAR.focs.txt index b4ed95d78a3..298c901da01 100644 --- a/default/scripting/ship_hulls/energy/SH_SOLAR.focs.txt +++ b/default/scripting/ship_hulls/energy/SH_SOLAR.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_SOLAR" description = "SH_SOLAR_DESC" speed = 120 - fuel = 5 + fuel = 1.5 + NoDefaultFuelEffect stealth = -55 structure = 100 slots = [ @@ -36,7 +37,7 @@ Hull ] buildCost = 250.0 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime =8 - tags = [ "ENERGY_HULL" "PEDIA_HULL_LINE_ENERGY" ] + tags = [ "ENERGY_HULL" "PEDIA_HULL_LINE_ENERGY" "BAD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ENRG_COMP" @@ -69,10 +70,7 @@ Hull OwnedBy affiliation = AllyOf empire = Source.Owner ] ] - activation = And [ - Source - InSystem - ] + activation = InSystem effects = SetFuel value = Target.MaxFuel EffectsGroup @@ -84,10 +82,11 @@ Hull OwnedBy affiliation = AllyOf empire = Source.Owner ] ] - activation = Source stackinggroup = "FLAGSHIP_EFFECT_SOLAR" effects = SetStealth value = Value - 100 + [[BAD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/monster/SH_BLACK_KRAKEN_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_BLACK_KRAKEN_BODY.focs.txt index 2f539438698..72a6607e6a1 100644 --- a/default/scripting/ship_hulls/monster/SH_BLACK_KRAKEN_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_BLACK_KRAKEN_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_BLACK_KRAKEN_BODY_DESC" speed = 200 fuel = 100 + NoDefaultFuelEffect stealth = 25 structure = 4000 slots = [ @@ -22,6 +23,8 @@ Hull [[MONSTER_MOVE_ALWAYS]] [[EXCELLENT_VISION]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] ] icon = "" graphic = "icons/monsters/kraken-4.png" diff --git a/default/scripting/ship_hulls/monster/SH_BLOATED_JUGGERNAUT_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_BLOATED_JUGGERNAUT_BODY.focs.txt index e4cbb6b9d2c..da71b730935 100644 --- a/default/scripting/ship_hulls/monster/SH_BLOATED_JUGGERNAUT_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_BLOATED_JUGGERNAUT_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_BLOATED_JUGGERNAUT_BODY_DESC" speed = 160 fuel = 100 + NoDefaultFuelEffect stealth = 15 structure = 7000 slots = [ @@ -24,6 +25,8 @@ Hull [[MONSTER_MOVE_ALWAYS]] [[EXCELLENT_VISION]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] ] icon = "" graphic = "icons/monsters/juggernaut-4.png" diff --git a/default/scripting/ship_hulls/monster/SH_COSMIC_DRAGON_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_COSMIC_DRAGON_BODY.focs.txt index 29fd334d790..ff5f3d975cf 100644 --- a/default/scripting/ship_hulls/monster/SH_COSMIC_DRAGON_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_COSMIC_DRAGON_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_COSMIC_DRAGON_BODY_DESC" speed = 25 fuel = 100 + NoDefaultFuelEffect stealth = 5 structure = 50000 slots = [ @@ -20,6 +21,8 @@ Hull [[HUNT_PLANETS]] [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = NumberOf number = 1 condition = And [ diff --git a/default/scripting/ship_hulls/monster/SH_DAMPENING_CLOUD_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_DAMPENING_CLOUD_BODY.focs.txt index d22e6f896f5..3fdc676fcf1 100644 --- a/default/scripting/ship_hulls/monster/SH_DAMPENING_CLOUD_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_DAMPENING_CLOUD_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_DAMPENING_CLOUD_BODY_DESC" speed = 20 fuel = 1 + NoDefaultFuelEffect stealth = 45 structure = 5000 slots = [ @@ -15,6 +16,8 @@ Hull location = All effectsgroups = [ [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] [[MONSTER_MOVE_PRE]]0.10[[MONSTER_MOVE_POST_NOT_OWNED]] EffectsGroup scope = NumberOf number = 1 condition = And [ diff --git a/default/scripting/ship_hulls/monster/SH_DRONE_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_DRONE_BODY.focs.txt index d52a6ec91e7..6a29f22e6d8 100644 --- a/default/scripting/ship_hulls/monster/SH_DRONE_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_DRONE_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_DRONE_BODY_DESC" speed = 30 fuel = 3 + NoDefaultFuelEffect stealth = 5 structure = 10 slots = [ @@ -18,7 +19,9 @@ Hull effectsgroups = [ [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[MODERATE_VISION]] + [[MONSTER_SHIELD_REGENERATION]] ] icon = "" graphic = "icons/monsters/drone.png" diff --git a/default/scripting/ship_hulls/monster/SH_EXP_OUTPOST_HULL.focs.txt b/default/scripting/ship_hulls/monster/SH_EXP_OUTPOST_HULL.focs.txt index c8b63df1ebf..046f84e5930 100644 --- a/default/scripting/ship_hulls/monster/SH_EXP_OUTPOST_HULL.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_EXP_OUTPOST_HULL.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_EXP_OUTPOST_HULL_DESC" speed = 0 fuel = 1 + NoDefaultFuelEffect stealth = 55 structure = 1700 slots = [ @@ -21,7 +22,8 @@ Hull Not Planet type = [Asteroids GasGiant] InSystem id = Source.SystemID ] - activation = Turn high = 0 + activation = Turn high = 0 + priority = [[EARLY_POPULATION_OVERRIDE_PRIORITY]] effects = [ SetSpecies name = "SP_EXPERIMENTOR" SetPopulation value = Target.TargetPopulation diff --git a/default/scripting/ship_hulls/monster/SH_FLOATER.focs.txt b/default/scripting/ship_hulls/monster/SH_FLOATER.focs.txt index 0edd22b66f1..6ea72b3a1e2 100644 --- a/default/scripting/ship_hulls/monster/SH_FLOATER.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_FLOATER.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_FLOATER_BODY_DESC" speed = 20 fuel = 3 + NoDefaultFuelEffect stealth = 15 structure = 20 slots = [ @@ -18,7 +19,9 @@ Hull effectsgroups = [ [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[MODERATE_VISION]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup // in systems with no other monsters, spawn trees scope = And [ @@ -32,7 +35,7 @@ Hull ] ] activation = And [ - Turn low = Source.LastTurnActiveInBattle + 1 + Turn low = LocalCandidate.LastTurnActiveInBattle + 1 InSystem Random probability = 0.1 ] diff --git a/default/scripting/ship_hulls/monster/SH_GUARD_0_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_GUARD_0_BODY.focs.txt index 5617d550440..2b3e63c0674 100644 --- a/default/scripting/ship_hulls/monster/SH_GUARD_0_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_GUARD_0_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_GUARD_0_BODY_DESC" speed = 0 fuel = 0 + NoDefaultFuelEffect stealth = 5 structure = 25 slots = [ @@ -16,9 +17,11 @@ Hull tags = [ "UNOWNED_FRIENDLY" "PEDIA_HULL_MONSTER_GUARD" ] location = All effectsgroups = [ + [[MONSTER_SHIELD_REGENERATION]] [[UNOWNED_OWNED_VISION_BONUS(WEAK,10,10)]] ] icon = "" graphic = "icons/monsters/maintenance_ship.png" #include "../ship_hulls.macros" +#include "monster.macros" \ No newline at end of file diff --git a/default/scripting/ship_hulls/monster/SH_GUARD_1_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_GUARD_1_BODY.focs.txt index 8e8eb635951..8f0b834c24f 100644 --- a/default/scripting/ship_hulls/monster/SH_GUARD_1_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_GUARD_1_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_GUARD_1_BODY_DESC" speed = 0 fuel = 0 + NoDefaultFuelEffect stealth = 5 structure = 35 slots = [ @@ -16,9 +17,11 @@ Hull tags = [ "UNOWNED_FRIENDLY" "PEDIA_HULL_MONSTER_GUARD" ] location = All effectsgroups = [ + [[MONSTER_SHIELD_REGENERATION]] [[UNOWNED_OWNED_VISION_BONUS(POOR,20,20)]] ] icon = "" graphic = "icons/monsters/sentry.png" #include "../ship_hulls.macros" +#include "monster.macros" \ No newline at end of file diff --git a/default/scripting/ship_hulls/monster/SH_GUARD_3_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_GUARD_3_BODY.focs.txt index 1798a482bc7..4406a21e66a 100644 --- a/default/scripting/ship_hulls/monster/SH_GUARD_3_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_GUARD_3_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_GUARD_3_BODY_DESC" speed = 0 fuel = 0 + NoDefaultFuelEffect stealth = 5 structure = 800 slots = [ @@ -21,10 +22,10 @@ Hull tags = [ "UNOWNED_FRIENDLY" "PEDIA_HULL_MONSTER_GUARD" ] location = All effectsgroups = [ + [[MONSTER_SHIELD_REGENERATION]] [[UNOWNED_OWNED_VISION_BONUS(GOOD,50,50)]] EffectsGroup scope = Source - activation = Source effects = [ SetMaxDamage partname = "SR_WEAPON_0_1" value = Value + 3 SetMaxDamage partname = "SR_WEAPON_4_1" value = Value + 15 @@ -36,3 +37,4 @@ Hull graphic = "icons/monsters/warden.png" #include "../ship_hulls.macros" +#include "monster.macros" diff --git a/default/scripting/ship_hulls/monster/SH_GUARD_MONSTER_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_GUARD_MONSTER_BODY.focs.txt index 8fbd79d2e72..e1060ed8add 100644 --- a/default/scripting/ship_hulls/monster/SH_GUARD_MONSTER_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_GUARD_MONSTER_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_GUARD_MONSTER_BODY_DESC" speed = 0 fuel = 0 + NoDefaultFuelEffect stealth = 5 structure = 35 slots = [ @@ -18,10 +19,10 @@ Hull tags = [ "UNOWNED_FRIENDLY" "PEDIA_HULL_MONSTER_GUARD" ] location = All effectsgroups = [ + [[MONSTER_SHIELD_REGENERATION]] [[UNOWNED_OWNED_VISION_BONUS(MODERATE,40,30)]] EffectsGroup scope = Source - activation = Source effects = [ SetMaxDamage partname = "SR_WEAPON_0_1" value = Value + 1 SetMaxDamage partname = "SR_WEAPON_4_1" value = Value + 5 diff --git a/default/scripting/ship_hulls/monster/SH_IMMOBILE_FACTOR.focs.txt b/default/scripting/ship_hulls/monster/SH_IMMOBILE_FACTOR.focs.txt index 20d904c477c..f2a51683448 100644 --- a/default/scripting/ship_hulls/monster/SH_IMMOBILE_FACTOR.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_IMMOBILE_FACTOR.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_IMMOBILE_FACTORY_DESC" speed = 0 fuel = 0 + NoDefaultFuelEffect stealth = 5 structure = 1000 slots = [ @@ -16,6 +17,7 @@ Hull tags = [ "PEDIA_HULL_MONSTER_GUARD" ] location = All effectsgroups = [ + [[MONSTER_SHIELD_REGENERATION]] [[UNOWNED_OWNED_VISION_BONUS(WEAK,10,10)]] EffectsGroup scope = Source diff --git a/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_1_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_1_BODY.focs.txt index 0a554d63302..d6fecd1082d 100644 --- a/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_1_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_1_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_JUGGERNAUT_1_BODY_DESC" speed = 30 fuel = 3 + NoDefaultFuelEffect stealth = 5 structure = 30 slots = [ @@ -43,6 +44,8 @@ Hull [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = And [ @@ -53,8 +56,8 @@ Hull ] ] activation = And [ - Turn low = max(30, Source.LastTurnActiveInBattle + 1) - Random probability = Source.Age*0.01 - 0.1 + Turn low = max(30, LocalCandidate.LastTurnActiveInBattle + 1) + Random probability = LocalCandidate.Age*0.01 - 0.1 ] effects = [ CreateShip designname = "SM_JUGGERNAUT_2" empire = Source.Owner diff --git a/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_2_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_2_BODY.focs.txt index 20d0be3dafc..ca724ef7540 100644 --- a/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_2_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_2_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_JUGGERNAUT_2_BODY_DESC" speed = 25 fuel = 2 + NoDefaultFuelEffect stealth = 5 structure = 200 slots = [ @@ -19,6 +20,8 @@ Hull effectsgroups = [ [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = And [ @@ -48,7 +51,6 @@ Hull EffectsGroup scope = Source - activation = Source effects = [ SetMaxCapacity partname = "SR_JAWS" value = Value + min(Source.Age*0.1, 8) SetCapacity partname = "SR_JAWS" value = Value + min(Source.Age*0.1, 8) diff --git a/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_3_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_3_BODY.focs.txt index d0c552ffcae..2515b971758 100644 --- a/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_3_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_JUGGERNAUT_3_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_JUGGERNAUT_3_BODY_DESC" speed = 25 fuel = 5 + NoDefaultFuelEffect stealth = 5 structure = 1000 slots = [ @@ -19,9 +20,10 @@ Hull effectsgroups = [ [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = Source - activation = Source effects = [ SetMaxCapacity partname = "SR_JAWS" value = Value + min(Source.Age*0.2, 8) SetCapacity partname = "SR_JAWS" value = Value + min(Source.Age*0.2, 8) diff --git a/default/scripting/ship_hulls/monster/SH_KRAKEN_1_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_KRAKEN_1_BODY.focs.txt index 6f305f8fb41..36b95326c83 100644 --- a/default/scripting/ship_hulls/monster/SH_KRAKEN_1_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_KRAKEN_1_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_KRAKEN_1_BODY_DESC" speed = 40 fuel = 4 + NoDefaultFuelEffect stealth = 5 structure = 20 slots = [ @@ -41,6 +42,8 @@ Hull [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = And [ @@ -51,9 +54,9 @@ Hull ] ] activation = And [ - Turn low = max(30, Source.LastTurnActiveInBattle + 1) + Turn low = max(30, LocalCandidate.LastTurnActiveInBattle + 1) //triple the chance of maturing when in a system with Krill - Random probability = (Source.Age*0.01 - 0.1) * 3^(Statistic If condition = ContainedBy Contains OR [ + Random probability = (LocalCandidate.Age*0.01 - 0.1) * 3^(Statistic If condition = ContainedBy Contains OR [ Design name = "SM_KRILL_1" Design name = "SM_KRILL_2" Design name = "SM_KRILL_3" diff --git a/default/scripting/ship_hulls/monster/SH_KRAKEN_2_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_KRAKEN_2_BODY.focs.txt index b441ded944f..37893540b18 100644 --- a/default/scripting/ship_hulls/monster/SH_KRAKEN_2_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_KRAKEN_2_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_KRAKEN_2_BODY_DESC" speed = 45 fuel = 4 + NoDefaultFuelEffect stealth = 5 structure = 100 slots = [ @@ -19,6 +20,8 @@ Hull effectsgroups = [ [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = And [ @@ -29,9 +32,9 @@ Hull ] ] activation = And [ - Turn low = max(60, Source.LastTurnActiveInBattle + 1) + Turn low = max(60, LocalCandidate.LastTurnActiveInBattle + 1) //triple the chance of maturing when in a system with Krill - Random probability = (Source.Age*0.01 - 0.1) * 3^(Statistic If condition = ContainedBy Contains OR [ + Random probability = (LocalCandidate.Age*0.01 - 0.1) * 3^(Statistic If condition = ContainedBy Contains OR [ Design name = "SM_KRILL_1" Design name = "SM_KRILL_2" Design name = "SM_KRILL_3" @@ -54,7 +57,6 @@ Hull EffectsGroup scope = Source - activation = Source effects = [ SetMaxCapacity partname = "SR_TENTACLE" value = Value + min(Source.Age*0.15, 4) SetCapacity partname = "SR_TENTACLE" value = Value + min(Source.Age*0.15, 4) diff --git a/default/scripting/ship_hulls/monster/SH_KRAKEN_3_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_KRAKEN_3_BODY.focs.txt index ac30a4d14f2..d0f9c684d1e 100644 --- a/default/scripting/ship_hulls/monster/SH_KRAKEN_3_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_KRAKEN_3_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_KRAKEN_3_BODY_DESC" speed = 50 fuel = 4 + NoDefaultFuelEffect stealth = 5 structure = 1000 slots = [ @@ -19,9 +20,10 @@ Hull effectsgroups = [ [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = Source - activation = Source effects = [ SetMaxCapacity partname = "SR_TENTACLE" value = Value + min(Source.Age*0.3, 15) SetCapacity partname = "SR_TENTACLE" value = Value + min(Source.Age*0.3, 15) diff --git a/default/scripting/ship_hulls/monster/SH_KRILL_1_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_KRILL_1_BODY.focs.txt index 2fe72082313..af6c2c5889f 100644 --- a/default/scripting/ship_hulls/monster/SH_KRILL_1_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_KRILL_1_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_KRILL_1_BODY_DESC" speed = 30 fuel = 5 + NoDefaultFuelEffect stealth = 15 structure = 20 slots = [ @@ -26,7 +27,7 @@ Hull ] ] activation = And [ - Turn low = max(30, Source.LastTurnActiveInBattle + 1) + Turn low = max(30, LocalCandidate.LastTurnActiveInBattle + 1) InSystem ] stackinggroup = "KRILL_1_ACTION_STACK" @@ -46,7 +47,7 @@ Hull ] ] activation = And [ - Turn low = max(30, Source.LastTurnActiveInBattle + 1) + Turn low = max(30, LocalCandidate.LastTurnActiveInBattle + 1) InSystem ] effects = Destroy @@ -60,7 +61,7 @@ Hull ] ] activation = And [ - Turn low = max(30, Source.LastTurnActiveInBattle + 1) + Turn low = max(30, LocalCandidate.LastTurnActiveInBattle + 1) Random probability = 0.1 InSystem ] @@ -81,7 +82,9 @@ Hull [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[WEAK_VISION]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = And [ diff --git a/default/scripting/ship_hulls/monster/SH_KRILL_2_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_KRILL_2_BODY.focs.txt index 69c51387442..2f7f9774a49 100644 --- a/default/scripting/ship_hulls/monster/SH_KRILL_2_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_KRILL_2_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_KRILL_2_BODY_DESC" speed = 30 fuel = 6 + NoDefaultFuelEffect stealth = 15 structure = 40 slots = [ @@ -26,7 +27,7 @@ Hull ] ] activation = And [ - Turn low = max(60, Source.LastTurnActiveInBattle + 1) + Turn low = max(60, LocalCandidate.LastTurnActiveInBattle + 1) InSystem ] stackinggroup = "KRILL_2_ACTION_STACK" @@ -56,7 +57,7 @@ Hull ] ] activation = And [ - Turn low = max(60, Source.LastTurnActiveInBattle + 1) + Turn low = max(60, LocalCandidate.LastTurnActiveInBattle + 1) InSystem ] effects = Destroy @@ -70,7 +71,7 @@ Hull ] ] activation = And [ - Turn low = max(60, Source.LastTurnActiveInBattle + 1) + Turn low = max(60, LocalCandidate.LastTurnActiveInBattle + 1) Random probability = 0.15 InSystem ] @@ -90,7 +91,9 @@ Hull [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[MODERATE_VISION]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = And [ diff --git a/default/scripting/ship_hulls/monster/SH_KRILL_3_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_KRILL_3_BODY.focs.txt index 10620885c87..15290e92f02 100644 --- a/default/scripting/ship_hulls/monster/SH_KRILL_3_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_KRILL_3_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_KRILL_3_BODY_DESC" speed = 25 fuel = 5 + NoDefaultFuelEffect stealth = 15 structure = 250 slots = [ @@ -28,7 +29,7 @@ Hull ] ] activation = And [ - Turn low = max(90, Source.LastTurnActiveInBattle + 1) + Turn low = max(90, LocalCandidate.LastTurnActiveInBattle + 1) InSystem ] stackinggroup = "KRILL_3_ACTION_STACK" @@ -58,7 +59,7 @@ Hull ] ] activation = And [ - Turn low = max(90, Source.LastTurnActiveInBattle + 1) + Turn low = max(90, LocalCandidate.LastTurnActiveInBattle + 1) InSystem ] effects = Destroy @@ -72,7 +73,7 @@ Hull ] ] activation = And [ - Turn low = max(90, Source.LastTurnActiveInBattle + 1) + Turn low = max(90, LocalCandidate.LastTurnActiveInBattle + 1) Random probability = 0.08 InSystem ] @@ -92,7 +93,9 @@ Hull [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[GOOD_VISION]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = And [ diff --git a/default/scripting/ship_hulls/monster/SH_KRILL_4_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_KRILL_4_BODY.focs.txt index a448fe9c5a9..ff851ec78e9 100644 --- a/default/scripting/ship_hulls/monster/SH_KRILL_4_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_KRILL_4_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_KRILL_4_BODY_DESC" speed = 20 fuel = 4 + NoDefaultFuelEffect stealth = 15 structure = 1000 slots = [ @@ -43,6 +44,8 @@ Hull [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] [[GOOD_VISION]] ] icon = "" diff --git a/default/scripting/ship_hulls/monster/SH_NEBULOUS.focs.txt b/default/scripting/ship_hulls/monster/SH_NEBULOUS.focs.txt index 97ff0857823..a08aff7d8d3 100644 --- a/default/scripting/ship_hulls/monster/SH_NEBULOUS.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_NEBULOUS.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_NEBULOUS_BODY" speed = 100 fuel = 1 + NoDefaultFuelEffect stealth = 75 structure = 5000 slots = [ @@ -16,6 +17,8 @@ Hull location = All effectsgroups = [ [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] [[MONSTER_MOVE_PRE]]1[[MONSTER_MOVE_POST]] ] diff --git a/default/scripting/ship_hulls/monster/SH_PSIONIC_SNOWFLAKE_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_PSIONIC_SNOWFLAKE_BODY.focs.txt index f87453b75fc..745a1223f00 100644 --- a/default/scripting/ship_hulls/monster/SH_PSIONIC_SNOWFLAKE_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_PSIONIC_SNOWFLAKE_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_PSIONIC_SNOWFLAKE_BODY_DESC" speed = 300 fuel = 100 + NoDefaultFuelEffect stealth = 25 structure = 1800 slots = [ @@ -20,12 +21,15 @@ Hull [[HUNT_SHIPS]] [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] [[EXCELLENT_VISION]] EffectsGroup scope = NumberOf number = 4 condition = And [ Ship WithinStarlaneJumps jumps = 1 condition = Source + /// @content_tag{ORGANIC} Can lose ownership of ships with tag to ships with this hull within 1 starlane jump HasTag name = "ORGANIC" ] activation = Random probability = 0.2 @@ -36,6 +40,7 @@ Hull Ship WithinStarlaneJumps jumps = 1 condition = Source HasTag name = "ORGANIC" + /// @content_tag{TELEPATHIC} Prevents increased chance of ownership loss of ships with ORGANIC tag to ships with this hull Not HasTag name = "TELEPATHIC" Not OwnerHasTech name = "LRN_PSY_DOM" ] diff --git a/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_1_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_1_BODY.focs.txt index 3d04b7398a6..b44d3818101 100644 --- a/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_1_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_1_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_SNOWFLAKE_1_BODY_DESC" speed = 55 fuel = 5 + NoDefaultFuelEffect stealth = 15 structure = 25 slots = [ @@ -44,6 +45,8 @@ Hull [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = And [ @@ -54,7 +57,7 @@ Hull ] ] activation = And [ - Turn low = max(30, Source.LastTurnActiveInBattle + 1) + Turn low = max(30, LocalCandidate.LastTurnActiveInBattle + 1) Random probability = Source.Age*0.01 - 0.1 ] accountinglabel = "SNOWFLAKE_GROWTH_LABEL" diff --git a/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_2_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_2_BODY.focs.txt index 9af1c641d81..295a4181048 100644 --- a/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_2_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_2_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_SNOWFLAKE_2_BODY_DESC" speed = 60 fuel = 5 + NoDefaultFuelEffect stealth = 5 structure = 60 slots = [ @@ -19,6 +20,8 @@ Hull effectsgroups = [ [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = And [ @@ -29,7 +32,7 @@ Hull ] ] activation = And [ - Turn low = max(60, Source.LastTurnActiveInBattle + 1) + Turn low = max(60, LocalCandidate.LastTurnActiveInBattle + 1) Random probability = Source.Age*0.01 - 0.1 ] effects = [ @@ -48,7 +51,6 @@ Hull EffectsGroup scope = Source - activation = Source effects = [ SetMaxCapacity partname = "SR_ICE_BEAM" value = Value + min(Source.Age*0.15, 4) SetCapacity partname = "SR_ICE_BEAM" value = Value + min(Source.Age*0.15, 4) diff --git a/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_3_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_3_BODY.focs.txt index 1adc6ae7881..0e3bdcc208c 100644 --- a/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_3_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_SNOWFLAKE_3_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_SNOWFLAKE_3_BODY_DESC" speed = 65 fuel = 5 + NoDefaultFuelEffect stealth = 5 structure = 120 slots = [ @@ -19,9 +20,10 @@ Hull effectsgroups = [ [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup scope = Source - activation = Source effects = [ SetMaxCapacity partname = "SR_ICE_BEAM" value = Value + min(Source.Age*0.3, 18) SetCapacity partname = "SR_ICE_BEAM" value = Value + min(Source.Age*0.3, 18) diff --git a/default/scripting/ship_hulls/monster/SH_STRONG_MONSTER_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_STRONG_MONSTER_BODY.focs.txt index 9e973fb5299..dd5e0c70926 100644 --- a/default/scripting/ship_hulls/monster/SH_STRONG_MONSTER_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_STRONG_MONSTER_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_STRONG_MONSTER_BODY_DESC" speed = 30 fuel = 5 + NoDefaultFuelEffect stealth = 5 structure = 80 slots = [ @@ -20,7 +21,9 @@ Hull effectsgroups = [ [[MONSTER_MOVE_ALWAYS]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[GOOD_VISION]] + [[MONSTER_SHIELD_REGENERATION]] ] icon = "" graphic = "icons/monsters/dragon-1.png" diff --git a/default/scripting/ship_hulls/monster/SH_TREE_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_TREE_BODY.focs.txt index 39317fa0eae..d55af2b2366 100644 --- a/default/scripting/ship_hulls/monster/SH_TREE_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_TREE_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_TREE_BODY_DESC" speed = 0 fuel = 0 + NoDefaultFuelEffect stealth = 5 structure = 5 slots = Slot type = External position = (0.30, 0.15) @@ -13,6 +14,7 @@ Hull location = All effectsgroups = [ [[MODERATE_VISION]] + [[MONSTER_SHIELD_REGENERATION]] EffectsGroup // remove self and recreate on first turn, so that trees start with age 0, and thus low initial health, instead of having thousands of structure at start of game scope = Source diff --git a/default/scripting/ship_hulls/monster/SH_WHITE_KRAKEN_BODY.focs.txt b/default/scripting/ship_hulls/monster/SH_WHITE_KRAKEN_BODY.focs.txt index 6324252ce48..c184914b3ed 100644 --- a/default/scripting/ship_hulls/monster/SH_WHITE_KRAKEN_BODY.focs.txt +++ b/default/scripting/ship_hulls/monster/SH_WHITE_KRAKEN_BODY.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_WHITE_KRAKEN_BODY_DESC" speed = 55 fuel = 10 + NoDefaultFuelEffect stealth = 25 structure = 530 slots = [ @@ -20,6 +21,8 @@ Hull effectsgroups = [ [[GOOD_VISION]] [[INFINITE_FUEL]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[MONSTER_SHIELD_REGENERATION]] ] icon = "" graphic = "icons/monsters/kraken-5.png" diff --git a/default/scripting/ship_hulls/monster/monster.macros b/default/scripting/ship_hulls/monster/monster.macros index a929df06357..e7f4f2c9631 100644 --- a/default/scripting/ship_hulls/monster/monster.macros +++ b/default/scripting/ship_hulls/monster/monster.macros @@ -1,3 +1,12 @@ +MONSTER_SHIELD_REGENERATION +'''EffectsGroup // Increase shields to max when not in battle + scope = Source + activation = (Source.LastTurnActiveInBattle < CurrentTurn) + stackinggroup = "SHIELD_REGENERATION_EFFECT" + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetShield value = Target.MaxShield +''' + MONSTER_MOVE_ALWAYS '''EffectsGroup scope = And [ diff --git a/default/scripting/ship_hulls/organic/SH_BIOADAPTIVE.focs.txt b/default/scripting/ship_hulls/organic/SH_BIOADAPTIVE.focs.txt index e89cbeae30a..1fe81c3b243 100644 --- a/default/scripting/ship_hulls/organic/SH_BIOADAPTIVE.focs.txt +++ b/default/scripting/ship_hulls/organic/SH_BIOADAPTIVE.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_BIOADAPTIVE" description = "SH_BIOADAPTIVE_DESC" speed = 100 - fuel = 2 + fuel = 1 + NoDefaultFuelEffect stealth = 35 structure = 15 slots = [ @@ -15,7 +16,7 @@ Hull ] buildCost = 21 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 6 - tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" ] + tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" "AVERAGE_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ORG_CELL_GRO_CHAMB" @@ -41,15 +42,15 @@ Hull EffectsGroup scope = Source - activation = Source effects = SetDetection value = Value + 75 EffectsGroup scope = Source - activation = Source accountinglabel = "ORGANIC_GROWTH" effects = SetMaxStructure value = Value + min(Source.Age*0.5, 25) + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[LIVING_HULL_BASE_FUEL_REGEN]] [[SCAVANGE_FUEL_UNOWNED]] [[UNOWNED_GOOD_VISION]] diff --git a/default/scripting/ship_hulls/organic/SH_ENDOMORPHIC.focs.txt b/default/scripting/ship_hulls/organic/SH_ENDOMORPHIC.focs.txt index 9df5786253d..43ea690ea1d 100644 --- a/default/scripting/ship_hulls/organic/SH_ENDOMORPHIC.focs.txt +++ b/default/scripting/ship_hulls/organic/SH_ENDOMORPHIC.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_ENDOMORPHIC_DESC" speed = 100 fuel = 2 + NoDefaultFuelEffect stealth = 5 structure = 5 slots = [ @@ -15,7 +16,7 @@ Hull ] buildCost = 23 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 5 - tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" ] + tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" "GOOD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ORG_XENO_FAC" @@ -34,15 +35,15 @@ Hull effectsgroups = [ EffectsGroup scope = Source - activation = Source effects = SetDetection value = Value + 50 EffectsGroup scope = Source - activation = Source accountinglabel = "ORGANIC_GROWTH" effects = SetMaxStructure value = Value + min(Source.Age*0.5, 15) + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[SCAVANGE_FUEL_UNOWNED]] [[UNOWNED_GOOD_VISION]] diff --git a/default/scripting/ship_hulls/organic/SH_ENDOSYMBIOTIC.focs.txt b/default/scripting/ship_hulls/organic/SH_ENDOSYMBIOTIC.focs.txt index a2d60060f25..379372b95e3 100644 --- a/default/scripting/ship_hulls/organic/SH_ENDOSYMBIOTIC.focs.txt +++ b/default/scripting/ship_hulls/organic/SH_ENDOSYMBIOTIC.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_ENDOSYMBIOTIC_DESC" speed = 100 fuel = 2 + NoDefaultFuelEffect stealth = 25 structure = 5 slots = [ @@ -16,7 +17,7 @@ Hull ] buildCost = 25 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 6 - tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" ] + tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" "GOOD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ORG_CELL_GRO_CHAMB" @@ -39,10 +40,11 @@ Hull effectsgroups = [ EffectsGroup scope = Source - activation = Source accountinglabel = "ORGANIC_GROWTH" effects = SetMaxStructure value = Value + min(Source.Age*0.5, 15) + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[LIVING_HULL_EFFECTS_GROUPS]] [[SCAVANGE_FUEL_UNOWNED]] [[UNOWNED_GOOD_VISION]] diff --git a/default/scripting/ship_hulls/organic/SH_ORGANIC.focs.txt b/default/scripting/ship_hulls/organic/SH_ORGANIC.focs.txt index 639461714d6..c59ec3a99af 100644 --- a/default/scripting/ship_hulls/organic/SH_ORGANIC.focs.txt +++ b/default/scripting/ship_hulls/organic/SH_ORGANIC.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_ORGANIC_DESC" speed = 90 fuel = 2 + NoDefaultFuelEffect stealth = 5 structure = 5 slots = [ @@ -13,7 +14,7 @@ Hull ] buildCost = 14 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 3 - tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" ] + tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" "GOOD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ORG_ORB_INC" @@ -28,7 +29,6 @@ Hull effectsgroups = [ EffectsGroup scope = Source - activation = Source effects = [ SetStructure value = Value + 2 SetDetection value = Value + 10 @@ -36,10 +36,11 @@ Hull EffectsGroup scope = Source - activation = Source accountinglabel = "ORGANIC_GROWTH" effects = SetMaxStructure value = Value + min(Source.Age*0.2, 5) + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[LIVING_HULL_BASE_FUEL_REGEN]] [[SCAVANGE_FUEL_UNOWNED]] [[UNOWNED_GOOD_VISION]] diff --git a/default/scripting/ship_hulls/organic/SH_PROTOPLASMIC.focs.txt b/default/scripting/ship_hulls/organic/SH_PROTOPLASMIC.focs.txt index c7f86e6c171..c537adfe404 100644 --- a/default/scripting/ship_hulls/organic/SH_PROTOPLASMIC.focs.txt +++ b/default/scripting/ship_hulls/organic/SH_PROTOPLASMIC.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_PROTOPLASMIC_DESC" speed = 100 fuel = 2 + NoDefaultFuelEffect stealth = 25 structure = 5 slots = [ @@ -14,7 +15,7 @@ Hull ] buildCost = 15 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 5 - tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" ] + tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" "GOOD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ORG_CELL_GRO_CHAMB" @@ -33,10 +34,11 @@ Hull effectsgroups = [ EffectsGroup scope = Source - activation = Source accountinglabel = "ORGANIC_GROWTH" effects = SetMaxStructure value = Value + min(Source.Age*0.5, 25) + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[LIVING_HULL_EFFECTS_GROUPS]] [[SCAVANGE_FUEL_UNOWNED]] [[UNOWNED_GOOD_VISION]] diff --git a/default/scripting/ship_hulls/organic/SH_RAVENOUS.focs.txt b/default/scripting/ship_hulls/organic/SH_RAVENOUS.focs.txt index 5db8c618a62..28a0499207c 100644 --- a/default/scripting/ship_hulls/organic/SH_RAVENOUS.focs.txt +++ b/default/scripting/ship_hulls/organic/SH_RAVENOUS.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_RAVENOUS" description = "SH_RAVENOUS_DESC" speed = 100 - fuel = 2 + fuel = 1 + NoDefaultFuelEffect stealth = 5 structure = 5 slots = [ @@ -16,7 +17,7 @@ Hull ] buildCost = 25 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 6 - tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" ] + tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" "AVERAGE_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ORG_XENO_FAC" @@ -35,15 +36,15 @@ Hull effectsgroups = [ EffectsGroup scope = Source - activation = Source effects = SetDetection value = Value + 75 EffectsGroup scope = Source - activation = Source accountinglabel = "ORGANIC_GROWTH" effects = SetMaxStructure value = Value + min(Source.Age*0.5, 20) + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[SCAVANGE_FUEL_UNOWNED]] [[UNOWNED_GOOD_VISION]] diff --git a/default/scripting/ship_hulls/organic/SH_SENTIENT.focs.txt b/default/scripting/ship_hulls/organic/SH_SENTIENT.focs.txt index e9a3c10cdf2..b8d59592517 100644 --- a/default/scripting/ship_hulls/organic/SH_SENTIENT.focs.txt +++ b/default/scripting/ship_hulls/organic/SH_SENTIENT.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_SENTIENT" description = "SH_SENTIENT_DESC" speed = 100 - fuel = 2 + fuel = 1 + NoDefaultFuelEffect stealth = 25 structure = 12 slots = [ @@ -19,7 +20,7 @@ Hull ] buildCost = 60 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 8 - tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" ] + tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" "AVERAGE_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_ORG_XENO_FAC" @@ -49,7 +50,6 @@ Hull OwnedBy affiliation = AllyOf empire = Source.Owner ] ] - activation = Source stackinggroup = "FLAGSHIP_EFFECT_SENTIENT" effects = [ SetStealth value = Value + 20 @@ -58,10 +58,11 @@ Hull EffectsGroup scope = Source - activation = Source accountinglabel = "ORGANIC_GROWTH" effects = SetMaxStructure value = Value + min(Source.Age*1, 45) + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[LIVING_HULL_EFFECTS_GROUPS]] [[SCAVANGE_FUEL_UNOWNED]] [[UNOWNED_GOOD_VISION]] diff --git a/default/scripting/ship_hulls/organic/SH_STATIC_MULTICELLULAR.focs.txt b/default/scripting/ship_hulls/organic/SH_STATIC_MULTICELLULAR.focs.txt index 7d0067e28d9..7b020817755 100644 --- a/default/scripting/ship_hulls/organic/SH_STATIC_MULTICELLULAR.focs.txt +++ b/default/scripting/ship_hulls/organic/SH_STATIC_MULTICELLULAR.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_STATIC_MULTICELLULAR_DESC" speed = 100 fuel = 2 + NoDefaultFuelEffect stealth = 5 structure = 16 slots = [ @@ -14,7 +15,7 @@ Hull ] buildCost = 18 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 4 - tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" ] + tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" "GOOD_FUEL_EFFICIENCY" ] location = And [ // Contains And [ // Building name = "BLD_SHIPYARD_ORG_XENO_FAC" @@ -31,6 +32,8 @@ Hull OwnedBy empire = Source.Owner ] effectsgroups = [ + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/organic/SH_SYMBIOTIC.focs.txt b/default/scripting/ship_hulls/organic/SH_SYMBIOTIC.focs.txt index 6c2052d5c7c..d54097d41e3 100644 --- a/default/scripting/ship_hulls/organic/SH_SYMBIOTIC.focs.txt +++ b/default/scripting/ship_hulls/organic/SH_SYMBIOTIC.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_SYMBIOTIC_DESC" speed = 100 fuel = 2 + NoDefaultFuelEffect stealth = 15 structure = 10 slots = [ @@ -13,7 +14,7 @@ Hull ] buildCost = 12 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 4 - tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" ] + tags = [ "ORGANIC_HULL" "PEDIA_HULL_LINE_ORGANIC" "GOOD_FUEL_EFFICIENCY" ] location = And [ // Contains And [ // Building name = "BLD_SHIPYARD_ORG_CELL_GRO_CHAMB" @@ -32,10 +33,11 @@ Hull effectsgroups = [ EffectsGroup scope = Source - activation = Source accountinglabel = "ORGANIC_GROWTH" effects = SetMaxStructure value = Value + min(Source.Age*0.2, 10) + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[LIVING_HULL_EFFECTS_GROUPS]] [[SCAVANGE_FUEL_UNOWNED]] [[UNOWNED_GOOD_VISION]] diff --git a/default/scripting/ship_hulls/organic/organic.macros b/default/scripting/ship_hulls/organic/organic.macros index 729b96fbc57..0217d1a66be 100644 --- a/default/scripting/ship_hulls/organic/organic.macros +++ b/default/scripting/ship_hulls/organic/organic.macros @@ -2,7 +2,6 @@ LIVING_HULL_EFFECTS_GROUPS '''EffectsGroup scope = Source - activation = Source effects = [ SetStructure value = Value + 2 SetDetection value = Value + 50 @@ -16,7 +15,7 @@ LIVING_HULL_BASE_FUEL_REGEN description = "LIVING_HULL_BASE_FUEL_REGEN_DESC" scope = Source activation = And [ - Turn low = Source.ArrivedOnTurn + 1 + Turn low = LocalCandidate.ArrivedOnTurn + 1 Stationary (Source.Fuel < Source.MaxFuel) ] @@ -24,6 +23,8 @@ LIVING_HULL_BASE_FUEL_REGEN accountinglabel = "BASE_FUEL_REGEN_LABEL" priority = [[LATE_PRIORITY]] effects = SetFuel value = min(Target.MaxFuel, Value + 0.3001) + + [[REFUEL_MESSAGE(0.3001)]] ''' #include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_hulls/robotic/SH_LOGITICS_FACILITATOR.focs.txt b/default/scripting/ship_hulls/robotic/SH_LOGITICS_FACILITATOR.focs.txt index 17c6e3f90b3..07b33d8f589 100644 --- a/default/scripting/ship_hulls/robotic/SH_LOGITICS_FACILITATOR.focs.txt +++ b/default/scripting/ship_hulls/robotic/SH_LOGITICS_FACILITATOR.focs.txt @@ -2,13 +2,13 @@ Hull name = "SH_LOGISTICS_FACILITATOR" description = "SH_LOGISTICS_FACILITATOR_DESC" speed = 80 - fuel = 3 + fuel = 1.2 + NoDefaultFuelEffect stealth = 5 structure = 110 slots = [ Slot type = External position = (0.10, 0.15) - Slot type = External position = (0.22, 0.15) - Slot type = External position = (0.34, 0.15) + Slot type = External position = (0.28, 0.15) Slot type = External position = (0.46, 0.15) Slot type = External position = (0.58, 0.15) Slot type = External position = (0.70, 0.15) @@ -19,7 +19,7 @@ Hull ] buildCost = 100 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 8 - tags = [ "ROBOTIC_HULL" "PEDIA_HULL_LINE_ROBOTIC" ] + tags = [ "ROBOTIC_HULL" "PEDIA_HULL_LINE_ROBOTIC" "BAD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_CON_ADV_ENGINE" @@ -83,6 +83,8 @@ Hull priority = [[VERY_LATE_PRIORITY]] effects = SetStructure value = max((Target.Structure + ( Target.Structure/10 )), (Target.Structure + 20)) + [[BAD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/robotic/SH_NANOROBOTIC.focs.txt b/default/scripting/ship_hulls/robotic/SH_NANOROBOTIC.focs.txt index caaf91d1240..8cc271de48e 100644 --- a/default/scripting/ship_hulls/robotic/SH_NANOROBOTIC.focs.txt +++ b/default/scripting/ship_hulls/robotic/SH_NANOROBOTIC.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_NANOROBOTIC" description = "SH_NANOROBOTIC_DESC" speed = 80 - fuel = 3 + fuel = 1.5 + NoDefaultFuelEffect stealth = 5 structure = 30 slots = [ @@ -16,7 +17,7 @@ Hull ] buildCost = 50 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 2 - tags = [ "ROBOTIC_HULL" "PEDIA_HULL_LINE_ROBOTIC" ] + tags = [ "ROBOTIC_HULL" "PEDIA_HULL_LINE_ROBOTIC" "AVERAGE_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_CON_NANOROBO" @@ -48,6 +49,8 @@ Hull activation = Turn low = Source.CreationTurn + 1 effects = SetStructure value = Value + Value + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/robotic/SH_ROBOTIC.focs.txt b/default/scripting/ship_hulls/robotic/SH_ROBOTIC.focs.txt index b7852e5bbc7..4831396fb29 100644 --- a/default/scripting/ship_hulls/robotic/SH_ROBOTIC.focs.txt +++ b/default/scripting/ship_hulls/robotic/SH_ROBOTIC.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_ROBOTIC" description = "SH_ROBOTIC_DESC" speed = 75 - fuel = 3 + fuel = 2 + NoDefaultFuelEffect stealth = 5 structure = 25 slots = [ @@ -14,7 +15,7 @@ Hull ] buildCost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 2 - tags = [ "ROBOTIC_HULL" "PEDIA_HULL_LINE_ROBOTIC" ] + tags = [ "ROBOTIC_HULL" "PEDIA_HULL_LINE_ROBOTIC" "AVERAGE_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_BASE" @@ -39,9 +40,10 @@ Hull ] Structure high = LocalCandidate.MaxStructure - 0.001 ] - activation = Source effects = SetStructure value = Value + 2 + [[AVERAGE_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/robotic/SH_SELF_GRAVITATING.focs.txt b/default/scripting/ship_hulls/robotic/SH_SELF_GRAVITATING.focs.txt index d9943498ce8..92f3867793b 100644 --- a/default/scripting/ship_hulls/robotic/SH_SELF_GRAVITATING.focs.txt +++ b/default/scripting/ship_hulls/robotic/SH_SELF_GRAVITATING.focs.txt @@ -2,14 +2,14 @@ Hull name = "SH_SELF_GRAVITATING" description = "SH_SELF_GRAVITATING_DESC" speed = 80 - fuel = 3 + fuel = 1.2 + NoDefaultFuelEffect stealth = -5 structure = 100 slots = [ Slot type = External position = (0.20, 0.15) Slot type = External position = (0.35, 0.15) Slot type = External position = (0.50, 0.15) - Slot type = External position = (0.65, 0.15) Slot type = External position = (0.40, 0.80) Slot type = Core position = (0.50, 0.50) Slot type = External position = (0.80, 0.50) @@ -18,7 +18,7 @@ Hull ] buildCost = 60 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 2 - tags = [ "ROBOTIC_HULL" "PEDIA_HULL_LINE_ROBOTIC" ] + tags = [ "ROBOTIC_HULL" "PEDIA_HULL_LINE_ROBOTIC" "BAD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_CON_GEOINT" @@ -35,6 +35,8 @@ Hull OwnedBy empire = Source.Owner ] effectsgroups = [ + [[BAD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/robotic/SH_SPACE_FLUX_BUBBLE.focs.txt b/default/scripting/ship_hulls/robotic/SH_SPACE_FLUX_BUBBLE.focs.txt new file mode 100644 index 00000000000..db091c47c58 --- /dev/null +++ b/default/scripting/ship_hulls/robotic/SH_SPACE_FLUX_BUBBLE.focs.txt @@ -0,0 +1,51 @@ +Hull + name = "SH_SPACE_FLUX_BUBBLE" + description = "SH_SPACE_FLUX_BUBBLE_DESC" + speed = 80 + fuel = 6 + NoDefaultFuelEffect + stealth = 15 + structure = 19 + slots = Slot type = Internal position = (0.50, 0.50) + buildCost = 17 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] + buildTime = 2 + tags = [ "PEDIA_HULL_LINE_ROBOTIC" "GREAT_FUEL_EFFICIENCY" ] + location = And [ + Contains And [ + Building name = "BLD_SHIPYARD_BASE" + OwnedBy empire = Source.Owner + ] + Contains And [ + Building name = "BLD_SHIPYARD_ORBITAL_DRYDOCK" + OwnedBy empire = Source.Owner + ] + OwnedBy empire = Source.Owner + ] + effectsgroups = [ + EffectsGroup + scope = Source + activation = Not Stationary + accountinglabel = "SPATIAL_FLUX_MALUS" + effects = SetStealth value = Value - 30 + EffectsGroup + scope = Source + activation = Not Aggressive + accountinglabel = "SPATIAL_FLUX_BONUS" + effects = SetStealth value = Value + 10 + (10 * Statistic If condition = OwnerHasTech name = "SPY_STEALTH_PART_1" ) + (10 * Statistic If condition = OwnerHasTech name = "SPY_STEALTH_PART_2" ) + (10 * Statistic If condition = OwnerHasTech name = "SPY_STEALTH_PART_3" ) + (10 * Statistic If condition = OwnerHasTech name = "SPY_STEALTH_4" ) + + [[GREAT_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] + [[AVERAGE_BASE_FUEL_REGEN]] + [[REGULAR_HULL_DETECTION]] + [[SCAVANGE_FUEL_UNOWNED]] + [[UNOWNED_GOOD_VISION]] + [[UNOWNED_MOVE]] + ] + icon = "icons/ship_hulls/bulk_freighter_hull_small.png" + graphic = "hulls_design/bulk_freighter_hull.png" + +#include "robotic.macros" + +#include "../ship_hulls.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_hulls/robotic/SH_SPATIAL_FLUX.focs.txt b/default/scripting/ship_hulls/robotic/SH_SPATIAL_FLUX.focs.txt index 04b06b708db..18512626d28 100644 --- a/default/scripting/ship_hulls/robotic/SH_SPATIAL_FLUX.focs.txt +++ b/default/scripting/ship_hulls/robotic/SH_SPATIAL_FLUX.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_SPATIAL_FLUX" description = "SH_SPATIAL_FLUX_DESC" speed = 80 - fuel = 4 + fuel = 8 + NoDefaultFuelEffect stealth = 15 structure = 3 slots = [ @@ -11,7 +12,7 @@ Hull ] buildCost = 11 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 2 - tags = [ "PEDIA_HULL_LINE_ROBOTIC" ] + tags = [ "PEDIA_HULL_LINE_ROBOTIC" "GREAT_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_BASE" @@ -35,6 +36,8 @@ Hull accountinglabel = "SPATIAL_FLUX_BONUS" effects = SetStealth value = Value + 10 + (10 * Statistic If condition = OwnerHasTech name = "SPY_STEALTH_PART_1" ) + (10 * Statistic If condition = OwnerHasTech name = "SPY_STEALTH_PART_2" ) + (10 * Statistic If condition = OwnerHasTech name = "SPY_STEALTH_PART_3" ) + (10 * Statistic If condition = OwnerHasTech name = "SPY_STEALTH_4" ) + [[GREAT_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/robotic/SH_TITANIC.focs.txt b/default/scripting/ship_hulls/robotic/SH_TITANIC.focs.txt index 65b1b900e60..a4eb4a86014 100644 --- a/default/scripting/ship_hulls/robotic/SH_TITANIC.focs.txt +++ b/default/scripting/ship_hulls/robotic/SH_TITANIC.focs.txt @@ -2,7 +2,8 @@ Hull name = "SH_TITANIC" description = "SH_TITANIC_DESC" speed = 80 - fuel = 3 + fuel = 1.2 + NoDefaultFuelEffect stealth = -35 structure = 160 slots = [ @@ -13,7 +14,8 @@ Hull Slot type = External position = (0.50, 0.15) Slot type = External position = (0.60, 0.15) Slot type = External position = (0.70, 0.15) - Slot type = External position = (0.80, 0.15) + //Slot type = External position = (0.80, 0.15) + Slot type = External position = (0.85, 0.50) Slot type = External position = (0.10, 0.85) Slot type = External position = (0.20, 0.85) Slot type = External position = (0.30, 0.85) @@ -21,7 +23,7 @@ Hull Slot type = External position = (0.50, 0.85) Slot type = External position = (0.60, 0.85) Slot type = External position = (0.70, 0.85) - Slot type = External position = (0.80, 0.85) + //Slot type = External position = (0.80, 0.85) Slot type = Internal position = (0.30, 0.50) Slot type = Internal position = (0.60, 0.50) Slot type = Internal position = (0.70, 0.50) @@ -29,7 +31,7 @@ Hull ] buildCost = 180 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 5 - tags = [ "ROBOTIC_HULL" "PEDIA_HULL_LINE_ROBOTIC" ] + tags = [ "ROBOTIC_HULL" "PEDIA_HULL_LINE_ROBOTIC" "BAD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_CON_GEOINT" @@ -46,6 +48,8 @@ Hull OwnedBy empire = Source.Owner ] effectsgroups = [ + [[BAD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/robotic/SH_TRANSSPATIAL.focs.txt b/default/scripting/ship_hulls/robotic/SH_TRANSSPATIAL.focs.txt index f847b990abd..a4e2b190805 100644 --- a/default/scripting/ship_hulls/robotic/SH_TRANSSPATIAL.focs.txt +++ b/default/scripting/ship_hulls/robotic/SH_TRANSSPATIAL.focs.txt @@ -3,6 +3,7 @@ Hull description = "SH_TRANSSPATIAL_DESC" speed = 80 fuel = 3 + NoDefaultFuelEffect stealth = 15 structure = 9 slots = [ @@ -11,7 +12,7 @@ Hull ] buildCost = 10 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_HULL_COST_MULTIPLIER]] buildTime = 3 - tags = [ "PEDIA_HULL_LINE_ROBOTIC" ] + tags = [ "PEDIA_HULL_LINE_ROBOTIC" "GOOD_FUEL_EFFICIENCY" ] location = And [ Contains And [ Building name = "BLD_SHIPYARD_CON_ADV_ENGINE" @@ -28,6 +29,8 @@ Hull OwnedBy empire = Source.Owner ] effectsgroups = [ + [[GOOD_FUEL_EFFICIENCY]] + [[ADD_HULL_FUEL_TO_MAX_FUEL_METER]] [[AVERAGE_BASE_FUEL_REGEN]] [[REGULAR_HULL_DETECTION]] [[SCAVANGE_FUEL_UNOWNED]] diff --git a/default/scripting/ship_hulls/ship_hulls.macros b/default/scripting/ship_hulls/ship_hulls.macros index 73598b78a8d..5d34ba4dc41 100644 --- a/default/scripting/ship_hulls/ship_hulls.macros +++ b/default/scripting/ship_hulls/ship_hulls.macros @@ -22,14 +22,12 @@ SCAVANGE_FUEL_UNOWNED REGULAR_HULL_DETECTION '''EffectsGroup scope = Source - activation = Source effects = SetDetection value = Value + 25 ''' WEAK_VISION '''EffectsGroup scope = Source - activation = Source accountinglabel = "WEAK_VISION_LABEL" effects = SetDetection value = Value + 10 ''' @@ -37,7 +35,6 @@ WEAK_VISION MODERATE_VISION '''EffectsGroup scope = Source - activation = Source accountinglabel = "MODERATE_VISION_LABEL" effects = SetDetection value = Value + 30 ''' @@ -45,7 +42,6 @@ MODERATE_VISION GOOD_VISION '''EffectsGroup scope = Source - activation = Source accountinglabel = "GOOD_VISION_LABEL" effects = SetDetection value = Value + 50 ''' @@ -53,7 +49,6 @@ GOOD_VISION EXCELLENT_VISION '''EffectsGroup scope = Source - activation = Source accountinglabel = "EXCELLENT_VISION_LABEL" effects = SetDetection value = Value + 150 ''' @@ -65,19 +60,13 @@ EXCELLENT_VISION UNOWNED_OWNED_VISION_BONUS '''EffectsGroup scope = Source - activation = And [ - Source - Unowned - ] + activation = Unowned stackinggroup = "@1@_VISION_BONUS" accountinglabel = "@1@_VISION_LABEL" effects = SetDetection value = Value + @2@ EffectsGroup scope = Source - activation = And [ - Source - Not Unowned - ] + activation = Not Unowned stackinggroup = "@1@_VISION_BONUS" accountinglabel = "@1@_VISION_LABEL" effects = SetDetection value = Value + @3@ @@ -86,10 +75,7 @@ EffectsGroup UNOWNED_GOOD_VISION '''EffectsGroup scope = Source - activation = And [ - Source - Unowned - ] + activation = Unowned accountinglabel = "GOOD_VISION_LABEL" effects = SetDetection value = Value + 50 ''' @@ -119,12 +105,48 @@ UNOWNED_MOVE ] ''' +// macro to tell player when a ship regenerates its fuel +// arg1: base regen rate +REFUEL_MESSAGE +''' + EffectsGroup + scope = Source + activation = And [ + Stationary + (Source.Fuel < 1) + (Source.MaxFuel >= 1) + (Source.Fuel + + @1@ * (Statistic If Condition = Turn low = Source.ArrivedOnTurn + 1) + + 0.1 * (Statistic If Condition = DesignHasPart name = "FU_RAMSCOOP") + + 0.1 * (Statistic If Condition = And [ + Species name = "SP_SLY" + ContainedBy condition = And [ + Object id = Source.SystemID + Contains condition = And [ Planet type = GasGiant + Not OwnedBy affiliation = EnemyOf empire = Source.Owner + ] ] ] ) + >= 1 + ) + ] + effects = [ + GenerateSitrepMessage + message = "EFFECT_SHIP_REFUELED" + label = "EFFECT_SHIP_REFUELED_LABEL" + icon = "icons/meter/fuel.png" + parameters = [ + tag = "system" data = Source.SystemID + tag = "ship" data = Source.ID + ] + empire = Source.Owner + ] +''' + AVERAGE_BASE_FUEL_REGEN '''EffectsGroup description = "AVERAGE_BASE_FUEL_REGEN_DESC" scope = Source activation = And [ - Turn low = Source.ArrivedOnTurn + 1 + Turn low = LocalCandidate.ArrivedOnTurn + 1 Stationary (Source.Fuel < Source.MaxFuel) ] @@ -132,6 +154,62 @@ AVERAGE_BASE_FUEL_REGEN accountinglabel = "BASE_FUEL_REGEN_LABEL" priority = [[VERY_LATE_PRIORITY]] effects = SetFuel value = min(Target.MaxFuel, Value + 0.1001) + + [[REFUEL_MESSAGE(0.1001)]] +''' + +GREAT_FUEL_EFFICIENCY +'''[[HULL_FUEL_EFFICIENCY_EFFECTSGROUP(GREAT, 4)]] +''' + +GOOD_FUEL_EFFICIENCY +'''[[HULL_FUEL_EFFICIENCY_EFFECTSGROUP(GOOD, 2)]] +''' + +AVERAGE_FUEL_EFFICIENCY +'''[[HULL_FUEL_EFFICIENCY_EFFECTSGROUP(AVERAGE, 1)]] ''' +BAD_FUEL_EFFICIENCY +'''[[HULL_FUEL_EFFICIENCY_EFFECTSGROUP(BAD, 0.6)]] +''' + +HULL_FUEL_EFFICIENCY_EFFECTSGROUP +''' EffectsGroup + description = "HULL_FUEL_EFFICIENCY_DESC" + scope = And [ + Source + Ship + ] + accountinglabel = "@1@_FUEL_EFFICIENCY_LABEL" + priority = [[VERY_LATE_PRIORITY]] + effects = SetMaxFuel value = Value * @2@ * Statistic If Condition = And [ Source HasTag name = "@1@_FUEL_EFFICIENCY" ] + + // If a ship has less than one maximum fuel it will never be able to travel out of supply so refueling does not make sense. + // In order to communicate that and to prevent wrong refuel sitreps, we set the maximum fuel for that case to zero + // I need the final MaxFuel value to decide if we should zero it. + // Scope and activation conditions are evaluated before the effects so using an If condition = Value() in effects expression instead + EffectsGroup + description = "MAX_FUEL_LESS_THAN_ONE_DESC" + scope = And [ + Source + Ship + ] + accountinglabel = "MAX_FUEL_LESS_THAN_ONE_LABEL" + priority = [[METER_OVERRIDE_PRIORITY]] + effects = If condition = (Value(Source.MaxFuel) < 1) effects = SetMaxFuel value = 0 +''' + +// This adds the hull's base fuel to the max fuel meter after applying the fuel efficiency multiplier. +// Note the use of the default accountinglabel. +ADD_HULL_FUEL_TO_MAX_FUEL_METER +'''EffectsGroup + scope = Source + accountinglabel = "TT_SHIP_HULL" + priority = [[VERY_VERY_LATE_PRIORITY]] + effects = SetMaxFuel value = ( Value + HullFuel name = Source.Hull ) +''' + + + #include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_parts/Armor.focs.txt b/default/scripting/ship_parts/Armor.focs.txt deleted file mode 100644 index 900933d7425..00000000000 --- a/default/scripting/ship_parts/Armor.focs.txt +++ /dev/null @@ -1,125 +0,0 @@ -Part - name = "AR_STD_PLATE" - description = "AR_STD_PLATE_DESC" - class = Armour - capacity = 6 - mountableSlotTypes = External - buildcost = 4 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_ARMOUR" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/std_armor_plating.png" - -Part - name = "AR_ZORTRIUM_PLATE" - description = "AR_ZORTRIUM_PLATE_DESC" - class = Armour - capacity = 11 - mountableSlotTypes = External - buildcost = 6 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_ARMOUR" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/zortrium_plating.png" - -Part - name = "AR_DIAMOND_PLATE" - description = "AR_DIAMOND_PLATE_DESC" - class = Armour - capacity = 18 - mountableSlotTypes = External - buildcost = 8 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_ARMOUR" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/diamond_plating.png" - -Part - name = "AR_XENTRONIUM_PLATE" - description = "AR_XENTRONIUM_PLATE_DESC" - class = Armour - capacity = 30 - mountableSlotTypes = External - buildcost = 12 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_ARMOUR" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/xentronium_plating.png" - -Part - name = "AR_ROCK_PLATE" - description = "AR_ROCK_PLATE_DESC" - class = Armour - capacity = 18 - mountableSlotTypes = External - buildcost = 6 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 2 - tags = [ "PEDIA_PC_ARMOUR" ] - location = And [ - ResourceSupplyConnected empire = Source.Owner condition = And [ - Building name = "BLD_SHIPYARD_AST_REF" - Or [ - OwnedBy empire = Source.Owner - OwnedBy affiliation = AllyOf empire = Source.Owner - ] - ] - OwnedBy empire = Source.Owner - ] - icon = "icons/ship_parts/rock_plating.png" - -Part - name = "AR_CRYSTAL_PLATE" - description = "AR_CRYSTAL_PLATE_DESC" - class = Armour - capacity = 30 - mountableSlotTypes = External - buildcost = 8 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 3 - tags = [ "PEDIA_PC_ARMOUR" ] - location = And [ - ResourceSupplyConnected empire = Source.Owner condition = And [ - Building name = "BLD_SHIPYARD_AST_REF" - Or [ - OwnedBy empire = Source.Owner - OwnedBy affiliation = AllyOf empire = Source.Owner - ] - ] - OwnedBy empire = Source.Owner - ] - icon = "icons/ship_parts/crystal_plating.png" - -Part - name = "AR_NEUTRONIUM_PLATE" - description = "AR_NEUTRONIUM_PLATE_DESC" - class = Armour - capacity = 40 - mountableSlotTypes = External - buildcost = 15 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 5 - tags = [ "PEDIA_PC_ARMOUR" ] - location = And [ - OwnedBy empire = Source.Owner - Contains Building name = "BLD_NEUTRONIUM_FORGE" - ResourceSupplyConnected empire = Source.Owner condition = And [ - OwnedBy empire = Source.Owner - Or [ - Building name = "BLD_NEUTRONIUM_EXTRACTOR" - Building name = "BLD_NEUTRONIUM_SYNTH" - ] - ] - ] - icon = "icons/ship_parts/neutronium_plating.png" - -Part - name = "AR_PRECURSOR_PLATE" - description = "AR_PRECURSOR_PLATE_DESC" - class = Armour - capacity = 400 - mountableSlotTypes = External - buildcost = 1 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_ARMOUR" ] - location = Not All // to keep this from making all other armor look redundant in DesignWnd; ok since not player-buildable currently - icon = "" - -#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Armour/AR_CRYSTAL_PLATE.focs.txt b/default/scripting/ship_parts/Armour/AR_CRYSTAL_PLATE.focs.txt new file mode 100644 index 00000000000..398bc6803c5 --- /dev/null +++ b/default/scripting/ship_parts/Armour/AR_CRYSTAL_PLATE.focs.txt @@ -0,0 +1,22 @@ +Part + name = "AR_CRYSTAL_PLATE" + description = "AR_CRYSTAL_PLATE_DESC" + class = Armour + capacity = 30 + mountableSlotTypes = External + buildcost = 8 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 3 + tags = [ "PEDIA_PC_ARMOUR" ] + location = And [ + ResourceSupplyConnected empire = Source.Owner condition = And [ + Building name = "BLD_SHIPYARD_AST_REF" + Or [ + OwnedBy empire = Source.Owner + OwnedBy affiliation = AllyOf empire = Source.Owner + ] + ] + OwnedBy empire = Source.Owner + ] + icon = "icons/ship_parts/crystal_plating.png" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Armour/AR_DIAMOND_PLATE.focs.txt b/default/scripting/ship_parts/Armour/AR_DIAMOND_PLATE.focs.txt new file mode 100644 index 00000000000..cfbe7edf55e --- /dev/null +++ b/default/scripting/ship_parts/Armour/AR_DIAMOND_PLATE.focs.txt @@ -0,0 +1,13 @@ +Part + name = "AR_DIAMOND_PLATE" + description = "AR_DIAMOND_PLATE_DESC" + class = Armour + capacity = 18 + mountableSlotTypes = External + buildcost = 8 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_ARMOUR" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/diamond_plating.png" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Armour/AR_NEUTRONIUM_PLATE.focs.txt b/default/scripting/ship_parts/Armour/AR_NEUTRONIUM_PLATE.focs.txt new file mode 100644 index 00000000000..c166d71b0bb --- /dev/null +++ b/default/scripting/ship_parts/Armour/AR_NEUTRONIUM_PLATE.focs.txt @@ -0,0 +1,23 @@ +Part + name = "AR_NEUTRONIUM_PLATE" + description = "AR_NEUTRONIUM_PLATE_DESC" + class = Armour + capacity = 40 + mountableSlotTypes = External + buildcost = 15 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 5 + tags = [ "PEDIA_PC_ARMOUR" ] + location = And [ + OwnedBy empire = Source.Owner + Contains Building name = "BLD_NEUTRONIUM_FORGE" + ResourceSupplyConnected empire = Source.Owner condition = And [ + OwnedBy empire = Source.Owner + Or [ + Building name = "BLD_NEUTRONIUM_EXTRACTOR" + Building name = "BLD_NEUTRONIUM_SYNTH" + ] + ] + ] + icon = "icons/ship_parts/neutronium_plating.png" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Armour/AR_PRECURSOR_PLATE.focs.txt b/default/scripting/ship_parts/Armour/AR_PRECURSOR_PLATE.focs.txt new file mode 100644 index 00000000000..c6c7b9d79a5 --- /dev/null +++ b/default/scripting/ship_parts/Armour/AR_PRECURSOR_PLATE.focs.txt @@ -0,0 +1,13 @@ +Part + name = "AR_PRECURSOR_PLATE" + description = "AR_PRECURSOR_PLATE_DESC" + class = Armour + capacity = 400 + mountableSlotTypes = External + buildcost = 1 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_ARMOUR" ] + location = Not All // to keep this from making all other armor look redundant in DesignWnd; ok since not player-buildable currently + icon = "" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Armour/AR_ROCK_PLATE.focs.txt b/default/scripting/ship_parts/Armour/AR_ROCK_PLATE.focs.txt new file mode 100644 index 00000000000..d0306f212b8 --- /dev/null +++ b/default/scripting/ship_parts/Armour/AR_ROCK_PLATE.focs.txt @@ -0,0 +1,22 @@ +Part + name = "AR_ROCK_PLATE" + description = "AR_ROCK_PLATE_DESC" + class = Armour + capacity = 18 + mountableSlotTypes = External + buildcost = 6 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 2 + tags = [ "PEDIA_PC_ARMOUR" ] + location = And [ + ResourceSupplyConnected empire = Source.Owner condition = And [ + Building name = "BLD_SHIPYARD_AST_REF" + Or [ + OwnedBy empire = Source.Owner + OwnedBy affiliation = AllyOf empire = Source.Owner + ] + ] + OwnedBy empire = Source.Owner + ] + icon = "icons/ship_parts/rock_plating.png" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Armour/AR_STD_PLATE.focs.txt b/default/scripting/ship_parts/Armour/AR_STD_PLATE.focs.txt new file mode 100644 index 00000000000..853262b30e7 --- /dev/null +++ b/default/scripting/ship_parts/Armour/AR_STD_PLATE.focs.txt @@ -0,0 +1,13 @@ +Part + name = "AR_STD_PLATE" + description = "AR_STD_PLATE_DESC" + class = Armour + capacity = 6 + mountableSlotTypes = External + buildcost = 4 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_ARMOUR" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/std_armor_plating.png" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Armour/AR_XENTRONIUM_PLATE.focs.txt b/default/scripting/ship_parts/Armour/AR_XENTRONIUM_PLATE.focs.txt new file mode 100644 index 00000000000..50843df4145 --- /dev/null +++ b/default/scripting/ship_parts/Armour/AR_XENTRONIUM_PLATE.focs.txt @@ -0,0 +1,13 @@ +Part + name = "AR_XENTRONIUM_PLATE" + description = "AR_XENTRONIUM_PLATE_DESC" + class = Armour + capacity = 30 + mountableSlotTypes = External + buildcost = 12 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_ARMOUR" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/xentronium_plating.png" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Armour/AR_ZORTRIUM_PLATE.focs.txt b/default/scripting/ship_parts/Armour/AR_ZORTRIUM_PLATE.focs.txt new file mode 100644 index 00000000000..0c9608b41f8 --- /dev/null +++ b/default/scripting/ship_parts/Armour/AR_ZORTRIUM_PLATE.focs.txt @@ -0,0 +1,13 @@ +Part + name = "AR_ZORTRIUM_PLATE" + description = "AR_ZORTRIUM_PLATE_DESC" + class = Armour + capacity = 11 + mountableSlotTypes = External + buildcost = 6 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_ARMOUR" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/zortrium_plating.png" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/SP_BIOTERM.focs.txt b/default/scripting/ship_parts/Bombard/SP_BIOTERM.focs.txt similarity index 84% rename from default/scripting/ship_parts/SP_BIOTERM.focs.txt rename to default/scripting/ship_parts/Bombard/SP_BIOTERM.focs.txt index 5255c0aad7f..e65a7be2fea 100644 --- a/default/scripting/ship_parts/SP_BIOTERM.focs.txt +++ b/default/scripting/ship_parts/Bombard/SP_BIOTERM.focs.txt @@ -20,8 +20,10 @@ Part Unowned ] VisibleToEmpire empire = Source.Owner + /// @content_tag{ORGANIC} When bombarding in-system enemy with this part, reduces population on visible populated Planet with tag HasTag name = "ORGANIC" ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 1 GenerateSitRepMessage @@ -43,12 +45,14 @@ Part InSystem id = Source.SystemID OwnedBy affiliation = AnyEmpire Stealth high = Source.Detection + /// @content_tag{ORGANIC} Each part owned by Unowned in system will reduce population on one visible populated empire Planet with tag HasTag name = "ORGANIC" ] activation = And [ Unowned InSystem ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 1 GenerateSitRepMessage @@ -66,3 +70,4 @@ Part icon = "icons/ship_parts/bioterm.png" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_parts/SP_CHAOS_WAVE.focs.txt b/default/scripting/ship_parts/Bombard/SP_CHAOS_WAVE.focs.txt similarity index 91% rename from default/scripting/ship_parts/SP_CHAOS_WAVE.focs.txt rename to default/scripting/ship_parts/Bombard/SP_CHAOS_WAVE.focs.txt index f09f00fbf4b..76b8fa4f4b5 100644 --- a/default/scripting/ship_parts/SP_CHAOS_WAVE.focs.txt +++ b/default/scripting/ship_parts/Bombard/SP_CHAOS_WAVE.focs.txt @@ -20,6 +20,7 @@ Part ] VisibleToEmpire empire = Source.Owner ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 2 RemoveSpecial name = "GAIA_SPECIAL" @@ -43,9 +44,10 @@ Part Stealth high = Source.Detection ] activation = And [ - Unowned - InSystem - ] + Unowned + InSystem + ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 2 RemoveSpecial name = "GAIA_SPECIAL" @@ -64,3 +66,4 @@ Part icon = "icons/ship_parts/chaos-wave.png" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_parts/SP_DARK_RAY.focs.txt b/default/scripting/ship_parts/Bombard/SP_DARK_RAY.focs.txt similarity index 82% rename from default/scripting/ship_parts/SP_DARK_RAY.focs.txt rename to default/scripting/ship_parts/Bombard/SP_DARK_RAY.focs.txt index 720a5136fff..36739157721 100644 --- a/default/scripting/ship_parts/SP_DARK_RAY.focs.txt +++ b/default/scripting/ship_parts/Bombard/SP_DARK_RAY.focs.txt @@ -19,8 +19,10 @@ Part Unowned ] VisibleToEmpire empire = Source.Owner + /// @content_tag{PHOTOTROPHIC} When bombarding in-system enemy with this part, reduces population on visible populated Planet with tag HasTag name = "PHOTOTROPHIC" ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 0.5 GenerateSitRepMessage @@ -41,12 +43,14 @@ Part InSystem id = Source.SystemID OwnedBy affiliation = AnyEmpire Stealth high = Source.Detection + /// @content_tag{PHOTOTROPHIC} Each part owned by Unowned in system will reduce population on one visible populated empire Planet with tag HasTag name = "PHOTOTROPHIC" ] activation = And [ - Unowned - InSystem - ] + Unowned + InSystem + ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 0.5 GenerateSitRepMessage @@ -64,3 +68,4 @@ Part icon = "icons/ship_parts/dark-ray.png" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_parts/SP_DEATH_SPORE.focs.txt b/default/scripting/ship_parts/Bombard/SP_DEATH_SPORE.focs.txt similarity index 84% rename from default/scripting/ship_parts/SP_DEATH_SPORE.focs.txt rename to default/scripting/ship_parts/Bombard/SP_DEATH_SPORE.focs.txt index 86a0f55e951..7e03b1608e4 100644 --- a/default/scripting/ship_parts/SP_DEATH_SPORE.focs.txt +++ b/default/scripting/ship_parts/Bombard/SP_DEATH_SPORE.focs.txt @@ -20,8 +20,10 @@ Part Unowned ] VisibleToEmpire empire = Source.Owner + /// @content_tag{ORGANIC} When bombarding in-system enemy with this part, reduces population on visible populated Planet with tag HasTag name = "ORGANIC" ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 0.5 GenerateSitRepMessage @@ -43,12 +45,14 @@ Part InSystem id = Source.SystemID OwnedBy affiliation = AnyEmpire Stealth high = Source.Detection + /// @content_tag{ORGANIC} Each part owned by Unowned in system will reduce population on one visible populated empire Planet with tag HasTag name = "ORGANIC" ] activation = And [ Unowned InSystem ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 0.5 GenerateSitRepMessage @@ -66,3 +70,4 @@ Part icon = "icons/ship_parts/death-spore.png" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_parts/SP_EMO.focs.txt b/default/scripting/ship_parts/Bombard/SP_EMO.focs.txt similarity index 82% rename from default/scripting/ship_parts/SP_EMO.focs.txt rename to default/scripting/ship_parts/Bombard/SP_EMO.focs.txt index 39daefe54aa..8a0e0362e7b 100644 --- a/default/scripting/ship_parts/SP_EMO.focs.txt +++ b/default/scripting/ship_parts/Bombard/SP_EMO.focs.txt @@ -20,8 +20,10 @@ Part Unowned ] VisibleToEmpire empire = Source.Owner + /// @content_tag{ROBOTIC} When bombarding in-system enemy with this part, reduces population on visible populated Planet with tag HasTag name = "ROBOTIC" ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 1 GenerateSitRepMessage @@ -42,12 +44,14 @@ Part InSystem id = Source.SystemID OwnedBy affiliation = AnyEmpire Stealth high = Source.Detection + /// @content_tag{ROBOTIC} Each part owned by Unowned in system will reduce population on one visible populated empire Planet with tag HasTag name = "ROBOTIC" ] activation = And [ - Unowned - InSystem - ] + Unowned + InSystem + ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 1 GenerateSitRepMessage @@ -65,3 +69,4 @@ Part icon = "icons/ship_parts/EMO.png" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_parts/SP_EMP.focs.txt b/default/scripting/ship_parts/Bombard/SP_EMP.focs.txt similarity index 82% rename from default/scripting/ship_parts/SP_EMP.focs.txt rename to default/scripting/ship_parts/Bombard/SP_EMP.focs.txt index d924fe8953c..902f6fc887b 100644 --- a/default/scripting/ship_parts/SP_EMP.focs.txt +++ b/default/scripting/ship_parts/Bombard/SP_EMP.focs.txt @@ -20,8 +20,10 @@ Part Unowned ] VisibleToEmpire empire = Source.Owner + /// @content_tag{ROBOTIC} When bombarding in-system enemy with this part, reduces population on visible populated Planet with tag HasTag name = "ROBOTIC" ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 0.5 GenerateSitRepMessage @@ -42,12 +44,14 @@ Part InSystem id = Source.SystemID OwnedBy affiliation = AnyEmpire Stealth high = Source.Detection + /// @content_tag{ROBOTIC} Each part owned by Unowned in system will reduce population on one visible populated empire Planet with tag HasTag name = "ROBOTIC" ] activation = And [ - Unowned - InSystem - ] + Unowned + InSystem + ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 0.5 GenerateSitRepMessage @@ -65,3 +69,4 @@ Part icon = "icons/ship_parts/EMP.png" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_parts/SP_GRV.focs.txt b/default/scripting/ship_parts/Bombard/SP_GRV.focs.txt similarity index 82% rename from default/scripting/ship_parts/SP_GRV.focs.txt rename to default/scripting/ship_parts/Bombard/SP_GRV.focs.txt index bd5dae0ad66..50b1396b6fc 100644 --- a/default/scripting/ship_parts/SP_GRV.focs.txt +++ b/default/scripting/ship_parts/Bombard/SP_GRV.focs.txt @@ -20,8 +20,10 @@ Part Unowned ] VisibleToEmpire empire = Source.Owner + /// @content_tag{LITHIC} When bombarding in-system enemy with this part, reduces population on visible populated Planet with tag HasTag name = "LITHIC" ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 1 GenerateSitRepMessage @@ -42,12 +44,14 @@ Part InSystem id = Source.SystemID OwnedBy affiliation = AnyEmpire Stealth high = Source.Detection + /// @content_tag{LITHIC} Each part owned by Unowned in system will reduce population on one visible populated empire Planet with tag HasTag name = "LITHIC" ] activation = And [ - Unowned - InSystem - ] + Unowned + InSystem + ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 1 GenerateSitRepMessage @@ -65,3 +69,4 @@ Part icon = "icons/ship_parts/gravitic_pulse.png" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_parts/SP_SONIC.focs.txt b/default/scripting/ship_parts/Bombard/SP_SONIC.focs.txt similarity index 82% rename from default/scripting/ship_parts/SP_SONIC.focs.txt rename to default/scripting/ship_parts/Bombard/SP_SONIC.focs.txt index 2e647941e78..4d1f3418167 100644 --- a/default/scripting/ship_parts/SP_SONIC.focs.txt +++ b/default/scripting/ship_parts/Bombard/SP_SONIC.focs.txt @@ -20,8 +20,10 @@ Part Unowned ] VisibleToEmpire empire = Source.Owner + /// @content_tag{LITHIC} When bombarding in-system enemy with this part, reduces population on visible populated Planet with tag HasTag name = "LITHIC" ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 0.5 GenerateSitRepMessage @@ -42,12 +44,14 @@ Part InSystem id = Source.SystemID OwnedBy affiliation = AnyEmpire Stealth high = Source.Detection + /// @content_tag{LITHIC} Each part owned by Unowned in system will reduce population on one visible populated empire Planet with tag HasTag name = "LITHIC" ] activation = And [ - Unowned - InSystem - ] + Unowned + InSystem + ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 0.5 GenerateSitRepMessage @@ -65,3 +69,4 @@ Part icon = "icons/ship_parts/sonic_wave.png" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_parts/SP_VOID_SHADOW.focs.txt b/default/scripting/ship_parts/Bombard/SP_VOID_SHADOW.focs.txt similarity index 82% rename from default/scripting/ship_parts/SP_VOID_SHADOW.focs.txt rename to default/scripting/ship_parts/Bombard/SP_VOID_SHADOW.focs.txt index a282777cc6f..65cd61a8198 100644 --- a/default/scripting/ship_parts/SP_VOID_SHADOW.focs.txt +++ b/default/scripting/ship_parts/Bombard/SP_VOID_SHADOW.focs.txt @@ -19,8 +19,10 @@ Part Unowned ] VisibleToEmpire empire = Source.Owner + /// @content_tag{PHOTOTROPHIC} When bombarding in-system enemy with this part, reduces population on visible populated Planet with tag HasTag name = "PHOTOTROPHIC" ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 1 GenerateSitRepMessage @@ -41,12 +43,14 @@ Part InSystem id = Source.SystemID OwnedBy affiliation = AnyEmpire Stealth high = Source.Detection + /// @content_tag{PHOTOTROPHIC} Each part owned by Unowned in system will reduce population on one visible populated empire Planet with tag HasTag name = "PHOTOTROPHIC" ] activation = And [ - Unowned - InSystem - ] + Unowned + InSystem + ] + priority = [[EARLY_POPULATION_PRIORITY]] effects = [ SetPopulation value = Value - 1 GenerateSitRepMessage @@ -64,3 +68,4 @@ Part icon = "icons/ship_parts/void-shadow.png" #include "/scripting/common/upkeep.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/ship_parts/Cloaks.focs.txt b/default/scripting/ship_parts/Cloaks.focs.txt deleted file mode 100644 index 073abeba3c5..00000000000 --- a/default/scripting/ship_parts/Cloaks.focs.txt +++ /dev/null @@ -1,66 +0,0 @@ -Part - name = "ST_CLOAK_4" - description = "ST_CLOAK_4_DESC" - exclusions = [[ALL_CLOAKS]] - class = Stealth - capacity = 80 - mountableSlotTypes = Internal - buildcost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 10 - tags = [ "PEDIA_PC_STEALTH" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/cloak-4.png" - -Part - name = "ST_CLOAK_3" - description = "ST_CLOAK_3_DESC" - exclusions = [[ALL_CLOAKS]] - class = Stealth - capacity = 60 - mountableSlotTypes = Internal - buildcost = 15 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 7 - tags = [ "PEDIA_PC_STEALTH" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/cloak-3.png" - -Part - name = "ST_CLOAK_2" - description = "ST_CLOAK_2_DESC" - exclusions = [[ALL_CLOAKS]] - class = Stealth - capacity = 40 - mountableSlotTypes = Internal - buildcost = 5 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 5 - tags = [ "PEDIA_PC_STEALTH" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/cloak-2.png" - -Part - name = "ST_CLOAK_1" - description = "ST_CLOAK_1_DESC" - exclusions = [[ALL_CLOAKS]] - class = Stealth - capacity = 20 - mountableSlotTypes = Internal - buildcost = 2 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 3 - tags = [ "PEDIA_PC_STEALTH" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/cloak-1.png" - - -// Helper macro containing the names of all cloaks -ALL_CLOAKS -''' -[ - "ST_CLOAK_1" - "ST_CLOAK_2" - "ST_CLOAK_3" - "ST_CLOAK_4" -] -''' - - -#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/CO_COLONY_POD.focs.txt b/default/scripting/ship_parts/Colony/CO_COLONY_POD.focs.txt similarity index 100% rename from default/scripting/ship_parts/CO_COLONY_POD.focs.txt rename to default/scripting/ship_parts/Colony/CO_COLONY_POD.focs.txt diff --git a/default/scripting/ship_parts/CO_COL_OUTPOST.focs.txt b/default/scripting/ship_parts/Colony/CO_COL_OUTPOST.focs.txt similarity index 100% rename from default/scripting/ship_parts/CO_COL_OUTPOST.focs.txt rename to default/scripting/ship_parts/Colony/CO_COL_OUTPOST.focs.txt diff --git a/default/scripting/ship_parts/CO_SUPEND_ANIM_POD.focs.txt b/default/scripting/ship_parts/Colony/CO_SUPEND_ANIM_POD.focs.txt similarity index 100% rename from default/scripting/ship_parts/CO_SUPEND_ANIM_POD.focs.txt rename to default/scripting/ship_parts/Colony/CO_SUPEND_ANIM_POD.focs.txt diff --git a/default/scripting/ship_parts/Detection/DT_DETECTOR_1.focs.txt b/default/scripting/ship_parts/Detection/DT_DETECTOR_1.focs.txt new file mode 100644 index 00000000000..1512a2ce625 --- /dev/null +++ b/default/scripting/ship_parts/Detection/DT_DETECTOR_1.focs.txt @@ -0,0 +1,16 @@ +Part + name = "DT_DETECTOR_1" + description = "DT_DETECTOR_1_DESC" + exclusions = [[ALL_DETECTORS]] + class = Detection + capacity = 25 + mountableSlotTypes = External + buildcost = 2 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 2 + tags = [ "PEDIA_PC_DETECTION" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/detector-1.png" + +#include "detection.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Detection/DT_DETECTOR_2.focs.txt b/default/scripting/ship_parts/Detection/DT_DETECTOR_2.focs.txt new file mode 100644 index 00000000000..56ec1718df7 --- /dev/null +++ b/default/scripting/ship_parts/Detection/DT_DETECTOR_2.focs.txt @@ -0,0 +1,16 @@ +Part + name = "DT_DETECTOR_2" + description = "DT_DETECTOR_2_DESC" + exclusions = [[ALL_DETECTORS]] + class = Detection + capacity = 75 + mountableSlotTypes = External + buildcost = 3 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 4 + tags = [ "PEDIA_PC_DETECTION" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/detector-2.png" + +#include "detection.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Detection/DT_DETECTOR_3.focs.txt b/default/scripting/ship_parts/Detection/DT_DETECTOR_3.focs.txt new file mode 100644 index 00000000000..74a6497803b --- /dev/null +++ b/default/scripting/ship_parts/Detection/DT_DETECTOR_3.focs.txt @@ -0,0 +1,16 @@ +Part + name = "DT_DETECTOR_3" + description = "DT_DETECTOR_3_DESC" + exclusions = [[ALL_DETECTORS]] + class = Detection + capacity = 150 + mountableSlotTypes = External + buildcost = 5 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 5 + tags = [ "PEDIA_PC_DETECTION" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/detector-3.png" + +#include "detection.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Detection/DT_DETECTOR_4.focs.txt b/default/scripting/ship_parts/Detection/DT_DETECTOR_4.focs.txt new file mode 100644 index 00000000000..9336c72e235 --- /dev/null +++ b/default/scripting/ship_parts/Detection/DT_DETECTOR_4.focs.txt @@ -0,0 +1,16 @@ +Part + name = "DT_DETECTOR_4" + description = "DT_DETECTOR_4_DESC" + exclusions = [[ALL_DETECTORS]] + class = Detection + capacity = 200 + mountableSlotTypes = External + buildcost = 6 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 5 + tags = [ "PEDIA_PC_DETECTION" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/detector-4.png" + +#include "detection.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Detection/detection.macros b/default/scripting/ship_parts/Detection/detection.macros new file mode 100644 index 00000000000..d296dd60735 --- /dev/null +++ b/default/scripting/ship_parts/Detection/detection.macros @@ -0,0 +1,10 @@ +// Helper macro that contains all detector parts +ALL_DETECTORS +''' +[ + "DT_DETECTOR_1" + "DT_DETECTOR_2" + "DT_DETECTOR_3" + "DT_DETECTOR_4" +] +''' diff --git a/default/scripting/ship_parts/Detectors.focs.txt b/default/scripting/ship_parts/Detectors.focs.txt deleted file mode 100644 index 9530551fe1b..00000000000 --- a/default/scripting/ship_parts/Detectors.focs.txt +++ /dev/null @@ -1,65 +0,0 @@ -Part - name = "DT_DETECTOR_4" - description = "DT_DETECTOR_4_DESC" - exclusions = [[ALL_DETECTORS]] - class = Detection - capacity = 200 - mountableSlotTypes = External - buildcost = 6 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 5 - tags = [ "PEDIA_PC_DETECTION" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/detector-4.png" - -Part - name = "DT_DETECTOR_3" - description = "DT_DETECTOR_3_DESC" - exclusions = [[ALL_DETECTORS]] - class = Detection - capacity = 150 - mountableSlotTypes = External - buildcost = 5 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 5 - tags = [ "PEDIA_PC_DETECTION" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/detector-3.png" - -Part - name = "DT_DETECTOR_2" - description = "DT_DETECTOR_2_DESC" - exclusions = [[ALL_DETECTORS]] - class = Detection - capacity = 75 - mountableSlotTypes = External - buildcost = 3 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 4 - tags = [ "PEDIA_PC_DETECTION" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/detector-2.png" - -Part - name = "DT_DETECTOR_1" - description = "DT_DETECTOR_1_DESC" - exclusions = [[ALL_DETECTORS]] - class = Detection - capacity = 25 - mountableSlotTypes = External - buildcost = 2 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 2 - tags = [ "PEDIA_PC_DETECTION" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/detector-1.png" - - -// Helper macro that contains all detector parts -ALL_DETECTORS -''' -[ - "DT_DETECTOR_1" - "DT_DETECTOR_2" - "DT_DETECTOR_3" - "DT_DETECTOR_4" -] -''' - -#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/FU_ANTIMATTER_TANK.focs.txt b/default/scripting/ship_parts/FU_ANTIMATTER_TANK.focs.txt deleted file mode 100644 index a4b91218cde..00000000000 --- a/default/scripting/ship_parts/FU_ANTIMATTER_TANK.focs.txt +++ /dev/null @@ -1,13 +0,0 @@ -Part - name = "FU_ANTIMATTER_TANK" - description = "FU_ANTIMATTER_TANK_DESC" - class = Fuel - capacity = 5 - mountableSlotTypes = Internal - buildcost = 3 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 6 - tags = [ "PEDIA_PC_FUEL" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/antimatter_tank.png" - -#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/FU_DEUTERIUM_TANK.focs.txt b/default/scripting/ship_parts/FU_DEUTERIUM_TANK.focs.txt deleted file mode 100644 index d1f4bf48ea2..00000000000 --- a/default/scripting/ship_parts/FU_DEUTERIUM_TANK.focs.txt +++ /dev/null @@ -1,13 +0,0 @@ -Part - name = "FU_DEUTERIUM_TANK" - description = "FU_DEUTERIUM_TANK_DESC" - class = Fuel - capacity = 2 - mountableSlotTypes = Internal - buildcost = 2 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_FUEL" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/deuterium_tank.png" - -#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/FT_BAY_1.focs.txt b/default/scripting/ship_parts/FighterBay/FT_BAY_1.focs.txt similarity index 84% rename from default/scripting/ship_parts/FT_BAY_1.focs.txt rename to default/scripting/ship_parts/FighterBay/FT_BAY_1.focs.txt index 102f4a9d1b5..c90afa3acd0 100644 --- a/default/scripting/ship_parts/FT_BAY_1.focs.txt +++ b/default/scripting/ship_parts/FighterBay/FT_BAY_1.focs.txt @@ -4,7 +4,7 @@ Part class = FighterBay capacity = 2 mountableSlotTypes = External - buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildcost = 10 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] buildtime = 1 tags = [ "PEDIA_PC_FIGHTER_BAY" ] location = OwnedBy empire = Source.Owner diff --git a/default/scripting/ship_parts/FT_BAY_KRILL.focs.txt b/default/scripting/ship_parts/FighterBay/FT_BAY_KRILL.focs.txt similarity index 100% rename from default/scripting/ship_parts/FT_BAY_KRILL.focs.txt rename to default/scripting/ship_parts/FighterBay/FT_BAY_KRILL.focs.txt diff --git a/default/scripting/ship_parts/FighterHangar/FT_HANGAR_0.focs.txt b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_0.focs.txt new file mode 100644 index 00000000000..31bf896299c --- /dev/null +++ b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_0.focs.txt @@ -0,0 +1,17 @@ +Part + name = "FT_HANGAR_0" + description = "FT_HANGAR_0_DESC" + exclusions = [ "FT_HANGAR_1" "FT_HANGAR_2" "FT_HANGAR_3" "FT_HANGAR_4" ] + class = FighterHangar + capacity = 2 + damage = 0 + combatTargets = None + mountableSlotTypes = Internal + buildcost = 10 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/fighter05.png" + +#include "/scripting/common/upkeep.macros" +//#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/FighterHangar/FT_HANGAR_1.focs.txt b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_1.focs.txt new file mode 100644 index 00000000000..b9b099d1c33 --- /dev/null +++ b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_1.focs.txt @@ -0,0 +1,35 @@ +Part + name = "FT_HANGAR_1" + description = "FT_HANGAR_1_DESC" + exclusions = [ "FT_HANGAR_0" "FT_HANGAR_2" "FT_HANGAR_3" "FT_HANGAR_4" ] + class = FighterHangar + capacity = 3 + damage = 1 + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + Fighter + ] + mountableSlotTypes = Internal + buildcost = 10 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] + location = OwnedBy empire = Source.Owner + effectsgroups = [ + EffectsGroup + scope = And [ + Source + [[EMPIRE_OWNED_SHIP_WITH_PART(FT_BAY_1)]] + ] + stackinggroup = "INTERCEPTOR_FAST_LAUNCH_EFFECT" + effects = SetMaxCapacity partname = "FT_BAY_1" value = ( + 3 + + Statistic If Condition = And [ Target OwnerHasTech Name = "SHP_FIGHTERS_2" ] + + Statistic If Condition = And [ Target OwnerHasTech Name = "SHP_FIGHTERS_3" ] + + Statistic If Condition = And [ Target OwnerHasTech Name = "SHP_FIGHTERS_4" ] + ) + ] + icon = "icons/ship_parts/fighter06.png" + +#include "/scripting/common/upkeep.macros" +#include "/scripting/techs/techs.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/FighterHangar/FT_HANGAR_2.focs.txt b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_2.focs.txt new file mode 100644 index 00000000000..7c13a8a358c --- /dev/null +++ b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_2.focs.txt @@ -0,0 +1,23 @@ +Part + name = "FT_HANGAR_2" + description = "FT_HANGAR_2_DESC" + exclusions = [ "FT_HANGAR_0" "FT_HANGAR_1" "FT_HANGAR_3" "FT_HANGAR_4" ] + class = FighterHangar + capacity = 3 + damage = 4 + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + Or [ + [[COMBAT_TARGETS_NOT_DESTROYED_SHIP]] + Fighter + ] + ] + mountableSlotTypes = Internal + buildcost = 15 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/fighter02.png" + +#include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/FighterHangar/FT_HANGAR_3.focs.txt b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_3.focs.txt new file mode 100644 index 00000000000..a571f832df5 --- /dev/null +++ b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_3.focs.txt @@ -0,0 +1,20 @@ +Part + name = "FT_HANGAR_3" + description = "FT_HANGAR_3_DESC" + exclusions = [ "FT_HANGAR_0" "FT_HANGAR_1" "FT_HANGAR_2" "FT_HANGAR_4" ] + class = FighterHangar + capacity = 2 + damage = 6 + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + [[COMBAT_TARGETS_NOT_DESTROYED_SHIP]] + ] + mountableSlotTypes = Internal + buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/fighter04.png" + +#include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/FighterHangar/FT_HANGAR_4.focs.txt b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_4.focs.txt new file mode 100644 index 00000000000..194fe572c10 --- /dev/null +++ b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_4.focs.txt @@ -0,0 +1,23 @@ +Part + name = "FT_HANGAR_4" + description = "FT_HANGAR_4_DESC" + exclusions = [ "FT_HANGAR_0" "FT_HANGAR_1" "FT_HANGAR_2" "FT_HANGAR_3" ] + class = FighterHangar + capacity = 1 + damage = 12 + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + Or [ + [[COMBAT_TARGETS_NOT_DESTROYED_SHIP]] + [[COMBAT_TARGETS_PLANET_WITH_DEFENSE]] + ] + ] + mountableSlotTypes = Internal + buildcost = 25 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/fighter01.png" + +#include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/FighterHangar/FT_HANGAR_KRILL.focs.txt b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_KRILL.focs.txt new file mode 100644 index 00000000000..c29fc7a47a6 --- /dev/null +++ b/default/scripting/ship_parts/FighterHangar/FT_HANGAR_KRILL.focs.txt @@ -0,0 +1,22 @@ +Part + name = "FT_HANGAR_KRILL" + description = "FT_HANGAR_KRILL_DESC" + class = FighterHangar + capacity = 30 + damage = 1 + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + Or [ + [[COMBAT_TARGETS_NOT_DESTROYED_SHIP]] + Fighter + ] + ] + mountableSlotTypes = Internal + buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/krill.png" + +#include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/FU_BASIC_TANK.focs.txt b/default/scripting/ship_parts/Fuel/FU_BASIC_TANK.focs.txt similarity index 87% rename from default/scripting/ship_parts/FU_BASIC_TANK.focs.txt rename to default/scripting/ship_parts/Fuel/FU_BASIC_TANK.focs.txt index 5a6790f7036..da413cfc858 100644 --- a/default/scripting/ship_parts/FU_BASIC_TANK.focs.txt +++ b/default/scripting/ship_parts/Fuel/FU_BASIC_TANK.focs.txt @@ -1,8 +1,9 @@ Part name = "FU_BASIC_TANK" description = "FU_BASIC_TANK_DESC" + exclusions = "SH_COLONY_BASE" class = Fuel - capacity = 1 + capacity = 0.5 mountableSlotTypes = Internal buildcost = 1 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] buildtime = 1 diff --git a/default/scripting/ship_parts/FU_RAMSCOOP.focs.txt b/default/scripting/ship_parts/Fuel/FU_RAMSCOOP.focs.txt similarity index 82% rename from default/scripting/ship_parts/FU_RAMSCOOP.focs.txt rename to default/scripting/ship_parts/Fuel/FU_RAMSCOOP.focs.txt index 5c3b4a51257..7d5796ef5d3 100644 --- a/default/scripting/ship_parts/FU_RAMSCOOP.focs.txt +++ b/default/scripting/ship_parts/Fuel/FU_RAMSCOOP.focs.txt @@ -1,8 +1,10 @@ Part name = "FU_RAMSCOOP" description = "FU_RAMSCOOP_DESC" + exclusions = "SH_COLONY_BASE" class = Fuel capacity = 0 + NoDefaultCapacityEffect mountableSlotTypes = External buildcost = 10 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] buildtime = 6 @@ -13,6 +15,7 @@ Part scope = Source activation = Stationary stackinggroup = "RAMSCOOP_STACK" + // Update ship_hulls.macros if this number changes effects = SetFuel value = Value + 0.1 icon = "icons/ship_parts/antimatter_tank.png" diff --git a/default/scripting/ship_parts/FU_ZERO_FUEL.focs.txt b/default/scripting/ship_parts/Fuel/FU_ZERO_FUEL.focs.txt similarity index 89% rename from default/scripting/ship_parts/FU_ZERO_FUEL.focs.txt rename to default/scripting/ship_parts/Fuel/FU_ZERO_FUEL.focs.txt index 2bb75017b4a..53d9041289e 100644 --- a/default/scripting/ship_parts/FU_ZERO_FUEL.focs.txt +++ b/default/scripting/ship_parts/Fuel/FU_ZERO_FUEL.focs.txt @@ -1,8 +1,10 @@ Part name = "FU_ZERO_FUEL" description = "FU_ZERO_FUEL_DESC" + exclusions = "SH_COLONY_BASE" class = Fuel capacity = 0 + NoDefaultCapacityEffect mountableSlotTypes = Core buildcost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] buildtime = 5 @@ -11,7 +13,6 @@ Part effectsgroups = EffectsGroup scope = Source - activation = Source effects = SetFuel value = Target.MaxFuel icon = "icons/ship_parts/zero-point-generator.png" diff --git a/default/scripting/ship_parts/SP_ASH.focs.txt b/default/scripting/ship_parts/General/SP_ASH.focs.txt similarity index 97% rename from default/scripting/ship_parts/SP_ASH.focs.txt rename to default/scripting/ship_parts/General/SP_ASH.focs.txt index 51960527e0c..c5feb3ba9b6 100644 --- a/default/scripting/ship_parts/SP_ASH.focs.txt +++ b/default/scripting/ship_parts/General/SP_ASH.focs.txt @@ -37,7 +37,6 @@ Part EffectsGroup scope = HasSpecial name = "CLOUD_COVER_MASTER_SPECIAL" - activation = Source effects = [ RemoveSpecial name = "CLOUD_COVER_MASTER_SPECIAL" AddSpecial name = "VOLCANIC_ASH_MASTER_SPECIAL" diff --git a/default/scripting/ship_parts/SP_CLOUD.focs.txt b/default/scripting/ship_parts/General/SP_CLOUD.focs.txt similarity index 100% rename from default/scripting/ship_parts/SP_CLOUD.focs.txt rename to default/scripting/ship_parts/General/SP_CLOUD.focs.txt diff --git a/default/scripting/ship_parts/SP_DIM.focs.txt b/default/scripting/ship_parts/General/SP_DIM.focs.txt similarity index 95% rename from default/scripting/ship_parts/SP_DIM.focs.txt rename to default/scripting/ship_parts/General/SP_DIM.focs.txt index b50abe2e2fa..4e71cdcb42b 100644 --- a/default/scripting/ship_parts/SP_DIM.focs.txt +++ b/default/scripting/ship_parts/General/SP_DIM.focs.txt @@ -29,7 +29,6 @@ Part EffectsGroup scope = HasSpecial name = "VOLCANIC_ASH_MASTER_SPECIAL" - activation = Source effects = [ RemoveSpecial name = "VOLCANIC_ASH_MASTER_SPECIAL" AddSpecial name = "DIM_RIFT_MASTER_SPECIAL" @@ -37,7 +36,6 @@ Part EffectsGroup scope = HasSpecial name = "CLOUD_COVER_MASTER_SPECIAL" - activation = Source effects = [ RemoveSpecial name = "CLOUD_COVER_MASTER_SPECIAL" AddSpecial name = "DIM_RIFT_MASTER_SPECIAL" diff --git a/default/scripting/ship_parts/SP_DISTORTION_MODULATOR.focs.txt b/default/scripting/ship_parts/General/SP_DISTORTION_MODULATOR.focs.txt similarity index 95% rename from default/scripting/ship_parts/SP_DISTORTION_MODULATOR.focs.txt rename to default/scripting/ship_parts/General/SP_DISTORTION_MODULATOR.focs.txt index 655b55105af..3f063321ef9 100644 --- a/default/scripting/ship_parts/SP_DISTORTION_MODULATOR.focs.txt +++ b/default/scripting/ship_parts/General/SP_DISTORTION_MODULATOR.focs.txt @@ -10,7 +10,6 @@ Part effectsgroups = EffectsGroup scope = WithinDistance distance = 0 condition = Source - activation = Source stackinggroup = "SP_DISTORTION_MODULATOR_STACK" effects = SetStealth value = Value - 20 icon = "icons/ship_parts/distortion_modulator.png" diff --git a/default/scripting/ship_parts/SP_KRILL_SPAWNER.focs.txt b/default/scripting/ship_parts/General/SP_KRILL_SPAWNER.focs.txt similarity index 91% rename from default/scripting/ship_parts/SP_KRILL_SPAWNER.focs.txt rename to default/scripting/ship_parts/General/SP_KRILL_SPAWNER.focs.txt index 21da1fa8646..8f92c64a583 100644 --- a/default/scripting/ship_parts/SP_KRILL_SPAWNER.focs.txt +++ b/default/scripting/ship_parts/General/SP_KRILL_SPAWNER.focs.txt @@ -38,9 +38,9 @@ Part EffectsGroup scope = Source activation = And [ - Not DesignHasPartClass Low=1 High=999 Class=Stealth - Not Armed - ] + Not DesignHasPartClass Low=1 High=999 Class=Stealth + Not Armed + ] effects = SetStealth value = Value + 40 ] diff --git a/default/scripting/ship_parts/SP_NOVA_BOMB.focs.txt b/default/scripting/ship_parts/General/SP_NOVA_BOMB.focs.txt similarity index 100% rename from default/scripting/ship_parts/SP_NOVA_BOMB.focs.txt rename to default/scripting/ship_parts/General/SP_NOVA_BOMB.focs.txt diff --git a/default/scripting/ship_parts/SP_PLANET_BEACON.focs.txt b/default/scripting/ship_parts/General/SP_PLANET_BEACON.focs.txt similarity index 100% rename from default/scripting/ship_parts/SP_PLANET_BEACON.focs.txt rename to default/scripting/ship_parts/General/SP_PLANET_BEACON.focs.txt diff --git a/default/scripting/ship_parts/SP_SOLAR_CONCENTRATOR.focs.txt b/default/scripting/ship_parts/General/SP_SOLAR_CONCENTRATOR.focs.txt similarity index 95% rename from default/scripting/ship_parts/SP_SOLAR_CONCENTRATOR.focs.txt rename to default/scripting/ship_parts/General/SP_SOLAR_CONCENTRATOR.focs.txt index ed705d85ad0..0ead505774d 100644 --- a/default/scripting/ship_parts/SP_SOLAR_CONCENTRATOR.focs.txt +++ b/default/scripting/ship_parts/General/SP_SOLAR_CONCENTRATOR.focs.txt @@ -8,6 +8,7 @@ Part tags = [ "PEDIA_PC_GENERAL" ] location = OwnedBy empire = Source.Owner effectsgroups = [ + /// @content_tag{ORGANIC_HULL} Ships with this part contribute towards increased weapon damage of other Ships with this part within 0 starlane jumps EffectsGroup scope = Source activation = Star type = [ Red ] diff --git a/default/scripting/ship_parts/SP_VOID.focs.txt b/default/scripting/ship_parts/General/SP_VOID.focs.txt similarity index 93% rename from default/scripting/ship_parts/SP_VOID.focs.txt rename to default/scripting/ship_parts/General/SP_VOID.focs.txt index 385a2e8ff0f..dc59bfb61ba 100644 --- a/default/scripting/ship_parts/SP_VOID.focs.txt +++ b/default/scripting/ship_parts/General/SP_VOID.focs.txt @@ -21,7 +21,6 @@ Part EffectsGroup scope = HasSpecial name = "DIM_RIFT_MASTER_SPECIAL" - activation = Source effects = [ RemoveSpecial name = "DIM_RIFT_MASTER_SPECIAL" AddSpecial name = "VOID_MASTER_SPECIAL" @@ -29,7 +28,6 @@ Part EffectsGroup scope = HasSpecial name = "VOLCANIC_ASH_MASTER_SPECIAL" - activation = Source effects = [ RemoveSpecial name = "VOLCANIC_ASH_MASTER_SPECIAL" AddSpecial name = "VOID_MASTER_SPECIAL" @@ -37,7 +35,6 @@ Part EffectsGroup scope = HasSpecial name = "CLOUD_COVER_MASTER_SPECIAL" - activation = Source effects = [ RemoveSpecial name = "CLOUD_COVER_MASTER_SPECIAL" AddSpecial name = "VOID_MASTER_SPECIAL" diff --git a/default/scripting/ship_parts/Hangars.focs.txt b/default/scripting/ship_parts/Hangars.focs.txt deleted file mode 100644 index 2e1c092a4e9..00000000000 --- a/default/scripting/ship_parts/Hangars.focs.txt +++ /dev/null @@ -1,84 +0,0 @@ -Part - name = "FT_HANGAR_0" - description = "FT_HANGAR_0_DESC" - exclusions = [ "FT_HANGAR_1" "FT_HANGAR_2" "FT_HANGAR_3" "FT_HANGAR_4" ] - class = FighterHangar - capacity = 2 - damage = 0 - mountableSlotTypes = Internal - buildcost = 10 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/fighter05.png" - -Part - name = "FT_HANGAR_1" - description = "FT_HANGAR_1_DESC" - exclusions = [ "FT_HANGAR_0" "FT_HANGAR_2" "FT_HANGAR_3" "FT_HANGAR_4" ] - class = FighterHangar - capacity = 4 - damage = 1 - mountableSlotTypes = Internal - buildcost = 15 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/fighter06.png" - -Part - name = "FT_HANGAR_2" - description = "FT_HANGAR_2_DESC" - exclusions = [ "FT_HANGAR_0" "FT_HANGAR_1" "FT_HANGAR_3" "FT_HANGAR_4" ] - class = FighterHangar - capacity = 3 - damage = 3 - mountableSlotTypes = Internal - buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/fighter02.png" - -Part - name = "FT_HANGAR_3" - description = "FT_HANGAR_3_DESC" - exclusions = [ "FT_HANGAR_0" "FT_HANGAR_1" "FT_HANGAR_2" "FT_HANGAR_4" ] - class = FighterHangar - capacity = 2 - damage = 5 - mountableSlotTypes = Internal - buildcost = 25 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/fighter04.png" - -Part - name = "FT_HANGAR_4" - description = "FT_HANGAR_4_DESC" - exclusions = [ "FT_HANGAR_0" "FT_HANGAR_1" "FT_HANGAR_2" "FT_HANGAR_3" ] - class = FighterHangar - capacity = 1 - damage = 10 - mountableSlotTypes = Internal - buildcost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/fighter01.png" - -Part - name = "FT_KRILL" - description = "FT_KRILL_DESC" - class = FighterHangar - capacity = 30 - damage = 1 - mountableSlotTypes = Internal - buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_FIGHTER_HANGAR" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/krill.png" - -#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Shield/SH_BLACK.focs.txt b/default/scripting/ship_parts/Shield/SH_BLACK.focs.txt new file mode 100644 index 00000000000..9329532b4cf --- /dev/null +++ b/default/scripting/ship_parts/Shield/SH_BLACK.focs.txt @@ -0,0 +1,16 @@ +Part + name = "SH_BLACK" + description = "SH_BLACK_DESC" + exclusions = [[ALL_SHIELDS]] + class = Shield + capacity = 15 + mountableSlotTypes = Internal + buildcost = 120 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 6 + tags = [ "PEDIA_PC_SHIELD" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/blackshield.png" + +#include "shield.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Shield/SH_DEFENSE_GRID.focs.txt b/default/scripting/ship_parts/Shield/SH_DEFENSE_GRID.focs.txt new file mode 100644 index 00000000000..0c3f4ee7d81 --- /dev/null +++ b/default/scripting/ship_parts/Shield/SH_DEFENSE_GRID.focs.txt @@ -0,0 +1,16 @@ +Part + name = "SH_DEFENSE_GRID" + description = "SH_DEFENSE_GRID_DESC" + exclusions = [[ALL_SHIELDS]] + class = Shield + capacity = 3 + mountableSlotTypes = Internal + buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 3 + tags = [ "PEDIA_PC_SHIELD" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/defense_grid.png" + +#include "shield.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Shield/SH_DEFLECTOR.focs.txt b/default/scripting/ship_parts/Shield/SH_DEFLECTOR.focs.txt new file mode 100644 index 00000000000..33f3cb226dd --- /dev/null +++ b/default/scripting/ship_parts/Shield/SH_DEFLECTOR.focs.txt @@ -0,0 +1,16 @@ +Part + name = "SH_DEFLECTOR" + description = "SH_DEFLECTOR_DESC" + exclusions = [[ALL_SHIELDS]] + class = Shield + capacity = 5 + mountableSlotTypes = Internal + buildcost = 35 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 4 + tags = [ "PEDIA_PC_SHIELD" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/deflector_shield.png" + +#include "shield.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Shield/SH_MULTISPEC.focs.txt b/default/scripting/ship_parts/Shield/SH_MULTISPEC.focs.txt new file mode 100644 index 00000000000..e3b59568033 --- /dev/null +++ b/default/scripting/ship_parts/Shield/SH_MULTISPEC.focs.txt @@ -0,0 +1,23 @@ +Part + name = "SH_MULTISPEC" + description = "SH_MULTISPEC_DESC" + exclusions = [[ALL_SHIELDS]] + class = Shield + capacity = 10 + mountableSlotTypes = Internal + buildcost = 80 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 8 + tags = [ "PEDIA_PC_SHIELD" ] + location = OwnedBy empire = Source.Owner + effectsgroups = [ + EffectsGroup + scope = Source + activation = Star type = [Red Orange Yellow White Blue] + stackinggroup = "STEALTH_SOLAR_STACK" + effects = SetStealth value = Value + 60 + ] + icon = "icons/ship_parts/multi-spectral.png" + +#include "shield.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Shield/SH_PLASMA.focs.txt b/default/scripting/ship_parts/Shield/SH_PLASMA.focs.txt new file mode 100644 index 00000000000..150287e7226 --- /dev/null +++ b/default/scripting/ship_parts/Shield/SH_PLASMA.focs.txt @@ -0,0 +1,16 @@ +Part + name = "SH_PLASMA" + description = "SH_PLASMA_DESC" + exclusions = [[ALL_SHIELDS]] + class = Shield + capacity = 9 + mountableSlotTypes = Internal + buildcost = 60 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 5 + tags = [ "PEDIA_PC_SHIELD" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/plasma_shield.png" + +#include "shield.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/SH_ROBOTIC_INTERFACE_SHIELDS.focs.txt b/default/scripting/ship_parts/Shield/SH_ROBOTIC_INTERFACE_SHIELDS.focs.txt similarity index 56% rename from default/scripting/ship_parts/SH_ROBOTIC_INTERFACE_SHIELDS.focs.txt rename to default/scripting/ship_parts/Shield/SH_ROBOTIC_INTERFACE_SHIELDS.focs.txt index 4c1a8a4c475..012f247e077 100644 --- a/default/scripting/ship_parts/SH_ROBOTIC_INTERFACE_SHIELDS.focs.txt +++ b/default/scripting/ship_parts/Shield/SH_ROBOTIC_INTERFACE_SHIELDS.focs.txt @@ -4,18 +4,22 @@ Part class = Shield capacity = 0 mountableSlotTypes = [Internal] - buildcost = 70 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildcost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] buildtime = 4 tags = [ "PEDIA_PC_SHIELD" ] location = And [ OwnedBy empire = Source.Owner + /// @content_tag{ROBOTIC} Allows a location to produce this ShipPart HasTag name = "ROBOTIC" ] effectsgroups = + /// @content_tag{ROBOTIC} Ships with this part and ROBOTIC_HULL tag contribute towards increased shields of other ships with this part within 0 starlane jumps + /// @content_tag{ROBOTIC_HULL} Ships with this part and ROBOTIC tag contribute towards increased shields of other ships with this part within 0 starlane jumps EffectsGroup scope = And [ Source HasTag name = "ROBOTIC" + /// @content_tag{ROBOTIC_HULL} Combined with ROBOTIC tag, allows shield increase from this ShipPart HasTag name = "ROBOTIC_HULL" ] activation = ([[ROBOTIC_SHIELD_EFFECT]] >= [[BEST_SHIELD_EFFECT]]) @@ -48,6 +52,20 @@ min( ) ''' -#include "stacking.macros" +BEST_SHIELD_EFFECT +''' +max(max(max(max( + min(1, PartsInShipDesign Name = "SH_DEFENSE_GRID" design = Source.DesignID) + * PartCapacity name = "SH_DEFENSE_GRID", + min(1, PartsInShipDesign Name = "SH_DEFLECTOR" design = Source.DesignID) + * PartCapacity name = "SH_DEFLECTOR"), + min(1, PartsInShipDesign Name = "SH_PLASMA" design = Source.DesignID) + * PartCapacity name = "SH_PLASMA"), + min(1, PartsInShipDesign Name = "SH_BLACK" design = Source.DesignID) + * PartCapacity name = "SH_BLACK"), + min(1, PartsInShipDesign Name = "SH_MULTISPEC" design = Source.DesignID) + * PartCapacity name = "SH_MULTISPEC" + ) +''' #include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Shield/shield.macros b/default/scripting/ship_parts/Shield/shield.macros new file mode 100644 index 00000000000..cc481a70058 --- /dev/null +++ b/default/scripting/ship_parts/Shield/shield.macros @@ -0,0 +1,11 @@ +// Helper macro containing the name of all shields +ALL_SHIELDS +''' +[ + "SH_MULTISPEC" + "SH_BLACK" + "SH_PLASMA" + "SH_DEFLECTOR" + "SH_DEFENSE_GRID" +] +''' diff --git a/default/scripting/ship_parts/Shields.focs.txt b/default/scripting/ship_parts/Shields.focs.txt deleted file mode 100644 index faf3f36d34d..00000000000 --- a/default/scripting/ship_parts/Shields.focs.txt +++ /dev/null @@ -1,86 +0,0 @@ -Part - name = "SH_DEFENSE_GRID" - description = "SH_DEFENSE_GRID_DESC" - exclusions = [[ALL_SHIELDS]] - class = Shield - capacity = 3 - mountableSlotTypes = Internal - buildcost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 2 - tags = [ "PEDIA_PC_SHIELD" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/defense_grid.png" - -Part - name = "SH_DEFLECTOR" - description = "SH_DEFLECTOR_DESC" - exclusions = [[ALL_SHIELDS]] - class = Shield - capacity = 5 - mountableSlotTypes = Internal - buildcost = 50 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 4 - tags = [ "PEDIA_PC_SHIELD" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/deflector_shield.png" - -Part - name = "SH_PLASMA" - description = "SH_PLASMA_DESC" - exclusions = [[ALL_SHIELDS]] - class = Shield - capacity = 9 - mountableSlotTypes = Internal - buildcost = 90 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 5 - tags = [ "PEDIA_PC_SHIELD" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/plasma_shield.png" - -Part - name = "SH_BLACK" - description = "SH_BLACK_DESC" - exclusions = [[ALL_SHIELDS]] - class = Shield - capacity = 15 - mountableSlotTypes = Internal - buildcost = 150 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 6 - tags = [ "PEDIA_PC_SHIELD" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/blackshield.png" - -Part - name = "SH_MULTISPEC" - description = "SH_MULTISPEC_DESC" - exclusions = [[ALL_SHIELDS]] - class = Shield - capacity = 10 - mountableSlotTypes = Internal - buildcost = 100 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 8 - tags = [ "PEDIA_PC_SHIELD" ] - location = OwnedBy empire = Source.Owner - effectsgroups = [ - EffectsGroup - scope = Source - activation = Star type = [Red Orange Yellow White Blue] - stackinggroup = "STEALTH_SOLAR_STACK" - effects = SetStealth value = Value + 60 - ] - icon = "icons/ship_parts/multi-spectral.png" - - -// Helper macro containing the name of all shields -ALL_SHIELDS -''' -[ - "SH_MULTISPEC" - "SH_BLACK" - "SH_PLASMA" - "SH_DEFLECTOR" - "SH_DEFENSE_GRID" -] -''' - -#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_ARC_DISRUPTOR.focs.txt b/default/scripting/ship_parts/ShortRange/SR_ARC_DISRUPTOR.focs.txt new file mode 100644 index 00000000000..0c75af5e6eb --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_ARC_DISRUPTOR.focs.txt @@ -0,0 +1,20 @@ +Part + name = "SR_ARC_DISRUPTOR" + description = "SR_ARC_DISRUPTOR_DESC" + class = ShortRange + damage = 2 + shots = 3 + NoDefaultCapacityEffect + mountableSlotTypes = External + buildcost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + effectsgroups = + [[WEAPON_BASE_DEFAULT_EFFECTS(SR_ARC_DISRUPTOR)]] + icon = "icons/ship_parts/pulse-laser.png" + +#include "shortrange.macros" + +#include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_ICE_BEAM.focs.txt b/default/scripting/ship_parts/ShortRange/SR_ICE_BEAM.focs.txt new file mode 100644 index 00000000000..9709b2736ee --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_ICE_BEAM.focs.txt @@ -0,0 +1,15 @@ +Part + name = "SR_ICE_BEAM" + description = "SR_ICE_BEAM_DESC" + class = ShortRange + damage = 9 + mountableSlotTypes = External + buildcost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 2 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/snowflake_laser.png" + +#include "shortrange.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_JAWS.focs.txt b/default/scripting/ship_parts/ShortRange/SR_JAWS.focs.txt new file mode 100644 index 00000000000..53c0d57aaee --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_JAWS.focs.txt @@ -0,0 +1,15 @@ +Part + name = "SR_JAWS" + description = "SR_JAWS_DESC" + class = ShortRange + damage = 5 + mountableSlotTypes = External + buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/teeth.png" + +#include "shortrange.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_PLASMA_DISCHARGE.focs.txt b/default/scripting/ship_parts/ShortRange/SR_PLASMA_DISCHARGE.focs.txt new file mode 100644 index 00000000000..f3cde0c2d42 --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_PLASMA_DISCHARGE.focs.txt @@ -0,0 +1,15 @@ +Part + name = "SR_PLASMA_DISCHARGE" + description = "SR_PLASMA_DISCHARGE_DESC" + class = ShortRange + damage = 20 + mountableSlotTypes = External + buildcost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 3 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/flame_thrower.png" + +#include "shortrange.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/SR_SPINAL_ANTIMATTER.focs.txt b/default/scripting/ship_parts/ShortRange/SR_SPINAL_ANTIMATTER.focs.txt similarity index 61% rename from default/scripting/ship_parts/SR_SPINAL_ANTIMATTER.focs.txt rename to default/scripting/ship_parts/ShortRange/SR_SPINAL_ANTIMATTER.focs.txt index b2b7ef6915b..233c3cea894 100644 --- a/default/scripting/ship_parts/SR_SPINAL_ANTIMATTER.focs.txt +++ b/default/scripting/ship_parts/ShortRange/SR_SPINAL_ANTIMATTER.focs.txt @@ -3,6 +3,13 @@ Part description = "SR_SPINAL_ANTIMATTER_DESC" class = ShortRange damage = 100 + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + Or [ + [[COMBAT_TARGETS_NOT_DESTROYED_SHIP]] + [[COMBAT_TARGETS_PLANET_WITH_DEFENSE]] + ] + ] mountableSlotTypes = Core buildcost = 250 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] buildtime = 4 @@ -10,4 +17,7 @@ Part location = OwnedBy empire = Source.Owner icon = "icons/ship_parts/spinal_antimatter.png" +#include "shortrange.macros" + #include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_SPINES.focs.txt b/default/scripting/ship_parts/ShortRange/SR_SPINES.focs.txt new file mode 100644 index 00000000000..843d0bda0bc --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_SPINES.focs.txt @@ -0,0 +1,15 @@ +Part + name = "SR_SPINES" + description = "SR_SPINES_DESC" + class = ShortRange + damage = 20 + mountableSlotTypes = External + buildcost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 3 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/claw.png" + +#include "shortrange.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_TENTACLE.focs.txt b/default/scripting/ship_parts/ShortRange/SR_TENTACLE.focs.txt new file mode 100644 index 00000000000..20a2d9d980d --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_TENTACLE.focs.txt @@ -0,0 +1,15 @@ +Part + name = "SR_TENTACLE" + description = "SR_TENTACLE_DESC" + class = ShortRange + damage = 5 + mountableSlotTypes = External + buildcost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 2 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/tentacle.png" + +#include "shortrange.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_WEAPON_0_1.focs.txt b/default/scripting/ship_parts/ShortRange/SR_WEAPON_0_1.focs.txt new file mode 100644 index 00000000000..c1eb62ce322 --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_WEAPON_0_1.focs.txt @@ -0,0 +1,23 @@ +Part + name = "SR_WEAPON_0_1" + description = "SR_WEAPON_0_1_DESC" + class = ShortRange + damage = 1 + shots = 3 + NoDefaultCapacityEffect + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + Fighter + ] + mountableSlotTypes = External + buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + effectsgroups = + [[WEAPON_BASE_DEFAULT_EFFECTS(SR_WEAPON_0_1)]] + icon = "icons/ship_parts/flak.png" + +#include "shortrange.macros" +#include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_WEAPON_1_1.focs.txt b/default/scripting/ship_parts/ShortRange/SR_WEAPON_1_1.focs.txt new file mode 100644 index 00000000000..b585ccf738b --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_WEAPON_1_1.focs.txt @@ -0,0 +1,26 @@ +Part + name = "SR_WEAPON_1_1" + description = "SR_WEAPON_1_1_DESC" + class = ShortRange + damage = 3 + NoDefaultCapacityEffect + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + Or [ + [[COMBAT_TARGETS_NOT_DESTROYED_SHIP]] + [[COMBAT_TARGETS_PLANET_WITH_DEFENSE]] + ] + ] + mountableSlotTypes = External + buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + effectsgroups = + [[WEAPON_BASE_DEFAULT_EFFECTS(SR_WEAPON_1_1)]] + icon = "icons/ship_parts/mass-driver.png" + +#include "shortrange.macros" + +#include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_WEAPON_2_1.focs.txt b/default/scripting/ship_parts/ShortRange/SR_WEAPON_2_1.focs.txt new file mode 100644 index 00000000000..b01dd9c42a6 --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_WEAPON_2_1.focs.txt @@ -0,0 +1,26 @@ +Part + name = "SR_WEAPON_2_1" + description = "SR_WEAPON_2_1_DESC" + class = ShortRange + damage = 5 + NoDefaultCapacityEffect + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + Or [ + [[COMBAT_TARGETS_NOT_DESTROYED_SHIP]] + [[COMBAT_TARGETS_PLANET_WITH_DEFENSE]] + ] + ] + mountableSlotTypes = External + buildcost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 2 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + effectsgroups = + [[WEAPON_BASE_DEFAULT_EFFECTS(SR_WEAPON_2_1)]] + icon = "icons/ship_parts/laser.png" + +#include "shortrange.macros" + +#include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_WEAPON_3_1.focs.txt b/default/scripting/ship_parts/ShortRange/SR_WEAPON_3_1.focs.txt new file mode 100644 index 00000000000..9b8a84a082a --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_WEAPON_3_1.focs.txt @@ -0,0 +1,26 @@ +Part + name = "SR_WEAPON_3_1" + description = "SR_WEAPON_3_1_DESC" + class = ShortRange + damage = 9 + NoDefaultCapacityEffect + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + Or [ + [[COMBAT_TARGETS_NOT_DESTROYED_SHIP]] + [[COMBAT_TARGETS_PLANET_WITH_DEFENSE]] + ] + ] + mountableSlotTypes = External + buildcost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 3 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + effectsgroups = + [[WEAPON_BASE_DEFAULT_EFFECTS(SR_WEAPON_3_1)]] + icon = "icons/ship_parts/plasma.png" + +#include "shortrange.macros" + +#include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/ShortRange/SR_WEAPON_4_1.focs.txt b/default/scripting/ship_parts/ShortRange/SR_WEAPON_4_1.focs.txt new file mode 100644 index 00000000000..fe4aa1d52f1 --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/SR_WEAPON_4_1.focs.txt @@ -0,0 +1,26 @@ +Part + name = "SR_WEAPON_4_1" + description = "SR_WEAPON_4_1_DESC" + class = ShortRange + damage = 15 + NoDefaultCapacityEffect + combatTargets = And [ + [[COMBAT_TARGETS_VISIBLE_ENEMY]] + Or [ + [[COMBAT_TARGETS_NOT_DESTROYED_SHIP]] + [[COMBAT_TARGETS_PLANET_WITH_DEFENSE]] + ] + ] + mountableSlotTypes = External + buildcost = 60 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 4 + tags = [ "PEDIA_PC_DIRECT_WEAPON" ] + location = OwnedBy empire = Source.Owner + effectsgroups = + [[WEAPON_BASE_DEFAULT_EFFECTS(SR_WEAPON_4_1)]] + icon = "icons/ship_parts/death-ray.png" + +#include "shortrange.macros" + +#include "/scripting/common/upkeep.macros" +#include "/scripting/ship_parts/targeting.macros" diff --git a/default/scripting/ship_parts/ShortRange/shortrange.macros b/default/scripting/ship_parts/ShortRange/shortrange.macros new file mode 100644 index 00000000000..65759d9bbdc --- /dev/null +++ b/default/scripting/ship_parts/ShortRange/shortrange.macros @@ -0,0 +1,16 @@ +// If unowned or owner has not unlocked the ship part yet. +// @1@ Weapon name +WEAPON_BASE_DEFAULT_EFFECTS +''' EffectsGroup + scope = Source + activation = Or [ + Unowned + Not OwnerHasShipPartAvailable name = "@1@" + ] + stackinggroup = "WEAPON_BASE_DEFAULT_EFFECTS_@1@" + accountinglabel = "@1@" + effects = [ + SetMaxCapacity partname = "@1@" value = Value + PartCapacity name = "@1@" + SetMaxSecondaryStat partname = "@1@" value = Value + PartSecondaryStat name = "@1@" + ] +''' diff --git a/default/scripting/ship_parts/FU_IMPROVED_ENGINE_COUPLINGS.focs.txt b/default/scripting/ship_parts/Speed/FU_IMPROVED_ENGINE_COUPLINGS.focs.txt similarity index 96% rename from default/scripting/ship_parts/FU_IMPROVED_ENGINE_COUPLINGS.focs.txt rename to default/scripting/ship_parts/Speed/FU_IMPROVED_ENGINE_COUPLINGS.focs.txt index 1a35f5fb25a..c356e096e4f 100644 --- a/default/scripting/ship_parts/FU_IMPROVED_ENGINE_COUPLINGS.focs.txt +++ b/default/scripting/ship_parts/Speed/FU_IMPROVED_ENGINE_COUPLINGS.focs.txt @@ -6,6 +6,7 @@ Part "FU_N_DIMENSIONAL_ENGINE_MATRIX" "FU_SINGULARITY_ENGINE_CORE" "FU_TRANSPATIAL_DRIVE" + "SH_COLONY_BASE" ] class = Speed capacity = 20 diff --git a/default/scripting/ship_parts/FU_N_DIMENSIONAL_ENGINE_MATRIX.focs.txt b/default/scripting/ship_parts/Speed/FU_N_DIMENSIONAL_ENGINE_MATRIX.focs.txt similarity index 96% rename from default/scripting/ship_parts/FU_N_DIMENSIONAL_ENGINE_MATRIX.focs.txt rename to default/scripting/ship_parts/Speed/FU_N_DIMENSIONAL_ENGINE_MATRIX.focs.txt index 5a0744ad4b7..c1fe8ef74ce 100644 --- a/default/scripting/ship_parts/FU_N_DIMENSIONAL_ENGINE_MATRIX.focs.txt +++ b/default/scripting/ship_parts/Speed/FU_N_DIMENSIONAL_ENGINE_MATRIX.focs.txt @@ -6,6 +6,7 @@ Part "FU_N_DIMENSIONAL_ENGINE_MATRIX" "FU_SINGULARITY_ENGINE_CORE" "FU_TRANSPATIAL_DRIVE" + "SH_COLONY_BASE" ] class = Speed capacity = 40 diff --git a/default/scripting/ship_parts/FU_SINGULARITY_ENGINE_CORE.focs.txt b/default/scripting/ship_parts/Speed/FU_SINGULARITY_ENGINE_CORE.focs.txt similarity index 96% rename from default/scripting/ship_parts/FU_SINGULARITY_ENGINE_CORE.focs.txt rename to default/scripting/ship_parts/Speed/FU_SINGULARITY_ENGINE_CORE.focs.txt index 85687acde46..602846294ee 100644 --- a/default/scripting/ship_parts/FU_SINGULARITY_ENGINE_CORE.focs.txt +++ b/default/scripting/ship_parts/Speed/FU_SINGULARITY_ENGINE_CORE.focs.txt @@ -6,6 +6,7 @@ Part "FU_N_DIMENSIONAL_ENGINE_MATRIX" "FU_SINGULARITY_ENGINE_CORE" "FU_TRANSPATIAL_DRIVE" + "SH_COLONY_BASE" ] class = Speed capacity = 80 diff --git a/default/scripting/ship_parts/FU_TRANSPATIAL_DRIVE.focs.txt b/default/scripting/ship_parts/Speed/FU_TRANSPATIAL_DRIVE.focs.txt similarity index 79% rename from default/scripting/ship_parts/FU_TRANSPATIAL_DRIVE.focs.txt rename to default/scripting/ship_parts/Speed/FU_TRANSPATIAL_DRIVE.focs.txt index 77c35cca102..bcf9d390594 100644 --- a/default/scripting/ship_parts/FU_TRANSPATIAL_DRIVE.focs.txt +++ b/default/scripting/ship_parts/Speed/FU_TRANSPATIAL_DRIVE.focs.txt @@ -6,6 +6,7 @@ Part "FU_N_DIMENSIONAL_ENGINE_MATRIX" "FU_SINGULARITY_ENGINE_CORE" "FU_TRANSPATIAL_DRIVE" + "SH_COLONY_BASE" ] class = Speed capacity = 60 @@ -20,18 +21,18 @@ Part effectsgroups = [ EffectsGroup scope = Source - activation = (1 <= [[BEST_CLOAK_EFFECT]] <= 40) + activation = (1 <= [[BEST_STEALTH_EFFECT]] <= 40) stackinggroup = "ENGINE_STEALTH_PART_STACK1" accountinglabel = "TRANSPATIAL_CLOAK_INTERACTION" - effects = SetStealth value = Value - [[BEST_CLOAK_EFFECT]] + effects = SetStealth value = Value - [[BEST_STEALTH_EFFECT]] EffectsGroup scope = Source - activation = (40 >= [[BEST_CLOAK_EFFECT]]) + activation = (40 >= [[BEST_STEALTH_EFFECT]]) stackinggroup = "ENGINE_STEALTH_PART_STACK2" effects = SetStealth value = Value + 40 ] icon = "icons/ship_parts/engine-4.png" -#include "stacking.macros" +#include "../Stealth/best_stealth.macros" #include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Stealth/ST_CLOAK_1.focs.txt b/default/scripting/ship_parts/Stealth/ST_CLOAK_1.focs.txt new file mode 100644 index 00000000000..0655f8070f0 --- /dev/null +++ b/default/scripting/ship_parts/Stealth/ST_CLOAK_1.focs.txt @@ -0,0 +1,16 @@ +Part + name = "ST_CLOAK_1" + description = "ST_CLOAK_1_DESC" + exclusions = [[ALL_CLOAKS]] + class = Stealth + capacity = 20 + mountableSlotTypes = Internal + buildcost = 2 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 3 + tags = [ "PEDIA_PC_STEALTH" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/cloak-1.png" + +#include "stealth.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Stealth/ST_CLOAK_2.focs.txt b/default/scripting/ship_parts/Stealth/ST_CLOAK_2.focs.txt new file mode 100644 index 00000000000..711ae39c3db --- /dev/null +++ b/default/scripting/ship_parts/Stealth/ST_CLOAK_2.focs.txt @@ -0,0 +1,16 @@ +Part + name = "ST_CLOAK_2" + description = "ST_CLOAK_2_DESC" + exclusions = [[ALL_CLOAKS]] + class = Stealth + capacity = 40 + mountableSlotTypes = Internal + buildcost = 5 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 5 + tags = [ "PEDIA_PC_STEALTH" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/cloak-2.png" + +#include "stealth.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Stealth/ST_CLOAK_3.focs.txt b/default/scripting/ship_parts/Stealth/ST_CLOAK_3.focs.txt new file mode 100644 index 00000000000..26089241327 --- /dev/null +++ b/default/scripting/ship_parts/Stealth/ST_CLOAK_3.focs.txt @@ -0,0 +1,16 @@ +Part + name = "ST_CLOAK_3" + description = "ST_CLOAK_3_DESC" + exclusions = [[ALL_CLOAKS]] + class = Stealth + capacity = 60 + mountableSlotTypes = Internal + buildcost = 15 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 7 + tags = [ "PEDIA_PC_STEALTH" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/cloak-3.png" + +#include "stealth.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Stealth/ST_CLOAK_4.focs.txt b/default/scripting/ship_parts/Stealth/ST_CLOAK_4.focs.txt new file mode 100644 index 00000000000..3f03349eaed --- /dev/null +++ b/default/scripting/ship_parts/Stealth/ST_CLOAK_4.focs.txt @@ -0,0 +1,16 @@ +Part + name = "ST_CLOAK_4" + description = "ST_CLOAK_4_DESC" + exclusions = [[ALL_CLOAKS]] + class = Stealth + capacity = 80 + mountableSlotTypes = Internal + buildcost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 10 + tags = [ "PEDIA_PC_STEALTH" ] + location = OwnedBy empire = Source.Owner + icon = "icons/ship_parts/cloak-4.png" + +#include "stealth.macros" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Stealth/best_stealth.macros b/default/scripting/ship_parts/Stealth/best_stealth.macros new file mode 100644 index 00000000000..a667c5b002d --- /dev/null +++ b/default/scripting/ship_parts/Stealth/best_stealth.macros @@ -0,0 +1,12 @@ +BEST_STEALTH_EFFECT +''' +max(max(max( + min(1, PartsInShipDesign Name = "ST_CLOAK_1" design = Source.DesignID) + * PartCapacity name = "ST_CLOAK_1", + min(1, PartsInShipDesign Name = "ST_CLOAK_2" design = Source.DesignID) + * PartCapacity name = "ST_CLOAK_2"), + min(1, PartsInShipDesign Name = "ST_CLOAK_3" design = Source.DesignID) + * PartCapacity name = "ST_CLOAK_3"), + min(1, PartsInShipDesign Name = "ST_CLOAK_4" design = Source.DesignID) + * PartCapacity name = "ST_CLOAK_4") +''' diff --git a/default/scripting/ship_parts/Stealth/stealth.macros b/default/scripting/ship_parts/Stealth/stealth.macros new file mode 100644 index 00000000000..44c5924a7c6 --- /dev/null +++ b/default/scripting/ship_parts/Stealth/stealth.macros @@ -0,0 +1,10 @@ +// Helper macro containing the names of all cloaks +ALL_CLOAKS +''' +[ + "ST_CLOAK_1" + "ST_CLOAK_2" + "ST_CLOAK_3" + "ST_CLOAK_4" +] +''' diff --git a/default/scripting/ship_parts/TroopPods.focs.txt b/default/scripting/ship_parts/Troops/GT_TROOP_POD.focs.txt similarity index 52% rename from default/scripting/ship_parts/TroopPods.focs.txt rename to default/scripting/ship_parts/Troops/GT_TROOP_POD.focs.txt index 88f9b0162c5..e94fba69d41 100644 --- a/default/scripting/ship_parts/TroopPods.focs.txt +++ b/default/scripting/ship_parts/Troops/GT_TROOP_POD.focs.txt @@ -9,27 +9,11 @@ Part tags = [ "COMFORTABLE" "PEDIA_PC_TROOPS" ] location = And [ OwnedBy empire = Source.Owner + /// @content_tag{NO_ATTACKTROOPS} Prevents production of ships with this part in their design at locations with this tag Not HasTag name = "NO_ATTACKTROOPS" Troops low = 2 ] // consumption = Troops consumption = 1 condition = Source icon = "icons/ship_parts/troops.png" -Part - name = "GT_TROOP_POD_2" - description = "GT_TROOP_POD_2_DESC" - class = Troops - capacity = 4 - mountableSlotTypes = [External Internal] - buildcost = 6 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_TROOPS" ] - location = And [ - OwnedBy empire = Source.Owner - Not HasTag name = "NO_ATTACKTROOPS" - Troops low = 4 - ] -// consumption = Troops consumption = 2 condition = Source - icon = "icons/meter/rebels.png" - #include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Troops/GT_TROOP_POD_2.focs.txt b/default/scripting/ship_parts/Troops/GT_TROOP_POD_2.focs.txt new file mode 100644 index 00000000000..51f0cbde5f7 --- /dev/null +++ b/default/scripting/ship_parts/Troops/GT_TROOP_POD_2.focs.txt @@ -0,0 +1,19 @@ +Part + name = "GT_TROOP_POD_2" + description = "GT_TROOP_POD_2_DESC" + class = Troops + capacity = 4 + mountableSlotTypes = [External Internal] + buildcost = 6 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] + buildtime = 1 + tags = [ "PEDIA_PC_TROOPS" ] + location = And [ + OwnedBy empire = Source.Owner + /// @content_tag{NO_ATTACKTROOPS} Prevents production of ships with this part in their design at locations with this tag + Not HasTag name = "NO_ATTACKTROOPS" + Troops low = 4 + ] +// consumption = Troops consumption = 2 condition = Source + icon = "icons/meter/rebels.png" + +#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/Weapons.focs.txt b/default/scripting/ship_parts/Weapons.focs.txt deleted file mode 100644 index 80b3643c821..00000000000 --- a/default/scripting/ship_parts/Weapons.focs.txt +++ /dev/null @@ -1,153 +0,0 @@ -Part - name = "SR_WEAPON_1_1" - description = "SR_WEAPON_1_1_DESC" - class = ShortRange - damage = 3 - NoDefaultCapacityEffect - mountableSlotTypes = External - buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_DIRECT_WEAPON" ] - location = OwnedBy empire = Source.Owner - effectsgroups = - [[WEAPON_BASE_DEFAULT_EFFECTS(SR_WEAPON_1_1)]] - icon = "icons/ship_parts/mass-driver.png" - -Part - name = "SR_WEAPON_0_1" - description = "SR_WEAPON_0_1_DESC" - class = ShortRange - damage = 1 - shots = 3 - NoDefaultCapacityEffect - mountableSlotTypes = External - buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_DIRECT_WEAPON" ] - location = OwnedBy empire = Source.Owner - effectsgroups = - [[WEAPON_BASE_DEFAULT_EFFECTS(SR_WEAPON_0_1)]] - icon = "icons/ship_parts/flak.png" - -Part - name = "SR_WEAPON_2_1" - description = "SR_WEAPON_2_1_DESC" - class = ShortRange - damage = 5 - NoDefaultCapacityEffect - mountableSlotTypes = External - buildcost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 2 - tags = [ "PEDIA_PC_DIRECT_WEAPON" ] - location = OwnedBy empire = Source.Owner - effectsgroups = - [[WEAPON_BASE_DEFAULT_EFFECTS(SR_WEAPON_2_1)]] - icon = "icons/ship_parts/laser.png" - -Part - name = "SR_WEAPON_3_1" - description = "SR_WEAPON_3_1_DESC" - class = ShortRange - damage = 9 - NoDefaultCapacityEffect - mountableSlotTypes = External - buildcost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 3 - tags = [ "PEDIA_PC_DIRECT_WEAPON" ] - location = OwnedBy empire = Source.Owner - effectsgroups = - [[WEAPON_BASE_DEFAULT_EFFECTS(SR_WEAPON_3_1)]] - icon = "icons/ship_parts/plasma.png" - -Part - name = "SR_WEAPON_4_1" - description = "SR_WEAPON_4_1_DESC" - class = ShortRange - damage = 15 - NoDefaultCapacityEffect - mountableSlotTypes = External - buildcost = 60 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 4 - tags = [ "PEDIA_PC_DIRECT_WEAPON" ] - location = OwnedBy empire = Source.Owner - effectsgroups = - [[WEAPON_BASE_DEFAULT_EFFECTS(SR_WEAPON_4_1)]] - icon = "icons/ship_parts/death-ray.png" - -Part - name = "SR_JAWS" - description = "SR_JAWS_DESC" - class = ShortRange - damage = 5 - mountableSlotTypes = External - buildcost = 20 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 1 - tags = [ "PEDIA_PC_DIRECT_WEAPON" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/teeth.png" - -Part - name = "SR_SPINES" - description = "SR_SPINES_DESC" - class = ShortRange - damage = 20 - mountableSlotTypes = External - buildcost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 3 - tags = [ "PEDIA_PC_DIRECT_WEAPON" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/claw.png" - -Part - name = "SR_TENTACLE" - description = "SR_TENTACLE_DESC" - class = ShortRange - damage = 5 - mountableSlotTypes = External - buildcost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 2 - tags = [ "PEDIA_PC_DIRECT_WEAPON" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/tentacle.png" - -Part - name = "SR_PLASMA_DISCHARGE" - description = "SR_PLASMA_DISCHARGE_DESC" - class = ShortRange - damage = 20 - mountableSlotTypes = External - buildcost = 40 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 3 - tags = [ "PEDIA_PC_DIRECT_WEAPON" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/flame_thrower.png" - -Part - name = "SR_ICE_BEAM" - description = "SR_ICE_BEAM_DESC" - class = ShortRange - damage = 9 - mountableSlotTypes = External - buildcost = 30 * [[FLEET_UPKEEP_MULTIPLICATOR]] * [[SHIP_PART_COST_MULTIPLIER]] - buildtime = 2 - tags = [ "PEDIA_PC_DIRECT_WEAPON" ] - location = OwnedBy empire = Source.Owner - icon = "icons/ship_parts/snowflake_laser.png" - -// If unowned or owner has not unlocked the ship part yet. -WEAPON_BASE_DEFAULT_EFFECTS -''' EffectsGroup - scope = Source - activation = Or [ - Unowned - Not OwnerHasShipPartAvailable name = "@1@" - ] - stackinggroup = "WEAPON_BASE_DEFAULT_EFFECTS_@1@" - accountinglabel = "@1@" - effects = [ - SetMaxCapacity partname = "@1@" value = Value + PartCapacity name = "@1@" - SetMaxSecondaryStat partname = "@1@" value = Value + PartSecondaryStat name = "@1@" - ] -''' - -#include "/scripting/common/upkeep.macros" diff --git a/default/scripting/ship_parts/stacking.macros b/default/scripting/ship_parts/stacking.macros deleted file mode 100644 index a3643389824..00000000000 --- a/default/scripting/ship_parts/stacking.macros +++ /dev/null @@ -1,40 +0,0 @@ -/* For the AI and general uniformity, shields, detectors and cloaks are assigned a capacity. -However, to prevent stacking, for each group X there is a X_STACKING macro which basically -first removes all effects of the used parts (by subtracting the summed capacity of all used parts) -and then only applies the effect of the strongest part. For this, there exist two more macros: -The SUM_X_CAPACITY macro returns the summed capacities of all used parts (including multiple copies of the same part type). -The BEST_X_EFFECT macro checks which of the relevant parts are used in the design and returns the capacity of only the strongest part. - -If new parts in these categories are introduced, make sure to add the X_STACKING macro to its effects and the part is added to the -SUM_X_CAPACITY and BEST_X_EFFECT macros. -*/ - -BEST_CLOAK_EFFECT -''' -max(max(max( - min(1, PartsInShipDesign Name = "ST_CLOAK_1" design = Source.DesignID) - * PartCapacity name = "ST_CLOAK_1", - min(1, PartsInShipDesign Name = "ST_CLOAK_2" design = Source.DesignID) - * PartCapacity name = "ST_CLOAK_2"), - min(1, PartsInShipDesign Name = "ST_CLOAK_3" design = Source.DesignID) - * PartCapacity name = "ST_CLOAK_3"), - min(1, PartsInShipDesign Name = "ST_CLOAK_4" design = Source.DesignID) - * PartCapacity name = "ST_CLOAK_4") -''' - - -BEST_SHIELD_EFFECT -''' -max(max(max(max( - min(1, PartsInShipDesign Name = "SH_DEFENSE_GRID" design = Source.DesignID) - * PartCapacity name = "SH_DEFENSE_GRID", - min(1, PartsInShipDesign Name = "SH_DEFLECTOR" design = Source.DesignID) - * PartCapacity name = "SH_DEFLECTOR"), - min(1, PartsInShipDesign Name = "SH_PLASMA" design = Source.DesignID) - * PartCapacity name = "SH_PLASMA"), - min(1, PartsInShipDesign Name = "SH_BLACK" design = Source.DesignID) - * PartCapacity name = "SH_BLACK"), - min(1, PartsInShipDesign Name = "SH_MULTISPEC" design = Source.DesignID) - * PartCapacity name = "SH_MULTISPEC" - ) -''' diff --git a/default/scripting/ship_parts/targeting.macros b/default/scripting/ship_parts/targeting.macros new file mode 100644 index 00000000000..f31c8e945d3 --- /dev/null +++ b/default/scripting/ship_parts/targeting.macros @@ -0,0 +1,33 @@ +// If unowned or owner has not unlocked the ship part yet. +COMBAT_TARGETS_VISIBLE_ENEMY +''' Or [ // unowned target, when attacker is owned by an empire, and target is visible to that empire + And [ + Unowned + (Source.Owner != LocalCandidate.Owner) + VisibleToEmpire empire = Source.Owner + ] + And [ // target owned by an empire, when attacker is owned by an enemy of the target's owner, and the target is visible to the attcker's owner + OwnedBy affiliation = AnyEmpire + OwnedBy affiliation = EnemyOf empire = Source.Owner + VisibleToEmpire empire = Source.Owner + ] + ] +''' + +COMBAT_TARGETS_NOT_DESTROYED_SHIP +''' And [ + Ship + Not Structure high = 0 + ] +''' + +COMBAT_TARGETS_PLANET_WITH_DEFENSE +''' And [ + Planet + Or [ + Not Shield high = 0 + Not Defense high = 0 + Not Construction high = 0 + ] + ] +''' diff --git a/default/scripting/specials/CONC_CAMP_SLAVE.focs.txt b/default/scripting/specials/CONC_CAMP_SLAVE.focs.txt index af07a78b573..eff0f06501b 100644 --- a/default/scripting/specials/CONC_CAMP_SLAVE.focs.txt +++ b/default/scripting/specials/CONC_CAMP_SLAVE.focs.txt @@ -5,7 +5,9 @@ Special effectsgroups = [ EffectsGroup scope = Source + priority = [[EARLY_POPULATION_OVERRIDE_PRIORITY]] effects = SetPopulation value = Value + 0.5*(101+Target.TargetPopulation-2*Value - ((101+Target.TargetPopulation-2*Value)^2 -4*(Value*(Value-1-Target.TargetPopulation)-2*100))^0.5) + EffectsGroup scope = Source activation = And [ @@ -14,6 +16,7 @@ Special Not Contains Building name = "BLD_CONC_CAMP_REMNANT" ] effects = CreateBuilding type = "BLD_CONC_CAMP_REMNANT" + EffectsGroup scope = Source activation = OR [ @@ -23,3 +26,5 @@ Special effects = RemoveSpecial name = "CONC_CAMP_SLAVE_SPECIAL" ] graphic = "icons/building/concentration-camp.png" + +#include "/scripting/common/priorities.macros" \ No newline at end of file diff --git a/default/scripting/specials/ExtinctSpecies.focs.txt b/default/scripting/specials/ExtinctSpecies.focs.txt deleted file mode 100644 index 1274c9f0810..00000000000 --- a/default/scripting/specials/ExtinctSpecies.focs.txt +++ /dev/null @@ -1,17 +0,0 @@ -Special - name = "EXTINCT_BANFORO_SPECIAL" - description = "EXTINCT_BANFORO_SPECIAL_DESC" - spawnrate = 0.0 - graphic = "icons/specials_huge/extinct_banforo.png" - -Special - name = "EXTINCT_KILANDOW_SPECIAL" - description = "EXTINCT_KILANDOW_SPECIAL_DESC" - spawnrate = 0.0 - graphic = "icons/specials_huge/extinct_kilandow.png" - -Special - name = "EXTINCT_MISIORLA_SPECIAL" - description = "EXTINCT_MISIORLA_SPECIAL_DESC" - spawnrate = 0.0 - graphic = "icons/specials_huge/extinct_misiorla.png" diff --git a/default/scripting/specials/ABANDONED_COLONY.focs.txt b/default/scripting/specials/planet/ABANDONED_COLONY.focs.txt similarity index 100% rename from default/scripting/specials/ABANDONED_COLONY.focs.txt rename to default/scripting/specials/planet/ABANDONED_COLONY.focs.txt diff --git a/default/scripting/specials/ANCIENT_RUINS.focs.txt b/default/scripting/specials/planet/ANCIENT_RUINS.focs.txt similarity index 99% rename from default/scripting/specials/ANCIENT_RUINS.focs.txt rename to default/scripting/specials/planet/ANCIENT_RUINS.focs.txt index ae3d64fe97c..1e9bb7bfe86 100644 --- a/default/scripting/specials/ANCIENT_RUINS.focs.txt +++ b/default/scripting/specials/planet/ANCIENT_RUINS.focs.txt @@ -281,4 +281,5 @@ Special graphic = "icons/specials_huge/ancient_ruins.png" #include "/scripting/common/base_prod.macros" + #include "/scripting/common/priorities.macros" diff --git a/default/scripting/specials/ANCIENT_RUINS_DEPLETED.focs.txt b/default/scripting/specials/planet/ANCIENT_RUINS_DEPLETED.focs.txt similarity index 99% rename from default/scripting/specials/ANCIENT_RUINS_DEPLETED.focs.txt rename to default/scripting/specials/planet/ANCIENT_RUINS_DEPLETED.focs.txt index d4924c48b0f..4bdc3ffa922 100644 --- a/default/scripting/specials/ANCIENT_RUINS_DEPLETED.focs.txt +++ b/default/scripting/specials/planet/ANCIENT_RUINS_DEPLETED.focs.txt @@ -18,4 +18,5 @@ Special graphic = "icons/specials_huge/ancient_ruins_excavated.png" #include "/scripting/common/base_prod.macros" + #include "/scripting/common/priorities.macros" diff --git a/default/scripting/specials/COMPUTRONIUM.focs.txt b/default/scripting/specials/planet/COMPUTRONIUM.focs.txt similarity index 97% rename from default/scripting/specials/COMPUTRONIUM.focs.txt rename to default/scripting/specials/planet/COMPUTRONIUM.focs.txt index e626522e2b7..4c31d0f01fc 100644 --- a/default/scripting/specials/COMPUTRONIUM.focs.txt +++ b/default/scripting/specials/planet/COMPUTRONIUM.focs.txt @@ -36,7 +36,8 @@ Special ] graphic = "icons/specials_huge/computronium.png" -#include "specials.macros" +#include "monster_guard.macros" #include "/scripting/common/base_prod.macros" + #include "/scripting/common/priorities.macros" diff --git a/default/scripting/specials/ECCENTRIC_ORBIT.focs.txt b/default/scripting/specials/planet/ECCENTRIC_ORBIT.focs.txt similarity index 99% rename from default/scripting/specials/ECCENTRIC_ORBIT.focs.txt rename to default/scripting/specials/planet/ECCENTRIC_ORBIT.focs.txt index 60ae07974b5..541a241234c 100644 --- a/default/scripting/specials/ECCENTRIC_ORBIT.focs.txt +++ b/default/scripting/specials/planet/ECCENTRIC_ORBIT.focs.txt @@ -19,4 +19,5 @@ Special SetMaxSupply value = Value -2 ] graphic = "icons/specials_huge/eccentric_orbit.png" + #include "/scripting/common/priorities.macros" diff --git a/default/scripting/specials/planet/EXTINCT_BANFORO.focs.txt b/default/scripting/specials/planet/EXTINCT_BANFORO.focs.txt new file mode 100644 index 00000000000..45cc653ba70 --- /dev/null +++ b/default/scripting/specials/planet/EXTINCT_BANFORO.focs.txt @@ -0,0 +1,5 @@ +Special + name = "EXTINCT_BANFORO_SPECIAL" + description = "EXTINCT_BANFORO_SPECIAL_DESC" + spawnrate = 0.0 + graphic = "icons/specials_huge/extinct_banforo.png" diff --git a/default/scripting/specials/planet/EXTINCT_KILANDOW.focs.txt b/default/scripting/specials/planet/EXTINCT_KILANDOW.focs.txt new file mode 100644 index 00000000000..505856c9ba1 --- /dev/null +++ b/default/scripting/specials/planet/EXTINCT_KILANDOW.focs.txt @@ -0,0 +1,5 @@ +Special + name = "EXTINCT_KILANDOW_SPECIAL" + description = "EXTINCT_KILANDOW_SPECIAL_DESC" + spawnrate = 0.0 + graphic = "icons/specials_huge/extinct_kilandow.png" diff --git a/default/scripting/specials/planet/EXTINCT_MISIORLA.focs.txt b/default/scripting/specials/planet/EXTINCT_MISIORLA.focs.txt new file mode 100644 index 00000000000..39839a42a85 --- /dev/null +++ b/default/scripting/specials/planet/EXTINCT_MISIORLA.focs.txt @@ -0,0 +1,5 @@ +Special + name = "EXTINCT_MISIORLA_SPECIAL" + description = "EXTINCT_MISIORLA_SPECIAL_DESC" + spawnrate = 0.0 + graphic = "icons/specials_huge/extinct_misiorla.png" diff --git a/default/scripting/specials/FORTRESS.focs.txt b/default/scripting/specials/planet/FORTRESS.focs.txt similarity index 100% rename from default/scripting/specials/FORTRESS.focs.txt rename to default/scripting/specials/planet/FORTRESS.focs.txt diff --git a/default/scripting/specials/GAIA.focs.txt b/default/scripting/specials/planet/GAIA.focs.txt similarity index 92% rename from default/scripting/specials/GAIA.focs.txt rename to default/scripting/specials/planet/GAIA.focs.txt index 4cf996d3073..52547d6f8b7 100644 --- a/default/scripting/specials/GAIA.focs.txt +++ b/default/scripting/specials/planet/GAIA.focs.txt @@ -48,9 +48,9 @@ Special Object id = Source.PlanetID Planet environment = Good ] - priority = [[LATE_PRIORITY]] + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] effects = [ - SetTargetPopulation value = Value + 3 * Target.SizeAsDouble accountinglabel = "GAIA_LABEL" + SetTargetPopulation value = Value + 3 * Target.HabitableSize accountinglabel = "GAIA_LABEL" SetTargetHappiness value = Value + 5 accountinglabel = "GAIA_LABEL" ] diff --git a/default/scripting/specials/HEAD_ON_A_SPIKE.focs.txt b/default/scripting/specials/planet/HEAD_ON_A_SPIKE.focs.txt similarity index 97% rename from default/scripting/specials/HEAD_ON_A_SPIKE.focs.txt rename to default/scripting/specials/planet/HEAD_ON_A_SPIKE.focs.txt index e9326cc198d..21a037d2671 100644 --- a/default/scripting/specials/HEAD_ON_A_SPIKE.focs.txt +++ b/default/scripting/specials/planet/HEAD_ON_A_SPIKE.focs.txt @@ -5,7 +5,6 @@ Special effectsgroups = [ EffectsGroup scope = Source - activation = Source effects = [ SetMaxDefense value = Value + 1 SetMaxShield value = Value + 1 diff --git a/default/scripting/specials/HIGH_TECH_NATIVES.focs.txt b/default/scripting/specials/planet/HIGH_TECH_NATIVES.focs.txt similarity index 94% rename from default/scripting/specials/HIGH_TECH_NATIVES.focs.txt rename to default/scripting/specials/planet/HIGH_TECH_NATIVES.focs.txt index 2fe919b258e..24b282b7b95 100644 --- a/default/scripting/specials/HIGH_TECH_NATIVES.focs.txt +++ b/default/scripting/specials/planet/HIGH_TECH_NATIVES.focs.txt @@ -8,6 +8,7 @@ Special Planet Unowned Species + /// @content_tag{PRIMITIVE} Prevents this special at a location with tag Not HasTag name = "PRIMITIVE" Not HasSpecial name = "MODERATE_TECH_NATIVES_SPECIAL" ] diff --git a/default/scripting/specials/HONEYCOMB.focs.txt b/default/scripting/specials/planet/HONEYCOMB.focs.txt similarity index 99% rename from default/scripting/specials/HONEYCOMB.focs.txt rename to default/scripting/specials/planet/HONEYCOMB.focs.txt index f617d083d94..8fa87b21ff5 100644 --- a/default/scripting/specials/HONEYCOMB.focs.txt +++ b/default/scripting/specials/planet/HONEYCOMB.focs.txt @@ -84,4 +84,5 @@ Special graphic = "icons/specials_huge/honeycomb.png" #include "/scripting/common/base_prod.macros" + #include "/scripting/common/priorities.macros" diff --git a/default/scripting/specials/KRAKEN_IN_THE_ICE.focs.txt b/default/scripting/specials/planet/KRAKEN_IN_THE_ICE.focs.txt similarity index 100% rename from default/scripting/specials/KRAKEN_IN_THE_ICE.focs.txt rename to default/scripting/specials/planet/KRAKEN_IN_THE_ICE.focs.txt diff --git a/default/scripting/specials/MODERATE_TECH_NATIVES.focs.txt b/default/scripting/specials/planet/MODERATE_TECH_NATIVES.focs.txt similarity index 88% rename from default/scripting/specials/MODERATE_TECH_NATIVES.focs.txt rename to default/scripting/specials/planet/MODERATE_TECH_NATIVES.focs.txt index a0292371975..cfe937e2883 100644 --- a/default/scripting/specials/MODERATE_TECH_NATIVES.focs.txt +++ b/default/scripting/specials/planet/MODERATE_TECH_NATIVES.focs.txt @@ -8,6 +8,7 @@ Special Planet Unowned Species + /// @content_tag{PRIMITIVE} Prevents this special from being added to a location with this tag during universe generation Not HasTag name = "PRIMITIVE" Not HasSpecial name = "HIGH_TECH_NATIVES_SPECIAL" ] diff --git a/default/scripting/specials/PANOPTICON.focs.txt b/default/scripting/specials/planet/PANOPTICON.focs.txt similarity index 100% rename from default/scripting/specials/PANOPTICON.focs.txt rename to default/scripting/specials/planet/PANOPTICON.focs.txt diff --git a/default/scripting/specials/PHILOSOPHER.focs.txt b/default/scripting/specials/planet/PHILOSOPHER.focs.txt similarity index 97% rename from default/scripting/specials/PHILOSOPHER.focs.txt rename to default/scripting/specials/planet/PHILOSOPHER.focs.txt index 0705b819564..fc27cf2b83e 100644 --- a/default/scripting/specials/PHILOSOPHER.focs.txt +++ b/default/scripting/specials/planet/PHILOSOPHER.focs.txt @@ -46,8 +46,8 @@ Special EffectsGroup scope = Source - activation = Source effects = SetTargetConstruction value = Value - 20 ] graphic = "icons/specials_huge/philospher-planet.png" + #include "/scripting/common/priorities.macros" diff --git a/default/scripting/specials/RESONANT_MOON.focs.txt b/default/scripting/specials/planet/RESONANT_MOON.focs.txt similarity index 86% rename from default/scripting/specials/RESONANT_MOON.focs.txt rename to default/scripting/specials/planet/RESONANT_MOON.focs.txt index 1efbb73faf2..82e9fc30bda 100644 --- a/default/scripting/specials/RESONANT_MOON.focs.txt +++ b/default/scripting/specials/planet/RESONANT_MOON.focs.txt @@ -22,11 +22,8 @@ Special EffectsGroup scope = And [ + InSystem id = Source.SystemID Ship - ContainedBy And [ - Object id = Source.SystemID - System - ] OwnedBy empire = Source.Owner ] stackinggroup = "RESONANT_MOON_STACK" diff --git a/default/scripting/specials/SCRYING_SPHERE.focs.txt b/default/scripting/specials/planet/SCRYING_SPHERE.focs.txt similarity index 100% rename from default/scripting/specials/SCRYING_SPHERE.focs.txt rename to default/scripting/specials/planet/SCRYING_SPHERE.focs.txt diff --git a/default/scripting/specials/TEMPORAL_ANOMOLY.focs.txt b/default/scripting/specials/planet/TEMPORAL_ANOMOLY.focs.txt similarity index 86% rename from default/scripting/specials/TEMPORAL_ANOMOLY.focs.txt rename to default/scripting/specials/planet/TEMPORAL_ANOMOLY.focs.txt index e05c442122f..0ca3d1780ab 100644 --- a/default/scripting/specials/TEMPORAL_ANOMOLY.focs.txt +++ b/default/scripting/specials/planet/TEMPORAL_ANOMOLY.focs.txt @@ -20,10 +20,11 @@ Special EffectsGroup scope = Object id = Source.PlanetID - priority = [[LATE_PRIORITY]] - effects = SetTargetPopulation value = Value - 5 * Target.SizeAsDouble accountinglabel = "TEMPORAL_ANOMALY_SPECIAL" + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value - 5 * Target.HabitableSize accountinglabel = "TEMPORAL_ANOMALY_SPECIAL" ] graphic = "icons/specials_huge/temporal_anomaly.png" #include "/scripting/common/base_prod.macros" + #include "/scripting/common/priorities.macros" diff --git a/default/scripting/specials/TIDAL_LOCK.focs.txt b/default/scripting/specials/planet/TIDAL_LOCK.focs.txt similarity index 82% rename from default/scripting/specials/TIDAL_LOCK.focs.txt rename to default/scripting/specials/planet/TIDAL_LOCK.focs.txt index de87b308a0e..f05f11e10c9 100644 --- a/default/scripting/specials/TIDAL_LOCK.focs.txt +++ b/default/scripting/specials/planet/TIDAL_LOCK.focs.txt @@ -21,8 +21,9 @@ Special EffectsGroup scope = Object id = Source.PlanetID - priority = [[DEFAULT_PRIORITY]] - effects = SetTargetPopulation value = Value - 1 * Target.SizeAsDouble accountinglabel = "TIDAL_LOCK_LABEL" + activation = Species + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value - 1 * Target.HabitableSize accountinglabel = "TIDAL_LOCK_LABEL" ] graphic = "icons/specials_huge/tidal_lock.png" diff --git a/default/scripting/specials/WORLDTREE.focs.txt b/default/scripting/specials/planet/WORLDTREE.focs.txt similarity index 93% rename from default/scripting/specials/WORLDTREE.focs.txt rename to default/scripting/specials/planet/WORLDTREE.focs.txt index 97a49c43f90..60d6fc7268a 100644 --- a/default/scripting/specials/WORLDTREE.focs.txt +++ b/default/scripting/specials/planet/WORLDTREE.focs.txt @@ -19,7 +19,6 @@ Special effectsgroups = [ EffectsGroup scope = Source - activation = Source accountinglabel = "WORLDTREE_LABEL" priority = [[LATE_PRIORITY]] effects = [ @@ -27,12 +26,14 @@ Special SetMaxSupply value = Value + 1 SetTargetHappiness value = Value + 5 ] + EffectsGroup scope = Source activation = TargetPopulation low = 1 accountinglabel = "WORLDTREE_LABEL" - priority = [[LATE_PRIORITY]] + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] effects = SetTargetPopulation value = Value + 1 + EffectsGroup scope = And [ Planet diff --git a/default/scripting/specials/CRYSTALS.focs.txt b/default/scripting/specials/planet/growth/CRYSTALS.focs.txt similarity index 90% rename from default/scripting/specials/CRYSTALS.focs.txt rename to default/scripting/specials/planet/growth/CRYSTALS.focs.txt index e1a83c3b0df..c925d6233ce 100644 --- a/default/scripting/specials/CRYSTALS.focs.txt +++ b/default/scripting/specials/planet/growth/CRYSTALS.focs.txt @@ -22,4 +22,6 @@ Special ] graphic = "icons/specials_huge/crystals.png" -#include "specials.macros" +#include "growth.macros" + +#include "../monster_guard.macros" diff --git a/default/scripting/specials/ELERIUM.focs.txt b/default/scripting/specials/planet/growth/ELERIUM.focs.txt similarity index 90% rename from default/scripting/specials/ELERIUM.focs.txt rename to default/scripting/specials/planet/growth/ELERIUM.focs.txt index 93e36db0bf5..9059819a5e8 100644 --- a/default/scripting/specials/ELERIUM.focs.txt +++ b/default/scripting/specials/planet/growth/ELERIUM.focs.txt @@ -22,4 +22,6 @@ Special ] graphic = "icons/specials_huge/metaloids.png" -#include "specials.macros" +#include "growth.macros" + +#include "../monster_guard.macros" diff --git a/default/scripting/specials/FRUIT.focs.txt b/default/scripting/specials/planet/growth/FRUIT.focs.txt similarity index 90% rename from default/scripting/specials/FRUIT.focs.txt rename to default/scripting/specials/planet/growth/FRUIT.focs.txt index fb6f46ef3e0..ea1808abc22 100644 --- a/default/scripting/specials/FRUIT.focs.txt +++ b/default/scripting/specials/planet/growth/FRUIT.focs.txt @@ -21,4 +21,6 @@ Special ] graphic = "icons/specials_huge/fruit.png" -#include "specials.macros" +#include "growth.macros" + +#include "../monster_guard.macros" diff --git a/default/scripting/specials/MINERALS.focs.txt b/default/scripting/specials/planet/growth/MINERALS.focs.txt similarity index 90% rename from default/scripting/specials/MINERALS.focs.txt rename to default/scripting/specials/planet/growth/MINERALS.focs.txt index 4d0ce33faa1..6556c21715f 100644 --- a/default/scripting/specials/MINERALS.focs.txt +++ b/default/scripting/specials/planet/growth/MINERALS.focs.txt @@ -22,4 +22,6 @@ Special ] graphic = "icons/specials_huge/minerals.png" -#include "specials.macros" +#include "growth.macros" + +#include "../monster_guard.macros" diff --git a/default/scripting/specials/MONOPOLE.focs.txt b/default/scripting/specials/planet/growth/MONOPOLE.focs.txt similarity index 91% rename from default/scripting/specials/MONOPOLE.focs.txt rename to default/scripting/specials/planet/growth/MONOPOLE.focs.txt index 1588bcc1f4d..c1d7c3dadfe 100644 --- a/default/scripting/specials/MONOPOLE.focs.txt +++ b/default/scripting/specials/planet/growth/MONOPOLE.focs.txt @@ -22,4 +22,6 @@ Special ] graphic = "icons/specials_huge/monopole.png" -#include "specials.macros" +#include "growth.macros" + +#include "../monster_guard.macros" diff --git a/default/scripting/specials/POSITRONIUM.focs.txt b/default/scripting/specials/planet/growth/POSITRONIUM.focs.txt similarity index 91% rename from default/scripting/specials/POSITRONIUM.focs.txt rename to default/scripting/specials/planet/growth/POSITRONIUM.focs.txt index f8ee440cce4..836b3229071 100644 --- a/default/scripting/specials/POSITRONIUM.focs.txt +++ b/default/scripting/specials/planet/growth/POSITRONIUM.focs.txt @@ -22,4 +22,6 @@ Special ] graphic = "icons/specials_huge/positronium.png" -#include "specials.macros" +#include "growth.macros" + +#include "../monster_guard.macros" diff --git a/default/scripting/specials/PROBIOTIC.focs.txt b/default/scripting/specials/planet/growth/PROBIOTIC.focs.txt similarity index 90% rename from default/scripting/specials/PROBIOTIC.focs.txt rename to default/scripting/specials/planet/growth/PROBIOTIC.focs.txt index fcb7fa48260..31160f128f9 100644 --- a/default/scripting/specials/PROBIOTIC.focs.txt +++ b/default/scripting/specials/planet/growth/PROBIOTIC.focs.txt @@ -21,4 +21,6 @@ Special ] graphic = "icons/specials_huge/probiotic.png" -#include "specials.macros" +#include "growth.macros" + +#include "../monster_guard.macros" diff --git a/default/scripting/specials/SPICE.focs.txt b/default/scripting/specials/planet/growth/SPICE.focs.txt similarity index 90% rename from default/scripting/specials/SPICE.focs.txt rename to default/scripting/specials/planet/growth/SPICE.focs.txt index 6f48dba259f..e2cd040e349 100644 --- a/default/scripting/specials/SPICE.focs.txt +++ b/default/scripting/specials/planet/growth/SPICE.focs.txt @@ -21,4 +21,6 @@ Special ] graphic = "icons/specials_huge/spice.png" -#include "specials.macros" +#include "growth.macros" + +#include "../monster_guard.macros" diff --git a/default/scripting/specials/SUPERCONDUCTOR.focs.txt b/default/scripting/specials/planet/growth/SUPERCONDUCTOR.focs.txt similarity index 91% rename from default/scripting/specials/SUPERCONDUCTOR.focs.txt rename to default/scripting/specials/planet/growth/SUPERCONDUCTOR.focs.txt index 35fae913c00..810bce9ac24 100644 --- a/default/scripting/specials/SUPERCONDUCTOR.focs.txt +++ b/default/scripting/specials/planet/growth/SUPERCONDUCTOR.focs.txt @@ -22,4 +22,6 @@ Special ] graphic = "icons/specials_huge/superconductor.png" -#include "specials.macros" +#include "growth.macros" + +#include "../monster_guard.macros" diff --git a/default/scripting/specials/specials.macros b/default/scripting/specials/planet/growth/growth.macros similarity index 68% rename from default/scripting/specials/specials.macros rename to default/scripting/specials/planet/growth/growth.macros index eb23c81b386..144fc9674ec 100644 --- a/default/scripting/specials/specials.macros +++ b/default/scripting/specials/planet/growth/growth.macros @@ -8,30 +8,6 @@ STANDARD_INDUSTRY_BOOST ''' -CHANCE_OF_GUARD_1 -''' EffectsGroup - scope = Source - activation = AND [ - Turn high = 0 - Random probability = 0.70 - (GalaxyMaxAIAggression >= 1) - (GalaxyMonsterFrequency >= 1) - Not ContainedBy Contains OR [ - Design name = "SM_EXP_OUTPOST" - Building name = "BLD_EXPERIMENTOR_OUTPOST" - And [ Planet HasSpecial name = "HIGH_TECH_NATIVES_SPECIAL" ] - ] - ] - effects = [ - If condition = Or [ Random probability = 0.6 Homeworld ] - effects = CreateShip designname = OneOf("SM_GUARD_0", "SM_GUARD_1") - else = [ - SetSpecies name = "SP_ANCIENT_GUARDIANS" - SetPopulation value = Target.TargetPopulation - ] - ] -''' - // NOTE: Unused ANCIENT_RUINS_DEPLETED_CONTENT ''' stealth = 0 @@ -59,14 +35,13 @@ POPULATION_BOOST_ORGANIC Planet OwnedBy empire = Source.Owner HasTag name = "ORGANIC" - //TargetPopulation low = 0 ResourceSupplyConnected empire = Source.Owner condition = Source ] activation = Focus type = "FOCUS_GROWTH" stackinggroup = "@1@_STACK" accountinglabel = "GROWTH_SPECIAL_LABEL" - priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize EffectsGroup description = "GROWTH_SPECIAL_POPULATION_ORGANIC_INCREASE" @@ -75,8 +50,8 @@ POPULATION_BOOST_ORGANIC HasTag name = "ORGANIC" ] stackinggroup = "@1@_STACK" - priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble // Provides the bonus locally, no matter the focus + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize // Provides the bonus locally, no matter the focus ''' // argument 1 is the name of the used stacking group @@ -88,13 +63,12 @@ POPULATION_BOOST_ROBOTIC OwnedBy empire = Source.Owner HasTag name = "ROBOTIC" ResourceSupplyConnected empire = Source.Owner condition = Source - //TargetPopulation low = 0 ] activation = Focus type = "FOCUS_GROWTH" stackinggroup = "@1@_STACK" accountinglabel = "GROWTH_SPECIAL_LABEL" - priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize EffectsGroup description = "GROWTH_SPECIAL_POPULATION_ROBOTIC_INCREASE" @@ -103,8 +77,8 @@ POPULATION_BOOST_ROBOTIC HasTag name = "ROBOTIC" ] stackinggroup = "@1@_STACK" - priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble // Provides the bonus locally, no matter the focus + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize // Provides the bonus locally, no matter the focus ''' // argument 1 is the name of the used stacking group @@ -116,13 +90,12 @@ POPULATION_BOOST_LITHIC OwnedBy empire = Source.Owner HasTag name = "LITHIC" ResourceSupplyConnected empire = Source.Owner condition = Source - //TargetPopulation low = 0 ] activation = Focus type = "FOCUS_GROWTH" stackinggroup = "@1@_STACK" accountinglabel = "GROWTH_SPECIAL_LABEL" - priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize EffectsGroup description = "GROWTH_SPECIAL_POPULATION_LITHIC_INCREASE" @@ -131,8 +104,8 @@ POPULATION_BOOST_LITHIC HasTag name = "LITHIC" ] stackinggroup = "@1@_STACK" - priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble // Provides the bonus locally, no matter the focus + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize // Provides the bonus locally, no matter the focus ''' #include "/scripting/common/base_prod.macros" diff --git a/default/scripting/specials/planet/monster_guard.macros b/default/scripting/specials/planet/monster_guard.macros new file mode 100644 index 00000000000..38ab318f672 --- /dev/null +++ b/default/scripting/specials/planet/monster_guard.macros @@ -0,0 +1,23 @@ +CHANCE_OF_GUARD_1 +''' EffectsGroup + scope = Source + activation = AND [ + Turn high = 0 + Random probability = 0.70 + (GalaxyMaxAIAggression >= 1) + (GalaxyMonsterFrequency >= 1) + Not ContainedBy Contains OR [ + Design name = "SM_EXP_OUTPOST" + Building name = "BLD_EXPERIMENTOR_OUTPOST" + And [ Planet HasSpecial name = "HIGH_TECH_NATIVES_SPECIAL" ] + ] + ] + effects = [ + If condition = Or [ Random probability = 0.6 Homeworld ] + effects = CreateShip designname = OneOf("SM_GUARD_0", "SM_GUARD_1") + else = [ + SetSpecies name = "SP_ANCIENT_GUARDIANS" + SetPopulation value = Target.TargetPopulation + ] + ] +''' diff --git a/default/scripting/specials/JUGGERNAUT_NEST.focs.txt b/default/scripting/specials/planet/monster_nest/JUGGERNAUT_NEST.focs.txt similarity index 100% rename from default/scripting/specials/JUGGERNAUT_NEST.focs.txt rename to default/scripting/specials/planet/monster_nest/JUGGERNAUT_NEST.focs.txt diff --git a/default/scripting/specials/KRAKEN_NEST.focs.txt b/default/scripting/specials/planet/monster_nest/KRAKEN_NEST.focs.txt similarity index 100% rename from default/scripting/specials/KRAKEN_NEST.focs.txt rename to default/scripting/specials/planet/monster_nest/KRAKEN_NEST.focs.txt diff --git a/default/scripting/specials/SNOWFLAKE_NEST.focs.txt b/default/scripting/specials/planet/monster_nest/SNOWFLAKE_NEST.focs.txt similarity index 100% rename from default/scripting/specials/SNOWFLAKE_NEST.focs.txt rename to default/scripting/specials/planet/monster_nest/SNOWFLAKE_NEST.focs.txt diff --git a/default/scripting/specials/monster_nest.macros b/default/scripting/specials/planet/monster_nest/monster_nest.macros similarity index 94% rename from default/scripting/specials/monster_nest.macros rename to default/scripting/specials/planet/monster_nest/monster_nest.macros index 6e5786ee772..9f6de059ada 100644 --- a/default/scripting/specials/monster_nest.macros +++ b/default/scripting/specials/planet/monster_nest/monster_nest.macros @@ -13,7 +13,7 @@ MONSTER_NEST OwnerHasTech name = "SHP_DOMESTIC_MONSTER" Random probability = @3@ ] - stackinggroup = "@1@_NEST_STACK" + stackinggroup = "@1@_NEST_STACK" // groups with BLD_NEST_ERADICATOR effects = [ CreateShip designname = "SM_@1@_1" empire = Source.Owner GenerateSitRepMessage diff --git a/default/scripting/specials/CLOUD_COVER_MASTER.focs.txt b/default/scripting/specials/planet/monster_stealth/CLOUD_COVER_MASTER.focs.txt similarity index 100% rename from default/scripting/specials/CLOUD_COVER_MASTER.focs.txt rename to default/scripting/specials/planet/monster_stealth/CLOUD_COVER_MASTER.focs.txt diff --git a/default/scripting/specials/CLOUD_COVER_SLAVE.focs.txt b/default/scripting/specials/planet/monster_stealth/CLOUD_COVER_SLAVE.focs.txt similarity index 89% rename from default/scripting/specials/CLOUD_COVER_SLAVE.focs.txt rename to default/scripting/specials/planet/monster_stealth/CLOUD_COVER_SLAVE.focs.txt index 98934311a0e..7a6c92322f0 100644 --- a/default/scripting/specials/CLOUD_COVER_SLAVE.focs.txt +++ b/default/scripting/specials/planet/monster_stealth/CLOUD_COVER_SLAVE.focs.txt @@ -1,3 +1,4 @@ +// @content_tag{ORBITAL} Prevents stealth bonus from CLOUD_COVER_SLAVE_SPECIAL Special name = "CLOUD_COVER_SLAVE_SPECIAL" description = "CLOUD_COVER_SLAVE_SPECIAL_DESC" diff --git a/default/scripting/specials/DIM_RIFT_MASTER.focs.txt b/default/scripting/specials/planet/monster_stealth/DIM_RIFT_MASTER.focs.txt similarity index 82% rename from default/scripting/specials/DIM_RIFT_MASTER.focs.txt rename to default/scripting/specials/planet/monster_stealth/DIM_RIFT_MASTER.focs.txt index ea1d982083d..75952fdf2ee 100644 --- a/default/scripting/specials/DIM_RIFT_MASTER.focs.txt +++ b/default/scripting/specials/planet/monster_stealth/DIM_RIFT_MASTER.focs.txt @@ -7,6 +7,7 @@ Special EffectsGroup scope = Source stackinggroup = "STEALTH_SPECIAL_MASTER_STACK" + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] effects = [ AddSpecial name = "DIM_RIFT_SLAVE_SPECIAL" SetTargetPopulation value = Value - 4 @@ -18,3 +19,4 @@ Special graphic = "icons/monsters/dim_rift.png" #include "remove_stealth.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/specials/DIM_RIFT_SLAVE.focs.txt b/default/scripting/specials/planet/monster_stealth/DIM_RIFT_SLAVE.focs.txt similarity index 89% rename from default/scripting/specials/DIM_RIFT_SLAVE.focs.txt rename to default/scripting/specials/planet/monster_stealth/DIM_RIFT_SLAVE.focs.txt index 3591f5f82d6..a4ec93ddf61 100644 --- a/default/scripting/specials/DIM_RIFT_SLAVE.focs.txt +++ b/default/scripting/specials/planet/monster_stealth/DIM_RIFT_SLAVE.focs.txt @@ -1,3 +1,4 @@ +// @content_tag{ORBITAL} Prevents stealth bonus from DIM_RIFT_SLAVE_SPECIAL Special name = "DIM_RIFT_SLAVE_SPECIAL" description = "DIM_RIFT_SLAVE_SPECIAL_DESC" diff --git a/default/scripting/specials/VOID_MASTER.focs.txt b/default/scripting/specials/planet/monster_stealth/VOID_MASTER.focs.txt similarity index 100% rename from default/scripting/specials/VOID_MASTER.focs.txt rename to default/scripting/specials/planet/monster_stealth/VOID_MASTER.focs.txt diff --git a/default/scripting/specials/VOID_SLAVE.focs.txt b/default/scripting/specials/planet/monster_stealth/VOID_SLAVE.focs.txt similarity index 88% rename from default/scripting/specials/VOID_SLAVE.focs.txt rename to default/scripting/specials/planet/monster_stealth/VOID_SLAVE.focs.txt index 28b539c29a0..4006d844316 100644 --- a/default/scripting/specials/VOID_SLAVE.focs.txt +++ b/default/scripting/specials/planet/monster_stealth/VOID_SLAVE.focs.txt @@ -1,3 +1,4 @@ +// @content_tag{ORBITAL} Prevents stealth bonus from VOID_SLAVE_SPECIAL Special name = "VOID_SLAVE_SPECIAL" description = "VOID_SLAVE_SPECIAL_DESC" diff --git a/default/scripting/specials/VOLCANIC_ASH_MASTER.focs.txt b/default/scripting/specials/planet/monster_stealth/VOLCANIC_ASH_MASTER.focs.txt similarity index 100% rename from default/scripting/specials/VOLCANIC_ASH_MASTER.focs.txt rename to default/scripting/specials/planet/monster_stealth/VOLCANIC_ASH_MASTER.focs.txt diff --git a/default/scripting/specials/VOLCANIC_ASH_SLAVE.focs.txt b/default/scripting/specials/planet/monster_stealth/VOLCANIC_ASH_SLAVE.focs.txt similarity index 89% rename from default/scripting/specials/VOLCANIC_ASH_SLAVE.focs.txt rename to default/scripting/specials/planet/monster_stealth/VOLCANIC_ASH_SLAVE.focs.txt index e5b419e6690..b795b4b7bbc 100644 --- a/default/scripting/specials/VOLCANIC_ASH_SLAVE.focs.txt +++ b/default/scripting/specials/planet/monster_stealth/VOLCANIC_ASH_SLAVE.focs.txt @@ -1,3 +1,4 @@ +// @content_tag{ORBITAL} Prevents stealth bonus from VOLCANIC_ASH_SLAVE_SPECIAL Special name = "VOLCANIC_ASH_SLAVE_SPECIAL" description = "VOLCANIC_ASH_SLAVE_SPECIAL_DESC" diff --git a/default/scripting/specials/remove_stealth.macros b/default/scripting/specials/planet/monster_stealth/remove_stealth.macros similarity index 81% rename from default/scripting/specials/remove_stealth.macros rename to default/scripting/specials/planet/monster_stealth/remove_stealth.macros index 157e91b3ae6..2216fce595c 100644 --- a/default/scripting/specials/remove_stealth.macros +++ b/default/scripting/specials/planet/monster_stealth/remove_stealth.macros @@ -1,27 +1,23 @@ REMOVE_CLOUD_COVER '''EffectsGroup scope = Source - activation = Source effects = RemoveSpecial name = "CLOUD_COVER_MASTER_SPECIAL" ''' REMOVE_ASH '''EffectsGroup scope = Source - activation = Source effects = RemoveSpecial name = "VOLCANIC_ASH_MASTER_SPECIAL" ''' REMOVE_DIM '''EffectsGroup scope = Source - activation = Source effects = RemoveSpecial name = "DIM_RIFT_MASTER_SPECIAL" ''' REMOVE_VOID '''EffectsGroup scope = Source - activation = Source effects = RemoveSpecial name = "VOID_MASTER_SPECIAL" ''' diff --git a/default/scripting/specials/ACCRETION_DISC.focs.txt b/default/scripting/specials/system/ACCRETION_DISC.focs.txt similarity index 95% rename from default/scripting/specials/ACCRETION_DISC.focs.txt rename to default/scripting/specials/system/ACCRETION_DISC.focs.txt index f9204a20e0d..79e6104bf49 100644 --- a/default/scripting/specials/ACCRETION_DISC.focs.txt +++ b/default/scripting/specials/system/ACCRETION_DISC.focs.txt @@ -32,8 +32,8 @@ Special EffectsGroup scope = And [ + InSystem id = Source.SystemID Planet - ContainedBy Source ] effects = SetMaxSupply value = Value - 1 ] diff --git a/default/scripting/specials/DERELICT_2.focs.txt b/default/scripting/specials/system/DERELICT_2.focs.txt similarity index 95% rename from default/scripting/specials/DERELICT_2.focs.txt rename to default/scripting/specials/system/DERELICT_2.focs.txt index a5144597394..6420ba98b0c 100644 --- a/default/scripting/specials/DERELICT_2.focs.txt +++ b/default/scripting/specials/system/DERELICT_2.focs.txt @@ -23,7 +23,7 @@ Special ] ] effects = [ - SetVisibility visibility = Full empire = Target.Owner condition = And [ + SetVisibility empire = Target.Owner visibility = Full condition = And [ Or [ System Planet diff --git a/default/scripting/specials/DERELICT_3.focs.txt b/default/scripting/specials/system/DERELICT_3.focs.txt similarity index 100% rename from default/scripting/specials/DERELICT_3.focs.txt rename to default/scripting/specials/system/DERELICT_3.focs.txt diff --git a/default/scripting/species/SP_ABADDONI.focs.txt b/default/scripting/species/SP_ABADDONI.focs.txt index 395a1938f1a..6fdebe2e57a 100644 --- a/default/scripting/species/SP_ABADDONI.focs.txt +++ b/default/scripting/species/SP_ABADDONI.focs.txt @@ -21,6 +21,7 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[BAD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_ACIREMA.focs.txt b/default/scripting/species/SP_ACIREMA.focs.txt index 2f0cd15a6e6..5b7f99fed16 100644 --- a/default/scripting/species/SP_ACIREMA.focs.txt +++ b/default/scripting/species/SP_ACIREMA.focs.txt @@ -17,6 +17,7 @@ Species effectsgroups = [ [[GREAT_INDUSTRY]] [[GOOD_RESEARCH]] + [[GREAT_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_ANCIENT_GUARDIANS.focs.txt b/default/scripting/species/SP_ANCIENT_GUARDIANS.focs.txt index fab6eb46059..506f062cd36 100644 --- a/default/scripting/species/SP_ANCIENT_GUARDIANS.focs.txt +++ b/default/scripting/species/SP_ANCIENT_GUARDIANS.focs.txt @@ -15,6 +15,7 @@ Species effectsgroups = [ [[NO_INDUSTRY]] [[NO_RESEARCH]] + [[NO_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] @@ -51,12 +52,17 @@ Species Planet ] activation = Turn high = 1 + priority = [[EARLY_POPULATION_OVERRIDE_PRIORITY]] effects = SetPopulation value = Target.TargetPopulation // self-destruct when captured EffectsGroup scope = Source - activation = OwnedBy affiliation = AnyEmpire + activation = And [ + Planet + OwnedBy affiliation = AnyEmpire + ] + priority = [[EARLY_POPULATION_OVERRIDE_PRIORITY]] effects = [ SetPopulation value = 0 GenerateSitRepMessage diff --git a/default/scripting/species/SP_BANFORO.focs.txt b/default/scripting/species/SP_BANFORO.focs.txt index cf2cc84f6aa..d174f08717b 100644 --- a/default/scripting/species/SP_BANFORO.focs.txt +++ b/default/scripting/species/SP_BANFORO.focs.txt @@ -19,9 +19,11 @@ Species effectsgroups = [ [[GOOD_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] + [[AVERAGE_SUPPLY]] [[AVERAGE_DEFENSE_TROOPS]] [[ULTIMATE_DETECTION]] @@ -35,7 +37,8 @@ Species Star type = [Blue] ] accountinglabel = "VERY_BRIGHT_STAR" - effects = SetTargetPopulation value = Value - 2 * Source.SizeAsDouble + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value - 2 * Source.HabitableSize EffectsGroup scope = Source @@ -44,7 +47,8 @@ Species Star type = [White] ] accountinglabel = "BRIGHT_STAR" - effects = SetTargetPopulation value = Value - Source.SizeAsDouble + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value - Source.HabitableSize // not for description [[AVERAGE_PLANETARY_SHIELDS]] diff --git a/default/scripting/species/SP_BEIGEGOO.focs.txt b/default/scripting/species/SP_BEIGEGOO.focs.txt index 36924ccf718..d6915564405 100644 --- a/default/scripting/species/SP_BEIGEGOO.focs.txt +++ b/default/scripting/species/SP_BEIGEGOO.focs.txt @@ -18,6 +18,7 @@ Species effectsgroups = [ [[GREAT_INDUSTRY]] [[NO_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[GOOD_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_CHATO.focs.txt b/default/scripting/species/SP_CHATO.focs.txt index 0b1094040ef..7d5608ebbb8 100644 --- a/default/scripting/species/SP_CHATO.focs.txt +++ b/default/scripting/species/SP_CHATO.focs.txt @@ -6,7 +6,7 @@ Species CanProduceShips CanColonize - tags = [ "PHOTOTROPHIC" "GREAT_RESEARCH" "AVERAGE_SUPPLY" "BAD_ATTACKTROOPS" "PEDIA_PHOTOTROPHIC_SPECIES_CLASS" ] + tags = [ "PHOTOTROPHIC" "GREAT_RESEARCH" "BAD_WEAPONS" "AVERAGE_SUPPLY" "BAD_ATTACKTROOPS" "PEDIA_PHOTOTROPHIC_SPECIES_CLASS" ] foci = [ [[HAS_INDUSTRY_FOCUS]] @@ -20,12 +20,14 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[GREAT_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] [[AVERAGE_SUPPLY]] [[BAD_DEFENSE_TROOPS]] [[BAD_OFFENSE_TROOPS]] + [[BAD_WEAPONS]] // not for description [[AVERAGE_PLANETARY_SHIELDS]] diff --git a/default/scripting/species/SP_CRAY.focs.txt b/default/scripting/species/SP_CRAY.focs.txt index d0b83164b07..2d3ab2082c6 100644 --- a/default/scripting/species/SP_CRAY.focs.txt +++ b/default/scripting/species/SP_CRAY.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[BAD_INDUSTRY]] [[GOOD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_CYNOS.focs.txt b/default/scripting/species/SP_CYNOS.focs.txt index f324605596f..9ca84c2b239 100644 --- a/default/scripting/species/SP_CYNOS.focs.txt +++ b/default/scripting/species/SP_CYNOS.focs.txt @@ -17,6 +17,7 @@ Species effectsgroups = [ [[NO_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_DERTHREAN.focs.txt b/default/scripting/species/SP_DERTHREAN.focs.txt index 6e084558a46..f67b83dce15 100644 --- a/default/scripting/species/SP_DERTHREAN.focs.txt +++ b/default/scripting/species/SP_DERTHREAN.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_EAXAW.focs.txt b/default/scripting/species/SP_EAXAW.focs.txt index d8661a9c4d1..68748ae305a 100644 --- a/default/scripting/species/SP_EAXAW.focs.txt +++ b/default/scripting/species/SP_EAXAW.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_EGASSEM.focs.txt b/default/scripting/species/SP_EGASSEM.focs.txt index 6e3fe6a893f..99e10669c1b 100644 --- a/default/scripting/species/SP_EGASSEM.focs.txt +++ b/default/scripting/species/SP_EGASSEM.focs.txt @@ -6,7 +6,7 @@ Species CanProduceShips CanColonize - tags = [ "LITHIC" "BAD_RESEARCH" "ULTIMATE_INDUSTRY" "BAD_POPULATION" "GREAT_SUPPLY" "GREAT_ATTACKTROOPS" "PEDIA_LITHIC_SPECIES_CLASS" ] + tags = [ "LITHIC" "BAD_RESEARCH" "GREAT_INDUSTRY" "BAD_POPULATION" "GREAT_SUPPLY" "GREAT_ATTACKTROOPS" "PEDIA_LITHIC_SPECIES_CLASS" ] foci = [ [[HAS_INDUSTRY_FOCUS]] @@ -18,8 +18,9 @@ Species preferredfocus = "FOCUS_INDUSTRY" effectsgroups = [ - [[ULTIMATE_INDUSTRY]] + [[GREAT_INDUSTRY]] [[BAD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[BAD_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_ETTY.focs.txt b/default/scripting/species/SP_ETTY.focs.txt index 12ccf654da9..5484d182ebb 100644 --- a/default/scripting/species/SP_ETTY.focs.txt +++ b/default/scripting/species/SP_ETTY.focs.txt @@ -21,6 +21,7 @@ Species effectsgroups = [ [[BAD_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_EXOBOT.focs.txt b/default/scripting/species/SP_EXOBOT.focs.txt index cf6c68cfa0e..d57d537d047 100644 --- a/default/scripting/species/SP_EXOBOT.focs.txt +++ b/default/scripting/species/SP_EXOBOT.focs.txt @@ -5,7 +5,7 @@ Species CanProduceShips CanColonize - Tags = [ "ROBOTIC" "BAD_RESEARCH" "BAD_WEAPONS" "AVERAGE_SUPPLY" "CTRL_ALWAYS_REPORT" "BAD_ATTACKTROOPS" "PEDIA_ROBOTIC_SPECIES_CLASS" ] + Tags = [ "ROBOTIC" "BAD_RESEARCH" "BAD_INDUSTRY" "BAD_WEAPONS" "BAD_SUPPLY" "BAD_ATTACKTROOPS" "PEDIA_ROBOTIC_SPECIES_CLASS" "MINDLESS_SPECIES" "CTRL_ALWAYS_REPORT"] foci = [ [[HAS_INDUSTRY_FOCUS]] @@ -15,12 +15,13 @@ Species ] effectsgroups = [ - [[AVERAGE_INDUSTRY]] + [[BAD_INDUSTRY]] [[BAD_RESEARCH]] + [[NO_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] - [[AVERAGE_SUPPLY]] + [[BAD_SUPPLY]] [[BAD_DEFENSE_TROOPS]] [[BAD_OFFENSE_TROOPS]] [[BAD_WEAPONS]] @@ -32,15 +33,15 @@ Species ] environments = [ - type = Swamp environment = Hostile - type = Toxic environment = Poor + type = Swamp environment = Adequate + type = Toxic environment = Adequate type = Inferno environment = Adequate type = Radiated environment = Adequate type = Barren environment = Adequate - type = Tundra environment = Poor - type = Desert environment = Hostile - type = Terran environment = Hostile - type = Ocean environment = Hostile + type = Tundra environment = Adequate + type = Desert environment = Adequate + type = Terran environment = Adequate + type = Ocean environment = Adequate type = Asteroids environment = Poor type = Gasgiant environment = Uninhabitable ] diff --git a/default/scripting/species/SP_EXPERIMENTOR.focs.txt b/default/scripting/species/SP_EXPERIMENTOR.focs.txt index 049bc5960b0..805b5c5b2fe 100644 --- a/default/scripting/species/SP_EXPERIMENTOR.focs.txt +++ b/default/scripting/species/SP_EXPERIMENTOR.focs.txt @@ -17,6 +17,7 @@ Species effectsgroups = [ [[NO_INDUSTRY]] [[ULTIMATE_RESEARCH]] + [[ULTIMATE_STOCKPILE]] [[GOOD_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_FIFTYSEVEN.focs.txt b/default/scripting/species/SP_FIFTYSEVEN.focs.txt index c507c6a6d05..0ba3b1ea9ae 100644 --- a/default/scripting/species/SP_FIFTYSEVEN.focs.txt +++ b/default/scripting/species/SP_FIFTYSEVEN.focs.txt @@ -18,6 +18,7 @@ Species effectsgroups = [ [[NO_INDUSTRY]] [[ULTIMATE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[BAD_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_FULVER.focs.txt b/default/scripting/species/SP_FULVER.focs.txt new file mode 100644 index 00000000000..aab0b967212 --- /dev/null +++ b/default/scripting/species/SP_FULVER.focs.txt @@ -0,0 +1,53 @@ +Species + name = "SP_FULVER" + description = "SP_FULVER_DESC" + gameplay_description = "SP_FULVER_GAMEPLAY_DESC" + Playable + //Native + CanProduceShips + CanColonize + + // TODO: BAD_HAPPINESS + // Future and change-oriented: TELEPATHIC, GOOD_WEAPONS,_STOCKPILE + // Balancing; instead of GOOD_ATTACKTROOPS: GREAT_FUEL + // bad at preserving knowledge or society: BAD_RESEARCH,_DEFENSE_TROOPS + // Never satisfied: BAD_HAPPINESS + tags = [ "LITHIC" "GOOD_STOCKPILE" "GOOD_WEAPONS" "BAD_RESEARCH" "AVERAGE_SUPPLY" "GREAT_FUEL" "TELEPATHIC" "PEDIA_LITHIC_SPECIES_CLASS"] + + foci = [ + [[HAS_INDUSTRY_FOCUS]] + [[HAS_RESEARCH_FOCUS]] + [[HAS_GROWTH_FOCUS]] + [[HAS_ADVANCED_FOCI]] + ] + + preferredfocus = "FOCUS_STOCKPILE" + + effectsgroups = [ + [[AVERAGE_INDUSTRY]] + [[BAD_RESEARCH]] + [[GOOD_STOCKPILE]] + + [[AVERAGE_POPULATION]] + [[AVERAGE_HAPPINESS]] + [[AVERAGE_SUPPLY]] + [[BAD_DEFENSE_TROOPS]] + [[AVERAGE_OFFENSE_TROOPS]] + [[GOOD_WEAPONS]] + [[GREAT_FUEL]] + + // not for description + [[AVERAGE_PLANETARY_SHIELDS]] + [[AVERAGE_PLANETARY_DEFENSE]] + [[LARGE_PLANET]] + [[NARROW_EP]] + [[STANDARD_SHIP_SHIELDS]] + ] + + [[TUNDRA_NARROW_EP]] + + graphic = "icons/species/insectoid-01.png" + +#include "common/*.macros" + +#include "/scripting/common/*.macros" diff --git a/default/scripting/species/SP_FURTHEST.focs.txt b/default/scripting/species/SP_FURTHEST.focs.txt index 0ae1854baf4..d73e7079d24 100644 --- a/default/scripting/species/SP_FURTHEST.focs.txt +++ b/default/scripting/species/SP_FURTHEST.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[BAD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[BAD_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_GEORGE.focs.txt b/default/scripting/species/SP_GEORGE.focs.txt index c1690a4c745..ddb09873f0d 100644 --- a/default/scripting/species/SP_GEORGE.focs.txt +++ b/default/scripting/species/SP_GEORGE.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[GOOD_INDUSTRY]] [[BAD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_GISGUSGTHRIM.focs.txt b/default/scripting/species/SP_GISGUSGTHRIM.focs.txt index 5aeb72afee4..8fe9eb06311 100644 --- a/default/scripting/species/SP_GISGUSGTHRIM.focs.txt +++ b/default/scripting/species/SP_GISGUSGTHRIM.focs.txt @@ -19,6 +19,7 @@ Species effectsgroups = [ [[BAD_INDUSTRY]] [[GOOD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_GYSACHE.focs.txt b/default/scripting/species/SP_GYSACHE.focs.txt index 2997bdb6198..a82b71395bd 100644 --- a/default/scripting/species/SP_GYSACHE.focs.txt +++ b/default/scripting/species/SP_GYSACHE.focs.txt @@ -6,7 +6,7 @@ Species CanProduceShips CanColonize - tags = [ "ORGANIC" "GOOD_INDUSTRY" "GREAT_RESEARCH" "GOOD_POPULATION" "BAD_WEAPONS" "AVERAGE_SUPPLY" "BAD_ATTACKTROOPS" "PEDIA_ORGANIC_SPECIES_CLASS" ] + tags = [ "AVERAGE_POPULATION" "ORGANIC" "GOOD_INDUSTRY" "GOOD_RESEARCH" "BAD_WEAPONS" "AVERAGE_SUPPLY" "BAD_ATTACKTROOPS" "PEDIA_ORGANIC_SPECIES_CLASS"] foci = [ [[HAS_INDUSTRY_FOCUS]] @@ -17,9 +17,10 @@ Species effectsgroups = [ [[GOOD_INDUSTRY]] - [[GREAT_RESEARCH]] + [[GOOD_RESEARCH]] + [[AVERAGE_STOCKPILE]] - [[GOOD_POPULATION]] + [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] [[AVERAGE_SUPPLY]] [[BAD_DEFENSE_TROOPS]] diff --git a/default/scripting/species/SP_HAPPY.focs.txt b/default/scripting/species/SP_HAPPY.focs.txt index ea6a043e426..8e51149e71b 100644 --- a/default/scripting/species/SP_HAPPY.focs.txt +++ b/default/scripting/species/SP_HAPPY.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[BAD_INDUSTRY]] [[GOOD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[BAD_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_HHOH.focs.txt b/default/scripting/species/SP_HHOH.focs.txt index 45b3eff866a..34bfe9512d7 100644 --- a/default/scripting/species/SP_HHOH.focs.txt +++ b/default/scripting/species/SP_HHOH.focs.txt @@ -21,6 +21,7 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_HIDDENGARDENER.disabled b/default/scripting/species/SP_HIDDENGARDENER.disabled index 172840c87eb..9892fea2acb 100644 --- a/default/scripting/species/SP_HIDDENGARDENER.disabled +++ b/default/scripting/species/SP_HIDDENGARDENER.disabled @@ -16,6 +16,7 @@ Species [[NO_INDUSTRY]] [[NO_RESEARCH]] + [[NO_STOCKPILE]] [[NO_DEFENSE_TROOPS]] diff --git a/default/scripting/species/SP_HUMAN.focs.txt b/default/scripting/species/SP_HUMAN.focs.txt index 2d25907a6c1..21b1a8b75fc 100644 --- a/default/scripting/species/SP_HUMAN.focs.txt +++ b/default/scripting/species/SP_HUMAN.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] @@ -27,8 +28,10 @@ Species [[AVERAGE_DEFENSE_TROOPS]] [[DESCRIPTION_EFFECTSGROUP_MACRO(AVERAGE_INDUSTRY_DESC)]] [[DESCRIPTION_EFFECTSGROUP_MACRO(AVERAGE_RESEARCH_DESC)]] + [[DESCRIPTION_EFFECTSGROUP_MACRO(AVERAGE_STOCKPILE_DESC)]] [[DESCRIPTION_EFFECTSGROUP_MACRO(AVERAGE_POPULATION_DESC)]] [[DESCRIPTION_EFFECTSGROUP_MACRO(AVERAGE_SUPPLY_DESC)]] + [[DESCRIPTION_EFFECTSGROUP_MACRO(AVERAGE_FUEL_DESC)]] [[DESCRIPTION_EFFECTSGROUP_MACRO(AVERAGE_DEFENSE_TROOPS_DESC)]] // not for description diff --git a/default/scripting/species/SP_KILANDOW.focs.txt b/default/scripting/species/SP_KILANDOW.focs.txt index c092e44ae57..2024c0661b7 100644 --- a/default/scripting/species/SP_KILANDOW.focs.txt +++ b/default/scripting/species/SP_KILANDOW.focs.txt @@ -19,9 +19,11 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[GREAT_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] + [[AVERAGE_SUPPLY]] [[AVERAGE_DEFENSE_TROOPS]] [[BAD_STEALTH]] diff --git a/default/scripting/species/SP_KOBUNTURA.focs.txt b/default/scripting/species/SP_KOBUNTURA.focs.txt index 78a229152d3..733e30cd51b 100644 --- a/default/scripting/species/SP_KOBUNTURA.focs.txt +++ b/default/scripting/species/SP_KOBUNTURA.focs.txt @@ -15,11 +15,12 @@ Species [[HAS_ADVANCED_FOCI]] ] - preferredfocus = "FOCUS_RESEARCH" + preferredfocus = "FOCUS_INDUSTRY" effectsgroups = [ [[GREAT_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] @@ -28,9 +29,12 @@ Species [[BAD_OFFENSE_TROOPS]] // not for description - [[NATIVE_PLANETARY_DEFENSE]] - [[NATIVE_PLANETARY_SHIELDS]] + [[AVERAGE_PLANETARY_DEFENSE]] + [[AVERAGE_PLANETARY_SHIELDS]] [[STANDARD_SHIP_SHIELDS]] + [[NATIVE_PLANETARY_DETECTION(10)]] + [[NATIVE_PLANETARY_DEFENSE(10)]] + [[NATIVE_PLANETARY_SHIELDS(10)]] ] [[BARREN_STANDARD_EP]] diff --git a/default/scripting/species/SP_LAENFA.focs.txt b/default/scripting/species/SP_LAENFA.focs.txt index 9430951f91c..be9bc4ddd82 100644 --- a/default/scripting/species/SP_LAENFA.focs.txt +++ b/default/scripting/species/SP_LAENFA.focs.txt @@ -6,7 +6,7 @@ Species CanProduceShips CanColonize - tags = [ "PHOTOTROPHIC" "TELEPATHIC" "BAD_RESEARCH" "GOOD_POPULATION" "AVERAGE_SUPPLY" "GREAT_DETECTION" "GREAT_STEALTH" "BAD_ATTACKTROOPS" "PEDIA_PHOTOTROPHIC_SPECIES_CLASS" ] + tags = [ "PHOTOTROPHIC" "TELEPATHIC" "GOOD_POPULATION" "AVERAGE_SUPPLY" "GREAT_DETECTION" "GREAT_STEALTH" "BAD_ATTACKTROOPS" "PEDIA_PHOTOTROPHIC_SPECIES_CLASS"] foci = [ [[HAS_INDUSTRY_FOCUS]] @@ -19,7 +19,8 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] - [[BAD_RESEARCH]] + [[AVERAGE_RESEARCH]] + [[GOOD_STOCKPILE]] [[GOOD_POPULATION]] [[AVERAGE_HAPPINESS]] @@ -34,10 +35,11 @@ Species [[AVERAGE_PLANETARY_SHIELDS]] [[AVERAGE_PLANETARY_DEFENSE]] [[LARGE_PLANET]] + [[BROAD_EP]] [[STANDARD_SHIP_SHIELDS]] ] - [[OCEAN_STANDARD_EP]] + [[OCEAN_BROAD_EP]] graphic = "icons/species/laenfa.png" diff --git a/default/scripting/species/SP_LEMBALALAM.focs.txt b/default/scripting/species/SP_LEMBALALAM.focs.txt index eba3543d0bb..f0a6a85c822 100644 --- a/default/scripting/species/SP_LEMBALALAM.focs.txt +++ b/default/scripting/species/SP_LEMBALALAM.focs.txt @@ -18,6 +18,7 @@ Species effectsgroups = [ [[NO_INDUSTRY]] [[GOOD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_HAPPINESS]] [[AVERAGE_SUPPLY]] @@ -54,11 +55,13 @@ Species EffectsGroup description = "FIXED_LOW_POPULATION_DESC" scope = Source - activation = Planet - priority = 130 - effects = [ - SetTargetPopulation value = 5 accountinglabel = "IMMORTAL_LABEL" + activation = And [ + Planet + Not Planet environment = [ uninhabitable ] ] + priority = [[TARGET_POPULATION_OVERRIDE_PRIORITY]] + effects = + SetTargetPopulation value = 5 accountinglabel = "IMMORTAL_LABEL" // set population to max @@ -68,14 +71,13 @@ Species Planet ] activation = Turn high = 1 + priority = [[EARLY_POPULATION_OVERRIDE_PRIORITY]] effects = SetPopulation value = Target.TargetPopulation // Give techs to empire EffectsGroup scope = Source - activation = And [ - Not OwnerHasTech name = "SPY_STEALTH_1" - ] + activation = Not OwnerHasTech name = "SPY_STEALTH_1" stackinggroup = "LEMBALALAM_TECH_UNLOCK" effects = [ GiveEmpireTech name = "SPY_STEALTH_1" empire = Target.Owner @@ -114,9 +116,7 @@ Species EffectsGroup scope = Source - activation = And [ - Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" - ] + activation = Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" stackinggroup = "LEMBALALAM_TECH_UNLOCK" effects = [ GiveEmpireTech name = "GRO_LIFECYCLE_MAN" empire = Target.Owner diff --git a/default/scripting/species/SP_MISIORLA.focs.txt b/default/scripting/species/SP_MISIORLA.focs.txt index 6c0364bec9e..a2505d50cfc 100644 --- a/default/scripting/species/SP_MISIORLA.focs.txt +++ b/default/scripting/species/SP_MISIORLA.focs.txt @@ -17,6 +17,7 @@ Species effectsgroups = [ [[BAD_INDUSTRY]] [[BAD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[BAD_POPULATION]] [[AVERAGE_HAPPINESS]] @@ -28,13 +29,7 @@ Species [[BAD_DETECTION]] [[BAD_STEALTH]] - EffectsGroup - description = "BAD_FUEL_DESC" - scope = And [ - Ship - Source - ] - effects = SetMaxFuel value = Value - 1 + [[BAD_FUEL]] // not for description [[AVERAGE_PLANETARY_SHIELDS]] diff --git a/default/scripting/species/SP_MUURSH.focs.txt b/default/scripting/species/SP_MUURSH.focs.txt index bc815e69a72..aee538d2ab1 100644 --- a/default/scripting/species/SP_MUURSH.focs.txt +++ b/default/scripting/species/SP_MUURSH.focs.txt @@ -18,6 +18,7 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[BAD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] @@ -26,9 +27,12 @@ Species [[GREAT_WEAPONS]] - [[NATIVE_PLANETARY_DEFENSE]] - [[NATIVE_PLANETARY_SHIELDS]] + [[AVERAGE_PLANETARY_DEFENSE]] + [[AVERAGE_PLANETARY_SHIELDS]] [[STANDARD_SHIP_SHIELDS]] + [[NATIVE_PLANETARY_DETECTION(10)]] + [[NATIVE_PLANETARY_DEFENSE(10)]] + [[NATIVE_PLANETARY_SHIELDS(10)]] ] [[DESERT_STANDARD_EP]] diff --git a/default/scripting/species/SP_NYMNMN.focs.txt b/default/scripting/species/SP_NYMNMN.focs.txt index 1e7ea5a2d4e..131793aa21d 100644 --- a/default/scripting/species/SP_NYMNMN.focs.txt +++ b/default/scripting/species/SP_NYMNMN.focs.txt @@ -16,6 +16,7 @@ Species effectsgroups = [ [[NO_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[BAD_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_OURBOOLS.focs.txt b/default/scripting/species/SP_OURBOOLS.focs.txt index 480e407dde1..66479a83c61 100644 --- a/default/scripting/species/SP_OURBOOLS.focs.txt +++ b/default/scripting/species/SP_OURBOOLS.focs.txt @@ -18,6 +18,7 @@ Species effectsgroups = [ [[BAD_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_PHINNERT.focs.txt b/default/scripting/species/SP_PHINNERT.focs.txt index 6a9c0af19b7..04f383d86e7 100644 --- a/default/scripting/species/SP_PHINNERT.focs.txt +++ b/default/scripting/species/SP_PHINNERT.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[NO_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_RAAAGH.focs.txt b/default/scripting/species/SP_RAAAGH.focs.txt index 10a26612c3d..d2e761ef1c8 100644 --- a/default/scripting/species/SP_RAAAGH.focs.txt +++ b/default/scripting/species/SP_RAAAGH.focs.txt @@ -18,6 +18,7 @@ Species effectsgroups = [ [[BAD_INDUSTRY]] [[NO_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_REPLICON.focs.txt b/default/scripting/species/SP_REPLICON.focs.txt index af5c74ee22b..a09a7d59dd6 100644 --- a/default/scripting/species/SP_REPLICON.focs.txt +++ b/default/scripting/species/SP_REPLICON.focs.txt @@ -6,7 +6,7 @@ Species CanProduceShips CanColonize - tags = [ "ROBOTIC" "GOOD_INDUSTRY" "BAD_RESEARCH" "AVERAGE_SUPPLY" ] + tags = [ "ROBOTIC" "GOOD_INDUSTRY" "BAD_RESEARCH" "AVERAGE_SUPPLY" "PEDIA_ROBOTIC_SPECIES_CLASS" ] foci = [ [[HAS_INDUSTRY_FOCUS]] @@ -20,6 +20,7 @@ Species effectsgroups = [ [[GOOD_INDUSTRY]] [[BAD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_SCYLIOR.focs.txt b/default/scripting/species/SP_SCYLIOR.focs.txt index 38b6becacea..610cc8902d9 100644 --- a/default/scripting/species/SP_SCYLIOR.focs.txt +++ b/default/scripting/species/SP_SCYLIOR.focs.txt @@ -7,7 +7,7 @@ Species CanProduceShips CanColonize - tags = [ "ORGANIC" "GREAT_RESEARCH" "GOOD_POPULATION" "AVERAGE_SUPPLY" "PEDIA_ORGANIC_SPECIES_CLASS" ] + tags = [ "ORGANIC" "GREAT_RESEARCH" "GOOD_POPULATION" "BAD_SUPPLY" "PEDIA_ORGANIC_SPECIES_CLASS" ] foci = [ [[HAS_INDUSTRY_FOCUS]] @@ -21,12 +21,15 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[GREAT_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[GOOD_POPULATION]] [[AVERAGE_HAPPINESS]] - [[AVERAGE_SUPPLY]] + [[BAD_SUPPLY]] [[AVERAGE_DEFENSE_TROOPS]] + [[BAD_FUEL]] + // not for description [[AVERAGE_PLANETARY_SHIELDS]] [[AVERAGE_PLANETARY_DEFENSE]] diff --git a/default/scripting/species/SP_SETINON.focs.txt b/default/scripting/species/SP_SETINON.focs.txt index a84bfc3fdb0..32df7415e39 100644 --- a/default/scripting/species/SP_SETINON.focs.txt +++ b/default/scripting/species/SP_SETINON.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[BAD_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[GOOD_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_SILEXIAN.focs.txt b/default/scripting/species/SP_SILEXIAN.focs.txt index 473f5db2769..c61d5d5ca99 100644 --- a/default/scripting/species/SP_SILEXIAN.focs.txt +++ b/default/scripting/species/SP_SILEXIAN.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[NO_INDUSTRY]] [[BAD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_SLY.focs.txt b/default/scripting/species/SP_SLY.focs.txt new file mode 100644 index 00000000000..5fe5c8baf88 --- /dev/null +++ b/default/scripting/species/SP_SLY.focs.txt @@ -0,0 +1,81 @@ +Species + name = "SP_SLY" + description = "SP_SLY_DESC" + gameplay_description = "SP_SLY_GAMEPLAY_DESC" + Playable + CanProduceShips + CanColonize + + tags = [ "VERY_BAD_SUPPLY" "PEDIA_PC_FUEL" "GASEOUS" "GREAT_STOCKPILE" "NO_TERRAFORM" "BAD_RESEARCH" "BAD_WEAPONS" "GOOD_DETECTION" "GREAT_STEALTH" "BAD_ATTACKTROOPS" "PEDIA_GASEOUS_SPECIES_CLASS"] + + foci = [ + [[HAS_INDUSTRY_FOCUS]] + [[HAS_RESEARCH_FOCUS]] + [[HAS_GROWTH_FOCUS]] + [[HAS_ADVANCED_FOCI]] + ] + + preferredfocus = "FOCUS_INDUSTRY" + + effectsgroups = [ + [[AVERAGE_INDUSTRY]] + [[GREAT_STOCKPILE]] + [[BAD_RESEARCH]] + + [[AVERAGE_POPULATION]] + [[AVERAGE_HAPPINESS]] + [[VERY_BAD_SUPPLY]] + [[GREAT_DEFENSE_TROOPS]] + [[BAD_OFFENSE_TROOPS]] + [[BAD_WEAPONS]] + + [[GOOD_DETECTION]] + [[GREAT_STEALTH]] + + // not for description + [[AVERAGE_PLANETARY_SHIELDS]] + [[AVERAGE_PLANETARY_DEFENSE]] + [[NARROW_EP]] + [[STANDARD_SHIP_SHIELDS]] + + // Extra refueling on gas giants + EffectsGroup + description = "GASEOUS_DESC" + scope = Source + activation = And [ + Ship + Stationary + ContainedBy And [ + Object id = RootCandidate.SystemID + System + Contains condition = And [ + Planet type = GasGiant + Not OwnedBy affiliation = EnemyOf empire = RootCandidate.Owner + ] + ] + ] + stackinggroup = "SP_SLY_FUEL_STACK" + // Update ship_hulls.macros if this number changes + effects = SetFuel value = Value + 0.1 + + ] + + environments = [ + type = Swamp environment = Hostile + type = Toxic environment = Hostile + type = Inferno environment = Hostile + type = Radiated environment = Hostile + type = Barren environment = Hostile + type = Tundra environment = Hostile + type = Desert environment = Hostile + type = Terran environment = Hostile + type = Ocean environment = Hostile + type = Asteroids environment = Uninhabitable + type = Gasgiant environment = Good + ] + + graphic = "icons/species/amorphous-05.png" + +#include "common/*.macros" + +#include "/scripting/common/*.macros" diff --git a/default/scripting/species/SP_SSLITH.focs.txt b/default/scripting/species/SP_SSLITH.focs.txt index c55c7191d97..8ce3d2f58b2 100644 --- a/default/scripting/species/SP_SSLITH.focs.txt +++ b/default/scripting/species/SP_SSLITH.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[BAD_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_SUPERTEST.focs.txt b/default/scripting/species/SP_SUPERTEST.focs.txt index fe2e6751b8c..346bd56fb3e 100644 --- a/default/scripting/species/SP_SUPERTEST.focs.txt +++ b/default/scripting/species/SP_SUPERTEST.focs.txt @@ -6,7 +6,7 @@ Species CanProduceShips CanColonize - tags = [ "ORGANIC" "ROBOTIC" "LITHIC" "PHOTOTROPHIC" "SELF_SUSTAINING" "TELEPATHIC" "GREAT_INDUSTRY" "GREAT_RESEARCH" "GOOD_POPULATION" "ULTIMATE_SUPPLY" "INFINITE_DETECTION" "GREAT_ATTACKTROOPS" ] + tags = [ "ORGANIC" "ROBOTIC" "LITHIC" "PHOTOTROPHIC" "SELF_SUSTAINING" "TELEPATHIC" "GREAT_INDUSTRY" "GREAT_RESEARCH" "GOOD_POPULATION" "ULTIMATE_SUPPLY" "INFINITE_DETECTION" "INFINITE_STEALTH" "GREAT_ATTACKTROOPS" ] foci = [ [[HAS_INDUSTRY_FOCUS]] @@ -18,6 +18,7 @@ Species effectsgroups = [ [[GREAT_INDUSTRY]] [[GREAT_RESEARCH]] + [[GREAT_STOCKPILE]] [[GOOD_POPULATION]] [[AVERAGE_HAPPINESS]] @@ -26,6 +27,7 @@ Species [[GREAT_OFFENSE_TROOPS]] [[ULTIMATE_WEAPONS]] + [[GREAT_FUEL]] // not for description [[AVERAGE_PLANETARY_SHIELDS]] diff --git a/default/scripting/species/SP_TAEGHIRUS.focs.txt b/default/scripting/species/SP_TAEGHIRUS.focs.txt index f4b044831fe..9be6d6b4def 100644 --- a/default/scripting/species/SP_TAEGHIRUS.focs.txt +++ b/default/scripting/species/SP_TAEGHIRUS.focs.txt @@ -6,7 +6,7 @@ Species CanProduceShips CanColonize - tags = [ "ORGANIC" "TELEPATHIC" "GOOD_INDUSTRY" "BAD_WEAPONS" "AVERAGE_SUPPLY" "BAD_ATTACKTROOPS" "PEDIA_ORGANIC_SPECIES_CLASS" ] + tags = [ "ORGANIC" "TELEPATHIC" "GOOD_POPULATION" "GOOD_INDUSTRY" "BAD_WEAPONS" "AVERAGE_SUPPLY" "BAD_ATTACKTROOPS" "PEDIA_ORGANIC_SPECIES_CLASS" ] foci = [ [[HAS_INDUSTRY_FOCUS]] @@ -20,6 +20,7 @@ Species effectsgroups = [ [[GOOD_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[GOOD_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_TRENCHERS.focs.txt b/default/scripting/species/SP_TRENCHERS.focs.txt index 72f265dc69a..343996bede8 100644 --- a/default/scripting/species/SP_TRENCHERS.focs.txt +++ b/default/scripting/species/SP_TRENCHERS.focs.txt @@ -16,6 +16,7 @@ Species effectsgroups = [ [[GOOD_INDUSTRY]] [[NO_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_TRITH.focs.txt b/default/scripting/species/SP_TRITH.focs.txt index 3a9d87843a8..7301b3af384 100644 --- a/default/scripting/species/SP_TRITH.focs.txt +++ b/default/scripting/species/SP_TRITH.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_UGMORS.focs.txt b/default/scripting/species/SP_UGMORS.focs.txt index eef5f8c3289..44ee62adf84 100644 --- a/default/scripting/species/SP_UGMORS.focs.txt +++ b/default/scripting/species/SP_UGMORS.focs.txt @@ -20,6 +20,7 @@ Species effectsgroups = [ [[GOOD_INDUSTRY]] [[BAD_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[AVERAGE_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SP_VOLP.focs.txt b/default/scripting/species/SP_VOLP.focs.txt index d368b45fb2c..b76237ec131 100644 --- a/default/scripting/species/SP_VOLP.focs.txt +++ b/default/scripting/species/SP_VOLP.focs.txt @@ -18,6 +18,7 @@ Species effectsgroups = [ [[AVERAGE_INDUSTRY]] [[AVERAGE_RESEARCH]] + [[AVERAGE_STOCKPILE]] [[GOOD_POPULATION]] [[AVERAGE_HAPPINESS]] diff --git a/default/scripting/species/SpeciesCensusOrdering.focs.txt b/default/scripting/species/SpeciesCensusOrdering.focs.txt new file mode 100644 index 00000000000..2a85da6d928 --- /dev/null +++ b/default/scripting/species/SpeciesCensusOrdering.focs.txt @@ -0,0 +1,20 @@ +// The SpeciesCensusOrdering.focs.txt file is used in +// default/scripting/species. The format is a single line, + +// `SpeciesCensusOrdering` + +// followed by any number of lines like, + +// `tag = "LITHIC"\n` + +// The list of species tags provides the order that species are displayed +// and ordered in the empire Census pop-up. + +SpeciesCensusOrdering + tag = "LITHIC" + tag = "ORGANIC" + tag = "PHOTOTROPHIC" + tag = "ROBOTIC" + tag = "SELF_SUSTAINING" + tag = "TELEPATHIC" + tag = "GASEOUS" \ No newline at end of file diff --git a/default/scripting/species/common/advanced_focus.macros b/default/scripting/species/common/advanced_focus.macros index d0f4e081195..b1ea7ac7741 100644 --- a/default/scripting/species/common/advanced_focus.macros +++ b/default/scripting/species/common/advanced_focus.macros @@ -19,6 +19,7 @@ ADVANCED_FOCUS_EFFECTS ] ] activation = Focus type = "FOCUS_BIOTERROR" + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] effects = SetTargetPopulation value = Value - 4 EffectsGroup diff --git a/default/scripting/species/common/detection.macros b/default/scripting/species/common/detection.macros index ca29c5391ae..b2636f83b2f 100644 --- a/default/scripting/species/common/detection.macros +++ b/default/scripting/species/common/detection.macros @@ -1,3 +1,16 @@ +// param1: value +NATIVE_PLANETARY_DETECTION +'''EffectsGroup + scope = Source + activation = And [ + Planet + Unowned + ] + accountinglabel = "NATIVE_PLANETARY_DETECTION_LABEL" + priority = [[DEFAULT_PRIORITY]] + effects = SetDetection value = Value + @1@ +''' + BAD_DETECTION '''EffectsGroup description = "BAD_DETECTION_DESC" @@ -31,7 +44,7 @@ ULTIMATE_DETECTION effects = SetDetection value = Value + 100 ''' -# param 1: species +// param 1: species COMMUNAL_VISION ''' EffectsGroup description = "COMMUNAL_VISION_DESC" @@ -44,5 +57,5 @@ COMMUNAL_VISION Planet Not Unowned ] - effects = SetVisibility visibility = Full empire = Source.Owner + effects = SetVisibility empire = Source.Owner visibility = Full ''' diff --git a/default/scripting/species/common/focus.macros b/default/scripting/species/common/focus.macros index 1c84b6b0dd5..fd2b1820d17 100644 --- a/default/scripting/species/common/focus.macros +++ b/default/scripting/species/common/focus.macros @@ -49,6 +49,12 @@ HAS_ADVANCED_FOCI location = OwnerHasTech name = "SHP_INTSTEL_LOG" graphic = "icons/focus/supply.png" + Focus + name = "FOCUS_STOCKPILE" + description = "FOCUS_STOCKPILE_DESC" + location = OwnerHasTech name = "PRO_GENERIC_SUPPLIES" + graphic = "icons/focus/stockpile.png" + Focus name = "FOCUS_STEALTH" description = "FOCUS_STEALTH_DESC" diff --git a/default/scripting/species/common/fuel.macros b/default/scripting/species/common/fuel.macros new file mode 100644 index 00000000000..d65ad23ec62 --- /dev/null +++ b/default/scripting/species/common/fuel.macros @@ -0,0 +1,42 @@ +// usually each "level" of fuel allows two jumps fuel +// (i.e. GREAT_FUEL is similar to GREAT_SUPPLY) +NO_FUEL +'''[[FUEL_EFFECTSGROUP(NO,0)]] +''' + +BAD_FUEL +'''[[FUEL_EFFECTSGROUP(BAD,Value - 0.5)]] +''' + +AVERAGE_FUEL +''' +''' + +GOOD_FUEL +'''[[FUEL_EFFECTSGROUP(GOOD,Value + 0.5)]] +''' + +GREAT_FUEL +'''[[FUEL_EFFECTSGROUP(GREAT,Value + 1)]] +''' + +ULTIMATE_FUEL +'''[[FUEL_EFFECTSGROUP(ULTIMATE,Value + 1.5)]] +''' + + +FUEL_EFFECTSGROUP +''' EffectsGroup + description = "@1@_FUEL_DESC" + scope = And [ + Ship + Source + ] + accountinglabel = "@1@_FUEL_LABEL" + priority = [[LATE_PRIORITY]] + effects = SetMaxFuel value = @2@ +''' + + +#include "/scripting/common/priorities.macros" +#include "/scripting/common/misc.macros" \ No newline at end of file diff --git a/default/scripting/species/common/general.macros b/default/scripting/species/common/general.macros index 3e93bd8a88e..59ba2c6b9f7 100644 --- a/default/scripting/species/common/general.macros +++ b/default/scripting/species/common/general.macros @@ -1,9 +1,59 @@ +STANDARD_METER_GROWTH +''' + // increase or decrease towards target meter, when not recently conquered + EffectsGroup + scope = Source + activation = And [ + Planet + (LocalCandidate.LastTurnConquered < CurrentTurn) + ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = [ + SetResearch value = Value + min(max(Value(Target.TargetResearch) - Value, -1), min(1, Value(Target.Happiness) - 4)) + SetIndustry value = Value + min(max(Value(Target.TargetIndustry) - Value, -1), min(1, Value(Target.Happiness) - 4)) + ] + + // removes residual production from a dead planet + EffectsGroup + scope = Source + activation = And [ + Planet + TargetPopulation high = 0 + Population high = 0.1 + ] + accountinglabel = "DYING_POPULATION_LABEL" + priority = [[END_CLEANUP_PRIORITY]] + effects = [ + SetResearch value = 0 + SetIndustry value = 0 + SetTargetIndustry value = 0 + SetTargetResearch value = 0 + ] +''' + STANDARD_CONSTRUCTION ''' EffectsGroup scope = Source activation = Planet accountinglabel = "STANDARD_CONSTRUCTION_LABEL" effects = SetTargetConstruction value = Value + 20 + + EffectsGroup // increase or decrease towards target meter, when not recently conquered + scope = Source + activation = And [ + Planet + (LocalCandidate.LastTurnConquered < CurrentTurn) + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn) + ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetConstruction value = Value + min(max(Value(Target.TargetConstruction) - Value, -1), 1) + + EffectsGroup // always ensure minimum value of one, as this is necessary for being attacked + scope = Source + activation = Planet + // has to happen after e.g. FORCE_ENERGY_STRC effects which also happens at AFTER_ALL_TARGET_MAX_METERS_PRIORITY + priority = [[METER_OVERRIDE_PRIORITY]] + effects = SetConstruction value = max(Value, 1) ''' FOCUS_CHANGE_PENALTY @@ -13,16 +63,32 @@ FOCUS_CHANGE_PENALTY activation = And [ Planet Not Focus type = "FOCUS_INDUSTRY" + (0 == LocalCandidate.TurnsSinceFocusChange) ] - effects = SetIndustry value = Value - max(0, 1 - Target.TurnsSinceFocusChange) + priority = [[FOCUS_CHANGE_PENALTY_PRIORITY]] + effects = SetIndustry value = Value - max(0, min(1 - Target.TurnsSinceFocusChange, Value - Value(Target.TargetIndustry))) EffectsGroup scope = Source activation = And [ Planet Not Focus type = "FOCUS_RESEARCH" + (0 == LocalCandidate.TurnsSinceFocusChange) + ] + priority = [[FOCUS_CHANGE_PENALTY_PRIORITY]] + effects = SetResearch value = Value - max(0, min(1 - Target.TurnsSinceFocusChange, Value - Value(Target.TargetResearch))) + +// TODO Delete this whole Stockpile penalty clause if Stockpile is determined to remain as a Max rather than a Target type meter +// If a decision is made to change Stockpile to a Target type meter, then simply change MaxStockpile below to TargetStockpile + EffectsGroup + scope = Source + activation = And [ + Planet + Not Focus type = "FOCUS_STOCKPILE" + (0 == LocalCandidate.TurnsSinceFocusChange) ] - effects = SetResearch value = Value - max(0, 1 - Target.TurnsSinceFocusChange) + priority = [[FOCUS_CHANGE_PENALTY_PRIORITY]] + effects = SetStockpile value = Value - max(0, min(1 - Target.TurnsSinceFocusChange, Value - Value(Target.MaxStockpile))) ''' //##### COLONIZATION SPEED #####// diff --git a/default/scripting/species/common/happiness.macros b/default/scripting/species/common/happiness.macros index a8faea60e95..10a3629fef3 100644 --- a/default/scripting/species/common/happiness.macros +++ b/default/scripting/species/common/happiness.macros @@ -1,5 +1,28 @@ AVERAGE_HAPPINESS ''' + EffectsGroup // increase or decrease 1 per turn towards target + scope = Source + activation = And [ + Planet + (LocalCandidate.LastTurnConquered < CurrentTurn) + ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetHappiness value = Value + + min(abs(Value(Target.TargetHappiness) - Value), 1) * + (1 - 2*(Statistic If condition = And [Target (Value > Value(Target.TargetHappiness))])) + + EffectsGroup + scope = Source + activation = And [ + Planet + (LocalCandidate.LastTurnConquered < CurrentTurn - 5) + (LocalCandidate.LastTurnColonized = CurrentTurn) + ] + effects = SetHappiness value = max(Value, + 1*(Statistic If Condition = And [Target Planet environment = Poor]) + + 2*(Statistic If Condition = And [Target Planet environment = Adequate]) + + 4*(Statistic If Condition = And [Target Planet environment = Good])) + EffectsGroup scope = Source activation = Planet @@ -32,5 +55,5 @@ AVERAGE_HAPPINESS effects = [ SetHappiness value = 0 SetTargetHappiness value = 0 - ] + ] ''' diff --git a/default/scripting/species/common/industry.macros b/default/scripting/species/common/industry.macros index d07035429a1..aea4f5d6592 100644 --- a/default/scripting/species/common/industry.macros +++ b/default/scripting/species/common/industry.macros @@ -23,14 +23,6 @@ BASIC_INDUSTRY accountinglabel = "DIFFICULTY" priority = [[VERY_LATE_PRIORITY]] effects = SetTargetIndustry value = Value * 2 - - EffectsGroup // removes residual production from a dead planet - scope = Source - activation = And [ - Planet - TargetPopulation high = 0 - ] - effects = SetIndustry value = 0 ''' BAD_INDUSTRY @@ -40,7 +32,6 @@ BAD_INDUSTRY description = "BAD_INDUSTRY_DESC" scope = Source activation = And [ - Source Planet TargetIndustry low = 0 Focus type = "FOCUS_INDUSTRY" @@ -61,7 +52,6 @@ GOOD_INDUSTRY description = "GOOD_INDUSTRY_DESC" scope = Source activation = And [ - Source Planet TargetIndustry low = 0 Focus type = "FOCUS_INDUSTRY" @@ -78,7 +68,6 @@ GREAT_INDUSTRY description = "GREAT_INDUSTRY_DESC" scope = Source activation = And [ - Source Planet TargetIndustry low = 0 Focus type = "FOCUS_INDUSTRY" @@ -95,7 +84,6 @@ ULTIMATE_INDUSTRY description = "ULTIMATE_INDUSTRY_DESC" scope = Source activation = And [ - Source Planet TargetIndustry low = 0 Focus type = "FOCUS_INDUSTRY" diff --git a/default/scripting/species/common/native_fortification.macros b/default/scripting/species/common/native_fortification.macros new file mode 100644 index 00000000000..d1a4733a012 --- /dev/null +++ b/default/scripting/species/common/native_fortification.macros @@ -0,0 +1,33 @@ +// param1: detection +// param2: defense +// param3: shields +// param4: troops +//currently unused +NATIVE_FORTIFICATION_CUSTOM +''' + [[NATIVE_PLANETARY_DETECTION(@1@)]] + [[NATIVE_PLANETARY_DEFENSE(@2@)]] + [[NATIVE_PLANETARY_SHIELDS(@3@)]] + [[NATIVE_PLANETARY_TROOPS(@4@)]] +''' + +//currently unused +NATIVE_FORTIFICATION_HIGH +'''[[NATIVE_FORTIFICATION_CUSTOM(30,30,30,30)]]''' + +//currently unused +NATIVE_FORTIFICATION_MEDIUM +'''[[NATIVE_FORTIFICATION_CUSTOM(20,20,20,20)]]''' + +//currently unused +NATIVE_FORTIFICATION_LOW +'''[[NATIVE_FORTIFICATION_CUSTOM(10,10,10,10)]]''' + +//currently unused +NATIVE_FORTIFICATION_MINIMAL +'''[[NATIVE_PLANETARY_SHIELDS(1)]]''' + +#include "/scripting/species/common/detection.macros" +#include "/scripting/species/common/planet_defense.macros" +#include "/scripting/species/common/planet_shields.macros" +#include "/scripting/species/common/troops.macros" diff --git a/default/scripting/species/common/planet_defense.macros b/default/scripting/species/common/planet_defense.macros index db0348ed5ab..f76c53b7400 100644 --- a/default/scripting/species/common/planet_defense.macros +++ b/default/scripting/species/common/planet_defense.macros @@ -1,4 +1,4 @@ - +// param1: value NATIVE_PLANETARY_DEFENSE '''EffectsGroup scope = Source @@ -8,15 +8,24 @@ NATIVE_PLANETARY_DEFENSE ] accountinglabel = "NATIVE_PLANETARY_DEFENSE_LABEL" priority = [[DEFAULT_PRIORITY]] - effects = [ - SetMaxDefense value = Value + 10 - SetDetection value = Value + 10 - ] - [[PROTECTION_FOCUS_DEFENSE]] + effects = SetMaxDefense value = Value + @1@ ''' AVERAGE_PLANETARY_DEFENSE -'''[[PROTECTION_FOCUS_DEFENSE]]''' +'''[[PROTECTION_FOCUS_DEFENSE]] +[[STANDARD_DEFENSE_GROWTH]]''' + +STANDARD_DEFENSE_GROWTH +''' EffectsGroup // increase 1 per turn, up to max + scope = Source + activation = And [ + Planet + Unowned + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn) + ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetDefense value = min(Value(Target.MaxDefense), Value + 1) +''' PROTECTION_FOCUS_DEFENSE '''EffectsGroup diff --git a/default/scripting/species/common/planet_shields.macros b/default/scripting/species/common/planet_shields.macros index 99864b40dea..4bdbc47ec7d 100644 --- a/default/scripting/species/common/planet_shields.macros +++ b/default/scripting/species/common/planet_shields.macros @@ -1,4 +1,4 @@ - +// param1: value NATIVE_PLANETARY_SHIELDS '''EffectsGroup scope = Source @@ -6,15 +6,28 @@ NATIVE_PLANETARY_SHIELDS Planet Unowned ] - effects = SetMaxShield value = Value + 10 accountinglabel = "NATIVE_PLANETARY_SHIELDS_LABEL" - - [[PROTECTION_FOCUS_SHIELDS]] + accountinglabel = "NATIVE_PLANETARY_SHIELDS_LABEL" + priority = [[DEFAULT_PRIORITY]] + effects = SetMaxShield value = Value + @1@ ''' AVERAGE_PLANETARY_SHIELDS -'''[[PROTECTION_FOCUS_SHIELDS]]''' +'''[[PROTECTION_FOCUS_SHIELDS]] +[[STANDARD_SHIELD_GROWTH]]''' +STANDARD_SHIELD_GROWTH +''' EffectsGroup // increase 1 per turn, up to max + scope = Source + activation = And [ + Planet + Unowned + (LocalCandidate.LastTurnConquered < CurrentTurn) + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn) + ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetShield value = min(Value(Target.MaxShield), Value + 1) +''' PROTECTION_FOCUS_SHIELDS '''EffectsGroup diff --git a/default/scripting/species/common/population.macros b/default/scripting/species/common/population.macros index 8cc70cbe535..d600e20420c 100644 --- a/default/scripting/species/common/population.macros +++ b/default/scripting/species/common/population.macros @@ -4,13 +4,32 @@ BASIC_POPULATION [[ENVIRONMENT_MODIFIER]] [[SELF_SUSTAINING_BONUS]] [[PHOTOTROPHIC_BONUS]] + [[GASEOUS_BONUS]] + [[MINDLESS_BONUS]] [[HOMEWORLD_GROWTH_FOCUS_BOOST]] - // Since all species have the same advanced focus effects and infrastructure, the macros are stashed here were they don't need to be manually included in each species macros. + // population growth or decay towards to target population + EffectsGroup + scope = Source + activation = And [ + Planet + (LocalCandidate.LastTurnConquered < CurrentTurn) + ] + priority = [[EARLY_FIRST_NATURAL_POPULATION_PRIORITY]] + effects = SetPopulation value = + (Statistic If condition = (Value < Target.TargetPopulation) * + min(Target.TargetPopulation, Value + Value*0.01*(Target.TargetPopulation + 1 - Value))) + + (Statistic If condition = (Value >= Target.TargetPopulation) * + max(Target.TargetPopulation, Value - 0.1*(Value - Target.TargetPopulation))) + + // Since all species have the same advanced focus effects and + // infrastructure, the macros are stashed here were they don't + // need to be manually included in each species macros. [[FOCUS_CHANGE_PENALTY]] [[ADVANCED_FOCUS_EFFECTS]] [[STANDARD_CONSTRUCTION]] + [[STANDARD_METER_GROWTH]] ''' AVERAGE_POPULATION @@ -24,7 +43,7 @@ BAD_POPULATION description = "BAD_POPULATION_DESC" scope = Source activation = Planet - priority = [[DEFAULT_PRIORITY]] + priority = [[TARGET_POPULATION_SCALING_PRIORITY]] effects = SetTargetPopulation value = Value -0.25*abs(Value) accountinglabel = "BAD_POPULATION_LABEL" ''' @@ -35,7 +54,7 @@ GOOD_POPULATION description = "GOOD_POPULATION_DESC" scope = Source activation = Planet - priority = [[DEFAULT_PRIORITY]] + priority = [[TARGET_POPULATION_SCALING_PRIORITY]] effects = SetTargetPopulation value = Value +0.25*abs(Value) accountinglabel = "GOOD_POPULATION_LABEL" ''' @@ -50,7 +69,7 @@ ENVIRONMENT_MODIFIER Planet Planet environment = Uninhabitable ] - priority = [[EARLY_PRIORITY]] + priority = [[TARGET_POPULATION_OVERRIDE_PRIORITY]] effects = SetTargetPopulation value = -999 accountinglabel = "UNINHABTIABLE_ENVIRONMENT_LABEL" EffectsGroup @@ -59,8 +78,8 @@ ENVIRONMENT_MODIFIER Planet Planet environment = Hostile ] - priority = [[EARLY_PRIORITY]] - effects = SetTargetPopulation value = Value - 4 * Source.SizeAsDouble accountinglabel = "HOSTILE_ENVIRONMENT_LABEL" + priority = [[TARGET_POPULATION_BEFORE_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value - 4 * Source.HabitableSize accountinglabel = "HOSTILE_ENVIRONMENT_LABEL" EffectsGroup scope = Source @@ -68,8 +87,8 @@ ENVIRONMENT_MODIFIER Planet Planet environment = Poor ] - priority = [[EARLY_PRIORITY]] - effects = SetTargetPopulation value = Value - 2 * Source.SizeAsDouble accountinglabel = "POOR_ENVIRONMENT_LABEL" + priority = [[TARGET_POPULATION_BEFORE_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value - 2 * Source.HabitableSize accountinglabel = "POOR_ENVIRONMENT_LABEL" /* EffectsGroup scope = Source @@ -77,14 +96,15 @@ ENVIRONMENT_MODIFIER Planet Planet environment = Adequate ] - effects = SetTargetPopulation value = Value + 0 * Source.SizeAsDouble accountinglabel = "ADEQUATE_ENVIRONMENT_LABEL" + priority = [[TARGET_POPULATION_BEFORE_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 0 * Source.HabitableSize accountinglabel = "ADEQUATE_ENVIRONMENT_LABEL" */ EffectsGroup scope = Source activation = Planet environment = Good - priority = [[EARLY_PRIORITY]] - effects = SetTargetPopulation value = Value + 3 * Target.SizeAsDouble accountinglabel = "GOOD_ENVIRONMENT_LABEL" + priority = [[TARGET_POPULATION_BEFORE_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 3 * Target.HabitableSize accountinglabel = "GOOD_ENVIRONMENT_LABEL" ''' HOMEWORLD_BONUS_POPULATION @@ -95,8 +115,8 @@ HOMEWORLD_BONUS_POPULATION ] activation = Planet stackinggroup = "HOMEWORLD_STACK" - priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 2 * Target.SizeAsDouble accountinglabel = "HOMEWORLD_BONUS" + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 2 * Target.HabitableSize accountinglabel = "HOMEWORLD_BONUS" ''' HOMEWORLD_GROWTH_FOCUS_BOOST @@ -106,7 +126,6 @@ HOMEWORLD_GROWTH_FOCUS_BOOST OwnedBy empire = Source.Owner Species name = Source.Species Not Homeworld name = Source.Species - //TargetPopulation low = 0 ResourceSupplyConnected empire = Source.Owner condition = Source ] activation = And [ @@ -115,22 +134,22 @@ HOMEWORLD_GROWTH_FOCUS_BOOST Homeworld ] stackinggroup = "HOMEWORLD_STACK" - priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble accountinglabel = "HOMEWORLD_SUPPLY" + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize accountinglabel = "HOMEWORLD_SUPPLY" ''' -// This is dependent on current placement in population effects calc, just after Homeworld and Environment +// This is dependent on current placement in population effects calc, +// just after Homeworld and Environment SELF_SUSTAINING_BONUS ''' EffectsGroup scope = Source activation = And [ Planet HasTag name = "SELF_SUSTAINING" - //TargetPopulation low = 0 ] accountinglabel = "SELF_SUSTAINING_LABEL" - priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 3 * Target.SizeAsDouble // Gets the same bonus as three growth specials + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 3 * Target.HabitableSize // Gets the same bonus as three growth specials ''' PHOTOTROPHIC_BONUS @@ -157,8 +176,8 @@ PHOTOTROPHIC_BONUS TargetPopulation low = 0 ] accountinglabel = "VERY_BRIGHT_STAR" - priority = [[LATEST_GROWTH_PRIORITY]] - effects = SetTargetPopulation value = Value + 3 * Source.SizeAsDouble + priority = [[TARGET_POPULATION_LAST_BEFORE_OVERRIDE_PRIORITY]] + effects = SetTargetPopulation value = Value + 3 * Source.HabitableSize EffectsGroup scope = Source @@ -169,8 +188,8 @@ PHOTOTROPHIC_BONUS TargetPopulation low = 0 ] accountinglabel = "BRIGHT_STAR" - priority = [[LATEST_GROWTH_PRIORITY]] - effects = SetTargetPopulation value = Value + 1.5 * Source.SizeAsDouble + priority = [[TARGET_POPULATION_LAST_BEFORE_OVERRIDE_PRIORITY]] + effects = SetTargetPopulation value = Value + 1.5 * Source.HabitableSize EffectsGroup scope = Source @@ -180,8 +199,8 @@ PHOTOTROPHIC_BONUS Star type = [Red Neutron] ] accountinglabel = "DIM_STAR" - priority = [[LATEST_GROWTH_PRIORITY]] - effects = SetTargetPopulation value = Value - 1 * Source.SizeAsDouble + priority = [[TARGET_POPULATION_LAST_BEFORE_OVERRIDE_PRIORITY]] + effects = SetTargetPopulation value = Value - 1 * Source.HabitableSize EffectsGroup scope = Source @@ -191,7 +210,67 @@ PHOTOTROPHIC_BONUS Star type = [BlackHole NoStar] ] accountinglabel = "NO_STAR" - priority = [[LATEST_GROWTH_PRIORITY]] - effects = SetTargetPopulation value = Value - 10 * Source.SizeAsDouble + priority = [[TARGET_POPULATION_LAST_BEFORE_OVERRIDE_PRIORITY]] + effects = SetTargetPopulation value = Value - 10 * Source.HabitableSize +''' + +GASEOUS_BONUS +''' EffectsGroup + scope = Source + activation = And [ + Planet type = GasGiant + HasTag name = "GASEOUS" + ] + accountinglabel = "GASEOUS_LABEL" + priority = [[TARGET_POPULATION_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value - 0.5*abs(Value) +''' + +MINDLESS_BONUS +''' EffectsGroup + scope = Source + activation = And [ + Planet + Not Planet type = Asteroids + HasTag name = "MINDLESS_SPECIES" + ] + accountinglabel = "MINDLESS_LABEL" + priority = [[TARGET_POPULATION_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value - 0.5*abs(Value) +''' + +// @1@ Species key +LIFECYCLE_MANIP_POPULATION_EFFECTS +''' + EffectsGroup + scope = And [ + Object id = Source.PlanetID + Planet + ] + activation = And [ + Not OwnerHasTech name = "GRO_LIFECYCLE_MAN" + Turn low = LocalCandidate.CreationTurn + 1 high = LocalCandidate.CreationTurn + 1 + ] + effects = [ + SetSpecies name = @1@ + SetPopulation value = 1 + ] + + EffectsGroup + scope = And [ + Object id = Source.PlanetID + Planet + ] + activation = And [ + OwnerHasTech name = "GRO_LIFECYCLE_MAN" + Turn low = LocalCandidate.CreationTurn + 1 high = LocalCandidate.CreationTurn + 1 + ] + effects = [ + SetSpecies name = @1@ + SetPopulation value = [[MIN_RECOLONIZING_SIZE]] + ] ''' +#include "/scripting/species/common/advanced_focus.macros" +#include "/scripting/species/common/focus.macros" +#include "/scripting/species/common/general.macros" diff --git a/default/scripting/species/common/research.macros b/default/scripting/species/common/research.macros index 94e2855c2e7..fdecb4e3dc6 100644 --- a/default/scripting/species/common/research.macros +++ b/default/scripting/species/common/research.macros @@ -19,14 +19,6 @@ BASIC_RESEARCH accountinglabel = "DIFFICULTY" priority = [[LATE_PRIORITY]] effects = SetTargetResearch value = Value * 2 - - EffectsGroup // removes residual production from a dead planet - scope = Source - activation = And [ - Planet - TargetPopulation high = 0 - ] - effects = SetResearch value = 0 ''' NO_RESEARCH @@ -39,7 +31,6 @@ BAD_RESEARCH description = "BAD_RESEARCH_DESC" scope = Source activation = And [ - Source Planet Focus type = "FOCUS_RESEARCH" ] @@ -59,7 +50,6 @@ GOOD_RESEARCH description = "GOOD_RESEARCH_DESC" scope = Source activation = And [ - Source Planet Focus type = "FOCUS_RESEARCH" ] @@ -75,7 +65,6 @@ GREAT_RESEARCH description = "GREAT_RESEARCH_DESC" scope = Source activation = And [ - Source Planet Focus type = "FOCUS_RESEARCH" ] @@ -91,7 +80,6 @@ ULTIMATE_RESEARCH description = "ULTIMATE_RESEARCH_DESC" scope = Source activation = And [ - Source Planet Focus type = "FOCUS_RESEARCH" ] diff --git a/default/scripting/species/common/shields.macros b/default/scripting/species/common/shields.macros index 772869146dd..68103ff02b3 100644 --- a/default/scripting/species/common/shields.macros +++ b/default/scripting/species/common/shields.macros @@ -9,6 +9,16 @@ STANDARD_SHIP_SHIELDS accountinglabel = "DIFFICULTY" priority = [[DEFAULT_PRIORITY]] effects = SetMaxShield value = Value + 1 + + EffectsGroup // increase to max when not in battle + scope = Source + activation = And [ + Ship + (LocalCandidate.LastTurnActiveInBattle < CurrentTurn) + ] + stackinggroup = "SHIELD_REGENERATION_EFFECT" + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetShield value = Target.MaxShield ''' GOOD_SHIP_SHIELDS diff --git a/default/scripting/species/common/stealth.macros b/default/scripting/species/common/stealth.macros index 24a5f72d527..0af0cc9e282 100644 --- a/default/scripting/species/common/stealth.macros +++ b/default/scripting/species/common/stealth.macros @@ -57,3 +57,5 @@ ULTIMATE_STEALTH stackinggroup = "SPECIES_STEALTH_STACK" effects = SetStealth value = Value + [[HIGH_STEALTH]] ''' + +#include "/scripting/common/stealth.macros" diff --git a/default/scripting/species/common/stockpile.macros b/default/scripting/species/common/stockpile.macros new file mode 100644 index 00000000000..ee42c7b8043 --- /dev/null +++ b/default/scripting/species/common/stockpile.macros @@ -0,0 +1,88 @@ +NO_STOCKPILE +''' +''' + +BAD_STOCKPILE +'''[[STOCKPILE_PER_POP_EFFECTSGROUP(BAD,Value + 0.5 * Target.Population * [[STOCKPILE_PER_POP]])]] +[[STANDARD_STOCKPILE]] +''' + +AVERAGE_STOCKPILE +'''EffectsGroup + // Skip the AVERAGE_STOCKPILE_DESC, same as for the other *_STOCKPILE macros + [[STOCKPILE_PER_POP_EFFECTSGROUP__SNIP(AVERAGE,Value + 1 * Target.Population * [[STOCKPILE_PER_POP]])]] +[[STANDARD_STOCKPILE]] +''' + +GOOD_STOCKPILE +'''[[STOCKPILE_PER_POP_EFFECTSGROUP(GOOD,Value + 3 * Target.Population * [[STOCKPILE_PER_POP]])]] +[[STANDARD_STOCKPILE]] +''' + +GREAT_STOCKPILE +'''[[STOCKPILE_PER_POP_EFFECTSGROUP(GREAT,Value + 10 * Target.Population * [[STOCKPILE_PER_POP]])]] +[[STANDARD_STOCKPILE]] +''' + +ULTIMATE_STOCKPILE +'''[[STOCKPILE_PER_POP_EFFECTSGROUP(ULTIMATE, Value + 15 * Target.Population * [[STOCKPILE_PER_POP]])]] +[[STANDARD_STOCKPILE]] +''' + +STOCKPILE_PER_POP_EFFECTSGROUP +''' EffectsGroup + description = "@1@_STOCKPILE_DESC" + [[STOCKPILE_PER_POP_EFFECTSGROUP__SNIP(@1@,@2@)]] +''' + +STOCKPILE_PER_POP_EFFECTSGROUP__SNIP +'''scope = Source + activation = Planet + accountinglabel = "@1@_STOCKPILE_LABEL" + priority = [[EARLY_PRIORITY]] + effects = SetMaxStockpile value = @2@ +''' + +STANDARD_STOCKPILE +''' EffectsGroup // increase or decrease towards target meter, when not recently conquered + scope = Source + activation = And [ + Planet + (LocalCandidate.LastTurnConquered < CurrentTurn) + (LocalCandidate.System.LastTurnBattleHere < CurrentTurn) + ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetStockpile value = Value + + min(abs(Value(Target.MaxStockpile) - Value), 1) * + (1 - 2*(Statistic If condition = And [Target (Value > Value(Target.MaxStockpile))])) + + // increase stockpile for species if Homeworld is set to stockpile focus + EffectsGroup + scope = And [ + ProductionCenter + OwnedBy empire = Source.Owner + Species name = Source.Species + Not Homeworld name = Source.Species + ] + activation = And [ + Planet + Focus type = "FOCUS_STOCKPILE" + Homeworld + ] + stackinggroup = "HOMEWORLD_STOCKPILE_FOCUS_BONUS_LABEL" + accountinglabel = "HOMEWORLD_STOCKPILE_FOCUS_BONUS_LABEL" + priority = [[DEFAULT_PRIORITY]] + effects = SetMaxStockpile value = (Value + 2 * Target.Population * [[STOCKPILE_PER_POP]]) + + // removes residual stockpile capacity from a dead planet + EffectsGroup + scope = Source + activation = And [ + Planet + TargetPopulation high = 0 + ] + effects = SetStockpile value = 0 +''' + +#include "/scripting/common/base_prod.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/species/common/supply.macros b/default/scripting/species/common/supply.macros index 87d9a65dd88..6b11c319fc6 100644 --- a/default/scripting/species/common/supply.macros +++ b/default/scripting/species/common/supply.macros @@ -2,6 +2,17 @@ NO_SUPPLY ''' ''' +VERY_BAD_SUPPLY +''' EffectsGroup + description = "VERY_BAD_SUPPLY_DESC" + scope = Source + activation = Planet + accountinglabel = "VERY_BAD_SUPPLY_LABEL" + effects = SetMaxSupply value = Value - 1 + +[[STANDARD_SUPPLY_GROWTH]] +''' + BAD_SUPPLY ''' EffectsGroup description = "BAD_SUPPLY_DESC" @@ -9,6 +20,8 @@ BAD_SUPPLY activation = Planet accountinglabel = "BAD_SUPPLY_LABEL" effects = SetMaxSupply value = Value + 0 + +[[STANDARD_SUPPLY_GROWTH]] ''' AVERAGE_SUPPLY @@ -17,6 +30,8 @@ AVERAGE_SUPPLY activation = Planet accountinglabel = "AVERAGE_SUPPLY_LABEL" effects = SetMaxSupply value = Value + 1 + +[[STANDARD_SUPPLY_GROWTH]] ''' GREAT_SUPPLY @@ -26,6 +41,8 @@ GREAT_SUPPLY activation = Planet accountinglabel = "GREAT_SUPPLY_LABEL" effects = SetMaxSupply value = Value + 2 + +[[STANDARD_SUPPLY_GROWTH]] ''' ULTIMATE_SUPPLY @@ -35,4 +52,18 @@ ULTIMATE_SUPPLY activation = Planet accountinglabel = "ULTIMATE_SUPPLY_LABEL" effects = SetMaxSupply value = Value + 3 + +[[STANDARD_SUPPLY_GROWTH]] +''' + +STANDARD_SUPPLY_GROWTH +''' EffectsGroup // increase 1 per turn, up to max + scope = Source + activation = And [ + Planet + (LocalCandidate.LastTurnConquered < CurrentTurn) + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn) + ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetSupply value = min(Value(Target.MaxSupply), Value + 1) ''' diff --git a/default/scripting/species/common/telepathic.macros b/default/scripting/species/common/telepathic.macros index d0495e9cafe..6a91df523f8 100644 --- a/default/scripting/species/common/telepathic.macros +++ b/default/scripting/species/common/telepathic.macros @@ -10,5 +10,5 @@ TELEPATHIC_DETECTION WithinStarlaneJumps jumps = @1@ condition = Source ] activation = OwnedBy affiliation = AnyEmpire - effects = SetVisibility visibility = Basic empire = Source.Owner + effects = SetVisibility empire = Source.Owner visibility = max(Basic, Value) ''' diff --git a/default/scripting/species/common/troops.macros b/default/scripting/species/common/troops.macros index 20dcf92092b..1eabddfd32f 100644 --- a/default/scripting/species/common/troops.macros +++ b/default/scripting/species/common/troops.macros @@ -1,5 +1,18 @@ -BASIC_DEFENSE_TROOPS +// param1: value +NATIVE_PLANETARY_TROOPS '''EffectsGroup + scope = Source + activation = And [ + Planet + Unowned + ] + accountinglabel = "NATIVE_PLANETARY_TROOPS_LABEL" + priority = [[VERY_EARLY_PRIORITY]] + effects = SetMaxTroops value = Value + @1@ +''' + +BASIC_DEFENSE_TROOPS +''' EffectsGroup scope = Source activation = And [ Homeworld @@ -7,9 +20,10 @@ BASIC_DEFENSE_TROOPS ] stackinggroup = "HOMEWORLD_TROOPS_STACK" priority = [[VERY_EARLY_PRIORITY]] - effects = SetMaxTroops value = Value + 4 accountinglabel = "HOMEWORLD_LABEL" + effects = SetMaxTroops value = Value + 4 + accountinglabel = "HOMEWORLD_LABEL" -EffectsGroup + EffectsGroup scope = Source activation = And [ Planet @@ -17,16 +31,18 @@ EffectsGroup ] stackinggroup = "BASIC_TROOPS_STACK" priority = [[VERY_EARLY_PRIORITY]] - effects = SetMaxTroops value = Value + 10 accountinglabel = "INDEPENDENT_TROOP_LABEL" + effects = SetMaxTroops value = Value + 10 + accountinglabel = "INDEPENDENT_TROOP_LABEL" -EffectsGroup + EffectsGroup scope = Source activation = Planet stackinggroup = "POPULATION_TROOPS_STACK" priority = [[VERY_EARLY_PRIORITY]] - effects = SetMaxTroops Value = Value + Target.Population * [[TROOPS_PER_POP]] accountinglabel = "DEF_ROOT_DEFENSE" + effects = SetMaxTroops Value = Value + Target.Population * [[TROOPS_PER_POP]] + accountinglabel = "DEF_ROOT_DEFENSE" -EffectsGroup // gives human bonuses when AI Aggression set to Beginner + EffectsGroup // gives human bonuses when AI Aggression set to Beginner scope = Source activation = And [ Human // human player, not human species @@ -36,10 +52,20 @@ EffectsGroup // gives human bonuses when AI Aggression set to Beginner accountinglabel = "DIFFICULTY" priority = [[DEFAULT_PRIORITY]] effects = SetMaxTroops value = max(6, Value * 2) + + EffectsGroup // increase 1 per turn, up to max + scope = Source + activation = And [ + Planet + (LocalCandidate.LastTurnConquered < CurrentTurn) + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn) + ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetTroops value = min(Value(Target.MaxTroops), Value + 1) ''' AFTER_SPECIES_MULTIPLICATOR_TROOPS -'''EffectsGroup +''' EffectsGroup scope = And [ Planet OwnedBy empire = Source.Owner @@ -47,7 +73,8 @@ AFTER_SPECIES_MULTIPLICATOR_TROOPS ] stackinggroup = "OUTPOST_TROOPS_STACK" priority = [[VERY_LATE_PRIORITY]] - effects = SetMaxTroops value = Value * .5 accountinglabel = "OUTPOST_TROOP_LABEL" + effects = SetMaxTroops value = Value * .5 + accountinglabel = "OUTPOST_TROOP_LABEL" [[PROTECTION_FOCUS_TROOPS]] ''' @@ -72,7 +99,8 @@ EffectsGroup scope = Source activation = Planet priority = [[DEFAULT_PRIORITY]] - effects = SetMaxTroops value = Value * 0.5 accountinglabel = "BAD_TROOPS_LABEL" + effects = SetMaxTroops value = Value * 0.5 + accountinglabel = "BAD_TROOPS_LABEL" [[AFTER_SPECIES_MULTIPLICATOR_TROOPS]] ''' @@ -91,7 +119,8 @@ EffectsGroup scope = Source activation = Planet priority = [[DEFAULT_PRIORITY]] - effects = SetMaxTroops value = Value * 1.5 accountinglabel = "GOOD_TROOPS_LABEL" + effects = SetMaxTroops value = Value * 1.5 + accountinglabel = "GOOD_TROOPS_LABEL" [[AFTER_SPECIES_MULTIPLICATOR_TROOPS]] ''' @@ -104,7 +133,8 @@ EffectsGroup scope = Source activation = Planet priority = [[DEFAULT_PRIORITY]] - effects = SetMaxTroops value = Value * 2 accountinglabel = "GREAT_TROOPS_LABEL" + effects = SetMaxTroops value = Value * 2 + accountinglabel = "GREAT_TROOPS_LABEL" [[AFTER_SPECIES_MULTIPLICATOR_TROOPS]] ''' @@ -117,7 +147,8 @@ EffectsGroup scope = Source activation = Planet priority = [[DEFAULT_PRIORITY]] - effects = SetMaxTroops value = Value * 3 accountinglabel = "ULTIMATE_TROOPS_LABEL" + effects = SetMaxTroops value = Value * 3 + accountinglabel = "ULTIMATE_TROOPS_LABEL" [[AFTER_SPECIES_MULTIPLICATOR_TROOPS]] ''' @@ -131,7 +162,8 @@ PROTECTION_FOCUS_TROOPS ] stackinggroup = "FOCUS_PROTECTION_TROOPS_STACK" priority = [[VERY_LATE_PRIORITY]] - effects = SetMaxTroops value = Value * 2 accountinglabel = "FOCUS_PROTECTION_LABEL" + effects = SetMaxTroops value = Value * 2 + accountinglabel = "FOCUS_PROTECTION_LABEL" ''' NO_OFFENSE_TROOPS diff --git a/default/scripting/species/common/weapons.macros b/default/scripting/species/common/weapons.macros index 8f0465bbb6c..6ac53ffa39f 100644 --- a/default/scripting/species/common/weapons.macros +++ b/default/scripting/species/common/weapons.macros @@ -25,17 +25,15 @@ BAD_WEAPONS Ship Armed Or [ - DesignHasPart name = "FT_HANGAR_1" DesignHasPart name = "FT_HANGAR_2" DesignHasPart name = "FT_HANGAR_3" DesignHasPart name = "FT_HANGAR_4" ] ] effects = [ - SetMaxSecondaryStat partname = "FT_HANGAR_1" value = Value - 0.5 SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value - 1 - SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value - 2 - SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value - 3 + SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value - 1 + SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value - 1 ] EffectsGroup @@ -76,17 +74,15 @@ GOOD_WEAPONS Ship Armed Or [ - DesignHasPart name = "FT_HANGAR_1" DesignHasPart name = "FT_HANGAR_2" DesignHasPart name = "FT_HANGAR_3" DesignHasPart name = "FT_HANGAR_4" ] ] effects = [ - SetMaxSecondaryStat partname = "FT_HANGAR_1" value = Value + 0.5 SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value + 1 - SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 2 - SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value + 3 + SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 1 + SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value + 1 ] EffectsGroup @@ -127,17 +123,15 @@ GREAT_WEAPONS Ship Armed Or [ - DesignHasPart name = "FT_HANGAR_1" DesignHasPart name = "FT_HANGAR_2" DesignHasPart name = "FT_HANGAR_3" DesignHasPart name = "FT_HANGAR_4" ] ] effects = [ - SetMaxSecondaryStat partname = "FT_HANGAR_1" value = Value + 1 SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value + 2 - SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 3 - SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value + 5 + SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 2 + SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value + 2 ] EffectsGroup @@ -178,17 +172,15 @@ ULTIMATE_WEAPONS Ship Armed Or [ - DesignHasPart name = "FT_HANGAR_1" DesignHasPart name = "FT_HANGAR_2" DesignHasPart name = "FT_HANGAR_3" DesignHasPart name = "FT_HANGAR_4" ] ] effects = [ - SetMaxSecondaryStat partname = "FT_HANGAR_1" value = Value + 2 - SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value + 4 - SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 6 - SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value + 8 + SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value + 3 + SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 3 + SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value + 3 ] EffectsGroup diff --git a/default/scripting/species/common/xenophobic.macros b/default/scripting/species/common/xenophobic.macros index 6c41549d64a..04418fa7126 100644 --- a/default/scripting/species/common/xenophobic.macros +++ b/default/scripting/species/common/xenophobic.macros @@ -13,9 +13,9 @@ XENOPHOBIC_SELF PopulationCenter Number low = 1 condition = And [ PopulationCenter - OwnedBy empire = Source.Owner + OwnedBy empire = RootCandidate.Owner Not OR [ - Species name = Source.Species + Species name = RootCandidate.Species Species name = "SP_EXOBOT" ] Not Population high = 0 @@ -53,10 +53,10 @@ XENOPHOBIC_SELF ] stackinggroup = "XENOPHOBIC_POP_SELF" accountinglabel = "XENOPHOBIC_LABEL_SELF" - priority = [[LATE_PRIORITY]] + priority = [[TARGET_POPULATION_LAST_BEFORE_OVERRIDE_PRIORITY]] effects = SetTargetPopulation value = Value - min( max(Value, 0) * 0.4 * (1 - 0.8^[[XENOPHOBIC_SELFSUSTAINING_QUALIFYING_PLANET_COUNT]]), - 3 * Target.SizeAsDouble // Cap malus at the self-sustaining bonus + 3 * Target.HabitableSize // Cap malus at the self-sustaining bonus ) ''' diff --git a/default/scripting/starting_unlocks/items.inf b/default/scripting/starting_unlocks/items.inf index b8c1636572e..eecfc3475ea 100644 --- a/default/scripting/starting_unlocks/items.inf +++ b/default/scripting/starting_unlocks/items.inf @@ -1,6 +1,6 @@ Item type = ShipHull name = "SH_BASIC_SMALL" Item type = ShipHull name = "SH_BASIC_MEDIUM" -Item type = ShipHull name = "SH_STANDARD" +Item type = ShipHull name = "SH_BASIC_LARGE" Item type = ShipHull name = "SH_COLONY_BASE" Item type = ShipPart name = "FU_BASIC_TANK" Item type = ShipPart name = "CO_OUTPOST_POD" @@ -19,9 +19,10 @@ Item type = Building name = "BLD_COL_DERTHREAN" Item type = Building name = "BLD_COL_EAXAW" Item type = Building name = "BLD_COL_EGASSEM" Item type = Building name = "BLD_COL_ETTY" +Item type = Building name = "BLD_COL_FULVER" +Item type = Building name = "BLD_COL_FURTHEST" Item type = Building name = "BLD_COL_GEORGE" Item type = Building name = "BLD_COL_GYSACHE" -Item type = Building name = "BLD_COL_FURTHEST" Item type = Building name = "BLD_COL_HAPPY" Item type = Building name = "BLD_COL_HHHOH" Item type = Building name = "BLD_COL_HUMAN" @@ -35,6 +36,7 @@ Item type = Building name = "BLD_COL_REPLICON" Item type = Building name = "BLD_COL_SCYLIOR" Item type = Building name = "BLD_COL_SETINON" Item type = Building name = "BLD_COL_SILEXIAN" +Item type = Building name = "BLD_COL_SLY" Item type = Building name = "BLD_COL_SSLITH" Item type = Building name = "BLD_COL_TAEGHIRUS" Item type = Building name = "BLD_COL_TRITH" @@ -48,3 +50,5 @@ Item type = Tech name = "SHP_ROOT_ARMOR" Item type = Tech name = "SHP_ROOT_AGGRESSION" Item type = Tech name = "SPY_ROOT_DECEPTION" Item type = Tech name = "SPY_CUSTOM_ADVISORIES" +Item type = Tech name = "PRO_PREDICTIVE_STOCKPILING" +Item type = Tech name = "CON_OUTPOST" \ No newline at end of file diff --git a/default/scripting/techs/construction/FORCE_ENERGY_STRC.focs.txt b/default/scripting/techs/construction/FORCE_ENERGY_STRC.focs.txt index 65900789894..87f65bca206 100644 --- a/default/scripting/techs/construction/FORCE_ENERGY_STRC.focs.txt +++ b/default/scripting/techs/construction/FORCE_ENERGY_STRC.focs.txt @@ -15,98 +15,48 @@ Tech scope = And [ ProductionCenter OwnedBy empire = Source.Owner - Industry high = RootCandidate.TargetIndustry - 3 ] - effects = SetIndustry value = Value + 2 + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = If + condition = (Value(LocalCandidate.Industry) <= Value(LocalCandidate.TargetIndustry)) + effects = SetIndustry value = min(Value + (2 * ( Statistic If condition = (Target.Happiness > 4) ) ), Value(Target.TargetIndustry)) + else = SetIndustry value = max(Value - 4, Value(Target.TargetIndustry)) EffectsGroup scope = And [ ProductionCenter OwnedBy empire = Source.Owner - Research high = RootCandidate.TargetResearch - 3 ] - effects = SetResearch value = Value + 2 - - EffectsGroup - scope = And [ - ProductionCenter - OwnedBy empire = Source.Owner - Construction high = RootCandidate.TargetConstruction - 3 - ] - effects = SetConstruction value = Value + 2 - - EffectsGroup - scope = And [ - ProductionCenter - OwnedBy empire = Source.Owner - Industry low = RootCandidate.TargetIndustry - 3 high = RootCandidate.TargetIndustry - ] - effects = SetIndustry value = Target.TargetIndustry - - EffectsGroup - scope = And [ - ProductionCenter - OwnedBy empire = Source.Owner - Research low = RootCandidate.TargetResearch - 3 high = RootCandidate.TargetResearch - ] - effects = SetResearch value = Target.TargetResearch - - EffectsGroup - scope = And [ - ProductionCenter - OwnedBy empire = Source.Owner - Construction low = RootCandidate.TargetConstruction - 3 high = RootCandidate.TargetConstruction - ] - effects = SetConstruction value = Target.TargetConstruction - - EffectsGroup - scope = And [ - ProductionCenter - OwnedBy empire = Source.Owner - Industry low = RootCandidate.TargetIndustry + 5 - ] - effects = SetIndustry value = Value - 3 - - EffectsGroup - scope = And [ - ProductionCenter - OwnedBy empire = Source.Owner - Research low = RootCandidate.TargetResearch + 5 - ] - effects = SetResearch value = Value - 3 - - EffectsGroup - scope = And [ - ProductionCenter - OwnedBy empire = Source.Owner - Construction low = RootCandidate.TargetConstruction + 5 - ] - effects = SetConstruction value = Value - 3 - + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = If + condition = (Value(LocalCandidate.Research) <= Value(LocalCandidate.TargetResearch)) + effects = SetResearch value = min(Value + (2 * ( Statistic If condition = (Target.Happiness > 4) ) ), Value(Target.TargetResearch)) + else = SetResearch value = max(Value - 4, Value(Target.TargetResearch)) + EffectsGroup scope = And [ ProductionCenter OwnedBy empire = Source.Owner - Industry low = RootCandidate.TargetIndustry high = RootCandidate.TargetIndustry + 5 ] - effects = SetIndustry value = Target.TargetIndustry + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = If + condition = (Value(LocalCandidate.Construction) <= Value(LocalCandidate.TargetConstruction)) + effects = SetConstruction value = min(Value + 2, Value(Target.TargetConstruction)) + else = SetConstruction value = max(Value - 4, Value(Target.TargetConstruction)) EffectsGroup scope = And [ ProductionCenter OwnedBy empire = Source.Owner - Research low = RootCandidate.TargetResearch high = RootCandidate.TargetResearch + 5 ] - effects = SetResearch value = Target.TargetResearch + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = If + condition = (Value(LocalCandidate.Stockpile) <= Value(LocalCandidate.MaxStockpile)) + effects = SetStockpile value = min(Value + 2, Value(Target.MaxStockpile)) + else = SetStockpile value = max(Value - 4, Value(Target.MaxStockpile)) - EffectsGroup - scope = And [ - ProductionCenter - OwnedBy empire = Source.Owner - Construction low = RootCandidate.TargetConstruction high = RootCandidate.TargetConstruction + 5 - ] - effects = SetConstruction value = Target.TargetConstruction ] graphic = "icons/tech/force_energy_structures.png" #include "/scripting/common/base_prod.macros" +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/techs/construction/N_DIMEN_STRUCT.focs.txt b/default/scripting/techs/construction/N_DIMEN_STRUCT.focs.txt index 3df614b7461..4eeb3ed6972 100644 --- a/default/scripting/techs/construction/N_DIMEN_STRUCT.focs.txt +++ b/default/scripting/techs/construction/N_DIMEN_STRUCT.focs.txt @@ -17,10 +17,10 @@ Tech OwnedBy empire = Source.Owner ] accountinglabel = "NDIM_STRC_LABEL" - priority = [[LATE_PRIORITY]] + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] effects = [ SetTargetConstruction value = Value + 10 - SetTargetPopulation value = Value + 2 * Target.SizeAsDouble + SetTargetPopulation value = Value + 2 * Target.HabitableSize ] ] graphic = "icons/tech/n-dimensional_structures.png" diff --git a/default/scripting/techs/construction/OUTPOST.focs.txt b/default/scripting/techs/construction/OUTPOST.focs.txt new file mode 100644 index 00000000000..dcedc6cdc9b --- /dev/null +++ b/default/scripting/techs/construction/OUTPOST.focs.txt @@ -0,0 +1,27 @@ +Tech + name = "CON_OUTPOST" + description = "CON_OUTPOST_DESC" + short_description = "CON_OUTPOST" + category = "CONSTRUCTION_CATEGORY" + researchcost = 1 + researchturns = 1 + tags = [ "PEDIA_CONSTRUCTION_CATEGORY" ] + + // Effects for outposts + effectsgroups = [ + // For colonies see STANDARD_CONSTRUCTION in default/scripting/species/common/general.macros + EffectsGroup // always ensure minimum value of one, as this is necessary for being attacked + scope = And [ + Planet + OwnedBy empire = Source.Owner + Population high = 0 + ] + // has to happen after e.g. FORCE_ENERGY_STRC effects which also happens at AFTER_ALL_TARGET_MAX_METERS_PRIORITY + priority = [[METER_OVERRIDE_PRIORITY]] + effects = [ + SetTargetConstruction value = max(Value, 1) + SetConstruction value = max(Value, 1) + ] + ] + +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/techs/defense/BarrierShield.focs.txt b/default/scripting/techs/defense/BarrierShield.focs.txt index 863af753ca8..9e045a6f508 100644 --- a/default/scripting/techs/defense/BarrierShield.focs.txt +++ b/default/scripting/techs/defense/BarrierShield.focs.txt @@ -7,17 +7,26 @@ Tech researchturns = 5 tags = [ "PEDIA_DEFENSE_CATEGORY" ] prerequisites = "LRN_FORCE_FIELD" - effectsgroups = + effectsgroups = [ EffectsGroup scope = And [ Planet OwnedBy empire = Source.Owner ] priority = [[DEFAULT_PRIORITY]] - effects = [ + effects = SetMaxShield value = Value + 30 accountinglabel = "DEF_PLAN_BARRIER_SHLD_1" - SetShield value = Value + max(min(1.0, Value), 0.25*Target.Construction) + + EffectsGroup + scope = And [ + Planet + OwnedBy empire = Source.Owner + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn - 1) ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetShield value = Value + max(min(1.0, Value), 0.25*Target.Construction) + ] + graphic = "icons/tech/planetary_barrier_shield.png" Tech @@ -29,17 +38,26 @@ Tech researchturns = 6 tags = [ "PEDIA_DEFENSE_CATEGORY" ] prerequisites = "DEF_PLAN_BARRIER_SHLD_1" - effectsgroups = + effectsgroups = [ EffectsGroup scope = And [ Planet OwnedBy empire = Source.Owner + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn - 1) ] priority = [[DEFAULT_PRIORITY]] - effects = [ - SetMaxShield value = Value + 60 accountinglabel = "DEF_PLAN_BARRIER_SHLD_2" - SetShield value = Value + max(min(3.0, Value), 0.75*Target.Construction) + effects = SetMaxShield value = Value + 60 accountinglabel = "DEF_PLAN_BARRIER_SHLD_2" + + EffectsGroup + scope = And [ + Planet + OwnedBy empire = Source.Owner + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn - 1) ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetShield value = Value + max(min(3.0, Value), 0.75*Target.Construction) + ] + graphic = "icons/tech/planetary_barrier_shield.png" Tech @@ -51,17 +69,26 @@ Tech researchturns = 8 tags = [ "PEDIA_DEFENSE_CATEGORY" ] prerequisites = "DEF_PLAN_BARRIER_SHLD_2" - effectsgroups = + effectsgroups = [ EffectsGroup scope = And [ Planet OwnedBy empire = Source.Owner + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn - 1) ] priority = [[DEFAULT_PRIORITY]] - effects = [ - SetMaxShield value = Value + 90 accountinglabel = "DEF_PLAN_BARRIER_SHLD_3" - SetShield value = Value + max(min(5.0, Value), 1.0*Target.Construction) + effects = SetMaxShield value = Value + 90 accountinglabel = "DEF_PLAN_BARRIER_SHLD_3" + + EffectsGroup + scope = And [ + Planet + OwnedBy empire = Source.Owner + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn - 1) ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetShield value = Value + max(min(5.0, Value), 1.0*Target.Construction) + ] + graphic = "icons/tech/planetary_barrier_shield.png" Tech @@ -73,17 +100,26 @@ Tech researchturns = 10 tags = [ "PEDIA_DEFENSE_CATEGORY" ] prerequisites = "DEF_PLAN_BARRIER_SHLD_3" - effectsgroups = + effectsgroups = [ EffectsGroup scope = And [ Planet OwnedBy empire = Source.Owner + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn - 1) ] priority = [[DEFAULT_PRIORITY]] - effects = [ - SetMaxShield value = Value + 150 accountinglabel = "DEF_PLAN_BARRIER_SHLD_4" - SetShield value = Value + max(min(9.0, Value), 1.5*Target.Construction) + effects = SetMaxShield value = Value + 150 accountinglabel = "DEF_PLAN_BARRIER_SHLD_4" + + EffectsGroup + scope = And [ + Planet + OwnedBy empire = Source.Owner + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn - 1) ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetShield value = Value + max(min(9.0, Value), 1.5*Target.Construction) + ] + graphic = "icons/tech/planetary_barrier_shield.png" Tech @@ -95,17 +131,25 @@ Tech researchturns = 12 tags = [ "PEDIA_DEFENSE_CATEGORY" ] prerequisites = "DEF_PLAN_BARRIER_SHLD_4" - effectsgroups = + effectsgroups = [ EffectsGroup scope = And [ Planet OwnedBy empire = Source.Owner ] priority = [[DEFAULT_PRIORITY]] - effects = [ - SetMaxShield value = Value + 150 accountinglabel = "DEF_PLAN_BARRIER_SHLD_5" - SetShield value = Value + max(min(14.0, Value), 2.5*Target.Construction) + effects = SetMaxShield value = Value + 150 accountinglabel = "DEF_PLAN_BARRIER_SHLD_5" + + EffectsGroup + scope = And [ + Planet + OwnedBy empire = Source.Owner + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn - 1) ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetShield value = Value + max(min(14.0, Value), 2.5*Target.Construction) + ] + graphic = "icons/tech/planetary_barrier_shield.png" #include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/defense/Defense.focs.txt b/default/scripting/techs/defense/Defense.focs.txt index 7511e62f146..8d8419c6b4b 100644 --- a/default/scripting/techs/defense/Defense.focs.txt +++ b/default/scripting/techs/defense/Defense.focs.txt @@ -15,6 +15,19 @@ Tech stackinggroup = "PLANET_SHIELDS_STACK_ROOT" priority = [[DEFAULT_PRIORITY]] effects = SetMaxShield value = Value + 1 accountinglabel = "DEF_ROOT_DEFENSE" + + EffectsGroup // regenerate 1 per turn + scope = And [ + Planet + OwnedBy empire = Source.Owner + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn) + ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = [ + SetShield value = min(Value + 1, Value(Target.MaxShield)) + SetDefense value = min(Value + 1, Value(Target.MaxDefense)) + SetTroops value = min(Value + 1, Value(Target.MaxTroops)) + ] ] graphic = "" @@ -73,7 +86,5 @@ Tech graphic = "icons/tech/system_defense_mines.png" #include "mines.macros" - #include "/scripting/common/priorities.macros" - #include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/defense/Garrison.focs.txt b/default/scripting/techs/defense/Garrison.focs.txt index dbd1d198eb6..2a2a1682918 100644 --- a/default/scripting/techs/defense/Garrison.focs.txt +++ b/default/scripting/techs/defense/Garrison.focs.txt @@ -33,7 +33,7 @@ Tech scope = And [ Planet OwnedBy empire = Source.Owner - [[CANDIDATE_BATTLE_CHECK]] + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn) ] effects = SetTroops value = Value + 1 @@ -63,7 +63,7 @@ Tech scope = And [ Planet OwnedBy empire = Source.Owner - [[CANDIDATE_BATTLE_CHECK]] + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn) ] effects = SetTroops value = Value + 2 @@ -92,7 +92,7 @@ Tech scope = And [ Planet OwnedBy empire = Source.Owner - [[CANDIDATE_BATTLE_CHECK]] + (LocalCandidate.LastTurnAttackedByShip < CurrentTurn) ] effects = SetTroops value = Value + 3 diff --git a/default/scripting/techs/growth/CYBORG.focs.txt b/default/scripting/techs/growth/CYBORG.focs.txt index a9dd2da7a6a..c7bb236062c 100644 --- a/default/scripting/techs/growth/CYBORG.focs.txt +++ b/default/scripting/techs/growth/CYBORG.focs.txt @@ -14,8 +14,8 @@ Tech OwnedBy empire = Source.Owner Planet environment = [ Hostile ] ] - priority = [[EARLY_PRIORITY]] - effects = SetTargetPopulation value = Value + 2 * Target.SizeAsDouble accountinglabel = "GRO_CYBORG" + priority = [[TARGET_POPULATION_BEFORE_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 2 * Target.HabitableSize accountinglabel = "GRO_CYBORG" EffectsGroup scope = And [ diff --git a/default/scripting/techs/growth/ENERGY_META.focs.txt b/default/scripting/techs/growth/ENERGY_META.focs.txt index adae0660043..4ff8fe3f79f 100644 --- a/default/scripting/techs/growth/ENERGY_META.focs.txt +++ b/default/scripting/techs/growth/ENERGY_META.focs.txt @@ -17,7 +17,7 @@ Tech Ship OwnedBy empire = Source.Owner ] - effects = SetMaxFuel value = Value + 2 + effects = SetMaxFuel value = Value + 1 EffectsGroup scope = And [ diff --git a/default/scripting/techs/growth/MEGA_ECO.focs.txt b/default/scripting/techs/growth/MEGA_ECO.focs.txt new file mode 100644 index 00000000000..ff192c704d9 --- /dev/null +++ b/default/scripting/techs/growth/MEGA_ECO.focs.txt @@ -0,0 +1,17 @@ +Tech + name = "GRO_MEGA_ECO" + description = "GRO_MEGA_ECO_DESC" + short_description = "GRO_MEGA_ECO_SHORT_DESC" + category = "GROWTH_CATEGORY" + researchcost = 135 * [[TECH_COST_MULTIPLIER]] + researchturns = 9 + tags = [ "PEDIA_GROWTH_CATEGORY" ] + prerequisites = [ + "SHP_ENDOCRINE_SYSTEMS" + "GRO_TERRAFORM" + "GRO_NANOTECH_MED" + ] + unlock = Item type = Building name = "BLD_NEST_ERADICATOR" + graphic = "icons/tech/megafauna_ecology.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/growth/ORBITAL_HAB.focs.txt b/default/scripting/techs/growth/ORBITAL_HAB.focs.txt index c7b05ae9a04..d3a34fd54bd 100644 --- a/default/scripting/techs/growth/ORBITAL_HAB.focs.txt +++ b/default/scripting/techs/growth/ORBITAL_HAB.focs.txt @@ -12,10 +12,9 @@ Tech scope = And [ Species OwnedBy empire = Source.Owner - //TargetPopulation low = 0 ] - priority = [[LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble accountinglabel = "ORBITAL_HAB_LABEL" + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize accountinglabel = "ORBITAL_HAB_LABEL" ] graphic = "icons/tech/orbital_gardens.png" diff --git a/default/scripting/techs/growth/PLANETARY_ECOLOGY.focs.txt b/default/scripting/techs/growth/PLANETARY_ECOLOGY.focs.txt index 0399f9ef726..ac632915ba4 100644 --- a/default/scripting/techs/growth/PLANETARY_ECOLOGY.focs.txt +++ b/default/scripting/techs/growth/PLANETARY_ECOLOGY.focs.txt @@ -17,7 +17,7 @@ Tech Adequate ] ] - priority = [[LATE_PRIORITY]] + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] effects = SetTargetPopulation value = Value + 1 accountinglabel = "GRO_PLANET_ECOL" ] graphic = "icons/tech/planetary_ecology.png" diff --git a/default/scripting/techs/growth/SUBTER_HAB.focs.txt b/default/scripting/techs/growth/SUBTER_HAB.focs.txt index bc1c1e28c05..a1521b499ec 100644 --- a/default/scripting/techs/growth/SUBTER_HAB.focs.txt +++ b/default/scripting/techs/growth/SUBTER_HAB.focs.txt @@ -13,8 +13,8 @@ Tech Species OwnedBy empire = Source.Owner ] - priority = [[LATE_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble accountinglabel = "GRO_SUBTER_HAB" + priority = [[TARGET_POPULATION_AFTER_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize accountinglabel = "GRO_SUBTER_HAB" ] graphic = "icons/tech/subterranean_construction.png" diff --git a/default/scripting/techs/growth/SYMBIOTIC_BIO.focs.txt b/default/scripting/techs/growth/SYMBIOTIC_BIO.focs.txt index 3ce5d118c41..b4b470f5058 100644 --- a/default/scripting/techs/growth/SYMBIOTIC_BIO.focs.txt +++ b/default/scripting/techs/growth/SYMBIOTIC_BIO.focs.txt @@ -18,8 +18,8 @@ Tech Poor ] ] - priority = [[EARLY_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble accountinglabel = "GRO_SYMBIOTIC_BIO" + priority = [[TARGET_POPULATION_BEFORE_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize accountinglabel = "GRO_SYMBIOTIC_BIO" ] graphic = "icons/tech/symbiosis_biology.png" diff --git a/default/scripting/techs/growth/XENO_GENETICS.focs.txt b/default/scripting/techs/growth/XENO_GENETICS.focs.txt index 5575cc9f6ef..3e3616450fd 100644 --- a/default/scripting/techs/growth/XENO_GENETICS.focs.txt +++ b/default/scripting/techs/growth/XENO_GENETICS.focs.txt @@ -20,16 +20,16 @@ Tech Poor ] ] - priority = [[EARLY_PRIORITY]] - effects = SetTargetPopulation value = Value + 2 * Target.SizeAsDouble accountinglabel = "GRO_XENO_GENETICS" + priority = [[TARGET_POPULATION_BEFORE_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 2 * Target.HabitableSize accountinglabel = "GRO_XENO_GENETICS" EffectsGroup scope = And [ Planet OwnedBy empire = Source.Owner Planet environment = [ Hostile ] ] - priority = [[EARLY_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble accountinglabel = "GRO_XENO_GENETICS" + priority = [[TARGET_POPULATION_BEFORE_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize accountinglabel = "GRO_XENO_GENETICS" ] graphic = "icons/tech/xenological_genetics.png" diff --git a/default/scripting/techs/growth/XENO_HYBRIDS.focs.txt b/default/scripting/techs/growth/XENO_HYBRIDS.focs.txt index aa8fad0ab0b..17ed76df3a7 100644 --- a/default/scripting/techs/growth/XENO_HYBRIDS.focs.txt +++ b/default/scripting/techs/growth/XENO_HYBRIDS.focs.txt @@ -14,8 +14,8 @@ Tech OwnedBy empire = Source.Owner Planet environment = [ Poor ] ] - priority = [[EARLY_PRIORITY]] - effects = SetTargetPopulation value = Value + 1 * Target.SizeAsDouble accountinglabel = "GRO_XENO_HYBRIDS" + priority = [[TARGET_POPULATION_BEFORE_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 1 * Target.HabitableSize accountinglabel = "GRO_XENO_HYBRIDS" EffectsGroup scope = And [ @@ -23,8 +23,8 @@ Tech OwnedBy empire = Source.Owner Planet environment = [ Hostile ] ] - priority = [[EARLY_PRIORITY]] - effects = SetTargetPopulation value = Value + 2 * Target.SizeAsDouble accountinglabel = "GRO_XENO_HYBRIDS" + priority = [[TARGET_POPULATION_BEFORE_SCALING_PRIORITY]] + effects = SetTargetPopulation value = Value + 2 * Target.HabitableSize accountinglabel = "GRO_XENO_HYBRIDS" ] graphic = "icons/tech/xenological_hybridization.png" diff --git a/default/scripting/techs/learning/PSIONICS.focs.txt b/default/scripting/techs/learning/PSIONICS.focs.txt index ce6cf91e303..71f59f11be1 100644 --- a/default/scripting/techs/learning/PSIONICS.focs.txt +++ b/default/scripting/techs/learning/PSIONICS.focs.txt @@ -5,6 +5,7 @@ Tech category = "LEARNING_CATEGORY" researchcost = 300 * [[TECH_COST_MULTIPLIER]] - (250 * [[TECH_COST_MULTIPLIER]] * Statistic If condition = AND [ OwnedBy empire = Source.Owner + /// @content_tag{TELEPATHIC} Decreases research cost of this tech for empires that own any object with this tag HasTag name = "TELEPATHIC" ]) researchturns = 4 diff --git a/default/scripting/techs/learning/PSY_DOM.focs.txt b/default/scripting/techs/learning/PSY_DOM.focs.txt index 8d6a0541072..b517c9277d2 100644 --- a/default/scripting/techs/learning/PSY_DOM.focs.txt +++ b/default/scripting/techs/learning/PSY_DOM.focs.txt @@ -18,6 +18,7 @@ Tech VisibleToEmpire empire = Source.Owner Random probability = 0.1 Not Monster + /// @content_tag{TELEPATHIC} For ships with this tag, prevents chance of ownership loss to suitable planet Not HasTag name = "TELEPATHIC" Not OwnerHasTech name = "LRN_PSY_DOM" ContainedBy And [ @@ -25,6 +26,7 @@ Tech Contains And [ Planet OwnedBy empire = Source.Owner + /// @content_tag{TELEPATHIC} On owned planet with focus FOCUS_DOMINATION and this tag, chance of taking ownership of suitable ships HasTag name = "TELEPATHIC" Focus type = "FOCUS_DOMINATION" ] diff --git a/default/scripting/techs/production/EXOBOTS.focs.txt b/default/scripting/techs/production/EXOBOTS.focs.txt index 18db3c6c246..1d279084634 100644 --- a/default/scripting/techs/production/EXOBOTS.focs.txt +++ b/default/scripting/techs/production/EXOBOTS.focs.txt @@ -3,10 +3,13 @@ Tech description = "PRO_EXOBOTS_DESC" short_description = "EXOBOT_SHORT_DESC" category = "PRODUCTION_CATEGORY" - researchcost = 50 * [[TECH_COST_MULTIPLIER]] - researchturns = 5 + researchcost = 60 * [[TECH_COST_MULTIPLIER]] + researchturns = 6 tags = [ "PEDIA_PRODUCTION_CATEGORY" ] - prerequisites = "PRO_SENTIENT_AUTOMATION" + prerequisites = [ + "LRN_ARTIF_MINDS" + "PRO_NANOTECH_PROD" + ] unlock = Item type = Building name = "BLD_COL_EXOBOT" graphic = "icons/species/robotic-01.png" diff --git a/default/scripting/techs/production/GENERIC_SUPPLIES.focs.txt b/default/scripting/techs/production/GENERIC_SUPPLIES.focs.txt new file mode 100644 index 00000000000..adeccd85849 --- /dev/null +++ b/default/scripting/techs/production/GENERIC_SUPPLIES.focs.txt @@ -0,0 +1,39 @@ +Tech + name = "PRO_GENERIC_SUPPLIES" + description = "PRO_GENERIC_SUPPLIES_DESC" + short_description = "IMPERIAL_STOCKPILE_SHORT_DESC" + category = "PRODUCTION_CATEGORY" + researchcost = 20 * [[TECH_COST_MULTIPLIER]] + researchturns = 4 + tags = [ "PEDIA_PRODUCTION_CATEGORY" ] + prerequisites = [ + "PRO_PREDICTIVE_STOCKPILING" + "CON_ASYMP_MATS" + "PRO_ROBOTIC_PROD" + ] + effectsgroups = [ + EffectsGroup + scope = And [ + Capital + OwnedBy empire = Source.Owner + ] + effects = SetMaxStockpile value = Value + 2 accountinglabel = "GENERIC_SUPPLIES_FIXED_BONUS_LABEL" + EffectsGroup + scope = And [ + ProductionCenter + OwnedBy empire = Source.Owner + Species + ] + effects = SetMaxStockpile value = (Value + 0.5 * Target.Population * [[STOCKPILE_PER_POP]]) + accountinglabel = "GENERIC_SUPPLIES_POPULATION_BONUS_LABEL" + EffectsGroup + scope = And [ + ProductionCenter + OwnedBy empire = Source.Owner + Focus type = "FOCUS_STOCKPILE" + ] + effects = SetMaxStockpile value = Value + 3 + accountinglabel = "GENERIC_SUPPLIES_FOCUS_BONUS_LABEL" + ] + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/production/INTERSTELLAR_ENTANGLEMENT_FACTORY.focs.txt b/default/scripting/techs/production/INTERSTELLAR_ENTANGLEMENT_FACTORY.focs.txt new file mode 100644 index 00000000000..fa07e223bd2 --- /dev/null +++ b/default/scripting/techs/production/INTERSTELLAR_ENTANGLEMENT_FACTORY.focs.txt @@ -0,0 +1,37 @@ +Tech + name = "PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY" + description = "PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY_DESC" + short_description = "IMPERIAL_STOCKPILE_SHORT_DESC" + category = "PRODUCTION_CATEGORY" + researchcost = 100 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_PRODUCTION_CATEGORY" ] + prerequisites = [ + "PRO_GENERIC_SUPPLIES" + "PRO_SENTIENT_AUTOMATION" + "LRN_GRAVITONICS" + ] + unlock = Item type = Building name = "BLD_STOCKPILING_CENTER" + effectsgroups = [ + EffectsGroup + scope = And [ + ProductionCenter + OwnedBy empire = Source.Owner + Species + ] + accountinglabel = "INTERSTELLAR_ENTANGLEMENT_FACTORY_POPULATION_BONUS_LABEL" + effects = SetMaxStockpile value = Value + 1.0 * Target.Population * [[STOCKPILE_PER_POP]] + + EffectsGroup + scope = And [ + ProductionCenter + OwnedBy empire = Source.Owner + Focus type = "FOCUS_STOCKPILE" + ] + accountinglabel = "INTERSTELLAR_ENTANGLEMENT_FACTORY_FIXED_BONUS_LABEL" + effects = SetMaxStockpile value = Value + 6 + ] + +#include "/scripting/species/common/stockpile.macros" +#include "/scripting/common/base_prod.macros" +#include "/scripting/common/priorities.macros" \ No newline at end of file diff --git a/default/scripting/techs/production/PREDICTIVE_STOCKPILING.focs.txt b/default/scripting/techs/production/PREDICTIVE_STOCKPILING.focs.txt new file mode 100644 index 00000000000..a27b80ac654 --- /dev/null +++ b/default/scripting/techs/production/PREDICTIVE_STOCKPILING.focs.txt @@ -0,0 +1,20 @@ +Tech + name = "PRO_PREDICTIVE_STOCKPILING" + description = "PRO_PREDICTIVE_STOCKPILING_DESC" + short_description = "IMPERIAL_STOCKPILE_SHORT_DESC" + category = "PRODUCTION_CATEGORY" + researchcost = 1 + researchturns = 1 + tags = [ "PEDIA_PRODUCTION_CATEGORY" ] + effectsgroups = [ + // Set initial meters + EffectsGroup + scope = And [ + ProductionCenter + OwnedBy empire = Source.Owner + Focus type = "FOCUS_STOCKPILE" + ] + effects = SetMaxStockpile value = Value + 1 + ] + +#include "/scripting/common/priorities.macros" diff --git a/default/scripting/techs/production/SENTIENT_AUTOMATION.focs.txt b/default/scripting/techs/production/SENTIENT_AUTOMATION.focs.txt index 6f115b72632..e84859d4bd5 100644 --- a/default/scripting/techs/production/SENTIENT_AUTOMATION.focs.txt +++ b/default/scripting/techs/production/SENTIENT_AUTOMATION.focs.txt @@ -7,7 +7,7 @@ Tech researchturns = 5 tags = [ "PEDIA_PRODUCTION_CATEGORY" ] prerequisites = [ - "LRN_ARTIF_MINDS" + "LRN_ALGO_ELEGANCE" "PRO_NANOTECH_PROD" ] effectsgroups = [ @@ -18,7 +18,7 @@ Tech TargetPopulation low = 0.0001 ] priority = [[VERY_LATE_PRIORITY]] - effects = SetTargetIndustry value = Value + 5 + effects = SetTargetIndustry value = Value + 25 * [[INDUSTRY_PER_POP]] ] graphic = "icons/tech/basic_autofactories.png" diff --git a/default/scripting/techs/production/TRANS_DESIGN.focs.txt b/default/scripting/techs/production/TRANS_DESIGN.focs.txt new file mode 100644 index 00000000000..c8542093ea0 --- /dev/null +++ b/default/scripting/techs/production/TRANS_DESIGN.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "PRO_TRANS_DESIGN" + description = "PRO_TRANS_DESIGN_DESC" + short_description = "BUILDING_UNLOCK_SHORT_DESC" + category = "PRODUCTION_CATEGORY" + researchcost = 75 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_PRODUCTION_CATEGORY" ] + prerequisites = [ + "LRN_PSIONICS" + "CON_ARCH_PSYCH" + ] + unlock = Item type = Building name = "BLD_INTERSPECIES_ACADEMY" + graphic = "icons/tech/transcendent_architecture.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/production/VOID_PREDICTION.focs.txt b/default/scripting/techs/production/VOID_PREDICTION.focs.txt new file mode 100644 index 00000000000..6a14502a99e --- /dev/null +++ b/default/scripting/techs/production/VOID_PREDICTION.focs.txt @@ -0,0 +1,29 @@ +Tech + name = "PRO_VOID_PREDICTION" + description = "PRO_VOID_PREDICTION_DESC" + short_description = "IMPERIAL_STOCKPILE_SHORT_DESC" + category = "PRODUCTION_CATEGORY" + researchcost = 500 * [[TECH_COST_MULTIPLIER]] + researchturns = 7 + tags = [ "PEDIA_PRODUCTION_CATEGORY" ] + prerequisites = [ + "LRN_MIND_VOID" + "PRO_GENERIC_SUPPLIES" + ] + effectsgroups = [ + EffectsGroup + scope = And [ + ProductionCenter + OwnedBy empire = Source.Owner + NOT Population high = 0 + Or [ + Focus type = "FOCUS_STOCKPILE" + ] + ] + effects = [ + SetMaxStockpile value = Value + 10 * Target.Population * [[STOCKPILE_PER_POP]] + ] + ] + +#include "/scripting/species/common/stockpile.macros" +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ships/SHP_DOMESTIC_MONSTER.focs.txt b/default/scripting/techs/ship_hulls/SHP_DOMESTIC_MONSTER.focs.txt similarity index 100% rename from default/scripting/techs/ships/SHP_DOMESTIC_MONSTER.focs.txt rename to default/scripting/techs/ship_hulls/SHP_DOMESTIC_MONSTER.focs.txt diff --git a/default/scripting/techs/ships/SHP_GAL_EXPLO.focs.txt b/default/scripting/techs/ship_hulls/SHP_GAL_EXPLO.focs.txt similarity index 73% rename from default/scripting/techs/ships/SHP_GAL_EXPLO.focs.txt rename to default/scripting/techs/ship_hulls/SHP_GAL_EXPLO.focs.txt index 36a5e708a10..361c88e77fb 100644 --- a/default/scripting/techs/ships/SHP_GAL_EXPLO.focs.txt +++ b/default/scripting/techs/ship_hulls/SHP_GAL_EXPLO.focs.txt @@ -12,7 +12,6 @@ Tech OwnedBy empire = Source.Owner Planet size = Tiny ] - activation = Planet accountinglabel = "TINY_PLANET_LABEL" effects = SetMaxSupply value = Value + 2 @@ -21,7 +20,6 @@ Tech OwnedBy empire = Source.Owner Planet size = Small ] - activation = Planet accountinglabel = "SMALL_PLANET_LABEL" effects = SetMaxSupply value = Value + 1 @@ -30,7 +28,6 @@ Tech OwnedBy empire = Source.Owner Planet size = Large ] - activation = Planet accountinglabel = "LARGE_PLANET_LABEL" effects = SetMaxSupply value = Value - 1 @@ -39,7 +36,6 @@ Tech OwnedBy empire = Source.Owner Planet size = Huge ] - activation = Planet accountinglabel = "HUGE_PLANET_LABEL" effects = SetMaxSupply value = Value - 2 @@ -48,28 +44,40 @@ Tech OwnedBy empire = Source.Owner Planet type = GasGiant ] - activation = Planet accountinglabel = "GAS_GIANT_LABEL" effects = SetMaxSupply value = Value - 1 + EffectsGroup // outpost supply increases 1 per turn up to max + scope = And [ + Planet + OwnedBy empire = Source.Owner + Not Species + ] + priority = [[AFTER_ALL_TARGET_MAX_METERS_PRIORITY]] + effects = SetSupply value = Min(Value(Target.MaxSupply), Value + 1) + + + // generate sitrep for any planet that is about to increase to above the + // recolonizing population, while also having sufficient happiness to do + // do so EffectsGroup scope = And [ Planet OwnedBy empire = Source.Owner CanColonize - //(6 > 4) // test - //(5 = 5) - //(3 != 83) - //(6 >= 0) - (LocalCandidate.NextTurnPopGrowth >= [[MIN_RECOLONIZING_SIZE]] - LocalCandidate.Population) - (LocalCandidate.TargetHappiness >= [[MIN_RECOLONIZING_HAPPINESS]] ) - (LocalCandidate.Happiness >= [[MIN_RECOLONIZING_HAPPINESS]] - 1 ) + + (LocalCandidate.Population < LocalCandidate.TargetPopulation) + (LocalCandidate.Population + + LocalCandidate.Population*0.01*(LocalCandidate.TargetPopulation + 1 - LocalCandidate.Population) + >= [[MIN_RECOLONIZING_SIZE]]) + + (LocalCandidate.TargetHappiness >= [[MIN_RECOLONIZING_HAPPINESS]] ) + (LocalCandidate.Happiness >= [[MIN_RECOLONIZING_HAPPINESS]] - 1 ) Or [ - (0.1 <= LocalCandidate.Population <= ([[MIN_RECOLONIZING_SIZE]] - 0.001) ) - (0.1 <= LocalCandidate.Happiness <= ([[MIN_RECOLONIZING_HAPPINESS]] - 0.001) ) + (0.1 <= LocalCandidate.Population <= ([[MIN_RECOLONIZING_SIZE]] - 0.001) ) + (0.1 <= LocalCandidate.Happiness <= ([[MIN_RECOLONIZING_HAPPINESS]] - 0.001) ) ] ] - activation = Source effects = [ GenerateSitRepMessage message = "SITREP_POP_THRESHOLD" @@ -95,5 +103,5 @@ Tech graphic = "icons/tech/galactic_exploration.png" #include "/scripting/common/misc.macros" - +#include "/scripting/common/priorities.macros" #include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ships/SHP_XENTRONIUM_HULL.focs.txt b/default/scripting/techs/ship_hulls/SHP_XENTRONIUM_HULL.focs.txt similarity index 100% rename from default/scripting/techs/ships/SHP_XENTRONIUM_HULL.focs.txt rename to default/scripting/techs/ship_hulls/SHP_XENTRONIUM_HULL.focs.txt diff --git a/default/scripting/techs/ships/asteroid/SHP_ASTEROID_HULLS.focs.txt b/default/scripting/techs/ship_hulls/asteroid/SHP_ASTEROID_HULLS.focs.txt similarity index 100% rename from default/scripting/techs/ships/asteroid/SHP_ASTEROID_HULLS.focs.txt rename to default/scripting/techs/ship_hulls/asteroid/SHP_ASTEROID_HULLS.focs.txt diff --git a/default/scripting/techs/ships/asteroid/SHP_ASTEROID_REFORM.focs.txt b/default/scripting/techs/ship_hulls/asteroid/SHP_ASTEROID_REFORM.focs.txt similarity index 100% rename from default/scripting/techs/ships/asteroid/SHP_ASTEROID_REFORM.focs.txt rename to default/scripting/techs/ship_hulls/asteroid/SHP_ASTEROID_REFORM.focs.txt diff --git a/default/scripting/techs/ships/asteroid/SHP_CAMO_AST_HULL.focs.txt b/default/scripting/techs/ship_hulls/asteroid/SHP_CAMO_AST_HULL.focs.txt similarity index 100% rename from default/scripting/techs/ships/asteroid/SHP_CAMO_AST_HULL.focs.txt rename to default/scripting/techs/ship_hulls/asteroid/SHP_CAMO_AST_HULL.focs.txt diff --git a/default/scripting/techs/ships/asteroid/SHP_HEAVY_AST_HULL.focs.txt b/default/scripting/techs/ship_hulls/asteroid/SHP_HEAVY_AST_HULL.focs.txt similarity index 100% rename from default/scripting/techs/ships/asteroid/SHP_HEAVY_AST_HULL.focs.txt rename to default/scripting/techs/ship_hulls/asteroid/SHP_HEAVY_AST_HULL.focs.txt diff --git a/default/scripting/techs/ships/asteroid/SHP_MINIAST_SWARM.focs.txt b/default/scripting/techs/ship_hulls/asteroid/SHP_MINIAST_SWARM.focs.txt similarity index 100% rename from default/scripting/techs/ships/asteroid/SHP_MINIAST_SWARM.focs.txt rename to default/scripting/techs/ship_hulls/asteroid/SHP_MINIAST_SWARM.focs.txt diff --git a/default/scripting/techs/ships/asteroid/SHP_MONOMOLEC_LATTIC.focs.txt b/default/scripting/techs/ship_hulls/asteroid/SHP_MONOMOLEC_LATTIC.focs.txt similarity index 100% rename from default/scripting/techs/ships/asteroid/SHP_MONOMOLEC_LATTIC.focs.txt rename to default/scripting/techs/ship_hulls/asteroid/SHP_MONOMOLEC_LATTIC.focs.txt diff --git a/default/scripting/techs/ships/asteroid/SHP_SCAT_AST_HULL.focs.txt b/default/scripting/techs/ship_hulls/asteroid/SHP_SCAT_AST_HULL.focs.txt similarity index 100% rename from default/scripting/techs/ships/asteroid/SHP_SCAT_AST_HULL.focs.txt rename to default/scripting/techs/ship_hulls/asteroid/SHP_SCAT_AST_HULL.focs.txt diff --git a/default/scripting/techs/ships/energy/SHP_ENRG_BOUND_MAN.focs.txt b/default/scripting/techs/ship_hulls/energy/SHP_ENRG_BOUND_MAN.focs.txt similarity index 100% rename from default/scripting/techs/ships/energy/SHP_ENRG_BOUND_MAN.focs.txt rename to default/scripting/techs/ship_hulls/energy/SHP_ENRG_BOUND_MAN.focs.txt diff --git a/default/scripting/techs/ships/energy/SHP_ENRG_FRIGATE.focs.txt b/default/scripting/techs/ship_hulls/energy/SHP_ENRG_FRIGATE.focs.txt similarity index 100% rename from default/scripting/techs/ships/energy/SHP_ENRG_FRIGATE.focs.txt rename to default/scripting/techs/ship_hulls/energy/SHP_ENRG_FRIGATE.focs.txt diff --git a/default/scripting/techs/ships/energy/SHP_FRC_ENRG_COMP.focs.txt b/default/scripting/techs/ship_hulls/energy/SHP_FRC_ENRG_COMP.focs.txt similarity index 100% rename from default/scripting/techs/ships/energy/SHP_FRC_ENRG_COMP.focs.txt rename to default/scripting/techs/ship_hulls/energy/SHP_FRC_ENRG_COMP.focs.txt diff --git a/default/scripting/techs/ships/energy/SHP_QUANT_ENRG_MAG.focs.txt b/default/scripting/techs/ship_hulls/energy/SHP_QUANT_ENRG_MAG.focs.txt similarity index 100% rename from default/scripting/techs/ships/energy/SHP_QUANT_ENRG_MAG.focs.txt rename to default/scripting/techs/ship_hulls/energy/SHP_QUANT_ENRG_MAG.focs.txt diff --git a/default/scripting/techs/ships/energy/SHP_SOLAR_CONT.focs.txt b/default/scripting/techs/ship_hulls/energy/SHP_SOLAR_CONT.focs.txt similarity index 100% rename from default/scripting/techs/ships/energy/SHP_SOLAR_CONT.focs.txt rename to default/scripting/techs/ship_hulls/energy/SHP_SOLAR_CONT.focs.txt diff --git a/default/scripting/techs/ships/organic/SHP_BIOADAPTIVE_SPEC.focs.txt b/default/scripting/techs/ship_hulls/organic/SHP_BIOADAPTIVE_SPEC.focs.txt similarity index 100% rename from default/scripting/techs/ships/organic/SHP_BIOADAPTIVE_SPEC.focs.txt rename to default/scripting/techs/ship_hulls/organic/SHP_BIOADAPTIVE_SPEC.focs.txt diff --git a/default/scripting/techs/ships/organic/SHP_CONT_BIOADAPT.focs.txt b/default/scripting/techs/ship_hulls/organic/SHP_CONT_BIOADAPT.focs.txt similarity index 100% rename from default/scripting/techs/ships/organic/SHP_CONT_BIOADAPT.focs.txt rename to default/scripting/techs/ship_hulls/organic/SHP_CONT_BIOADAPT.focs.txt diff --git a/default/scripting/techs/ships/organic/SHP_CONT_SYMB.focs.txt b/default/scripting/techs/ship_hulls/organic/SHP_CONT_SYMB.focs.txt similarity index 100% rename from default/scripting/techs/ships/organic/SHP_CONT_SYMB.focs.txt rename to default/scripting/techs/ship_hulls/organic/SHP_CONT_SYMB.focs.txt diff --git a/default/scripting/techs/ships/organic/SHP_ENDOCRINE_SYSTEMS.focs.txt b/default/scripting/techs/ship_hulls/organic/SHP_ENDOCRINE_SYSTEMS.focs.txt similarity index 100% rename from default/scripting/techs/ships/organic/SHP_ENDOCRINE_SYSTEMS.focs.txt rename to default/scripting/techs/ship_hulls/organic/SHP_ENDOCRINE_SYSTEMS.focs.txt diff --git a/default/scripting/techs/ships/organic/SHP_ENDOSYMB_HULL.focs.txt b/default/scripting/techs/ship_hulls/organic/SHP_ENDOSYMB_HULL.focs.txt similarity index 100% rename from default/scripting/techs/ships/organic/SHP_ENDOSYMB_HULL.focs.txt rename to default/scripting/techs/ship_hulls/organic/SHP_ENDOSYMB_HULL.focs.txt diff --git a/default/scripting/techs/ships/organic/SHP_MONOCELL_EXP.focs.txt b/default/scripting/techs/ship_hulls/organic/SHP_MONOCELL_EXP.focs.txt similarity index 100% rename from default/scripting/techs/ships/organic/SHP_MONOCELL_EXP.focs.txt rename to default/scripting/techs/ship_hulls/organic/SHP_MONOCELL_EXP.focs.txt diff --git a/default/scripting/techs/ships/organic/SHP_MULTICELL_CAST.focs.txt b/default/scripting/techs/ship_hulls/organic/SHP_MULTICELL_CAST.focs.txt similarity index 100% rename from default/scripting/techs/ships/organic/SHP_MULTICELL_CAST.focs.txt rename to default/scripting/techs/ship_hulls/organic/SHP_MULTICELL_CAST.focs.txt diff --git a/default/scripting/techs/ships/organic/SHP_ORG_HULL.focs.txt b/default/scripting/techs/ship_hulls/organic/SHP_ORG_HULL.focs.txt similarity index 100% rename from default/scripting/techs/ships/organic/SHP_ORG_HULL.focs.txt rename to default/scripting/techs/ship_hulls/organic/SHP_ORG_HULL.focs.txt diff --git a/default/scripting/techs/ships/organic/SHP_SENT_HULL.focs.txt b/default/scripting/techs/ship_hulls/organic/SHP_SENT_HULL.focs.txt similarity index 100% rename from default/scripting/techs/ships/organic/SHP_SENT_HULL.focs.txt rename to default/scripting/techs/ship_hulls/organic/SHP_SENT_HULL.focs.txt diff --git a/default/scripting/techs/ships/robotic/SHP_CONTGRAV_MAINT.focs.txt b/default/scripting/techs/ship_hulls/robotic/SHP_CONTGRAV_MAINT.focs.txt similarity index 100% rename from default/scripting/techs/ships/robotic/SHP_CONTGRAV_MAINT.focs.txt rename to default/scripting/techs/ship_hulls/robotic/SHP_CONTGRAV_MAINT.focs.txt diff --git a/default/scripting/techs/ships/robotic/SHP_MASSPROP_SPEC.focs.txt b/default/scripting/techs/ship_hulls/robotic/SHP_MASSPROP_SPEC.focs.txt similarity index 100% rename from default/scripting/techs/ships/robotic/SHP_MASSPROP_SPEC.focs.txt rename to default/scripting/techs/ship_hulls/robotic/SHP_MASSPROP_SPEC.focs.txt diff --git a/default/scripting/techs/ships/robotic/SHP_MIDCOMB_LOG.focs.txt b/default/scripting/techs/ship_hulls/robotic/SHP_MIDCOMB_LOG.focs.txt similarity index 100% rename from default/scripting/techs/ships/robotic/SHP_MIDCOMB_LOG.focs.txt rename to default/scripting/techs/ship_hulls/robotic/SHP_MIDCOMB_LOG.focs.txt diff --git a/default/scripting/techs/ships/robotic/SHP_MIL_ROBO_CONT.focs.txt b/default/scripting/techs/ship_hulls/robotic/SHP_MIL_ROBO_CONT.focs.txt similarity index 100% rename from default/scripting/techs/ships/robotic/SHP_MIL_ROBO_CONT.focs.txt rename to default/scripting/techs/ship_hulls/robotic/SHP_MIL_ROBO_CONT.focs.txt diff --git a/default/scripting/techs/ships/robotic/SHP_NANOROBO_MAINT.focs.txt b/default/scripting/techs/ship_hulls/robotic/SHP_NANOROBO_MAINT.focs.txt similarity index 100% rename from default/scripting/techs/ships/robotic/SHP_NANOROBO_MAINT.focs.txt rename to default/scripting/techs/ship_hulls/robotic/SHP_NANOROBO_MAINT.focs.txt diff --git a/default/scripting/techs/ship_hulls/robotic/SHP_SPACE_FLUX_BUBBLE.focs.txt b/default/scripting/techs/ship_hulls/robotic/SHP_SPACE_FLUX_BUBBLE.focs.txt new file mode 100644 index 00000000000..d044d8f7709 --- /dev/null +++ b/default/scripting/techs/ship_hulls/robotic/SHP_SPACE_FLUX_BUBBLE.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_SPACE_FLUX_BUBBLE" + description = "SHP_SPACE_FLUX_BUBBLE_DESC" + short_description = "SHIP_HULL_UNLOCK_SHORT_DESC" + category = "SHIP_HULLS_CATEGORY" + researchcost = 12 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_ROBOTIC_HULL_TECHS" ] + prerequisites = [ + "CON_ASYMP_MATS" + "PRO_ROBOTIC_PROD" + ] + unlock = Item type = ShipHull name = "SH_SPACE_FLUX_BUBBLE" + graphic = "icons/ship_hulls/bulk_freighter_hull_small.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ships/robotic/SHP_SPACE_FLUX_DRIVE.focs.txt b/default/scripting/techs/ship_hulls/robotic/SHP_SPACE_FLUX_DRIVE.focs.txt similarity index 100% rename from default/scripting/techs/ships/robotic/SHP_SPACE_FLUX_DRIVE.focs.txt rename to default/scripting/techs/ship_hulls/robotic/SHP_SPACE_FLUX_DRIVE.focs.txt diff --git a/default/scripting/techs/ships/robotic/SHP_TRANSSPACE_DRIVE.focs.txt b/default/scripting/techs/ship_hulls/robotic/SHP_TRANSSPACE_DRIVE.focs.txt similarity index 100% rename from default/scripting/techs/ships/robotic/SHP_TRANSSPACE_DRIVE.focs.txt rename to default/scripting/techs/ship_hulls/robotic/SHP_TRANSSPACE_DRIVE.focs.txt diff --git a/default/scripting/techs/ship_parts/armor/SHP_DIAMOND_PLATE.focs.txt b/default/scripting/techs/ship_parts/armor/SHP_DIAMOND_PLATE.focs.txt new file mode 100644 index 00000000000..0006db4d6d8 --- /dev/null +++ b/default/scripting/techs/ship_parts/armor/SHP_DIAMOND_PLATE.focs.txt @@ -0,0 +1,13 @@ +Tech + name = "SHP_DIAMOND_PLATE" + description = "SHP_DIAMOND_PLATE_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 150 * [[TECH_COST_MULTIPLIER]] + researchturns = 4 + tags = [ "PEDIA_ARMOR_PART_TECHS" ] + prerequisites = "SHP_ZORTRIUM_PLATE" + unlock = Item type = ShipPart name = "AR_DIAMOND_PLATE" + graphic = "icons/tech/armor_plating.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/armor/SHP_ROOT_ARMOR.focs.txt b/default/scripting/techs/ship_parts/armor/SHP_ROOT_ARMOR.focs.txt new file mode 100644 index 00000000000..011f81532a2 --- /dev/null +++ b/default/scripting/techs/ship_parts/armor/SHP_ROOT_ARMOR.focs.txt @@ -0,0 +1,12 @@ +Tech + name = "SHP_ROOT_ARMOR" + description = "SHP_ROOT_ARMOR_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 1 + researchturns = 1 + tags = [ "PEDIA_ARMOR_PART_TECHS" ] + unlock = Item type = ShipPart name = "AR_STD_PLATE" + graphic = "icons/tech/armor_plating.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/armor/SHP_XENTRONIUM_PLATE.focs.txt b/default/scripting/techs/ship_parts/armor/SHP_XENTRONIUM_PLATE.focs.txt new file mode 100644 index 00000000000..3e5f73f675f --- /dev/null +++ b/default/scripting/techs/ship_parts/armor/SHP_XENTRONIUM_PLATE.focs.txt @@ -0,0 +1,13 @@ +Tech + name = "SHP_XENTRONIUM_PLATE" + description = "SHP_XENTRONIUM_PLATE_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 750 * [[TECH_COST_MULTIPLIER]] + researchturns = 6 + tags = [ "PEDIA_ARMOR_PART_TECHS" ] + prerequisites = "SHP_DIAMOND_PLATE" + unlock = Item type = ShipPart name = "AR_XENTRONIUM_PLATE" + graphic = "icons/tech/armor_plating.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/armor/SHP_ZORTRIUM_PLATE.focs.txt b/default/scripting/techs/ship_parts/armor/SHP_ZORTRIUM_PLATE.focs.txt new file mode 100644 index 00000000000..fdefa02f08d --- /dev/null +++ b/default/scripting/techs/ship_parts/armor/SHP_ZORTRIUM_PLATE.focs.txt @@ -0,0 +1,14 @@ +Tech + name = "SHP_ZORTRIUM_PLATE" + description = "SHP_ZORTRIUM_PLATE_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 30 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_ARMOR_PART_TECHS" ] + prerequisites = "SHP_ROOT_ARMOR" + unlock = Item type = ShipPart name = "AR_ZORTRIUM_PLATE" + graphic = "icons/tech/armor_plating.png" + + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/damage_control/SHP_ADV_DAM_CONT.focs.txt b/default/scripting/techs/ship_parts/damage_control/SHP_ADV_DAM_CONT.focs.txt new file mode 100644 index 00000000000..e08da01e890 --- /dev/null +++ b/default/scripting/techs/ship_parts/damage_control/SHP_ADV_DAM_CONT.focs.txt @@ -0,0 +1,24 @@ +Tech + name = "SHP_ADV_DAM_CONT" + description = "SHP_ADV_DAM_CONT_DESC" + short_description = "STRUCTURE_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 100 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_DAMAGE_CONTROL_PART_TECHS" ] + prerequisites = [ + "SHP_FLEET_REPAIR" + "CON_GAL_INFRA" + ] + effectsgroups = + EffectsGroup + scope = And [ + Ship + OwnedBy empire = Source.Owner + InSystem + Stationary + Turn low = LocalCandidate.System.LastTurnBattleHere + 1 + ] + effects = SetStructure value = Value + (Target.MaxStructure/10) + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/damage_control/SHP_BASIC_DAM_CONT.focs.txt b/default/scripting/techs/ship_parts/damage_control/SHP_BASIC_DAM_CONT.focs.txt new file mode 100644 index 00000000000..fe65cc1ffb0 --- /dev/null +++ b/default/scripting/techs/ship_parts/damage_control/SHP_BASIC_DAM_CONT.focs.txt @@ -0,0 +1,26 @@ +Tech + name = "SHP_BASIC_DAM_CONT" + description = "SHP_BASIC_DAM_CONT_DESC" + short_description = "STRUCTURE_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 40 * [[TECH_COST_MULTIPLIER]] + researchturns = 4 + tags = [ "PEDIA_DAMAGE_CONTROL_PART_TECHS" ] + prerequisites = "SHP_MIL_ROBO_CONT" + effectsgroups = + EffectsGroup + scope = And [ + Ship + OwnedBy empire = Source.Owner + Or [ + Not InSystem + And [ + InSystem + Turn low = LocalCandidate.System.LastTurnBattleHere + 1 + ] + ] + Structure high = LocalCandidate.MaxStructure - 0.001 + ] + effects = SetStructure value = Value + 1 + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/damage_control/SHP_FLEET_REPAIR.focs.txt b/default/scripting/techs/ship_parts/damage_control/SHP_FLEET_REPAIR.focs.txt new file mode 100644 index 00000000000..e474202a5ab --- /dev/null +++ b/default/scripting/techs/ship_parts/damage_control/SHP_FLEET_REPAIR.focs.txt @@ -0,0 +1,33 @@ +Tech + name = "SHP_FLEET_REPAIR" + description = "SHP_FLEET_REPAIR_DESC" + short_description = "SHIP_REPAIR_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 80 * [[TECH_COST_MULTIPLIER]] + researchturns = 10 + tags = [ "PEDIA_DAMAGE_CONTROL_PART_TECHS" ] + prerequisites = [ + "SHP_INTSTEL_LOG" + "SHP_BASIC_DAM_CONT" + ] + effectsgroups = + EffectsGroup + scope = And [ + Ship + InSystem + Stationary + Or [ + OwnedBy empire = Source.Owner + And [ + ((GameRule name = "RULE_ENABLE_ALLIED_REPAIR") > 0) + OwnedBy affiliation = AllyOf empire = Source.Owner + ] + ] + Turn low = LocalCandidate.System.LastTurnBattleHere + 1 + Structure high = LocalCandidate.MaxStructure - 0.001 + ResupplyableBy empire = Source.Owner + ] + stackinggroup = "FLEET_REPAIR" + effects = SetStructure value = Value + (Target.MaxStructure/10) + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/damage_control/SHP_REINFORCED_HULL.focs.txt b/default/scripting/techs/ship_parts/damage_control/SHP_REINFORCED_HULL.focs.txt new file mode 100644 index 00000000000..a21cc835594 --- /dev/null +++ b/default/scripting/techs/ship_parts/damage_control/SHP_REINFORCED_HULL.focs.txt @@ -0,0 +1,19 @@ +Tech + name = "SHP_REINFORCED_HULL" + description = "SHP_REINFORCED_HULL_DESC" + short_description = "STRUCTURE_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 36 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_DAMAGE_CONTROL_PART_TECHS" ] + prerequisites = "CON_ARCH_MONOFILS" + effectsgroups = + EffectsGroup + scope = And [ + Ship + OwnedBy empire = Source.Owner + ] + effects = SetMaxStructure value = Value + 5 + graphic = "icons/tech/structural_integrity_fields.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/fuel/SHP_ANTIMATTER_TANK.focs.txt b/default/scripting/techs/ship_parts/fuel/SHP_ANTIMATTER_TANK.focs.txt new file mode 100644 index 00000000000..45e469877aa --- /dev/null +++ b/default/scripting/techs/ship_parts/fuel/SHP_ANTIMATTER_TANK.focs.txt @@ -0,0 +1,20 @@ +Tech + name = "SHP_ANTIMATTER_TANK" + description = "SHP_ANTIMATTER_TANK_DESC" + short_description = "SHIP_FUEL_IMPROVE_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 175 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_FUEL_PART_TECHS" ] + prerequisites = [ + "SHP_DEUTERIUM_TANK" + "LRN_FORCE_FIELD" + ] + effectsgroups = [ + [[PART_UPGRADE_MAXFUEL_EFFECTS(SHP_ANTIMATTER_TANK_EFFECT,1.5)]] + ] + graphic = "icons/ship_parts/antimatter_tank.png" + +#include "fuel.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/fuel/SHP_DEUTERIUM_TANK.focs.txt b/default/scripting/techs/ship_parts/fuel/SHP_DEUTERIUM_TANK.focs.txt new file mode 100644 index 00000000000..1582cb9d8d1 --- /dev/null +++ b/default/scripting/techs/ship_parts/fuel/SHP_DEUTERIUM_TANK.focs.txt @@ -0,0 +1,23 @@ +Tech + name = "SHP_DEUTERIUM_TANK" + description = "SHP_DEUTERIUM_TANK_DESC" + short_description = "SHIP_FUEL_IMPROVE_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 24 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_FUEL_PART_TECHS" ] + prerequisites = [ + "SHP_GAL_EXPLO" + "PRO_FUSION_GEN" + ] + unlock = [ + Item type = ShipPart name = "FU_RAMSCOOP" + ] + effectsgroups = [ + [[PART_UPGRADE_MAXFUEL_EFFECTS(SHP_DEUTERIUM_TANK_EFFECT,0.5)]] + ] + graphic = "icons/ship_parts/deuterium_tank.png" + +#include "fuel.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/fuel/SHP_ZERO_POINT.focs.txt b/default/scripting/techs/ship_parts/fuel/SHP_ZERO_POINT.focs.txt new file mode 100644 index 00000000000..ca3a66a1950 --- /dev/null +++ b/default/scripting/techs/ship_parts/fuel/SHP_ZERO_POINT.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_ZERO_POINT" + description = "SHP_ZERO_POINT_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 175 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_FUEL_PART_TECHS" ] + prerequisites = [ + "SHP_ANTIMATTER_TANK" + "PRO_ZERO_GEN" + ] + unlock = Item type = ShipPart name = "FU_ZERO_FUEL" + graphic = "icons/ship_parts/zero-point-generator.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/fuel/fuel.macros b/default/scripting/techs/ship_parts/fuel/fuel.macros new file mode 100644 index 00000000000..b43d4e8b3fb --- /dev/null +++ b/default/scripting/techs/ship_parts/fuel/fuel.macros @@ -0,0 +1,19 @@ +/** + * Increase the fuel capacity of ships with FU_BASIC_TANK parts. + * Adds the given increase amount times the number of FU_BASIC_TANK parts in the design to the max fuel meter. + * @1@ Accounting label to present this effect with + * @2@ Fuel capacity increase per fuel part + */ +PART_UPGRADE_MAXFUEL_EFFECTS +''' + EffectsGroup + scope = And [ + Ship + OwnedBy empire = Source.Owner + DesignHasPart name = "FU_BASIC_TANK" + ] + accountinglabel = "@1@" + effects = [ + SetMaxFuel value = Value + (@2@ * (PartsInShipDesign Name = "FU_BASIC_TANK" design = Target.DesignID)) + ] +''' diff --git a/default/scripting/techs/ship_parts/shield/SHP_BLACKSHIELD.focs.txt b/default/scripting/techs/ship_parts/shield/SHP_BLACKSHIELD.focs.txt new file mode 100644 index 00000000000..8924eadb9e1 --- /dev/null +++ b/default/scripting/techs/ship_parts/shield/SHP_BLACKSHIELD.focs.txt @@ -0,0 +1,13 @@ +Tech + name = "SHP_BLACKSHIELD" + description = "SHP_BLACKSHIELD_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 3750 * [[TECH_COST_MULTIPLIER]] + researchturns = 10 + tags = [ "PEDIA_SHIELD_PART_TECHS" ] + prerequisites = "SHP_PLASMA_SHIELD" + unlock = Item type = ShipPart name = "SH_BLACK" + graphic = "icons/ship_parts/blackshield.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/shield/SHP_DEFLECTOR_SHIELD.focs.txt b/default/scripting/techs/ship_parts/shield/SHP_DEFLECTOR_SHIELD.focs.txt new file mode 100644 index 00000000000..79e9649d7a5 --- /dev/null +++ b/default/scripting/techs/ship_parts/shield/SHP_DEFLECTOR_SHIELD.focs.txt @@ -0,0 +1,13 @@ +Tech + name = "SHP_DEFLECTOR_SHIELD" + description = "SHP_DEFLECTOR_SHIELD_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 150 * [[TECH_COST_MULTIPLIER]] + researchturns = 10 + tags = [ "PEDIA_SHIELD_PART_TECHS" ] + prerequisites = "LRN_FORCE_FIELD" + unlock = Item type = ShipPart name = "SH_DEFLECTOR" + graphic = "icons/ship_parts/deflector_shield.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/shield/SHP_MULTISPEC_SHIELD.focs.txt b/default/scripting/techs/ship_parts/shield/SHP_MULTISPEC_SHIELD.focs.txt new file mode 100644 index 00000000000..6262f99d2cf --- /dev/null +++ b/default/scripting/techs/ship_parts/shield/SHP_MULTISPEC_SHIELD.focs.txt @@ -0,0 +1,17 @@ +Tech + name = "SHP_MULTISPEC_SHIELD" + description = "SHP_MULTISPEC_SHIELD_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 2000 * [[TECH_COST_MULTIPLIER]] + researchturns = 10 + //Unresearchable + tags = [ "PEDIA_SHIELD_PART_TECHS" ] + prerequisites = [ + "SHP_PLASMA_SHIELD" + "SPY_DIST_MOD" + ] + unlock = Item type = ShipPart name = "SH_MULTISPEC" + graphic = "icons/ship_parts/multi-spectral.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/shield/SHP_PLASMA_SHIELD.focs.txt b/default/scripting/techs/ship_parts/shield/SHP_PLASMA_SHIELD.focs.txt new file mode 100644 index 00000000000..ca0a962cc3a --- /dev/null +++ b/default/scripting/techs/ship_parts/shield/SHP_PLASMA_SHIELD.focs.txt @@ -0,0 +1,13 @@ +Tech + name = "SHP_PLASMA_SHIELD" + description = "SHP_PLASMA_SHIELD_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 750 * [[TECH_COST_MULTIPLIER]] + researchturns = 10 + tags = [ "PEDIA_SHIELD_PART_TECHS" ] + prerequisites = "SHP_DEFLECTOR_SHIELD" + unlock = Item type = ShipPart name = "SH_PLASMA" + graphic = "icons/ship_parts/plasma_shield.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/speed/SHP_IMPROVED_ENGINE_COUPLINGS.focs.txt b/default/scripting/techs/ship_parts/speed/SHP_IMPROVED_ENGINE_COUPLINGS.focs.txt new file mode 100644 index 00000000000..74322cc475b --- /dev/null +++ b/default/scripting/techs/ship_parts/speed/SHP_IMPROVED_ENGINE_COUPLINGS.focs.txt @@ -0,0 +1,13 @@ +Tech + name = "SHP_IMPROVED_ENGINE_COUPLINGS" + description = "SHP_IMPROVED_ENGINE_COUPLINGS_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 24 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_ENGINE_PART_TECHS" ] + prerequisites = "SHP_GAL_EXPLO" + unlock = Item type = ShipPart name = "FU_IMPROVED_ENGINE_COUPLINGS" + graphic = "icons/ship_parts/engine-1.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/speed/SHP_N_DIMENSIONAL_ENGINE_MATRIX.focs.txt b/default/scripting/techs/ship_parts/speed/SHP_N_DIMENSIONAL_ENGINE_MATRIX.focs.txt new file mode 100644 index 00000000000..2dd5eff71e0 --- /dev/null +++ b/default/scripting/techs/ship_parts/speed/SHP_N_DIMENSIONAL_ENGINE_MATRIX.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX" + description = "SHP_N_DIMENSIONAL_ENGINE_MATRIX_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 250 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_ENGINE_PART_TECHS" ] + prerequisites = [ + "SHP_IMPROVED_ENGINE_COUPLINGS" + "LRN_NDIM_SUBSPACE" + ] + unlock = Item type = ShipPart name = "FU_N_DIMENSIONAL_ENGINE_MATRIX" + graphic = "icons/ship_parts/engine-2.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_parts/speed/SHP_SINGULATIRY_ENGINE_CORE.focs.txt b/default/scripting/techs/ship_parts/speed/SHP_SINGULATIRY_ENGINE_CORE.focs.txt new file mode 100644 index 00000000000..4941e8f3078 --- /dev/null +++ b/default/scripting/techs/ship_parts/speed/SHP_SINGULATIRY_ENGINE_CORE.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_SINGULARITY_ENGINE_CORE" + description = "SHP_SINGULARITY_ENGINE_CORE_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_PARTS_CATEGORY" + researchcost = 500 * [[TECH_COST_MULTIPLIER]] + researchturns = 10 + tags = [ "PEDIA_ENGINE_PART_TECHS" ] + prerequisites = [ + "SHP_N_DIMENSIONAL_ENGINE_MATRIX" + "PRO_SINGULAR_GEN" + ] + unlock = Item type = ShipPart name = "FU_SINGULARITY_ENGINE_CORE" + graphic = "icons/ship_parts/engine-3.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/SHP_KRILL_SPAWN.focs.txt b/default/scripting/techs/ship_weapons/SHP_KRILL_SPAWN.focs.txt new file mode 100644 index 00000000000..deec308daf7 --- /dev/null +++ b/default/scripting/techs/ship_weapons/SHP_KRILL_SPAWN.focs.txt @@ -0,0 +1,12 @@ +Tech + name = "SHP_KRILL_SPAWN" + description = "SHP_KRILL_SPAWN_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 9999 * [[TECH_COST_MULTIPLIER]] + researchturns = 9999 + Unresearchable + tags = [ "PEDIA_SHIP_WEAPONS_CATEGORY" ] + unlock = Item type = ShipPart name = "SP_KRILL_SPAWNER" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/SHP_NOVA_BOMB.focs.txt b/default/scripting/techs/ship_weapons/SHP_NOVA_BOMB.focs.txt new file mode 100644 index 00000000000..ef42ae311c0 --- /dev/null +++ b/default/scripting/techs/ship_weapons/SHP_NOVA_BOMB.focs.txt @@ -0,0 +1,19 @@ +Tech + name = "SHP_NOVA_BOMB" + description = "SHP_NOVA_BOMB_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 750 * [[TECH_COST_MULTIPLIER]] + researchturns = 12 + tags = [ "PEDIA_SHIP_WEAPONS_CATEGORY" ] + prerequisites = [ + "LRN_TIME_MECH" + "PRO_ZERO_GEN" + ] + unlock = [ + Item type = ShipPart name = "SP_NOVA_BOMB" + Item type = Building name = "BLD_NOVA_BOMB_ACTIVATOR" + ] + graphic = "icons/ship_parts/nova-bomb.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/SHP_ROOT_AGGRESSION.focs.txt b/default/scripting/techs/ship_weapons/SHP_ROOT_AGGRESSION.focs.txt new file mode 100644 index 00000000000..6d61ae48ec0 --- /dev/null +++ b/default/scripting/techs/ship_weapons/SHP_ROOT_AGGRESSION.focs.txt @@ -0,0 +1,22 @@ +Tech + name = "SHP_ROOT_AGGRESSION" + description = "SHP_ROOT_AGGRESSION_DESC" + short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 1 + researchturns = 1 + tags = [ "PEDIA_SHIP_WEAPONS_CATEGORY" ] + unlock = [ + Item type = ShipPart name = "SR_WEAPON_1_1" + Item type = ShipPart name = "GT_TROOP_POD" + Item type = ShipPart name = "SR_WEAPON_0_1" + ] + effectsgroups = [ + [[WEAPON_BASE_EFFECTS(SR_WEAPON_0_1)]] + [[WEAPON_BASE_EFFECTS(SR_WEAPON_1_1)]] + ] + graphic = "icons/tech/planetary_colonialism.png" + +#include "ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/bombard/SHP_BIOTERM.focs.txt b/default/scripting/techs/ship_weapons/bombard/SHP_BIOTERM.focs.txt new file mode 100644 index 00000000000..2e303f93183 --- /dev/null +++ b/default/scripting/techs/ship_weapons/bombard/SHP_BIOTERM.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_BIOTERM" + description = "SHP_BIOTERM_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 500 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] + prerequisites = [ + "SHP_DEATH_SPORE" + "GRO_BIOTERROR" + ] + unlock = Item type = ShipPart name = "SP_BIOTERM" + graphic = "icons/ship_parts/bioterm.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/bombard/SHP_BOMBARD.focs.txt b/default/scripting/techs/ship_weapons/bombard/SHP_BOMBARD.focs.txt new file mode 100644 index 00000000000..96fade04d64 --- /dev/null +++ b/default/scripting/techs/ship_weapons/bombard/SHP_BOMBARD.focs.txt @@ -0,0 +1,12 @@ +Tech + name = "SHP_BOMBARD" + description = "SHP_BOMBARD_DESC" + short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 100 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] + prerequisites = "SHP_ROOT_AGGRESSION" + graphic = "" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/bombard/SHP_CHAOS_WAVE.focs.txt b/default/scripting/techs/ship_weapons/bombard/SHP_CHAOS_WAVE.focs.txt new file mode 100644 index 00000000000..a65d8479814 --- /dev/null +++ b/default/scripting/techs/ship_weapons/bombard/SHP_CHAOS_WAVE.focs.txt @@ -0,0 +1,18 @@ +Tech + name = "SHP_CHAOS_WAVE" + description = "SHP_CHAOS_WAVE_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 1500 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] + prerequisites = [ + "SHP_BIOTERM" + "SHP_GRV" + "SHP_EMO" + "SHP_VOID_SHADOW" + ] + unlock = Item type = ShipPart name = "SP_CHAOS_WAVE" + graphic = "icons/ship_parts/chaos-wave.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/bombard/SHP_DARK_RAY.focs.txt b/default/scripting/techs/ship_weapons/bombard/SHP_DARK_RAY.focs.txt new file mode 100644 index 00000000000..cce392b84e2 --- /dev/null +++ b/default/scripting/techs/ship_weapons/bombard/SHP_DARK_RAY.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_DARK_RAY" + description = "SHP_DARK_RAY_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 75 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] + prerequisites = [ + "SHP_BOMBARD" + "SPY_STEALTH_2" + ] + unlock = Item type = ShipPart name = "SP_DARK_RAY" + graphic = "icons/ship_parts/dark-ray.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/bombard/SHP_DEATH_SPORE.focs.txt b/default/scripting/techs/ship_weapons/bombard/SHP_DEATH_SPORE.focs.txt new file mode 100644 index 00000000000..de6095d71e2 --- /dev/null +++ b/default/scripting/techs/ship_weapons/bombard/SHP_DEATH_SPORE.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_DEATH_SPORE" + description = "SHP_DEATH_SPORE_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 75 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] + prerequisites = [ + "SHP_BOMBARD" + "GRO_ADV_ECOMAN" + ] + unlock = Item type = ShipPart name = "SP_DEATH_SPORE" + graphic = "icons/ship_parts/death-spore.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/bombard/SHP_EMO.focs.txt b/default/scripting/techs/ship_weapons/bombard/SHP_EMO.focs.txt new file mode 100644 index 00000000000..7be67c21866 --- /dev/null +++ b/default/scripting/techs/ship_weapons/bombard/SHP_EMO.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_EMO" + description = "SHP_EMO_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 500 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] + prerequisites = [ + "SHP_EMP" + "SHP_FRC_ENRG_COMP" + ] + unlock = Item type = ShipPart name = "SP_EMO" + graphic = "icons/ship_parts/EMO.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/bombard/SHP_EMP.focs.txt b/default/scripting/techs/ship_weapons/bombard/SHP_EMP.focs.txt new file mode 100644 index 00000000000..cb4e3cf2932 --- /dev/null +++ b/default/scripting/techs/ship_weapons/bombard/SHP_EMP.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_EMP" + description = "SHP_EMP_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 75 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] + prerequisites = [ + "SHP_BOMBARD" + "LRN_FORCE_FIELD" + ] + unlock = Item type = ShipPart name = "SP_EMP" + graphic = "icons/ship_parts/EMP.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/bombard/SHP_GRV.focs.txt b/default/scripting/techs/ship_weapons/bombard/SHP_GRV.focs.txt new file mode 100644 index 00000000000..574c2c3a377 --- /dev/null +++ b/default/scripting/techs/ship_weapons/bombard/SHP_GRV.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_GRV" + description = "SHP_GRV_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 500 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] + prerequisites = [ + "SHP_SONIC" + "LRN_GRAVITONICS" + ] + unlock = Item type = ShipPart name = "SP_GRV" + graphic = "icons/ship_parts/gravitic_pulse.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/bombard/SHP_SONIC.focs.txt b/default/scripting/techs/ship_weapons/bombard/SHP_SONIC.focs.txt new file mode 100644 index 00000000000..c0a80c2f834 --- /dev/null +++ b/default/scripting/techs/ship_weapons/bombard/SHP_SONIC.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_SONIC" + description = "SHP_SONIC_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 75 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] + prerequisites = [ + "SHP_BOMBARD" + "LRN_FORCE_FIELD" + ] + unlock = Item type = ShipPart name = "SP_SONIC" + graphic = "icons/ship_parts/sonic_wave.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/bombard/SHP_VOID_SHADOW.focs.txt b/default/scripting/techs/ship_weapons/bombard/SHP_VOID_SHADOW.focs.txt new file mode 100644 index 00000000000..e4c874893c2 --- /dev/null +++ b/default/scripting/techs/ship_weapons/bombard/SHP_VOID_SHADOW.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_VOID_SHADOW" + description = "SHP_VOID_SHADOW_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 500 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] + prerequisites = [ + "SHP_DARK_RAY" + "LRN_GATEWAY_VOID" + ] + unlock = Item type = ShipPart name = "SP_VOID_SHADOW" + graphic = "icons/ship_parts/void-shadow.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_1.focs.txt b/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_1.focs.txt new file mode 100644 index 00000000000..2fcede68096 --- /dev/null +++ b/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_1.focs.txt @@ -0,0 +1,19 @@ +Tech + name = "SHP_FIGHTERS_1" + description = "SHP_FIGHTERS_1_DESC" + short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 6 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_FIGHTER_TECHS" ] + prerequisites = "SHP_ROOT_AGGRESSION" + unlock = [ + Item type = ShipPart name = "FT_BAY_1" + Item type = ShipPart name = "FT_HANGAR_1" + Item type = ShipPart name = "FT_HANGAR_2" + Item type = ShipPart name = "FT_HANGAR_3" + Item type = ShipPart name = "FT_HANGAR_4" + ] + graphic = "icons/ship_parts/fighter05.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_2.focs.txt b/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_2.focs.txt new file mode 100644 index 00000000000..e4efeb48c15 --- /dev/null +++ b/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_2.focs.txt @@ -0,0 +1,36 @@ +Tech + name = "SHP_FIGHTERS_2" + description = "SHP_FIGHTERS_2_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 20 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_FIGHTER_TECHS" ] + prerequisites = [ + "SHP_WEAPON_2_1" + "SHP_FIGHTERS_1" + ] + effectsgroups = + EffectsGroup + scope = And [ + Ship + OwnedBy empire = Source.Owner + Or [ + DesignHasPart name = "FT_HANGAR_1" + DesignHasPart name = "FT_HANGAR_2" + DesignHasPart name = "FT_HANGAR_3" + DesignHasPart name = "FT_HANGAR_4" + ] + ] + accountinglabel = "SHP_FIGHTERS_2" + effects = [ +// FIXME WTF Target.DesignId is accepted but returns zero while Target.DesignID does the right thing +// TODO also test/document (PartOfClassInShipDesign class = FighterWeapon design = Target.DesignID) + SetMaxCapacity partname = "FT_HANGAR_1" value = Value + (PartsInShipDesign name = "FT_HANGAR_1" design = Target.DesignID) + SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value + 2 + SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 3 + SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value + 6 + ] + graphic = "icons/ship_parts/fighter05.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_3.focs.txt b/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_3.focs.txt new file mode 100644 index 00000000000..42fec757ece --- /dev/null +++ b/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_3.focs.txt @@ -0,0 +1,34 @@ +Tech + name = "SHP_FIGHTERS_3" + description = "SHP_FIGHTERS_3_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 100 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_FIGHTER_TECHS" ] + prerequisites = [ + "SHP_WEAPON_3_1" + "SHP_FIGHTERS_2" + ] + effectsgroups = + EffectsGroup + scope = And [ + Ship + OwnedBy empire = Source.Owner + Or [ + DesignHasPart name = "FT_HANGAR_1" + DesignHasPart name = "FT_HANGAR_2" + DesignHasPart name = "FT_HANGAR_3" + DesignHasPart name = "FT_HANGAR_4" + ] + ] + accountinglabel = "SHP_FIGHTERS_3" + effects = [ + SetMaxCapacity partname = "FT_HANGAR_1" value = Value + (PartsInShipDesign name = "FT_HANGAR_1" design = Target.DesignID) + SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value + 2 + SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 3 + SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value + 6 + ] + graphic = "icons/ship_parts/fighter05.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_4.focs.txt b/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_4.focs.txt new file mode 100644 index 00000000000..3b09c30180a --- /dev/null +++ b/default/scripting/techs/ship_weapons/fighters/SHP_FIGHTERS_4.focs.txt @@ -0,0 +1,35 @@ +Tech + name = "SHP_FIGHTERS_4" + description = "SHP_FIGHTERS_4_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 500 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_FIGHTER_TECHS" ] + prerequisites = [ + "SHP_WEAPON_4_1" + "SHP_FIGHTERS_3" + ] + effectsgroups = + EffectsGroup + scope = And [ + Ship + OwnedBy empire = Source.Owner + Or [ + DesignHasPart name = "FT_HANGAR_1" + DesignHasPart name = "FT_HANGAR_2" + DesignHasPart name = "FT_HANGAR_3" + DesignHasPart name = "FT_HANGAR_4" + ] + ] + accountinglabel = "SHP_FIGHTERS_3" + effects = [ + SetMaxCapacity partname = "FT_HANGAR_1" value = Value + (PartsInShipDesign name = "FT_HANGAR_1" design = Target.DesignID) + SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value + 2 + SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 3 + SetMaxSecondaryStat partname = "FT_HANGAR_4" value = Value + 6 + ] + graphic = "icons/ship_parts/fighter05.png" + +#include "/scripting/common/base_prod.macros" + diff --git a/default/scripting/techs/ship_weapons/ship_weapons.macros b/default/scripting/techs/ship_weapons/ship_weapons.macros new file mode 100644 index 00000000000..4d1f79e12b1 --- /dev/null +++ b/default/scripting/techs/ship_weapons/ship_weapons.macros @@ -0,0 +1,54 @@ +// @1@ part name +WEAPON_BASE_EFFECTS +''' EffectsGroup + scope = [[EMPIRE_OWNED_SHIP_WITH_PART]] + accountinglabel = "@1@" + effects = [ + SetMaxCapacity partname = "@1@" value = Value + PartCapacity name = "@1@" + SetMaxSecondaryStat partname = "@1@" value = Value + PartSecondaryStat name = "@1@" + ] + +// The following is really only needed on the first resupplied turn after an upgrade is researched, since the resupply currently +// takes place in a portion of the turn before meters are updated, but currently there is no good way to restrict this to +// only that first resupply (and it is simply mildly inefficient to repeat the topup later). + EffectsGroup + scope = And [ + [[EMPIRE_OWNED_SHIP_WITH_PART]] + Turn high = LocalCandidate.LastTurnResupplied + ] + accountinglabel = "@1@" + effects = SetCapacity partname = "@1@" value = Value + [[ARBITRARY_BIG_NUMBER_FOR_METER_TOPUP]] + +''' + +// @1@ part name +// @2@ value added to max capacity +WEAPON_UPGRADE_CAPACITY_EFFECTS +''' [ + EffectsGroup + scope = And [ + [[EMPIRE_OWNED_SHIP_WITH_PART]] + [[SHIP_PART_UPGRADE_RESUPPLY_CHECK(CurrentContent)]] + ] + accountinglabel = "@1@" + effects = SetMaxCapacity partname = "@1@" value = Value + @2@ + +// Inform the researching empire that ships in supply will get upgraded before next combat + EffectsGroup + scope = Source + activation = (CurrentTurn == TurnTechResearched empire = Source.Owner name = CurrentContent) + effects = GenerateSitRepMessage + message = "SITREP_WEAPONS_UPGRADED" + label = "SITREP_WEAPONS_UPGRADED_LABEL" + icon = "icons/sitrep/weapon_upgrade.png" + parameters = [ + tag = "empire" data = Source.Owner + tag = "shippart" data = "@1@" + tag = "tech" data = CurrentContent + tag = "dam" data = @2@ + ] + empire = Target.Owner + + ]''' + +#include "../techs.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_ARC_DISRUPTOR.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_ARC_DISRUPTOR.focs.txt new file mode 100644 index 00000000000..4ec0fe0ae43 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_ARC_DISRUPTOR.focs.txt @@ -0,0 +1,47 @@ +// In absense of shields this weapon can get almost as good as Plasma. Against shields its pretty useless +Tech + name = "SHP_WEAPON_ARC_DISRUPTOR_1" + description = "SHP_WEAPON_ARC_DISRUPTOR_1_DESC" + short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 6 * [[TECH_COST_MULTIPLIER]] + researchturns = 4 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_ROOT_AGGRESSION" + unlock = Item type = ShipPart name = "SR_ARC_DISRUPTOR" + effectsgroups = [ + [[WEAPON_BASE_EFFECTS(SR_ARC_DISRUPTOR)]] + ] + graphic = "icons/ship_parts/pulse-laser-1.png" + +Tech + name = "SHP_WEAPON_ARC_DISRUPTOR_2" + description = "SHP_WEAPON_ARC_DISRUPTOR_2_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 60 * [[TECH_COST_MULTIPLIER]] + researchturns = 8 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_ARC_DISRUPTOR_1" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_ARC_DISRUPTOR, 2)]] + graphic = "icons/ship_parts/pulse-laser-2.png" + + +Tech + name = "SHP_WEAPON_ARC_DISRUPTOR_3" + description = "SHP_WEAPON_ARC_DISRUPTOR_3_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 360 * [[TECH_COST_MULTIPLIER]] + researchturns = 12 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_ARC_DISRUPTOR_2" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_ARC_DISRUPTOR, 3)]] + graphic = "icons/ship_parts/pulse-laser-3.png" + + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_ORGANIC_WAR_ADAPTION.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_ORGANIC_WAR_ADAPTION.focs.txt new file mode 100644 index 00000000000..a1b08890c06 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_ORGANIC_WAR_ADAPTION.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_ORGANIC_WAR_ADAPTION" + description = "SHP_ORGANIC_WAR_ADAPTION_DESC" + short_description = "SHP_ORGANIC_WAR_ADAPTION_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 75 * [[TECH_COST_MULTIPLIER]] + researchturns = 5 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = [ + "SHP_WEAPON_2_2" + "SHP_ORG_HULL" + ] + unlock = Item type = ShipPart name = "SP_SOLAR_CONCENTRATOR" + graphic = "icons/ship_parts/solarcollector.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_SOLAR_CONNECTION.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_SOLAR_CONNECTION.focs.txt new file mode 100644 index 00000000000..956cd123bab --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_SOLAR_CONNECTION.focs.txt @@ -0,0 +1,12 @@ +Tech + name = "SHP_SOLAR_CONNECTION" + description = "SHP_SOLAR_CONNECTION_DESC" + short_description = "SHP_SOLAR_CONNECTION_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 75 * [[TECH_COST_MULTIPLIER]] + researchturns = 4 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_ORGANIC_WAR_ADAPTION" + graphic = "icons/ship_parts/solarcollector.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_SPINAL_ANTIMATTER.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_SPINAL_ANTIMATTER.focs.txt new file mode 100644 index 00000000000..f775a897fe1 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_SPINAL_ANTIMATTER.focs.txt @@ -0,0 +1,15 @@ +Tech + name = "SHP_SPINAL_ANTIMATTER" + description = "SHP_SPINAL_ANTIMATTER_DESC" + short_description = "SHIP_PART_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 250 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = [ + "PRO_ZERO_GEN" + ] + unlock = Item type = ShipPart name = "SR_SPINAL_ANTIMATTER" + graphic = "icons/ship_parts/spinal_antimatter.png" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_1_2.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_1_2.focs.txt new file mode 100644 index 00000000000..ffff42ce74d --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_1_2.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_1_2" + description = "SHP_WEAPON_1_2_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 4 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_ROOT_AGGRESSION" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_1_1, 1)]] + graphic = "icons/ship_parts/mass-driver-2.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_1_3.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_1_3.focs.txt new file mode 100644 index 00000000000..e41c9e8a2f5 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_1_3.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_1_3" + description = "SHP_WEAPON_1_3_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 6 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_1_2" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_1_1, 1)]] + graphic = "icons/ship_parts/mass-driver-3.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_1_4.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_1_4.focs.txt new file mode 100644 index 00000000000..6a4c911a5e5 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_1_4.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_1_4" + description = "SHP_WEAPON_1_4_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 10 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_1_3" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_1_1, 1)]] + graphic = "icons/ship_parts/mass-driver-4.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_1.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_1.focs.txt new file mode 100644 index 00000000000..55915cfbf91 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_1.focs.txt @@ -0,0 +1,18 @@ +Tech + name = "SHP_WEAPON_2_1" + description = "SHP_WEAPON_2_1_DESC" + short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 30 * [[TECH_COST_MULTIPLIER]] + researchturns = 8 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_ROOT_AGGRESSION" + unlock = Item type = ShipPart name = "SR_WEAPON_2_1" + effectsgroups = [ + [[WEAPON_BASE_EFFECTS(SR_WEAPON_2_1)]] + ] + graphic = "icons/ship_parts/laser-1.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_2.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_2.focs.txt new file mode 100644 index 00000000000..0caa6532723 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_2.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_2_2" + description = "SHP_WEAPON_2_2_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 20 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_2_1" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_2_1, 2)]] + graphic = "icons/ship_parts/laser-2.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_3.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_3.focs.txt new file mode 100644 index 00000000000..2862eda13ad --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_3.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_2_3" + description = "SHP_WEAPON_2_3_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 30 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_2_2" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_2_1, 2)]] + graphic = "icons/ship_parts/laser-3.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_4.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_4.focs.txt new file mode 100644 index 00000000000..81a310d34b7 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_2_4.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_2_4" + description = "SHP_WEAPON_2_4_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 50 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_2_3" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_2_1, 2)]] + graphic = "icons/ship_parts/laser-4.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_1.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_1.focs.txt new file mode 100644 index 00000000000..54972855fde --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_1.focs.txt @@ -0,0 +1,18 @@ +Tech + name = "SHP_WEAPON_3_1" + description = "SHP_WEAPON_3_1_DESC" + short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 150 * [[TECH_COST_MULTIPLIER]] + researchturns = 8 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_2_1" + unlock = Item type = ShipPart name = "SR_WEAPON_3_1" + effectsgroups = [ + [[WEAPON_BASE_EFFECTS(SR_WEAPON_3_1)]] + ] + graphic = "icons/ship_parts/plasma-1.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_2.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_2.focs.txt new file mode 100644 index 00000000000..b3373f673b4 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_2.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_3_2" + description = "SHP_WEAPON_3_2_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 100 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_3_1" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_3_1, 3)]] + graphic = "icons/ship_parts/plasma-2.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_3.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_3.focs.txt new file mode 100644 index 00000000000..ce2a481bdc7 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_3.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_3_3" + description = "SHP_WEAPON_3_3_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 150 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_3_2" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_3_1, 3)]] + graphic = "icons/ship_parts/plasma-3.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_4.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_4.focs.txt new file mode 100644 index 00000000000..4777ed81a47 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_3_4.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_3_4" + description = "SHP_WEAPON_3_4_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 250 * [[TECH_COST_MULTIPLIER]] + researchturns = 2 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_3_3" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_3_1, 3)]] + graphic = "icons/ship_parts/plasma-4.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_1.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_1.focs.txt new file mode 100644 index 00000000000..b8e18c9fd1f --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_1.focs.txt @@ -0,0 +1,18 @@ +Tech + name = "SHP_WEAPON_4_1" + description = "SHP_WEAPON_4_1_DESC" + short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 750 * [[TECH_COST_MULTIPLIER]] + researchturns = 10 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_3_1" + unlock = Item type = ShipPart name = "SR_WEAPON_4_1" + effectsgroups = [ + [[WEAPON_BASE_EFFECTS(SR_WEAPON_4_1)]] + ] + graphic = "icons/ship_parts/death-ray-1.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_2.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_2.focs.txt new file mode 100644 index 00000000000..a2a226ebf21 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_2.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_4_2" + description = "SHP_WEAPON_4_2_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 500 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_4_1" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_4_1, 5)]] + graphic = "icons/ship_parts/death-ray-2.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_3.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_3.focs.txt new file mode 100644 index 00000000000..4297695f575 --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_3.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_4_3" + description = "SHP_WEAPON_4_3_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 750 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_4_2" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_4_1, 5)]] + graphic = "icons/ship_parts/death-ray-3.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_4.focs.txt b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_4.focs.txt new file mode 100644 index 00000000000..f0647cdf11b --- /dev/null +++ b/default/scripting/techs/ship_weapons/short_range/SHP_WEAPON_4_4.focs.txt @@ -0,0 +1,16 @@ +Tech + name = "SHP_WEAPON_4_4" + description = "SHP_WEAPON_4_4_DESC" + short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" + category = "SHIP_WEAPONS_CATEGORY" + researchcost = 1250 * [[TECH_COST_MULTIPLIER]] + researchturns = 3 + tags = [ "PEDIA_SR_WEAPON_TECHS" ] + prerequisites = "SHP_WEAPON_4_3" + effectsgroups = + [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_4_1, 5)]] + graphic = "icons/ship_parts/death-ray-4.png" + +#include "../ship_weapons.macros" + +#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ships/Armor.focs.txt b/default/scripting/techs/ships/Armor.focs.txt deleted file mode 100644 index 7e4d393c862..00000000000 --- a/default/scripting/techs/ships/Armor.focs.txt +++ /dev/null @@ -1,48 +0,0 @@ -Tech - name = "SHP_ROOT_ARMOR" - description = "SHP_ROOT_ARMOR_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 1 - researchturns = 1 - tags = [ "PEDIA_ARMOR_PART_TECHS" ] - unlock = Item type = ShipPart name = "AR_STD_PLATE" - graphic = "icons/tech/armor_plating.png" - -Tech - name = "SHP_ZORTRIUM_PLATE" - description = "SHP_ZORTRIUM_PLATE_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 30 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_ARMOR_PART_TECHS" ] - prerequisites = "SHP_ROOT_ARMOR" - unlock = Item type = ShipPart name = "AR_ZORTRIUM_PLATE" - graphic = "icons/tech/armor_plating.png" - -Tech - name = "SHP_DIAMOND_PLATE" - description = "SHP_DIAMOND_PLATE_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 150 * [[TECH_COST_MULTIPLIER]] - researchturns = 4 - tags = [ "PEDIA_ARMOR_PART_TECHS" ] - prerequisites = "SHP_ZORTRIUM_PLATE" - unlock = Item type = ShipPart name = "AR_DIAMOND_PLATE" - graphic = "icons/tech/armor_plating.png" - -Tech - name = "SHP_XENTRONIUM_PLATE" - description = "SHP_XENTRONIUM_PLATE_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 750 * [[TECH_COST_MULTIPLIER]] - researchturns = 6 - tags = [ "PEDIA_ARMOR_PART_TECHS" ] - prerequisites = "SHP_DIAMOND_PLATE" - unlock = Item type = ShipPart name = "AR_XENTRONIUM_PLATE" - graphic = "icons/tech/armor_plating.png" - -#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ships/DamageControl.focs.txt b/default/scripting/techs/ships/DamageControl.focs.txt deleted file mode 100644 index 83889e72475..00000000000 --- a/default/scripting/techs/ships/DamageControl.focs.txt +++ /dev/null @@ -1,91 +0,0 @@ -Tech - name = "SHP_REINFORCED_HULL" - description = "SHP_REINFORCED_HULL_DESC" - short_description = "STRUCTURE_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 36 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_DAMAGE_CONTROL_PART_TECHS" ] - prerequisites = "CON_ARCH_MONOFILS" - effectsgroups = - EffectsGroup - scope = And [ - Ship - OwnedBy empire = Source.Owner - ] - effects = SetMaxStructure value = Value + 5 - graphic = "icons/tech/structural_integrity_fields.png" - -Tech - name = "SHP_BASIC_DAM_CONT" - description = "SHP_BASIC_DAM_CONT_DESC" - short_description = "STRUCTURE_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 40 * [[TECH_COST_MULTIPLIER]] - researchturns = 4 - tags = [ "PEDIA_DAMAGE_CONTROL_PART_TECHS" ] - prerequisites = "SHP_MIL_ROBO_CONT" - effectsgroups = - EffectsGroup - scope = And [ - Ship - OwnedBy empire = Source.Owner - Or [ - Not InSystem - And [ - InSystem - Turn low = LocalCandidate.System.LastTurnBattleHere + 1 - ] - ] - Structure high = LocalCandidate.MaxStructure - 0.001 - ] - effects = SetStructure value = Value + 1 - -Tech - name = "SHP_FLEET_REPAIR" - description = "SHP_FLEET_REPAIR_DESC" - short_description = "SHIP_REPAIR_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 80 * [[TECH_COST_MULTIPLIER]] - researchturns = 10 - tags = [ "PEDIA_DAMAGE_CONTROL_PART_TECHS" ] - prerequisites = [ - "SHP_INTSTEL_LOG" - "SHP_BASIC_DAM_CONT" - ] - effectsgroups = - EffectsGroup - scope = And [ - Ship - InSystem - Stationary - OwnedBy empire = Source.Owner - Turn low = LocalCandidate.System.LastTurnBattleHere + 1 - Structure high = LocalCandidate.MaxStructure - 0.001 - ResupplyableBy empire = Source.Owner - ] - effects = SetStructure value = Value + (Target.MaxStructure/10) - -Tech - name = "SHP_ADV_DAM_CONT" - description = "SHP_ADV_DAM_CONT_DESC" - short_description = "STRUCTURE_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 100 * [[TECH_COST_MULTIPLIER]] - researchturns = 5 - tags = [ "PEDIA_DAMAGE_CONTROL_PART_TECHS" ] - prerequisites = [ - "SHP_FLEET_REPAIR" - "CON_GAL_INFRA" - ] - effectsgroups = - EffectsGroup - scope = And [ - Ship - OwnedBy empire = Source.Owner - Stationary - Turn low = LocalCandidate.System.LastTurnBattleHere + 1 - ] - effects = SetStructure value = Value + (Target.MaxStructure/10) - -#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ships/Fighters.focs.txt b/default/scripting/techs/ships/Fighters.focs.txt deleted file mode 100644 index 6950ebb1190..00000000000 --- a/default/scripting/techs/ships/Fighters.focs.txt +++ /dev/null @@ -1,111 +0,0 @@ -Tech - name = "SHP_FIGHTERS_1" - description = "SHP_FIGHTERS_1_DESC" - short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 6 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_FIGHTER_TECHS" ] - prerequisites = "SHP_ROOT_AGGRESSION" - unlock = [ - Item type = ShipPart name = "FT_BAY_1" - Item type = ShipPart name = "FT_HANGAR_1" - Item type = ShipPart name = "FT_HANGAR_2" - Item type = ShipPart name = "FT_HANGAR_3" - ] - graphic = "icons/ship_parts/fighter05.png" - -Tech - name = "SHP_FIGHTERS_2" - description = "SHP_FIGHTERS_2_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 20 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_FIGHTER_TECHS" ] - prerequisites = [ - "SHP_WEAPON_2_1" - "SHP_FIGHTERS_1" - ] - effectsgroups = - EffectsGroup - scope = And [ - Ship - OwnedBy empire = Source.Owner - Or [ - DesignHasPart name = "FT_HANGAR_1" - DesignHasPart name = "FT_HANGAR_2" - DesignHasPart name = "FT_HANGAR_3" - ] - ] - accountinglabel = "SHP_FIGHTERS_2" - effects = [ - SetMaxSecondaryStat partname = "FT_HANGAR_1" value = Value + 1 - SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value + 2 - SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 3 - ] - graphic = "icons/ship_parts/fighter05.png" - -Tech - name = "SHP_FIGHTERS_3" - description = "SHP_FIGHTERS_3_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 100 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_FIGHTER_TECHS" ] - prerequisites = [ - "SHP_WEAPON_3_1" - "SHP_FIGHTERS_2" - ] - effectsgroups = - EffectsGroup - scope = And [ - Ship - OwnedBy empire = Source.Owner - Or [ - DesignHasPart name = "FT_HANGAR_1" - DesignHasPart name = "FT_HANGAR_2" - DesignHasPart name = "FT_HANGAR_3" - ] - ] - accountinglabel = "SHP_FIGHTERS_3" - effects = [ - SetMaxSecondaryStat partname = "FT_HANGAR_1" value = Value + 1 - SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value + 3 - SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 4 - ] - graphic = "icons/ship_parts/fighter05.png" - -Tech - name = "SHP_FIGHTERS_4" - description = "SHP_FIGHTERS_4_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 500 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_FIGHTER_TECHS" ] - prerequisites = [ - "SHP_WEAPON_4_1" - "SHP_FIGHTERS_3" - ] - effectsgroups = - EffectsGroup - scope = And [ - Ship - OwnedBy empire = Source.Owner - Or [ - DesignHasPart name = "FT_HANGAR_1" - DesignHasPart name = "FT_HANGAR_2" - DesignHasPart name = "FT_HANGAR_3" - ] - ] - accountinglabel = "SHP_FIGHTERS_3" - effects = [ - SetMaxSecondaryStat partname = "FT_HANGAR_1" value = Value + 1 - SetMaxSecondaryStat partname = "FT_HANGAR_2" value = Value + 5 - SetMaxSecondaryStat partname = "FT_HANGAR_3" value = Value + 7 - ] - graphic = "icons/ship_parts/fighter05.png" - -#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ships/Parts.focs.txt b/default/scripting/techs/ships/Parts.focs.txt deleted file mode 100644 index 0f67d90a12b..00000000000 --- a/default/scripting/techs/ships/Parts.focs.txt +++ /dev/null @@ -1,260 +0,0 @@ -Tech - name = "SHP_IMPROVED_ENGINE_COUPLINGS" - description = "SHP_IMPROVED_ENGINE_COUPLINGS_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 24 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_ENGINE_PART_TECHS" ] - prerequisites = "SHP_GAL_EXPLO" - unlock = Item type = ShipPart name = "FU_IMPROVED_ENGINE_COUPLINGS" - graphic = "icons/ship_parts/engine-1.png" - -Tech - name = "SHP_N_DIMENSIONAL_ENGINE_MATRIX" - description = "SHP_N_DIMENSIONAL_ENGINE_MATRIX_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 250 * [[TECH_COST_MULTIPLIER]] - researchturns = 5 - tags = [ "PEDIA_ENGINE_PART_TECHS" ] - prerequisites = [ - "SHP_IMPROVED_ENGINE_COUPLINGS" - "LRN_NDIM_SUBSPACE" - ] - unlock = Item type = ShipPart name = "FU_N_DIMENSIONAL_ENGINE_MATRIX" - graphic = "icons/ship_parts/engine-2.png" - -Tech - name = "SHP_SINGULARITY_ENGINE_CORE" - description = "SHP_SINGULARITY_ENGINE_CORE_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 500 * [[TECH_COST_MULTIPLIER]] - researchturns = 10 - tags = [ "PEDIA_ENGINE_PART_TECHS" ] - prerequisites = [ - "SHP_N_DIMENSIONAL_ENGINE_MATRIX" - "PRO_SINGULAR_GEN" - ] - unlock = Item type = ShipPart name = "FU_SINGULARITY_ENGINE_CORE" - graphic = "icons/ship_parts/engine-3.png" - -Tech - name = "SHP_DEUTERIUM_TANK" - description = "SHP_DEUTERIUM_TANK_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 24 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_FUEL_PART_TECHS" ] - prerequisites = [ - "SHP_GAL_EXPLO" - "PRO_FUSION_GEN" - ] - unlock = [ - Item type = ShipPart name = "FU_DEUTERIUM_TANK" - Item type = ShipPart name = "FU_RAMSCOOP" - ] - graphic = "icons/ship_parts/deuterium_tank.png" - -Tech - name = "SHP_ANTIMATTER_TANK" - description = "SHP_ANTIMATTER_TANK_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 175 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_FUEL_PART_TECHS" ] - prerequisites = [ - "SHP_DEUTERIUM_TANK" - "LRN_FORCE_FIELD" - ] - unlock = Item type = ShipPart name = "FU_ANTIMATTER_TANK" - graphic = "icons/ship_parts/antimatter_tank.png" - -Tech - name = "SHP_ZERO_POINT" - description = "SHP_ZERO_POINT_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 175 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_FUEL_PART_TECHS" ] - prerequisites = [ - "SHP_ANTIMATTER_TANK" - "PRO_ZERO_GEN" - ] - unlock = Item type = ShipPart name = "FU_ZERO_FUEL" - graphic = "icons/ship_parts/zero-point-generator.png" - -Tech - name = "SHP_SPINAL_ANTIMATTER" - description = "SHP_SPINAL_ANTIMATTER_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 250 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = [ - "PRO_ZERO_GEN" - ] - unlock = Item type = ShipPart name = "SR_SPINAL_ANTIMATTER" - graphic = "icons/ship_parts/spinal_antimatter.png" - -Tech - name = "SHP_NOVA_BOMB" - description = "SHP_NOVA_BOMB_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 750 * [[TECH_COST_MULTIPLIER]] - researchturns = 12 - tags = [ "PEDIA_SHIP_WEAPONS_CATEGORY" ] - prerequisites = [ - "LRN_TIME_MECH" - "PRO_ZERO_GEN" - ] - unlock = [ - Item type = ShipPart name = "SP_NOVA_BOMB" - Item type = Building name = "BLD_NOVA_BOMB_ACTIVATOR" - ] - graphic = "icons/ship_parts/nova-bomb.png" - -Tech - name = "SHP_DEATH_SPORE" - description = "SHP_DEATH_SPORE_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 75 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] - prerequisites = [ - "SHP_BOMBARD" - "GRO_ADV_ECOMAN" - ] - unlock = Item type = ShipPart name = "SP_DEATH_SPORE" - graphic = "icons/ship_parts/death-spore.png" - -Tech - name = "SHP_BIOTERM" - description = "SHP_BIOTERM_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 500 * [[TECH_COST_MULTIPLIER]] - researchturns = 5 - tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] - prerequisites = [ - "SHP_DEATH_SPORE" - "GRO_BIOTERROR" - ] - unlock = Item type = ShipPart name = "SP_BIOTERM" - graphic = "icons/ship_parts/bioterm.png" - -Tech - name = "SHP_EMP" - description = "SHP_EMP_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 75 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] - prerequisites = [ - "SHP_BOMBARD" - "LRN_FORCE_FIELD" - ] - unlock = Item type = ShipPart name = "SP_EMP" - graphic = "icons/ship_parts/EMP.png" - -Tech - name = "SHP_EMO" - description = "SHP_EMO_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 500 * [[TECH_COST_MULTIPLIER]] - researchturns = 5 - tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] - prerequisites = [ - "SHP_EMP" - "SHP_FRC_ENRG_COMP" - ] - unlock = Item type = ShipPart name = "SP_EMO" - graphic = "icons/ship_parts/EMO.png" - -Tech - name = "SHP_SONIC" - description = "SHP_SONIC_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 75 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] - prerequisites = [ - "SHP_BOMBARD" - "LRN_FORCE_FIELD" - ] - unlock = Item type = ShipPart name = "SP_SONIC" - graphic = "icons/ship_parts/sonic_wave.png" - -Tech - name = "SHP_GRV" - description = "SHP_GRV_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 500 * [[TECH_COST_MULTIPLIER]] - researchturns = 5 - tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] - prerequisites = [ - "SHP_SONIC" - "LRN_GRAVITONICS" - ] - unlock = Item type = ShipPart name = "SP_GRV" - graphic = "icons/ship_parts/gravitic_pulse.png" - -Tech - name = "SHP_DARK_RAY" - description = "SHP_DARK_RAY_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 75 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] - prerequisites = [ - "SHP_BOMBARD" - "SPY_STEALTH_2" - ] - unlock = Item type = ShipPart name = "SP_DARK_RAY" - graphic = "icons/ship_parts/dark-ray.png" - -Tech - name = "SHP_VOID_SHADOW" - description = "SHP_VOID_SHADOW_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 500 * [[TECH_COST_MULTIPLIER]] - researchturns = 5 - tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] - prerequisites = [ - "SHP_DARK_RAY" - "LRN_GATEWAY_VOID" - ] - unlock = Item type = ShipPart name = "SP_VOID_SHADOW" - graphic = "icons/ship_parts/void-shadow.png" - -Tech - name = "SHP_CHAOS_WAVE" - description = "SHP_CHAOS_WAVE_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 1500 * [[TECH_COST_MULTIPLIER]] - researchturns = 5 - tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] - prerequisites = [ - "SHP_BIOTERM" - "SHP_GRV" - "SHP_EMO" - "SHP_VOID_SHADOW" - ] - unlock = Item type = ShipPart name = "SP_CHAOS_WAVE" - graphic = "icons/ship_parts/chaos-wave.png" - -#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ships/Shields.focs.txt b/default/scripting/techs/ships/Shields.focs.txt deleted file mode 100644 index 5ddd61b3bf0..00000000000 --- a/default/scripting/techs/ships/Shields.focs.txt +++ /dev/null @@ -1,53 +0,0 @@ -Tech - name = "SHP_DEFLECTOR_SHIELD" - description = "SHP_DEFLECTOR_SHIELD_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 150 * [[TECH_COST_MULTIPLIER]] - researchturns = 10 - tags = [ "PEDIA_SHIELD_PART_TECHS" ] - prerequisites = "LRN_FORCE_FIELD" - unlock = Item type = ShipPart name = "SH_DEFLECTOR" - graphic = "icons/ship_parts/deflector_shield.png" - -Tech - name = "SHP_PLASMA_SHIELD" - description = "SHP_PLASMA_SHIELD_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 750 * [[TECH_COST_MULTIPLIER]] - researchturns = 10 - tags = [ "PEDIA_SHIELD_PART_TECHS" ] - prerequisites = "SHP_DEFLECTOR_SHIELD" - unlock = Item type = ShipPart name = "SH_PLASMA" - graphic = "icons/ship_parts/plasma_shield.png" - -Tech - name = "SHP_BLACKSHIELD" - description = "SHP_BLACKSHIELD_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 3750 * [[TECH_COST_MULTIPLIER]] - researchturns = 10 - tags = [ "PEDIA_SHIELD_PART_TECHS" ] - prerequisites = "SHP_PLASMA_SHIELD" - unlock = Item type = ShipPart name = "SH_BLACK" - graphic = "icons/ship_parts/blackshield.png" - -Tech - name = "SHP_MULTISPEC_SHIELD" - description = "SHP_MULTISPEC_SHIELD_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_PARTS_CATEGORY" - researchcost = 2000 * [[TECH_COST_MULTIPLIER]] - researchturns = 10 - //Unresearchable - tags = [ "PEDIA_SHIELD_PART_TECHS" ] - prerequisites = [ - "SHP_PLASMA_SHIELD" - "SPY_DIST_MOD" - ] - unlock = Item type = ShipPart name = "SH_MULTISPEC" - graphic = "icons/ship_parts/multi-spectral.png" - -#include "/scripting/common/base_prod.macros" diff --git a/default/scripting/techs/ships/Weapons.focs.txt b/default/scripting/techs/ships/Weapons.focs.txt deleted file mode 100644 index cc276352140..00000000000 --- a/default/scripting/techs/ships/Weapons.focs.txt +++ /dev/null @@ -1,306 +0,0 @@ -Tech - name = "SHP_ROOT_AGGRESSION" - description = "SHP_ROOT_AGGRESSION_DESC" - short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 1 - researchturns = 1 - tags = [ "PEDIA_SHIP_WEAPONS_CATEGORY" ] - unlock = [ - Item type = ShipPart name = "SR_WEAPON_1_1" - Item type = ShipPart name = "GT_TROOP_POD" - Item type = ShipPart name = "SR_WEAPON_0_1" - ] - effectsgroups = [ - [[WEAPON_BASE_EFFECTS(SR_WEAPON_0_1)]] - [[WEAPON_BASE_EFFECTS(SR_WEAPON_1_1)]] - ] - graphic = "icons/tech/planetary_colonialism.png" - -Tech - name = "SHP_BOMBARD" - description = "SHP_BOMBARD_DESC" - short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 100 * [[TECH_COST_MULTIPLIER]] - researchturns = 5 - tags = [ "PEDIA_BOMBARD_WEAPON_TECHS" ] - prerequisites = "SHP_ROOT_AGGRESSION" - graphic = "" - -Tech - name = "SHP_WEAPON_1_2" - description = "SHP_WEAPON_1_2_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 4 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_ROOT_AGGRESSION" - effectsgroups = - [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_1_1, 1)]] - graphic = "icons/ship_parts/mass-driver-2.png" - -Tech - name = "SHP_WEAPON_1_3" - description = "SHP_WEAPON_1_3_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 6 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_1_2" - effectsgroups = - [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_1_1, 1)]] - graphic = "icons/ship_parts/mass-driver-3.png" - -Tech - name = "SHP_WEAPON_1_4" - description = "SHP_WEAPON_1_4_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 10 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_1_3" - effectsgroups = - [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_1_1, 1)]] - graphic = "icons/ship_parts/mass-driver-4.png" - -Tech - name = "SHP_WEAPON_2_1" - description = "SHP_WEAPON_2_1_DESC" - short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 30 * [[TECH_COST_MULTIPLIER]] - researchturns = 8 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_ROOT_AGGRESSION" - unlock = Item type = ShipPart name = "SR_WEAPON_2_1" - effectsgroups = - [[WEAPON_BASE_EFFECTS(SR_WEAPON_2_1)]] - graphic = "icons/ship_parts/laser-1.png" - -Tech - name = "SHP_WEAPON_2_2" - description = "SHP_WEAPON_2_2_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 20 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_2_1" - effectsgroups = - [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_2_1, 2)]] - graphic = "icons/ship_parts/laser-2.png" - -Tech - name = "SHP_WEAPON_2_3" - description = "SHP_WEAPON_2_3_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 30 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_2_2" - effectsgroups = - [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_2_1, 2)]] - graphic = "icons/ship_parts/laser-3.png" - -Tech - name = "SHP_WEAPON_2_4" - description = "SHP_WEAPON_2_4_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 50 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_2_3" - effectsgroups = - [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_2_1, 2)]] - graphic = "icons/ship_parts/laser-4.png" - -Tech - name = "SHP_WEAPON_3_1" - description = "SHP_WEAPON_3_1_DESC" - short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 150 * [[TECH_COST_MULTIPLIER]] - researchturns = 8 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_2_1" - unlock = Item type = ShipPart name = "SR_WEAPON_3_1" - effectsgroups = - [[WEAPON_BASE_EFFECTS(SR_WEAPON_3_1)]] - graphic = "icons/ship_parts/plasma-1.png" - -Tech - name = "SHP_WEAPON_3_2" - description = "SHP_WEAPON_3_2_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 100 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_3_1" - effectsgroups = - [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_3_1, 3)]] - graphic = "icons/ship_parts/plasma-2.png" - -Tech - name = "SHP_WEAPON_3_3" - description = "SHP_WEAPON_3_3_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 150 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_3_2" - effectsgroups = - [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_3_1, 3)]] - graphic = "icons/ship_parts/plasma-3.png" - -Tech - name = "SHP_WEAPON_3_4" - description = "SHP_WEAPON_3_4_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 250 * [[TECH_COST_MULTIPLIER]] - researchturns = 2 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_3_3" - effectsgroups = - [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_3_1, 3)]] - graphic = "icons/ship_parts/plasma-4.png" - -Tech - name = "SHP_WEAPON_4_1" - description = "SHP_WEAPON_4_1_DESC" - short_description = "SHIP_WEAPON_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 750 * [[TECH_COST_MULTIPLIER]] - researchturns = 10 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_3_1" - unlock = Item type = ShipPart name = "SR_WEAPON_4_1" - effectsgroups = - [[WEAPON_BASE_EFFECTS(SR_WEAPON_4_1)]] - graphic = "icons/ship_parts/death-ray-1.png" - -Tech - name = "SHP_WEAPON_4_2" - description = "SHP_WEAPON_4_2_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 500 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_4_1" - effectsgroups = - [[WEAPON_UPGRADE_CAPACITY_EFFECTS(SR_WEAPON_4_1, 5)]] - graphic = "icons/ship_parts/death-ray-2.png" - -Tech - name = "SHP_WEAPON_4_3" - description = "SHP_WEAPON_4_3_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 750 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_4_2" - effectsgroups = - EffectsGroup - scope = And [ - Ship - OwnedBy empire = Source.Owner - DesignHasPart name = "SR_WEAPON_4_1" - ] - accountinglabel = "SR_WEAPON_4_3" - effects = SetMaxCapacity partname = "SR_WEAPON_4_1" value = Value + 5 - graphic = "icons/ship_parts/death-ray-3.png" - -Tech - name = "SHP_WEAPON_4_4" - description = "SHP_WEAPON_4_4_DESC" - short_description = "SHIP_WEAPON_IMPROVE_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 1250 * [[TECH_COST_MULTIPLIER]] - researchturns = 3 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_WEAPON_4_3" - effectsgroups = - EffectsGroup - scope = And [ - Ship - OwnedBy empire = Source.Owner - DesignHasPart name = "SR_WEAPON_4_1" - ] - accountinglabel = "SR_WEAPON_4_4" - effects = SetMaxCapacity partname = "SR_WEAPON_4_1" value = Value + 5 - graphic = "icons/ship_parts/death-ray-4.png" - -Tech - name = "SHP_ORGANIC_WAR_ADAPTION" - description = "SHP_ORGANIC_WAR_ADAPTION_DESC" - short_description = "SHP_ORGANIC_WAR_ADAPTION_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 75 * [[TECH_COST_MULTIPLIER]] - researchturns = 5 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = [ - "SHP_WEAPON_2_2" - "SHP_ORG_HULL" - ] - unlock = Item type = ShipPart name = "SP_SOLAR_CONCENTRATOR" - graphic = "icons/ship_parts/solarcollector.png" - -Tech - name = "SHP_SOLAR_CONNECTION" - description = "SHP_SOLAR_CONNECTION_DESC" - short_description = "SHP_SOLAR_CONNECTION_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 75 * [[TECH_COST_MULTIPLIER]] - researchturns = 4 - tags = [ "PEDIA_SR_WEAPON_TECHS" ] - prerequisites = "SHP_ORGANIC_WAR_ADAPTION" - graphic = "icons/ship_parts/solarcollector.png" - -Tech - name = "SHP_KRILL_SPAWN" - description = "SHP_KRILL_SPAWN_DESC" - short_description = "SHIP_PART_UNLOCK_SHORT_DESC" - category = "SHIP_WEAPONS_CATEGORY" - researchcost = 9999 * [[TECH_COST_MULTIPLIER]] - researchturns = 9999 - Unresearchable - tags = [ "PEDIA_SHIP_WEAPONS_CATEGORY" ] - unlock = Item type = ShipPart name = "SP_KRILL_SPAWNER" - -#include "/scripting/common/base_prod.macros" - -WEAPON_BASE_EFFECTS -''' EffectsGroup - scope = And [ - Ship - OwnedBy empire = Source.Owner - DesignHasPart name = "@1@" - ] - accountinglabel = "@1@" - effects = [ - SetMaxCapacity partname = "@1@" value = Value + PartCapacity name = "@1@" - SetMaxSecondaryStat partname = "@1@" value = Value + PartSecondaryStat name = "@1@" - ] -''' - -WEAPON_UPGRADE_CAPACITY_EFFECTS -''' - EffectsGroup - scope = And [ - Ship - OwnedBy empire = Source.Owner - DesignHasPart name = "@1@" - ] - accountinglabel = "@1@" - effects = SetMaxCapacity partname = "@1@" value = Value + @2@ -''' diff --git a/default/scripting/techs/spy/DETECT_3.focs.txt b/default/scripting/techs/spy/DETECT_3.focs.txt index 17d381ae6d7..9293730ea8f 100644 --- a/default/scripting/techs/spy/DETECT_3.focs.txt +++ b/default/scripting/techs/spy/DETECT_3.focs.txt @@ -17,7 +17,7 @@ Tech activation = And [ Not OwnerHasTech name = "SPY_DETECT_4" Not OwnerHasTech name = "SPY_DETECT_5" - ] + ] effects = SetDetection value = Value + 150 EffectsGroup @@ -25,7 +25,7 @@ Tech activation = And [ Not OwnerHasTech name = "SPY_DETECT_4" Not OwnerHasTech name = "SPY_DETECT_5" - ] + ] effects = SetEmpireMeter empire = Source.Owner meter = "METER_DETECTION_STRENGTH" value = Value + 50 ] diff --git a/default/scripting/techs/techs.macros b/default/scripting/techs/techs.macros index 378dc40e3fe..7f03946680c 100644 --- a/default/scripting/techs/techs.macros +++ b/default/scripting/techs/techs.macros @@ -1,2 +1,26 @@ -CANDIDATE_BATTLE_CHECK -'''Turn low = LocalCandidate.System.LastTurnBattleHere + 1''' + +// In typical usage this is used as a plain macro, not a substitution macro; the substitution is instead done within the calling macro +EMPIRE_OWNED_SHIP_WITH_PART +''' + And [ + Ship + OwnedBy empire = Source.Owner + DesignHasPart name = "@1@" + ] +''' + +// For inclusion in a scope macro for an upgrade increase to a Max type meter (MaxCapacity or MaxSecondaryStat), and that scope +// should have already limited scope to ships owned by an empire +// In most cases the substitution provided for @1@ may simply be "CurrentContent" (without the quote marks) but +// if providing an explicit tech name then include quotes with the substitution, +// i.e., either use +// SHIP_PART_UPGRADE_RESUPPLY_CHECK("SOME_TECH") +// or +// SHIP_PART_UPGRADE_RESUPPLY_CHECK(CurrentContent) +SHIP_PART_UPGRADE_RESUPPLY_CHECK +''' + ( LocalCandidate.LastTurnResupplied >= TurnTechResearched empire = LocalCandidate.Owner name = @1@ ) +''' + +ARBITRARY_BIG_NUMBER_FOR_METER_TOPUP +''' 1000000 ''' diff --git a/default/stringtables/cz.txt b/default/stringtables/cz.txt index af229a2c1d7..0f048650a9b 100644 --- a/default/stringtables/cz.txt +++ b/default/stringtables/cz.txt @@ -96,18 +96,6 @@ Průzkumník SD_SCOUT_DESC Malé a levné neozbrojené plavidlo určené pro průzkum. -SD_MARK1_DESC -Cenově dostupná ozbrojená hlídka. - -SD_MARK2_DESC -Křižník se silnou obrannou a útočnou schopností. - -SD_MARK3_DESC -Rozšířené křižník s těžkými zbraněmi a obrněním na dělaní špinavé práce. - -SD_MARK4_DESC -Masivní válečná loď ozbrojená a chráněná nejnovějšími technologiemi. Cena se od toho odvíjí. - SD_COLONY_SHIP Kolonizační loď @@ -163,9 +151,6 @@ COMMAND_LINE_USAGE COMMAND_LINE_DEFAULT '''Standardní: ''' -OPTIONS_DB_HELP -Vytisknout tuto nápovědu. - OPTIONS_DB_GENERATE_CONFIG_XML Využívá všech nastavení z jakékoli stávající config.xml souboru a ty, uvedené na příkazové řádce vytvořit config.xml souboru. Tím se přepíše stávající soubor config.xml, pokud existuje. @@ -583,9 +568,6 @@ Rychle-zavřít okna OPTIONS_MISC_UI Různé UI Nastavení -OPTIONS_SINGLEPLAYER -Jeden hráč - OPTIONS_AUTOSAVE_TURNS_BETWEEN Tahy mezi autouložením @@ -1063,15 +1045,9 @@ SITREP_EMPIRE_ELIMINATED ANCIENT_RUINS_SPECIAL Starobylé ruiny -ANCIENT_RUINS_SPECIAL_DESCRIPTION -Tato planeta má na povrchu zříceniny budov vyspělých starověkých ras, které byly zapomenuty v historických knihách.Objevením získáte bonus k výzkumu. - ECCENTRIC_ORBIT_SPECIAL Excentrická orbita -ECCENTRIC_ORBIT_SPECIAL_DESC -Dráha této planety je velmi výstřední. Má velký rozdíl mezi jeho nejbližší a nejdelší vzdálenosti k její hvězdě. Celkové oslunění se výrazně liší v průběhu celého roku. Různé podmínky brzdí rozvoj infrastruktury, ale poskytne prospěšný základ pro výzkum. - MINERALS_SPECIAL Bohatá na minerály @@ -1248,10 +1224,6 @@ Obchod METER_POPULATION populace -# Meter types -METER_HEALTH -zdraví - # Meter types METER_INDUSTRY průmysl @@ -1380,33 +1352,18 @@ Zkoumánním struktury mozku a jeho funkcí jako např. elektrochemické a kvant LRN_ALGO_ELEGANCE Elegantní algoritmy -LRN_ALGO_ELEGANCE_DESC -S rostoucí složitostí problémů v analýze dat jsou konvenční algoritmy přetíženy. Od této fáze vývoje je nutné najít jiné algoritmické přístupy a funkce, pro které je nezbytná elegance řešení a také musí být optimalizovány esteticky a metaforicky. - LRN_TRANSLING_THT Multinárodní jazyk -LRN_TRANSLING_THT_DESC -Různí myslitelé se potýkají s pravidly cizího jazyka,který se chtějí naučit, jsou vnímáni průměrně,protože jsou omezeny stanovenými druhy projevu. Výjimečných myslitelé mohou překonat omezení jazyka,myšlení a analýzy na vynikající úrovni. Ale pouze velcí myslitelé zůstávají v izolaci a zbytečné,pokládající si otázku: Jak všem vyjádřit bez adekvátního jazyka záblesky jejich inspirace? - LRN_PSIONICS Psionická komunikace -LRN_PSIONICS_DESC -Díky hlubokému zkoumání nebo umělému vylepšení, může mozek rozvíjet dovednosti, které mu umožní komunikovat přímo bez omezení fyzického těla s okolním vesmírem. Komunikace jako jsou např.:telepatie, empatie, jasnovidectví, předvídání, psychokineze a psychoenergetika mohou nahradit banální biologické nebo technologické alternativy.Avšak vedlejší účinky těchto sil, včetně sebekontroly, změny osobnosti, mohou mít dalekosáhlé důsledky pro vztah mezi takto nadanými a nenadaných organismy. - LRN_ARTIF_MINDS Umělá inteligence -LRN_ARTIF_MINDS_DESC -Zatímco klasické počítače, mají téměř nulovou inteligenci a znají pouze matematický systém, a tak jim chybí důležité vlastnosti, jako jsou sebeuvědomění, vědomí nebo pocit. Vytvořením dokonalé umělé inteligence, lze tyto vlastnosti vytvářet a upravovat. Tato zjištění otevírají nové možnosti v oblasti kognitivních věd a vytváření nových Metaparadigmat. - LRN_XENOARCH Xenoarcheologie -LRN_XENOARCH_DESC -Současná soustava říší, ras a civilizací, nejsou první ani jediní, kteří žili v této galaxii. Památky, zříceniny a zvěsti o starších lidských uskupení se mohou najít na opuštěnch planetách, asteroidech, či zničené lodě plovoucí v prázdném prostoru. Hledáním a dešifrováním těchto informací je poskytnuta příležitost k objevování dávných tajemstvích a možnost poučit se z nich, nebo dokonce varovaní díky starým poznatkům. - LRN_GRAVITONICS Graviton @@ -1654,6 +1611,5 @@ Pevnost: %1% ## Name lists ## -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/da.txt b/default/stringtables/da.txt index 141af61d61f..6f841f68b8a 100644 --- a/default/stringtables/da.txt +++ b/default/stringtables/da.txt @@ -554,6 +554,5 @@ Teknologien %tech% er blevet opfundet. ## Name lists ## -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/de.txt b/default/stringtables/de.txt index be69e2ba3a5..19e148d3561 100644 --- a/default/stringtables/de.txt +++ b/default/stringtables/de.txt @@ -8,6 +8,7 @@ Deutsch # * shivu # * Markus Sinner [kroddn] # * Martin Wuttke [thesilentone] +# * Patrick Lübcke [PaddiLu] # # Notes: to avoid potential conflict with functional keys in other files, do not # make any stringtable keys beginning with "FUNCTIONAL_". @@ -287,121 +288,244 @@ FORMAT_LIST_10_ITEMS ## SD_CARRIER -Prototyp Raumträger +Geleitträger SD_CARRIER_DESC -Prototypentwurf, welcher Jäger in den Kampf sendet, anstatt Geschütze zu nutzen. +Trägerschiff, welches Abfangjäger in den Kampf sendet, um andere Kriegsschiffe zu beschützen. -SD_FLAK -Prototyp Raumabwehrschiff +SD_CARRIER_2 +Flottenträger -SD_FLAK_DESC -Prototypentwurf, welcher Flakkanonen als kostengünstige Maßnahme gegen Jäger nutzt. +SD_CARRIER_2_DESC +Trägerschiff für Offensivflotten, ausgestattet mit Bombergeschwadern und Massenkatapult. SD_SCOUT -Kundschafter +Erkundungsschiff SD_SCOUT_DESC -Kleines und billiges unbewaffnetes Schiff entworfen für Aufklärung und Erforschung. Weitere [[metertype METER_RESEARCH]], welche die [[metertype METER_DETECTION]] verbessert, würde bessere Entwürfe ermöglichen. Dieses Schiff kann nur auf Kolonien mit einem [[buildingtype BLD_SHIPYARD_BASE]] gebaut werden. - -SD_AST_SCOUT -Asteroidenkundschafter +Kleines und billiges unbewaffnetes Schiff für Aufklärungs- und Erkundungsmissionen. [[SHIPDESIGN_DETECTION_RESEARCH_TIPS]] [[BLD_SHIPYARD_BASE_REQUIRED]] SD_SCOUT_2 -Radarkundschafter +Radarschiff + +SD_SCOUT_2_DESC +Kleines und billiges unbewaffnetes Schiff mit verbesserter [[metertype METER_DETECTION]] für Aufklärungs- und Erkundungsmissionen. [[BLD_SHIPYARD_BASE_REQUIRED]] SD_SCOUT_3 -Scannerkundschafter +Scannerschiff + +SD_SCOUT_3_DESC +Kleines und billiges unbewaffnetes Schiff mit verbesserter [[metertype METER_DETECTION]] für Aufklärungs- und Erkundungsmissionen. [[BLD_SHIPYARD_BASE_REQUIRED]] SD_SCOUT_4 -Sensorkundschafter +Sensorenschiff + +SD_SCOUT_4_DESC +Kleines und billiges unbewaffnetes Schiff mit verbesserter [[metertype METER_DETECTION]] für Aufklärungs- und Erkundungsmissionen. [[BLD_SHIPYARD_BASE_REQUIRED]] SD_ENG_SCOUT -Energiekundschafter +Energieaufklärer + +SD_ENG_SCOUT_DESC +Kleines und billiges unbewaffnetes Schiff für Aufklärungs- und Erkundungsmissionen. [[SHIPDESIGN_DETECTION_RESEARCH_TIPS]] [[BLD_SHIPYARD_BASE_ENRG_COMP_THREE_STARS_REQUIRED]] SD_SMALL_MARK_1 -Korvette (M) +Korvette Ms -SD_SMALL_MARK_2 -Korvette (L) +SD_SMALL_MARK1_DESC +Kleines, billiges Schiff mit Massenkatapult. [[BLD_SHIPYARD_BASE_REQUIRED]] SD_MARK_1 -Fregatte (M1) +Fregatte Ms SD_MARK1_DESC -Einfache Fregatte mit Massenkatapult. Kann nur auf Kolonien mit einem [[buildingtype BLD_SHIPYARD_BASE]] gebaut werden. +Einfache Fregatte mit Massenkatapult. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_LARGE_MARK_1 +Kreuzer Ms + +SD_LARGE_MARK1_DESC +Kriegsschiff, das für unabhängige Einsätze ausgerüstet wurde. [[BLD_SHIPYARD_BASE_REQUIRED]] -SD_MARK_2 -Fregatte (M2) +SD_LARGE_MARK_2 +Kreuzer Lz -SD_MARK2_DESC -Verbesserte Fregatte mit Massenkatapult. Kann nur auf Kolonien mit einem [[buildingtype BLD_SHIPYARD_BASE]] gebaut werden. +SD_LARGE_MARK2_DESC +Kriegsschiff, dss für unabhängige Einsätze ausgerüstet wurde. Bewaffnung und Panzerung wurden verbessert. [[BLD_SHIPYARD_BASE_REQUIRED]] -SD_MARK_3 -Fregatte (L1) +SD_LARGE_MARK_3 +Zerstörer Ms -SD_MARK3_DESC -Einfache Laserfregatte. Kann nur auf Kolonien mit einem [[buildingtype BLD_SHIPYARD_BASE]] gebaut werden. +SD_LARGE_MARK3_DESC +Kriegsschiff, das für den Einsatz in Flotten ausgerüstet wurde. [[BLD_SHIPYARD_BASE_REQUIRED]] -SD_MARK_4 -Fregatte (L2) +SD_LARGE_MARK_4 +Zerstörer Lz -SD_MARK4_DESC -Verbesserte Laserfrigatte. Kann nur auf Kolonien mit einem [[buildingtype BLD_SHIPYARD_BASE]] gebaut werden. +SD_LARGE_MARK4_DESC +Kriegsschiff, dass für den Einsatz in Flotten ausgerüstet wurde. Bewaffnung und Panzerung wurden verbessert. [[BLD_SHIPYARD_BASE_REQUIRED]] -SD_LARGE_MARK_1 -Zerstörer (M1) +SD_ROBOTIC_OUTPOST +Robotik-Außenpostenschiff -SD_LARGE_MARK_2 -Zerstörer (M2) +SD_ROBOTIC_OUTPOST_DESC +Kann auf fernen Welten [[OUTPOSTS_TITLE]] errichten. -SD_LARGE_MARK_3 -Kreuzer (L1) +SD_ROBO_FLUX_SCOUT +Flux-Aufklärer -SD_LARGE_MARK_4 -Kreuzer (L2) +SD_ROBO_FLUX_SCOUT_DESC +Schneller Aufklärer mit Tarnfähigkeit. + +SD_ROBO_FLUX_TROOPS +Flux-Truppenträger + +SD_ROBO_FLUX_TROOPS_DESC +Schneller Truppentransporter. + +SD_ROBO_FLUX_TROOPS_HVY +Flux-Cyborgträger + +SD_ROBO_FLUX_TROOPS_HVY_DESC +Schneller Transporter für fortschrittliche Kampftruppen. + +SD_ROBOTIC1 +Robokreuzer MFs + +SD_ROBOTIC1_DESC +Kriegsschiff, geeignet für Flotten und unabhängige Einsätze. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] + +SD_ROBOTIC2 +Zerstörer MFs-SF + +SD_ROBOTIC2_DESC +Robotisches Kriegsschiff, das für den Einsatz in Flotten ausgerüstet wurde. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] + +SD_ROBOTIC3 +Robokreuzer Lzv + +SD_ROBOTIC3_DESC +Robotisches Kriegsschiff, das für unabhängige Einsätze ausgerüstet wurde. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_ROBOTIC_CARRIER1 +Flottenträger MBs + +SD_ROBOTIC_CARRIER1_DESC +Trägerschiff für Offensivflotten. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] + +SD_ROBOTIC_CARRIER2 +Flottenträger LBz + +SD_ROBOTIC_CARRIER2_DESC +Trägerschiff für Offensivflotten. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] + +SD_ROBOTIC_CARRIER3 +Geleitträger MAs + +SD_ROBOTIC_CARRIER3_DESC +Trägerschiff für den Einsatz in Konvois und Vorposten. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] SD_GRAVITATING1 -Gravitierend I +Schlachtschiff PFd-DS SD_GRAVITATING1_DESC -Massives Kriegsschiff bewaffnet und gepanzert mit der neusten Technologie. Preis entsprechend. +Massives Kriegsschiff, das für den Einsatz in Flotten ausgerüstet wurde. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED]] + +SD_GRAVITATING2 +Battleship TFx-SS + +SD_GRAVITATING2_DESC +Massives Kriegsschiff, bewaffnet und gepanzert mit der neusten Technologie. Entsprechend teuer. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED]] + +#SD_ROBO_TITAN1 +#Dreadnaught + +SD_ROBO_TITAN1_DESC +Massives Kriegsschiff, bewaffnet und gepanzert mit der neusten Technologie. Entsprechend teuer. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED]] + +SD_AST_1 +Felszerstörer (L) + +SD_AST_1_DESC +Asteroidenschiff, das für den Einsatz in Flotten ausgerüstet wurde. [[BLD_SHIPYARD_BASE_AST_REQUIRED]] SD_COLONY_SHIP Siedlungsschiff SD_COLONY_SHIP_DESC -Unbewaffnetes Schiff fähig Millionen von Bürger sicher zum Ort einer neuen Siedlung zu bringen. +Unbewaffnetes Schiff, [[SHIPDESIGN_MILLIONS_COLONIZATION_CAPACITY]]. [[MIN_POPULATION_THREE_REQUIRED]]. EIne [[buildingtype BLD_SHIPYARD_BASE]] ist ebenfalls erforderlich. + +SD_CRYONIC_COLONY_SHIP +Kryostase-Siedlungsschiff + +SD_CRYONIC_COLONY_SHIP_DESC +Unbewaffnetes Schiff, [[SHIPDESIGN_MANY_MILLIONS_COLONIZATION_CAPACITY]]. [[MIN_POPULATION_THREE_REQUIRED]]. EIne [[buildingtype BLD_SHIPYARD_BASE]] ist ebenfalls erforderlich. SD_OUTPOST_SHIP Außenpostenschiff SD_OUTPOST_SHIP_DESC -Unbewaffnetes Schiff, fähig einen Außenposten auf einer unbewohnbaren Welt zu erstellen. +Unbewaffnetes Schiff [[SHIPDESIGN_OUTPOSTS_CAPACITY]]. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_ORG_OUTPOST_SHIP +Organisches Außenpostenschiff + +SD_ORG_OUTPOST_SHIP_DESC +Unbewaffnetes organisches Schiff [[SHIPDESIGN_OUTPOSTS_CAPACITY]]. [[BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED]] SD_COLONY_BASE Siedlungsbasis SD_COLONY_BASE_DESC -Unbewaffnetes Schiff, nur fähig eine Siedlung in dem System zu gründen, in welchem es gebaut wurde. +Unbewaffnetes Schiff, dass eine [[SHIPDESIGN_COLONIZATION_CAPACITY_SAME_SYSTEM]] gründen kann. [[MIN_POPULATION_THREE_REQUIRED]]. [[SHIPDESIGN_NO_TRAVEL]] + +SD_CRYONIC_COLONY_BASE +Kryostase-Siedlungsbasis + +SD_CRYONIC_COLONY_BASE_DESC +Unbewaffnetes Schiff, dass eine große [[SHIPDESIGN_COLONIZATION_CAPACITY_SAME_SYSTEM]] gründen kann. [[MIN_POPULATION_THREE_REQUIRED]]. [[SHIPDESIGN_NO_TRAVEL]] SD_OUTPOST_BASE Außenpostenbasis SD_OUTPOST_BASE_DESC -Unbewaffnetes Schiff, nur fähig einen Außenposten in dem System zu gründen, in welchem es gebaut wurde. +Unbewaffnetes Schiff [[SHIPDESIGN_OUTPOSTS_CAPACITY]] in dem System, in dem es gebaut wurde. [[SHIPDESIGN_NO_TRAVEL]] + +SD_BASE_DECOY +Kommunikationssatellit + +SD_BASE_DECOY_DESC +Unbewaffneter Satellit, welcher die planetare Abwehr auch gegen passive Feinde auslösen kann, aber ansonsten nur als Ziel für feindliches Feuer dient. Kann auf allen Planeten gebaut werden, deren Bewohner Schiffe bauen können. Eine [[buildingtype BLD_SHIPYARD_BASE]] wird nicht benötigt. + +SD_TROOP_DROP +Truppenschiff + +SD_TROOP_DROP_DESC +Transportiert einen Verband von [[SHIPDESIGN_PLANET_INVASION]] im selben System. + +SD_TROOP_DROP_HVY +Cyborgtruppenschiff + +SD_TROOP_DROP_HVY_DESC +Transportiert einen Verband fortschrittlicher [[SHIPDESIGN_PLANET_INVASION]] im selben System. + +SD_SMALL_TROOP_SHIP +Kleiner Truppentransporter + +SD_SMALL_TROOP_SHIP_DESC +Transportiert einen Verband von [[SHIPDESIGN_PLANET_INVASION]]. [[BLD_SHIPYARD_BASE_REQUIRED]]. SD_TROOP_SHIP Truppentransporter SD_TROOP_SHIP_DESC -Transportiert eine Brigade [[metertype METER_TROOPS]] mit Ausrüstung, welcher zur Einnahme eines Planeten genutzt werden können. +Transportiert drei Verbände von [[SHIPDESIGN_PLANET_INVASION]]. [[BLD_SHIPYARD_BASE_REQUIRED]]. SD_DRAGON_TOOTH Drachenzahn SD_DRAGON_TOOTH_DESC -Alter Schiffsentwurf mit fortgeschrittener Bewaffnung und Verteidigungssysteme. +Antiker Schiffsentwurf mit fortschrittlicher Bewaffnung und Verteidigungssystemen. [[BLD_SHIPYARD_BASE_REQUIRED]] ## @@ -412,43 +536,43 @@ SM_KRILL_1 Kleiner Krillschwarm SM_KRILL_1_DESC -Raumkrill sind kleine, insektenähnliche Organismen, welche sich weit entfernt von jedem Gravitationsfeld von Staub und Felsen ernähren. Obwohl als Individuum einfach, kommuniziert Krill durch kohärentes Licht, was der Schwarm zur Koordination und zur Berechnung von Flugbahnen nutzt. Obwohl sie in der Regel nicht aggressiv sind, werden sie durch ihre Zahl eine Gefahr für die Navigation der Raumfahrt und können dadurch den Nachschub blockieren. Die hohe Anzahl von Ressourcen mit niedrigem Gravitationsfeld innerhalb eines Asteroidengürtels bieten Krill perfekte Bedingungen, um sich schnell zu vermehren. +[[SM_KRILL_MACRO_1]]. Obwohl sie in der Regel nicht aggressiv sind, werden sie durch ihre Zahl eine Gefahr für die Navigation der Raumfahrt und können dadurch den Nachschub blockieren. [[SM_KRILL_MACRO_2]] SM_KRILL_2 Mittlerer Krillschwarm SM_KRILL_2_DESC -Raumkrill sind kleine, insektenähnliche Organismen, welche sich weit entfernt von jedem Gravitationsfeld von Staub und Felsen ernähren. Obwohl als Individuum einfach, kommuniziert Krill durch kohärentes Licht, was der Schwarm zur Koordination und zur Berechnung von Flugbahnen nutzt. Obwohl sie in der Regel nicht aggressiv sind, werden sie durch ihre Zahl eine Gefahr für die Navigation der Raumfahrt und können dadurch den Nachschub blockieren. Die hohe Anzahl von Ressourcen mit niedrigem Gravitationsfeld innerhalb eines Asteroidengürtels bieten Krill perfekte Bedingungen, um sich schnell zu vermehren. +[[SM_KRILL_MACRO_1]]. Obwohl sie in der Regel nicht aggressiv sind, werden sie durch ihre Zahl eine Gefahr für die Navigation der Raumfahrt und können dadurch den Nachschub blockieren. [[SM_KRILL_MACRO_2]] SM_KRILL_3 Großer Krillschwarm SM_KRILL_3_DESC -Raumkrill sind kleine, insektenähnliche Organismen, welche sich weit entfernt von jedem Gravitationsfeld von Staub und Felsen ernähren. Obwohl als Individuum einfach, kommuniziert Krill durch kohärentes Licht, was der Schwarm zur Koordination und zur Berechnung von Flugbahnen nutzt. Wenn ihre Anzahl hoch genug ist, beginnen sie ein aggressives Verhalten gegenüber Schiffen zu zeigen. Die hohe Anzahl von Ressourcen mit niedrigem Gravitationsfeld innerhalb eines Asteroidengürtels bieten Krill perfekte Bedingungen, um sich schnell zu vermehren. +[[SM_KRILL_MACRO_1]]. Wenn ihre Anzahl hoch genug ist, beginnen sie ein aggressives Verhalten gegenüber Schiffen zu zeigen. [[SM_KRILL_MACRO_2]] SM_KRILL_4 Krillplage SM_KRILL_4_DESC -Raumkrill sind kleine, insektenähnliche Organismen, welche sich weit entfernt von jedem Gravitationsfeld von Staub und Felsen ernähren. Obwohl als Individuum einfach, kommuniziert Krill durch kohärentes Licht, was der Schwarm zur Koordination und zur Berechnung von Flugbahnen nutzt. Das Verhalten der des Krill ändert sich radikal, sobald ein kritischer Bestand von ungefähr 10 Millionen Individuen überschritten wurde. Der normalerweise scheue Krill attackiert nun Schiffe und orbitale Strukturen. Die hohe Anzahl von Ressourcen mit niedrigem Gravitationsfeld innerhalb eines Asteroidengürtels bieten Krill perfekte Bedingungen, um sich schnell zu vermehren. +[[SM_KRILL_MACRO_1]]. Das Verhalten der des Krill ändert sich radikal, sobald ein kritischer Bestand von ungefähr 10 Millionen Individuen überschritten wurde. Die normalerweise scheuen Krill attackieren nun Schiffe und orbitale Strukturen. [[SM_KRILL_MACRO_2]] SM_TREE Dysonwald SM_TREE_DESC -Der Dysonwald setzt sich aus vielerlei im Weltraum entwickelten "Bäumen" mit dichtem Astwerk zusammen. Die Bäume ordnen sich in einem geeigneten Abstand als Hülle um ihren Wirtsstern an. Dysonwälder sind eine Gefahr für die Raumnavigation. Sie dehnen sich aus und es wird schwierig sie mit fortschreitendem Alter auszurotten. Von Zeit zu Zeit senden sie Samen aus um andere Sterne zu besiedeln. +Der [[SM_TREE]] setzt sich aus vielerlei im Weltraum entwickelten "Bäumen" mit dichtem Astwerk zusammen. Diese Bäume ordnen sich in einem geeigneten Abstand als Hülle um ihren Wirtsstern an. Dysonwälder sind eine Gefahr für die Raumnavigation. Mit fortschreitendem Alter dehnen sie sich aus und es wird schwierig, sie auszurotten. Von Zeit zu Zeit senden sie Samen aus, um andere Sterne zu besiedeln. Diese Samen - [[predefinedshipdesign SM_FLOATER]] genannt - sind nur schwer zu orten. SM_FLOATER Schwimmer SM_FLOATER_DESC -Gasgefüllter knollartiger Beutel, der im Weltraum umhertreibt. +Gasgefüllter knollartiger Beutel, der im Weltraum umhertreibt. Von einem [[predefinedshipdesign SM_TREE]] stammend, wird dieser Samen in einem anderen System zu einem neuen Wald heranwachsen. Es ist ratsam, den [[SM_FLOATER]] vorher zu vernichten; allerdings sind die kleinen Samen ohne [[tech SPY_DETECT_2]]-Technologie kaum zu orten. SM_DRAGON Vakuumdrache SM_DRAGON_DESC -Schreckliches, riesiges Monster, das im Raum auf Beutezug umherschleicht. +Schreckliches, riesiges Monster, das im Raum auf Beutezug umherschleicht und bewohnte Systeme verwüstet. SM_DRONE Drohne @@ -462,128 +586,167 @@ Drohnenfabrik SM_DRONE_FACTORY_DESC In einem lang vergessenen Krieg erbaut, um die vor ewigen Zeiten verschwundenen Erbauer zu schützen, erfüllt die Drohnenfabrik erstaunlicherweise immer noch ihre Funktion, langsam neue Drohnen zu erstellen. +SM_GUARD_0 +Wartungsschiff + +SM_GUARD_0_DESC +Ein unbemanntes Schiff der Vorläufer, das die Sternenstraßen in diesem System bewacht. Nur leicht bewaffnet, aber dennoch aggressiv. + SM_GUARD_1 Wache SM_GUARD_1_DESC -Wache bewachen lässige Kleinigkeiten. +Ein kleines unbemanntes Wachschiff, [[SM_GUARD_MACRO]]. SM_GUARD_2 Wächter SM_GUARD_2_DESC -Wächter bewachen lässiges Zeug. +Ein unbemanntes Wachschiff, [[SM_GUARD_MACRO]]. SM_GUARD_3 Aufseher SM_GUARD_3_DESC -Aufseher bewachen wichtiges Zeug. +Ein starkes unbemanntes Wachschiff, [[SM_GUARD_MACRO]]. SM_KRAKEN_1 Krakenlarve SM_KRAKEN_1_DESC -In ihrer Larvenform ist der Kraken eine vorsichtige und harmlose Art der im Raum gebornen Fauna. Seine natürliche Beue sind Raumkrillschwärme, welche sie damit reguliert. Aber eine gut genährte Krakenlarve kann sich in eine größere und gefährlichere erwachsene Form weiterentwickeln. +'''In ihrer Larvenform ist der Kraken eine vorsichtige und harmlose Art der im Raum gebornen Fauna. Seine natürliche Beute sind Raumkrillschwärme. Eine gut genährte Krakenlarve kann zu einer größeren und gefährlicheren erwachsenen Form heranwachsen. + +[[SM_KRAKEN_ENVIRONMENT]]''' SM_KRAKEN_2_DESC -Ein gewaltiges, mittelschweres Weltraummonster. +'''Ein mächtiges, mittelschweres Weltraummonster, dass sich von Krillschwärmen ernährt. Ein gut genährter Kraken zu einer noch größeren, gefährlicheren Form heranwachsen. + +[[SM_KRAKEN_ENVIRONMENT]]''' SM_KRAKEN_3 Großer Kraken SM_KRAKEN_3_DESC -Ein gewaltiges, schweres Weltraummonster. +'''Ein gewaltiges und zähes Weltraummonster. + +[[SM_KRAKEN_ENVIRONMENT]]''' + +SM_WHITE_KRAKEN +Weißer Kraken + +SM_WHITE_KRAKEN_DESC +Ein weiß gefärbter prähistorischer Vorfahr des Krakenmonsters. SM_BLACK_KRAKEN Schwarzer Kraken SM_BLACK_KRAKEN_DESC -Ein mächtiges, unnatürlich wirkendes Weltraummonster. +'''Ein mächtiges, unnatürlich wirkendes Weltraummonster. + +Schwarze Kraken sind biotechnisch hergestellte Weltraummonster. Sie sind außerordentlich zäh, schwer bewaffnet und haben hohe [[metertype METER_STEALTH]]. Eine mächtige Flotte und hohe [[encyclopedia DETECTION_TITLE]] sind nötig, um sie aufzuspüren. Schwarze Kraken jagen nach Planeten mit sichtbaren Gebäuden und greifen dort die [[metertype METER_POPULATION]] an.''' SM_SNOWFLAKE_1 -Kleiner Schneeflocke +Kleine Schneeflocke SM_SNOWFLAKE_1_DESC -Ein gewaltiges, leichtgewichtiges Weltraummonster. +'''Ein kleines, harmloses Weltraummonster mit hervorragendem Sehvermögen. + +[[SM_SNOWFLAKE_ENVIRONMENT]]''' SM_SNOWFLAKE_2 Schneeflocke SM_SNOWFLAKE_2_DESC -Ein gewaltiges, leichtgewichtiges Weltraummonster. +'''Ein leichtgewichtiges Weltraummonster mit hervorragendem Sehvermögen. + +[[SM_SNOWFLAKE_ENVIRONMENT]]''' SM_SNOWFLAKE_3 -Großen Schneeflocke +Große Schneeflocke SM_SNOWFLAKE_3_DESC -Ein gewaltiges, leichtgewichtiges Weltraummonster. +'''Ein gefährliches leichtgewichtiges Weltraummonster. + +[[SM_SNOWFLAKE_ENVIRONMENT]]''' SM_PSIONIC_SNOWFLAKE Psionische Schneeflocke SM_PSIONIC_SNOWFLAKE_DESC -Ein Monster, welches die Kraft besitzt den Verstand von organischen Wesen zu neutralisieren. +'''Ein Monster, welches die Kraft besitzt, den Verstand von organischen Wesen zu neutralisieren. + +Psionische Schneeflocken sind biotechnisch hergestellte, stark bewaffnete Monster, die die Kontrolle über Schiffe mit [[encyclopedia ORGANIC_SPECIES_TITLE]]-Besatzung an sich reißen können. Eine mächtige Flotte ist nötig, um sie zu bekämpfen. Psionische Schneeflocken jagen nach feindlichen Raumschiffen, um sie anzugreifen oder zu übernehmen, können aber auch die [[metertype METER_POPULATION]] von Planeten direkt angreifen.''' SM_JUGGERNAUT_1 Kleiner Koloss SM_JUGGERNAUT_1_DESC -Ein gewaltiges, schwergewichtiges Weltraummonster. +'''Ein schwergewichtiges Weltraummonster mit einem schwachen Schutzschild. + +[[SM_JUGGERNAUT_ENVIRONMENT]]''' SM_JUGGERNAUT_2 Koloss SM_JUGGERNAUT_2_DESC -Ein gewaltiges, schwergewichtiges Weltraummonster. +'''Ein schwergewichtiges Weltraummonster mit einem guten Schutzschild. + +[[SM_JUGGERNAUT_ENVIRONMENT]]''' SM_JUGGERNAUT_3 Großer Koloss SM_JUGGERNAUT_3_DESC -Ein gewaltiges, schwergewichtiges Weltraummonster. +'''Ein schwergewichtiges Weltraummonster mit einem kräftigen Schutzschild. + +[[SM_JUGGERNAUT_ENVIRONMENT]]''' SM_BLOATED_JUGGERNAUT Aufgeblähter Koloss SM_BLOATED_JUGGERNAUT_DESC -Ein massives, aber krankhaft aussehendes schwergewichtes Weltraummonster. +'''Ein massives, aber krankhaft aussehendes Weltraummonster. + +Aufgeblähte Kolosse sind biotechnisch hergestellte Weltraummonster. Sie sind außerordentlich zäh, schwer bewaffnet und haben hohe [[metertype METER_STEALTH]]. Eine mächtige Flotte und hohe [[encyclopedia DETECTION_TITLE]] sind nötig, um sie aufzuspüren. Aufgeblähte Kolosse jagen nach Planeten mit sichtbaren Gebäuden und greifen dort die [[metertype METER_POPULATION]] an.''' SM_CLOUD Raumwolke SM_CLOUD_DESC -Eine nebelartige Kreatur, welche [[metertype METER_STEALTH]] zu zufälligen Planeten hinzufügt. +Eine nebelartige Kreatur, welche zufälligen Planeten [[metertype METER_STEALTH]] verleiht. SM_ASH Weltraumvulkan SM_ASH_DESC -Eine nebelartige Kreatur, welche [[metertype METER_STEALTH]] zu zufälligen Planeten hinzufügt. +Eine nebelartige Kreatur, welche zufälligen Planeten [[metertype METER_STEALTH]] verleiht. SM_DIM Dimensionswanderer SM_DIM_DESC -Eine nebelartige Kreatur, welche [[metertype METER_STEALTH]] zu zufälligen Planeten hinzufügt. +Eine nebelartige Kreatur, welche zufälligen Planeten [[metertype METER_STEALTH]] verleiht. + +SM_VOID +Moloch der Leere SM_VOID_DESC -Eine nebelartige Kreatur, welche [[metertype METER_STEALTH]] zu zufälligen Planeten hinzufügt. +Eine nebelartige Kreatur, welche zufälligen Planeten [[metertype METER_STEALTH]] verleiht. SM_SNAIL Asteroidenschnecke SM_SNAIL_DESC -Die Gehäuse der Asteroidenschnecken ähneln aufgrund ihrer auf Mineralien basiernden Ernährung stark normalen Asteroiden, was das Aufspüren erheblich erschwert. +Ein ängstliches Monster, dass sich von Mineralien ernährt. Die resultierende Ähnlichkeit zu gewöhnlichen Asteroiden verleiht der [[SM_SNAIL]] erhöhte [[metertype METER_STEALTH]] in Asteroidengürteln. SM_DAMPENING_CLOUD Dämpfende Wolke SM_DAMPENING_CLOUD_DESC -Eine kosmische Wolke aus hochenergetischen elektrischen Partikeln, welche eine Art von Empfindung erlangt hat. Sie wird von Energiefeldern angezogen und entlädt jedes größere Energiepotenzial, was dazu führt das ganze Raumschiffflotten innerhalb von Sekunden ihres Treibstoffes beraubt werden können. +Eine kosmische Wolke aus hochenergetischen Partikeln mit einer Art Empfindungsvermögen. Sie vermeidet die Energiesignaturen bevölkerter Kolonien, wird von potentiellen Energiereserven hingegen angezogen und stiehlt nahen Fahrzeugen den [[metertype METER_FUEL]]. SM_ACIREMA_GUARD -Acirema Wächterschiff +Acirema-Wächterschiff SM_ACIREMA_GUARD_DESC Ein unbemanntes bewaffnetes Schiff, welches von den Acirema zur Bewachung des Systems gebaut wurde. @@ -592,13 +755,17 @@ SM_EXP_OUTPOST Experiment Null SM_EXP_OUTPOST_DESC -Ein Außenpostenschiff, welches von den Experimentors gebaut wurde um experimentelle Asurüstung zwischen Galaxien zu transportieren. +'''Ein uraltes Werk der Experimentoren, welches [[encyclopedia DAMAGE_TITLE]] und [[metertype METER_SHIELD]] reduziert. + +[[SM_EXP_OUTPOST]] bewacht den Außenposten der Experimentoren in dieser Galaxie. Anstatt direkt anzugreifen, füllt es das gesamte System mit Billiarden winziger Einheiten, die Schilde und Waffen schwächen. Nur die beste Ausrüstung lässt sich unter diesen Umständen noch effektiv einsetzen.''' SM_COSMIC_DRAGON Kosmischer Drache SM_COSMIC_DRAGON_DESC -Ein furchteregendes Weltraummonster mit der Macht ganze Sternensysteme zu zerstören. +'''Ein furchteregendes Weltraummonster mit der Macht, ganze Sternensysteme zu zerstören. + +Kosmische Drachen sind biotechnisch hergestellte Weltraummonster. Sie sind außerordentlich zäh, schwer bewaffnet und haben hohe [[metertype METER_STEALTH]]. Eine mächtige Flotte und hohe [[encyclopedia DETECTION_TITLE]] sind nötig, um sie aufzuspüren. Kosmische Drachen jagen nach bevölkerten Planeten und können ganze Sternensysteme vaporisieren.''' ## @@ -724,9 +891,6 @@ COMMAND_LINE_USAGE COMMAND_LINE_DEFAULT '''Standard: ''' -OPTIONS_DB_HELP -Diese Hilfemeldung ausgeben. - OPTIONS_DB_GENERATE_CONFIG_XML Übernimmt die Standardeinstellungen, die Einstellungen einer bestehenden config.xml Datei und jene aus der Kommandozeile um eine config.xml Datei zu erzeugen. Dies überschreibt die momentane config.xml Datei, falls vorhanden. @@ -1043,21 +1207,6 @@ OPTIONS_DB_GAMESETUP_PLANET_DENSITY '''Anzahl der Planeten je System, die in der Galaxie erzeugt werden soll. ''' -OPTIONS_DB_GAMESETUP_STARLANE_FREQUENCY -Anzahl der Sternstraßen, die in der Galaxie erzeugt werden sollen. - -OPTIONS_DB_GAMESETUP_SPECIALS_FREQUENCY -Wie häufig Besonderheiten in der Galaxie auftreten sollen. - -OPTIONS_DB_GAMESETUP_MONSTER_FREQUENCY -Wie häufig Monster in der Galaxie auftreten sollen. - -OPTIONS_DB_GAMESETUP_NATIVE_FREQUENCY -Wie häufig Eingeborene in der Galaxie auftreten sollen. - -OPTIONS_DB_GAMESETUP_AI_MAX_AGGRESSION -Die maximale Aggressionsstufe für KI Gegner. - OPTIONS_DB_GAMESETUP_EMPIRE_NAME Der Name Ihres Imperiums. @@ -1072,9 +1221,6 @@ OPTIONS_DB_GAMESETUP_STARTING_SPECIES_NAME Es hat keine Wirkung auf Planeten Ihres Imperiums die durch andere Spezies Kolonisiert worden sind.''' -OPTIONS_DB_GAMESETUP_NUM_AI_PLAYERS -Die Anzahl KI Gegner im Spiel. - OPTIONS_DB_UI_TECH_LAYOUT_HORZ_SPACING Horizontaler Abstand zwischen Technologien im Technologiebildschirm, gemessen in Vielfachen der Breite einer einzelnen Theorie Technologie. @@ -1093,12 +1239,6 @@ Setzt den Grad ab dem Meldungen im Log aufgezeichnet werden. (Grade in abfallend OPTIONS_DB_STRINGTABLE_FILENAME Setzt die sprachspezifischen Übersetzungsdateien. -OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER -Wenn gesetzt, wird während eines Einzelspielerspiels automatisch gespeichert. - -OPTIONS_DB_AUTOSAVE_MULTIPLAYER -Wenn gesetzt, wird während eines Mehrspielerspiels automatisch gespeichert. - OPTIONS_DB_AUTOSAVE_TURNS Setzt die Anzahl der Runden, welche zwischen automatischen Speicherungen liegen sollen. @@ -1114,18 +1254,12 @@ Die Lautstärke (0 bis 255) in welcher Musik gespielt werden soll. OPTIONS_DB_QUICKSTART Beginnt ein neues Schnellstartspiel. Das Hauptmenü wird umgangen. -OPTIONS_DB_CHECKED_GL_VERSION -Speichert ob die OpenGL Version des Systems überprüft wurde. Wenn nicht, werden einige Zeichenoptionen abhängig von der GL Version angepasst, nachdem die Version überprüft wurde. - OPTIONS_DB_RESET_FSSIZE Speichert ob der Vollbildmodus zu den gespeicherten Wert zurückgesetzt werden soll. Wenn nicht gesetzt, wird die gespeicherte Auflösung genutzt, ansonsten wird die höchste Auflösung genutzt, welche unterstützt wird. OPTIONS_DB_DUMP_EFFECTS_GROUPS_DESC Entscheidet ob ein Abbild der Effektgruppen in die Beschreibung von Technologien, Gebäuden und Schiffsteilen eingefügt werden. -OPTIONS_DB_VERBOSE_LOGGING_DESC -Schaltet die ausführliche Protokollierung der Inhalte des Universums und Effektberechnung. - OPTIONS_DB_VERBOSE_SITREP_DESC Schaltet das Einfügen der Situationsberichte mit Fehlerausgabe ein. @@ -1561,12 +1695,6 @@ Zeigt das Planeten Seitenfenster OPTIONS_MISC_UI Verschiedene UI-Einstellungen -OPTIONS_SINGLEPLAYER -Einzelspieler - -OPTIONS_MULTIPLAYER -Mehrspieler - OPTIONS_AUTOSAVE_TURNS_BETWEEN Runden zwischen dem automatischem Speichern @@ -1961,31 +2089,9 @@ Nachrichten MAP_PRODUCTION_TITLE Produktion -MAP_PROD_WASTED_TITLE -Ungenutzte Produktion - -# %1% number of production points generated. -# %2% number of production points currently not used for production. -MAP_PROD_WASTED_TEXT -'''Gesamte Produktion : %1% -Verschwendete Produkt. : %2% - -Hier klicken um das Produktionsmenü zu öffnen''' - MAP_RESEARCH_TITLE Forschung -MAP_RES_WASTED_TITLE -Ungenutzte Forschung - -# %1% number of research points generated. -# %2% number of research points currently not used for research. -MAP_RES_WASTED_TEXT -'''Gesamte Forschung : %1% -Verschwendete Forsch. : %2% - -Hier klicken um das Forschungsmenü zu öffnen''' - MAP_POPULATION_DISTRIBUTION Volkszählung @@ -2007,12 +2113,6 @@ Handel MAP_TRADE_TEXT Handelsleistung des gesamten Imperiums. (Handel tut nichts) -MAP_FLEET_TITLE -Flottenstärke - -MAP_FLEET_TEXT -Gesamtanzahl Schiffe - ## ## Side panel/System information @@ -2450,7 +2550,7 @@ Krieg erklären PEACE_PROPOSAL Friedensangebot -PEACE_PROPOSAL_CANEL +PEACE_PROPOSAL_CANCEL Friedensangebot annullieren PEACE_ACCEPT @@ -2554,9 +2654,6 @@ RESEARCH_QUEUE_EMPIRE RESEARCH_INFO_RP FP -RESEARCH_QUEUE_PROMPT -Klicke doppelt oder mit Shift auf ein verfügbares Objekt um es zur Forschungswarteschlange hinzufügen. - ## ## Build selector window @@ -2672,12 +2769,6 @@ Neuen Entwurfsnamen eingeben: DESIGN_INVALID Ungültiger Entwurf -DESIGN_OBSOLETE -Veralteter Entwurf - -DESIGN_WND_ADD -Neuen Entwurf hinzufügen - ## ## Statistics @@ -2890,9 +2981,6 @@ CATEGORY_GUIDES CATEGORY_GAME_CONCEPTS *Spielkonzepte* -CATEGORY_USER_INTERFACE -*Benutzeroberfläche* - GROWTH_ARTICLE_SHORT_DESC Wachstum @@ -2934,18 +3022,12 @@ Handel METER_CONSTRUCTION_VALUE_LABEL Infrastruktur -METER_CONSTRUCTION_VALUE_DESC -Die Bezeichnung Infrastruktur bezieht sich auf sämtliche Netzwerke, die einen kolonialisierten Planeten betreffen: Energie, Transport, Telekommunikation und soziale Einrichtungen für die Bewohner. - METER_HAPPINESS_VALUE_LABEL Zufriedenheit METER_FUEL_VALUE_LABEL Treibstoff -METER_FUEL_VALUE_DESC -Treibstoff erlaubt Schiffe entlang der Sternstraßen zu reisen. Für jede Sternstraße verbraucht ein Schiff 1 Einheit Treibstoff. Treibstoff wird bei keiner anderen Aktion verbraucht. Die [[metertype METER_SUPPLY]] tanken ein Schiff vollständig auf. Wenn ein Schiff jenseits einer verbündeten Sternstraße reist, wird der Treibstoff langsam wiederhergestellt, sobald sich das Schiff nicht mehr auf einer Sternstraße befindet. - METER_SHIELD_VALUE_LABEL Schild @@ -2972,22 +3054,9 @@ Die Truppenstärke auf einem Planeten spiegelt die Schlagkraft der dort stationi METER_DETECTION_VALUE_LABEL Erkennungsreichweite -METER_DETECTION_VALUE_DESC -'''Die Erkennungsreichweite sagt aus, wie weit die Sensoren reichen. Ein Imperium kann nur Objekte erkennen, deren [[metertype METER_STEALTH]] kleiner oder gleich der [[encyclopedia DETECTION_TITLE]] sind und innerhalb der Erkennungsreichweite eines eigenen Raumschiffes oder Planeten liegt. - -Raumschiffe und Planeten haben einen Erkennungsreichweitenanzeiger. Es gibt im Optionsmenü die Möglichkeit, die Erkennungsreichweite für die Galaxiekarte anzuzeigen oder auszublenden.''' - METER_STEALTH_VALUE_LABEL Tarnung -METER_DETECTION_STRENGTH_VALUE_LABEL -Erkennung Stärke - -METER_DETECTION_STRENGTH_VALUE_DESC -'''Der Begriff Erkennung Stärke stellt das technologische Niveau der Sensoren eines Imperiums dar. Ein Imperium kann nur Objekte sehen, die einen [[metertype METER_STEALTH]]s-Wert haben, der kleiner oder gleich groß ist, wie die, seiner eigenen Erkennung Stärke und sich innerhalb der [[metertype METER_DETECTION]] eines Raumschiffes oder Planeten befindet. - -Imperien haben eines Erkennungsstärke Mass.''' - PEACE_TITLE Frieden @@ -3027,16 +3096,6 @@ Roboterspezies sind intelligente Maschinen. Die maximale Bevölkerung dieser Spe PHOTOTROPHIC_SPECIES_TITLE Phototropher Stoffwechsel -PHOTOTROPHIC_SPECIES_TEXT -'''Phototrophe Spezies leben hauptsächlich oder vollständig von Sonnelicht. Die maximalen Bevölkerungen dieser Spezies sind stark beeinflusst von der Helligkeit des Sternes im jeweiligen System - umso heller, desto besser. Es gibt keine speziellen Wachstumsboni. - -Population changes: -Blue Star: Tiny +3 , Small +6 , Medium +9 , Large +12 , Huge +15. -White Star: Tiny +1.5 , Small +3 , Medium +4.5 , Large +6 , Huge +7.5. -Yellow Star, Orange Star: No changes. -Red Star, Neutron Star: Tiny -1 , Small -2 , Medium -3 , Large -4 , Huge -5. -Black Hole, No Star: Tiny -10 , Small -20 , Medium -30 , Large -40 , Huge -50.''' - TELEPATHIC_TITLE Telepathie @@ -3371,17 +3430,17 @@ In %system%: Ein %shipdesign% wurde beschädigt. SITREP_UNOWNED_SHIP_DAMAGED_AT_SYSTEM_LABEL Fremdes Schiff beschädigt -SITREP_PLANET_BOMBARDED_AT_SYSTEM -Der Planet %planet% im System %system% vom Imperium %empire% ist bombardiert worden. +SITREP_PLANET_ATTACKED_AT_SYSTEM +Der Planet %planet% im System %system% vom Imperium %empire% ist angegriffen worden. -SITREP_PLANET_BOMBARDED_AT_SYSTEM_LABEL -Imperiumsplanet bombardiert +SITREP_PLANET_ATTACKED_AT_SYSTEM_LABEL +Imperiumsplanet angegriffen -SITREP_UNOWNED_PLANET_BOMBARDED_AT_SYSTEM -Der Planet %planet% von %system% ist bombardiert worden. +SITREP_UNOWNED_PLANET_ATTACKED_AT_SYSTEM +Der Planet %planet% von %system% ist angegriffen worden. -SITREP_UNOWNED_PLANET_BOMBARDED_AT_SYSTEM_LABEL -Unbekannter Planet bombardiert +SITREP_UNOWNED_PLANET_ATTACKED_AT_SYSTEM_LABEL +Unbekannter Planet angegriffen SITREP_GROUND_BATTLE Ein Bodenangriff fand auf %planet% statt. @@ -3495,7 +3554,7 @@ EFFECT_DERELICT_MAP_LABEL Karte aus einem Wrack EFFECT_DERELICT_FUEL -Schiffe sind durch restlichen Treibstoff von verlassenen Wracks auf %system% aufgetankt worden. +Schiffe sind mit dem restlichen Treibstoff verlassener Wracks auf %system% aufgetankt worden. EFFECT_DERELICT_FUEL_LABEL Treibstoff aus Wrack @@ -3624,181 +3683,367 @@ Bevorzugt erdähnliche Planeten. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' -#SP_SCYLIOR -#Scylior - -SP_SCYLIOR_GAMEPLAY_DESC -'''Besitzt drei Tentakel, aquatischer Nautilus. +SP_OURBOOLS_GAMEPLAY_DESC +'''Riesige Blinde Schwimmer die, die Schwerkraft "sehen" können. Bevorzugt ozeanische Planeten. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' -#SP_GYSACHE -#Gysache -#SP_CHATO -#Chato +## +## Specials +## -#SP_TRITH -#Trith +MODERATE_TECH_NATIVES_SPECIAL +Fortschrittliche Eingeborene -#SP_HAPPY -#Happybirthday +MODERATE_TECH_NATIVES_SPECIAL_DESC +Die Ureinwohner dieses Planeten sind technologisch recht weit entwickelt, weshalb sie Angriffe und Invasionen besser abwehren können, solange sie noch keinem Imperium angehören. -#SP_HHHOH -#Hhhoh +HIGH_TECH_NATIVES_SPECIAL +High Tech-Eingeborene -#SP_EAXAW -#Eaxaw +HIGH_TECH_NATIVES_SPECIAL_DESC +Die Ureinwohner dieses Planeten sind technologisch sehr hoch entwickelt, weshalb sie Angriffe und Invasionen weit besser abwehren können, solange sie noch keinem Imperium angehören. -#SP_DERTHREAN -#Derthrean +#GAIA_SPECIAL +#Gaia -#SP_LAENFA -#Laenfa +GAIA_SPECIAL_DESC +'''Terraformiert diesen Planeten und erhöht anschließend [[metertype METER_TARGET_POPULATION]], entsprechend der Planetengröße: +• [[SZ_TINY]] (+3) +• [[SZ_SMALL]] (+6) +• [[SZ_MEDIUM]] (+9) +• [[SZ_LARGE]] (+12) +• [[SZ_HUGE]] (+15) +Erhöht außerdem [[metertype METER_TARGET_HAPPINESS]] um 5. -#SP_TAEGHIRUS -#Tae Ghirus +Dieser Planet wurde in einen lebenden Organismus umgewandelt, welcher sich den Vorlieben seiner Bewohner anpassen kann. Wenn dieser Planet eine [[PE_GOOD]]e [[encyclopedia ENVIRONMENT_TITLE]] hat, erhält er einen hohen Bonus auf [[METER_POPULATION]]. Andernfalls terraformiert er sich selbst stufenweise, um sich seinen Bewohnern anzupassen. (Für [[species SP_EXOBOT]]s gibt es keine [[encyclopedia ENVIRONMENT_TITLE]], der besser ist als [[PE_ADEQUATE]].)''' -#SP_MUURSH -#Mu Ursh +WORLDTREE_SPECIAL +Weltenbaum -#SP_GEORGE -#George +WORLDTREE_SPECIAL_DESC +'''Erhöht [[metertype METER_SUPPLY]] um 1, [[metertype METER_DETECTION]] um 10, [[metertype METER_TARGET_POPULATION]] um 1 und [[metertype METER_TARGET_HAPPINESS]] um 5. Erhöht [[metertype METER_TARGET_HAPPINESS]] auf allen anderen Planeten im selben Imperium um 1. -#SP_CRAY -#Cray +Ein riesiger Baum, dessen Krone aus der Atmosphäre des Planeten herausragt. Wissenschaftler vermuten, dass er vor langer Zeit von den Weltenplegern gepflanzt wurde.''' -#SP_ETTY -#Etty +ANCIENT_RUINS_DEPLETED_SPECIAL +Uralte Ruinen (Ausgegraben) -#SP_CYNOS -#Cynos +ANCIENT_RUINS_DEPLETED_SPECIAL_DESC +'''Erhöht [[metertype METER_TARGET_RESEARCH]] um den Wert der [[metertype METER_POPULATION]], solange der Fokus des Planeten auf Forschung steht. -#SP_PHINNERT -#Phinnert +Diesen Planeten zieren die Ruinen einer vergessenen, hochentwickelten Kultur. Die wertvollsten Funde wurden bereits gesichert.''' -#SP_EGASSEM -#Egassem +ANCIENT_RUINS_SPECIAL +Uralte Ruinen -#SP_SSLITH -#Sslith +ANCIENT_RUINS_SPECIAL_DESCRIPTION +'''Erhöht [[metertype METER_TARGET_RESEARCH]] um den Wert der [[metertype METER_POPULATION]], solange der Fokus des Planeten auf Forschung steht. -#SP_FIFTYSEVEN -#Fifty-seven +Diesen Planeten zieren die Ruinen einer vergessenen, hochentwickelten Kultur. Das erste Imperium mit erforschter [[tech LRN_XENOARCH]], das diesen Planeten besitzt, erhält eine forschrittliche Technologie oder entdeckt ein mächtiges Gebäude oder Schiff. Zudem finden sich möglicherweise gut erhaltene Überreste einer ausgestorbenen Spezies.''' -#SP_SETINON -#Setinon +EXTINCT_BANFORO_SPECIAL +Banforo-Überreste -#SP_NYMNMN -#Nymnmn +EXTINCT_BANFORO_SPECIAL_DESC +Auf diesem Planeten wurden gut erhaltene Leichen der ausgestorbenen [[species SP_BANFORO]] entdeckt. Ein [[buildingtype BLD_XENORESURRECTION_LAB]] hier ermöglicht den Bau einer [[buildingtype BLD_COL_BANFORO]] auf einem durch [[metertype METER_SUPPLY]] verbundenen Planeten. -#SP_TRENCHERS -#Trenchers +TECH_COL_BANFORO +Banforo-Resequenzierung -#SP_RAAAGH -#Raaagh +TECH_COL_BANFORO_DESC +Ermöglicht die Wiederherstellung der uralten [[species SP_BANFORO]]-Spezies. -#SP_BEIGEGOO -#Beige Goo +EXTINCT_KILANDOW_SPECIAL +Kilandow-Gebeine -#SP_SILEXIAN -#Silexian +EXTINCT_KILANDOW_SPECIAL_DESC +Auf diesem Planeten wurden gut erhaltene Leichen der ausgestorbenen [[species SP_KILANDOW]] entdeckt. Ein [[buildingtype BLD_XENORESURRECTION_LAB]] hier ermöglicht den Bau einer [[buildingtype BLD_COL_KILANDOW]] auf einem durch [[metertype METER_SUPPLY]] verbundenen Planeten. -#SP_KOBUNTURA -#Kobuntura +TECH_COL_KILANDOW +Kilandow-Resequenzierung -#SP_UGMORS -#Ugmors +TECH_COL_KILANDOW_DESC +Ermöglicht die Wiederherstellung der uralten [[species SP_KILANDOW]]-Spezies. -#SP_GISGUFGTHRIM -#Gis Guf Gthrim +EXTINCT_MISIORLA_SPECIAL +Misiorla-Fossilien -#SP_HIDDENGARDENER -#Hidden Gardener +EXTINCT_MISIORLA_SPECIAL_DESC +Auf diesem Planeten wurden gut erhaltene Leichen der ausgestorbenen [[species SP_MISIORLA]] entdeckt. Ein [[buildingtype BLD_XENORESURRECTION_LAB]] hier ermöglicht den Bau einer [[buildingtype BLD_COL_MISIORLA]] auf einem durch [[metertype METER_SUPPLY]] verbundenen Planeten. -#SP_ABADDONI -#Abaddoni +TECH_COL_MISIORLA +Misiorla-Resequenzierung -#SP_ACIREMA -#Acirema +TECH_COL_MISIORLA_DESC +Ermöglicht die Wiederherstellung der uralten [[species SP_MISIORLA]]-Spezies. -#SP_OURBOOLS -#Ourbools +EXTINCT_UNLOCK_SHORT_DESC +Schaltet eine ausgestorbene Spezies frei. -SP_OURBOOLS_GAMEPLAY_DESC -'''Riesige Blinde Schwimmer die, die Schwerkraft "sehen" können. -Bevorzugt ozeanische Planeten. -[[encyclopedia ORGANIC_SPECIES_TITLE]] -''' +PANOPTICON_SPECIAL +Panopticon-Komplex -#SP_VOLP -#Volp-Uglush +PANOPTICON_SPECIAL_DESC +'''Erhöht die [[encyclopedia DETECTION_TITLE]] des Imperiums um 10 und die [[metertype METER_DETECTION]] des Planeten um 75. -#SP_FURTHEST -#Furthest +Tief unter der Oberfläche dieses Planeten verborgen liegt das Panopticon, ein mächtiges Relikt einer unbekannten Zivilisation. Es kann die Daten der Ortungsgeräte eines ganzen Imperiums auswerten und selbst den kleinsten Details und lückenhaftesten Daten wertvolle Informationen abgewinnen.''' -#SP_BANFORO -#Banforo +FORTRESS_SPECIAL +Festung -#SP_KILANDOW -#Kilandow +FORTRESS_SPECIAL_DESC +'''Erhöht [[metertype METER_SHIELD]] um 100, [[metertype METER_DEFENSE]] um 30, [[metertype METER_TROOPS]] um 30 und [[metertype METER_DETECTION]] um 30. Mit jedem Zug erholen sich [[METER_SHIELD]] um 5 und [[METER_DEFENSE]] um 1. Minen reduzieren die [[metertype METER_STRUCTURE]] feindlicher Schiffe im System um zusätzliche 3 Punkte pro Zug. In jedem Zug besteht eine Wahrscheinlichkeit von 5%, dass eine [[SM_GUARD_1]] erzeugt wird, die das System beschützt, sofern noch keine vorhanden ist. -#SP_MISIORLA -#Misiorla +Dieser Planet wurde von den Vorläufern auf alle Angriffe vorbereitet. Er ist vollgepackt mit planetaren Schilden, Waffen und Überwachungssensoren, mit denen er jeder Belagerung standhalten kann. Alle Anlagen sind noch immer voll funktionstüchtig und überraschend nutzerfreundlich.''' -#SP_EXOBOT -#Exobot +ECCENTRIC_ORBIT_SPECIAL +Exzentrischer Orbit -#SP_EXPERIMENTOR -#Experimentor +ECCENTRIC_ORBIT_SPECIAL_DESC +'''Erhöht [[metertype METER_TARGET_RESEARCH]] um 3, aber reduziert [[metertype METER_SUPPLY]] um 2. -#SP_SUPER_TEST -#Super Testers +Die Umlaufbahn dieses Planeten ist sehr exzentrisch. Sein Abstand zu seinem Stern, und damit die Stärke der Bestrahlung, variiert über das Jahr hinweg deutlich. Die wechselhaften Verhältnisse erschweren jegliche [[METER_SUPPLY]] durch den Planeten, sind für Forschungsprojekte allerdings hilfreich.''' +TIDAL_LOCK_SPECIAL +Gebundene Rotation -## -## Specials -## +TIDAL_LOCK_SPECIAL_DESC +'''Erhöht [[metertype METER_TARGET_INDUSTRY]] um 0,2 pro [[metertype METER_POPULATION]], solange der Fokus des Planeten auf [[metertype METER_INDUSTRY]] steht. Reduziert [[metertype METER_TARGET_POPULATION]] (unabhängig vom Fokus) je nach Planetengröße: +• [[SZ_TINY]] (-1) +• [[SZ_SMALL]] (-2) +• [[SZ_MEDIUM]] (-3) +• [[SZ_LARGE]] (-4) +• [[SZ_HUGE]] (-5) -#MODERATE_TECH_NATIVES_SPECIAL -#Moderate Tech Natives +Dieser Planet steht mit seinem Stern in gebundener Rotation. Auf einer Seite ist dauerhaft Tag, auf der anderen herrscht ewige Nacht. Dies schränkt das Bevölkerungswachstum ein, aber die stabilen Bedingungen sind der örtlichen Industrie sehr zuträglich.''' -#HIGH_TECH_NATIVES_SPECIAL -#High Tech Natives +TEMPORAL_ANOMALY_SPECIAL +Temporale Anomalie -#GAIA_SPECIAL -#Gaia +TEMPORAL_ANOMALY_SPECIAL_DESC +'''Erhöht [[metertype METER_TARGET_RESEARCH]] um 5 pro [[metertype METER_POPULATION]], solange der Fokus des Planeten auf [[metertype METER_RESEARCH]] steht. Reduziert [[metertype METER_TARGET_POPULATION]] (unabhängig vom Fokus) je nach Planetengröße: +• [[SZ_TINY]] (-5) +• [[SZ_SMALL]] (-10) +• [[SZ_MEDIUM]] (-15) +• [[SZ_LARGE]] (-20) +• [[SZ_HUGE]] (-25) -ANCIENT_RUINS_DEPLETED_SPECIAL -Uralte Ruinen +Der Fluss der Zeit ist auf diesem Planeten gestört. Die scheinbar willkürlichen Änderungen der temporalen Geschwindigkeit sind für Lebensformen so verwirrend, dass ein soziales oder produktives Leben nahezu unmöglich ist. Nur durch gute Vorbereitung und extreme Anpassungsfähigkeit lässt sich hier eine Kolonie errichten. +Dafür ermöglicht diese Anomalie aber auch das Durchführen von Experimenten, die auf normalen Planeten schlichtweg nicht machbar wären.''' -ANCIENT_RUINS_SPECIAL -Uralte Ruinen +RESONANT_MOON_SPECIAL +Resonanzmond -ANCIENT_RUINS_SPECIAL_DESCRIPTION -'''Increases [[metertype METER_RESEARCH]] on this planet by 1 per Population when Focus is set to Research. +COMPUTRONIUM_SPECIAL +Computroniummond -Auf diesem Planet befinden sich die Überreste einer uralten, technologisch forschrittlichen Rasse. Das erster Imperium, welches die Technologie tech [[tech LRN_XENOARCH]] erforscht hat und diesen Planeten in Besitz nimmt erhält eine kostenlose fortgeschrittene Technologie oder entdeckt ein mächtiges Gebäude oder Schiff.''' +COMPUTRONIUM_SPECIAL_DESC +'''Solange dieser Planet auf [[metertype METER_RESEARCH]] fokussiert ist, erhalten alle Planeten im Imperium, die auf [[METER_RESEARCH]] fokussiert sind, 0,2 [[metertype METER_TARGET_RESEARCH]] pro [[metertype METER_POPULATION]]. -FORTRESS_SPECIAL -Festung +Der Mond dieses Planeten ist in Wirklichkeit ein von einer verschwundenen Zivilisation erbauter, gigantischer Supercomputer, der komplexe Berechnungen und Simulationen schneller durchführt, als man sie eingeben kann.''' -ECCENTRIC_ORBIT_SPECIAL -Exzentrischer Orbit +HONEYCOMB_SPECIAL +Honigwabe + +PHILOSOPHER_SPECIAL +Philosophenplanet + +PHILOSOPHER_SPECIAL_DESC +'''Reduziert [[metertype METER_TARGET_CONSTRUCTION]] um 20. Solange dieser Planet unbewohnt ist, erhöht er [[metertype METER_TARGET_RESEARCH]] um 5 auf allen bewohnten Planeten im selben System. + +Der letzte Verbliebene einer planetengroßen Nomadenspezies hat sich im Orbit dieses Systems zur Ruhe gesetzt. Nach tausenden von Lebensjahren hat er den Willen und die Fähigkeit zum Reisen verloren und verbringt seine Zeit nun damit, über die Mysterien des Universums zu sinnieren. Exzentrisch und leicht dement, wie er nun ist, spricht er nur mit jenen, deren Form und Größe seiner Art entsprechen, weshalb Wissenschaftler nur über die Planeten, auf denen sie leben, mit ihm kommunizieren können. Verständlicherweise würde es den [[PHILOSOPHER_SPECIAL]] sehr verstören, kolonisiert zu werden, und Konstruktionen auf seiner Oberfläche wären so schwierig, wie es bei einem lebenden Wesen zu erwarten wäre.''' + +ABANDONED_COLONY_SPECIAL +Verlassene Kolonie + +ABANDONED_COLONY_SPECIAL_DESC +Eine alte, aufgegebene Kolonie. Manche der [[metertype METER_CONSTRUCTION]]en hier könnten noch einen Nutzen haben. + +KRAKEN_NEST_SPECIAL +Krakennest + +KRAKEN_NEST_SPECIAL_DESC +Irgendwann werden hier junge Kraken entstehen. Mit Wissen über [[tech SHP_DOMESTIC_MONSTER]] lassen sie sich zähmen. SNOWFLAKE_NEST_SPECIAL Schneeflockennest +SNOWFLAKE_NEST_SPECIAL_DESC +Irgendwann werden hier junge Schneeflocken entstehen. Mit Wissen über [[tech SHP_DOMESTIC_MONSTER]] lassen sie sich zähmen. + JUGGERNAUT_NEST_SPECIAL -Nest der Kolosse +Kolossnest + +JUGGERNAUT_NEST_SPECIAL_DESC +Irgendwann werden hier junge Kolosse entstehen. Mit Wissen über [[tech SHP_DOMESTIC_MONSTER]] lassen sie sich zähmen. + +KRAKEN_IN_THE_ICE_SPECIAL +Kraken im Eis + +KRAKEN_IN_THE_ICE_SPECIAL_DESC +'''Mit genug Wissen über [[tech SHP_DOMESTIC_MONSTER]] und [[tech LRN_XENOARCH]] könnte ein [[buildingtype BLD_XENORESURRECTION_LAB]] auf diesem Planeten den [[predefinedshipdesign SM_WHITE_KRAKEN]] wiederbeleben und kontrollieren. + +Tief unter der eisigen Kruste dieses Planeten liegt ein prähistorischer weißer Vorfahr der heutigen Krakenmonster.''' HEAD_ON_A_SPIKE_SPECIAL Kopf auf einem Pfahl +HEAD_ON_A_SPIKE_SPECIAL_EFFECT +Angsterfüllt + +CONC_CAMP_MASTER_SPECIAL +Mantel des Todes + +CONC_CAMP_MASTER_SPECIAL_DESC +Die [[buildingtype BLD_CONC_CAMP]] haben der [[metertype METER_POPULATION]] dieses Planeten schwer zugesetzt; Sie sind zwar vor den Blicken von Besuchern verborgen, aber sensitive Wesen können das Leiden in der Luft spüren. + +CONC_CAMP_SLAVE_SPECIAL +Geistige Erschöpfung + +CONC_CAMP_SLAVE_SPECIAL_DESC +Auch wenn heute keine [[buildingtype BLD_CONC_CAMP]] mehr auf diesem Planeten betrieben werden, sind die Erinnerungen noch frisch und Überreste wie schlecht versteckte Massengräber kommen noch immer gelegentlich zu Tage. Die [[metertype METER_POPULATION]] leidet an einer seelischen Wunde und kann nicht gedeihen. + +CLOUD_COVER_MASTER_SPECIAL +Wolkenbrut + +CLOUD_COVER_MASTER_SPECIAL_DESC +Auf diesem Planeten leben fliegende Kreaturen, die Feuchtigkeit absondern und so dichte Wolken verursachen. Später in ihrem Lebenszyklus werden sie sich eingraben und dabei vulkanische Aktivität verursachen. + +CLOUD_COVER_SLAVE_SPECIAL +Wolkenmantel + +CLOUD_COVER_SLAVE_SPECIAL_DESC +Dieser Planet hat ungewöhnlich dicke Wolken, die [[metertype METER_STEALTH]] um 20 erhöhen. Dies könnte natürliche Ursachen haben, aber auch mit Weltraummonstern oder [[tech SPY_STEALTH_1]]-Technologie zusammenhängen. + +VOLCANIC_ASH_MASTER_SPECIAL +Versteckte Wühler + +VOLCANIC_ASH_MASTER_SPECIAL_DESC +Monster wühlen im Kern dieses Planeten herum, was hohe vulkanische Aktivität verursacht. Diese Kreaturen durchlaufen eine dort Metamorphose und wachsen bald zu Kreaturen heran, die dimensionalene Risse in das Raumgefüge reißen können. + +VOLCANIC_ASH_SLAVE_SPECIAL +Vulkanasche + +VOLCANIC_ASH_SLAVE_SPECIAL_DESC +Dieser Planet ist in Aschewolken gehüllt, die [[metertype METER_STEALTH]] um 40 erhöhen. Dies könnte natürliche Ursachen haben, aber auch mit Weltraummonstern oder [[tech SPY_STEALTH_2]]-Technologie zusammenhängen. + +DIM_RIFT_MASTER_SPECIAL +Dimensionswühler + +DIM_RIFT_MASTER_SPECIAL_DESC +Monster wühlen im Kern dieses Planeten herum, was Dimensionsrisse im Inneren und in der Umgebung des Planeten verursacht. Diese Kreaturen sind bald erwachsen und werden dann versuchen, den Planeten in die Leere zu zerren. + +DIM_RIFT_SLAVE_SPECIAL +Dimensionsriss + +DIM_RIFT_SLAVE_SPECIAL_DESC +Die Oberfläche dieses Planeten ist zum Teil durch einen dimensionalen Riss verdeckt, was [[metertype METER_STEALTH]] um 60 erhöht. Dies könnte natürliche Ursachen haben, aber auch mit Weltraummonstern oder [[tech SPY_STEALTH_3]]-Technologie zusammenhängen. + +VOID_MASTER_SPECIAL +Leeremonster + +VOID_MASTER_SPECIAL_DESC +Monster im Kern dieses Planeten zerren selbigen in die Leere. Das könnte irgendwann zu Problemen führen. + +VOID_SLAVE_SPECIAL +Leere-Umwandlung + +VOID_SLAVE_SPECIAL_DESC +Dieser Planet existiert zum Teil in unserem Universum, zum Teil in der Leere, was [[metertype METER_STEALTH]] um 80 erhöht. Dies könnte natürliche Ursachen haben, aber auch mit Weltraummonstern oder [[tech SPY_STEALTH_4]]-Technologie zusammenhängen. + +DERELICT_SPECIAL2 +Aufklärerwrack + +DERELICT_SPECIAL3 +Tankerwrack + +DERELICT_SPECIAL_DESC +Ein uraltes Wrack einer unbekannten Spezies. Wer weiß, was es verbirgt… + +ACCRETION_DISC_SPECIAL +Akkretionsscheibe + +ACCRETION_DISC_SPECIAL_DESC +Eine Akkretionsscheibe ist ein scheibenförmiges Gebilde aus loser Materie, das um ein massives Objekt rotiert. Reduziert auf allen Planeten im betroffenen System [[metertype METER_SUPPLY]] um 1. + +SUPERNOVA_SPECIAL +Neue Supernova + +SUPERNOVA_SPECIAL2 +Junge Supernova + +SUPERNOVA_SPECIAL3 +Reife Supernova + +SUPERNOVA_SPECIAL4 +Supernovaüberrest + +SUPERNOVA_SPECIAL_DESC +In diesem System hat sich vor relativ kurzer Zeit eine Supernova ereignet. Wo einst ein majestätisches Sternensystem war, sind nun nur noch Trümmer übrig. + +NOVA_BOMB_ACTIVATOR_SPECIAL +Novabombenauslöser + +NOVA_BOMB_ACTIVATOR_SPECIAL_DESC +Diese Region ist auf dimensionaler Ebene höchst instabil und bringt alle Novabomben in Reichweite zur Detonation. + +PROBIOTIC_SPECIAL +Ursuppe + +PROBIOTIC_SPECIAL_DESC +Die große Vielfalt an einzigartigen Einzellern lässt sich für verschiedene medizinische und pharmazeutische Zwecke nutzen. + +FRUIT_SPECIAL +Frucht der Weltenpfleger + +FRUIT_SPECIAL_DESC +Was zunächst wie ein Wald aussieht, ist tatsächlich Teil eines symbiotischen Organismus, der einen ganzen Kontinent umfasst. Dessen Früchte enthalten komplexe organische Chemikalien, die alle Arten von organischem Leben begünstigen. + +SPICE_SPECIAL +Ki-Spice + +SPICE_SPECIAL_DESC +Das Ökosystem auf diesem Planeten produziert eine besondere Substanz, die auf organische Wesen lebensverlängernd, gedankenklärend und sinnesschärfend wirkt. + +MONOPOLE_SPECIAL +Monopol-Magnete + +MONOPOLE_SPECIAL_DESC +Mit [[MONOPOLE_SPECIAL]]n kann man stärkere Roboter bauen! + +SUPERCONDUCTOR_SPECIAL +Raumtemperatursupraleiter + +SUPERCONDUCTOR_SPECIAL_DESC +Mit [[SUPERCONDUCTOR_SPECIAL]]n kann man schnellere Roboter bauen! + +POSITRONIUM_SPECIAL +Positroniumasche + +POSITRONIUM_SPECIAL_DESC +Mit [[POSITRONIUM_SPECIAL]] kann man klügere Roboter bauen! + MINERALS_SPECIAL Mineralienreichtum +MINERALS_SPECIAL_DESC +Dieser Planet ist reich an Mineralienvorkommen, was Produktion oder Lithisches Wachstum beschleunigt. + +CRYSTALS_SPECIAL +Silmalinkristalle + +CRYSTALS_SPECIAL_DESC +Dieser Planet ist reich an Mineralienvorkommen, was Produktion oder Lithisches Wachstum beschleunigt. + +ELERIUM_SPECIAL +Natürliches Elerium + +ELERIUM_SPECIAL_DESC +Dieser Planet hat reine Eleriumvorkommen, was Produktion oder Lithisches Wachstum beschleunigt. + ## ## Enumeration values @@ -4035,10 +4280,6 @@ Ungultige messungstyp METER_TARGET_POPULATION Ziel Bevölkerung -# Meter types -METER_TARGET_HEALTH -Ziel Gesundheit - # Meter types METER_TARGET_INDUSTRY Ziel Industrie @@ -4079,10 +4320,6 @@ Max Verteidigung METER_MAX_TROOPS Max Truppen -# Meter types -METER_MAX_REBEL_TROOPS -Max Rebellen Truppen - # Meter types METER_MAX_SUPPLY Max Versorgung @@ -4091,14 +4328,6 @@ Max Versorgung METER_POPULATION Bevölkerung -# Meter types -METER_HEALTH -Gesundheit - -# Meter types -METER_GROWTH -Wachstum - # Meter types METER_INDUSTRY Industrie @@ -4333,9 +4562,6 @@ Sternenklasse #DESC_VAR_TARGETPOPULATION #[[METER_TARGET_POPULATION]] -#DESC_VAR_TARGETHEALTH -#[[METER_TARGET_HEALTH]] - #DESC_VAR_TARGETINDUSTRY #[[METER_TARGET_INDUSTRY]] @@ -4372,9 +4598,6 @@ Sternenklasse #DESC_VAR_POPULATION #[[METER_POPULATION]] -#DESC_VAR_HEALTH -#[[METER_HEALTH]] - #DESC_VAR_INDUSTRY #[[METER_INDUSTRY]] @@ -5003,9 +5226,6 @@ Was ist ein Gott? Befasst sich ein Gott damit, ob er sich moralisch und wohlwoll GRO_PLANET_ECOL Planetare Ökologie -GRO_PLANET_ECOL_DESC -Agrikultureller und medizinischer Fortschritt kann die Bevölkerungssterblichkeit drastisch reduzieren; daraufhin entwickelt sich ein zeitlich begrenztes hyperexponentielles Wachstum. Schliesslich ergeben sich durch die Flächenkapazität des Planeten und das Netzwerk des Lebens, das er versorgen muss, neue Grenzen für das Wachstum. Das Verständnis der natürlichen Ökologie und ihrer Reaktion auf Belastungen hilft dabei, sie zu erhalten, damit auch künftige Generationen ihre Vorzüge ausschöpfen können. - GRO_GENETIC_ENG Gentechnik @@ -5027,24 +5247,12 @@ Traditionelle Gentherapie verändert den genetischen Kode und bringt ihn dann in GRO_LIFECYCLE_MAN Lebenszyklus Manipulation -GRO_LIFECYCLE_MAN_DESC -'''Through chemical manipulation and cryonics, life processes may be halted without terminating them permanently. Beings in this state are preserved indefinitely, consume no resources and require very little storage - not living - space. With this technique, the capacity of colony ships can be greatly increased. - -Die meisten komplexen Organismen durchschreiten während ihres Lebens physiologische Stadien, manchmal abgegrenzt durch Metamorphose oder auch kaum wahrnehmlich durch einen langsamen Altersprozess. In vielen Fällen sind eines oder mehrere dieser Stadien, zumindest zu einer gewissen Zeit, nützlicher und wünschenswerter als andere. Durch exakte Kontrolle hormonaler und mentaler Mechanismen wird es möglich, Lebensstadien drastisch zu verkürzen oder herauszuzögern, wodurch in einem Bruchteil des natürlichen Zeitrahmens voll entwickelte Individuen geschaffen werden können und die existierenden Individuen ihre Funktionalität niemals einbüßen und so praktisch unsterblich werden.''' - GRO_ADV_ECOMAN Fortgeschrittene Ökomanipulation GRO_XENO_GENETICS Xenogenetik -GRO_XENO_GENETICS_DESC -'''Erhöht die maximale Bevölkerung von Planeten des Typs [[PE_ADEQUATE]] & [[PE_POOR]], abhängig von deren Größe; [[SZ_TINY]]: +2, [[SZ_SMALL]]: +4, [[SZ_MEDIUM]]: +6, [[SZ_LARGE]]: +8, [[SZ_HUGE]]: +10, sowie Planeten des Typs [[PE_HOSTILE]]; [[SZ_TINY]]: +1, [[SZ_SMALL]]: +2, [[SZ_MEDIUM]]: +3, [[SZ_LARGE]]: +4, [[SZ_HUGE]]: +5. - -The study of other life forms often exemplifies the weaknesses inherent in one's own species. By studying other life forms and using that knowledge to improve upon one's own genetic code, it is possible to develop many useful adaptations that would be otherwise impossible. - -Der frühen Genetik sind durch die natürlich vorhandenen Organismen Grenzen gesetzt, da die Methoden der Gentechnik ausschliesslich auf ihnen basieren. Darüber hinaus ist die Genetik durch die physikalischen Mechanismen des genetischen Kodes wie seine Unterbringung, Veränderung und Ausdruck in erwachsenen Individuen limitiert. Durch den Vergleich sich entsprechender Systeme in unterschiedlichen Ökosystemen und ihren Organismen sind weitreichende Erkenntnisse möglich, die zu neuen Entwicklungen in den altbekannten genetischen Systemen Ansporn geben.''' - GRO_NANOTECH_MED Nanomedizin @@ -5056,13 +5264,6 @@ Während genetische Modifikationen die Effekte unerwünschter oder schädlicher GRO_XENO_HYBRIDS Xenohybridisierung -GRO_XENO_HYBRIDS_DESC -'''Erhöht die Maximale Bevölkerung von Planeten des Typs [[PE_POOR]], abhängig von deren Größe; [[SZ_TINY]]: +1, [[SZ_SMALL]]: +2, [[SZ_MEDIUM]]: +3, [[SZ_LARGE]]: +4, [[SZ_HUGE]]: +5, sowie Planeten des Typs [[PE_HOSTILE]]; [[SZ_TINY]]: +2, [[SZ_SMALL]]: +4, [[SZ_MEDIUM]]: +6, [[SZ_LARGE]]: +8, [[SZ_HUGE]]: +10. - -Often, the native fauna of a planet are far better suited to a planet's environment than any colonists who would try to inhabit it. These adverse effects can include anything from weather patterns to the planet's size. Through xenological hybridization, a genetically adapted form of the colonizing species can be developed with the advantageous traits of the native fauna, thus allowing more of the planet to be inhabited. - -Ganz gleich, wie umfassend das Verständnis für ein biologisches System ist oder wie geschickt es verbessert wurde, es wird immer durch sein innewohnendes Wesen eingeschränkt. Strukturen und grundlegende Physiologie können nur in den Grenzen der Biologie eines einzelnen Planeten verändert werden. Durch das Verständnis fremder Biologie können unterschiedliche Systeme vereint werden und so eine völlig neue hybride Biologie geschaffen werden, die bessere Fähigkeiten als ihre einzelnen Ursprünge besitzt.''' - GRO_NANO_CYBERNET Nanokybernetik @@ -5145,13 +5346,6 @@ Mit zunehmender Automatisierung in der Produktion wird die Maschinerie fähig, s PRO_NDIM_ASSMB N-Dimensionale Montage -PRO_NDIM_ASSMB_DESC -''' Unlocks the Hyperspatial Dam - -Once the greatest fear of N-dimensional physicists, now the noblest goal. To deliberately create a hyperspatial rift between our universe and the next, but inside a dimensional bubble, safely separate from known space-time. Once this is accomplished, it would be a relatively simple matter to tap into the enormous flow of energy that would travel across the rift. - -Die Geometrie des physikalischen Raums hat schon immer die Montage von Objekten in diesem Raum behindert. Objekte können sich nicht am gleichen Ort befinden und Volumen nicht gleichzeitig offen und geschlossen vorliegen, wenn sich ihre Existenz auf drei Dimensionen beschränkt. Mit dem Überschreiten dieser Grenzen können störende Objektteile in Taschendimensionen und somit aus dem Weg gefaltet werden; kontinuierliche Linien können an Massen vorbeigeleitet werden, indem man "von der Achse" abweicht. Ähnliche Methoden werden auch angewandt, um niederdimensionale Objekte in Konfigurationen zu arrangieren, die ohne höherdimensionale Montagewege unmöglich wären.''' - PRO_SINGULAR_GEN Singularitätskonstruktion @@ -5252,13 +5446,6 @@ Erhöht die Bevölkerungskapazität auf allen bewohnbaren Planeten, abhängig vo CON_NDIM_STRC N-Dimensionale Gebäude -CON_NDIM_STRC_DESC -'''Erhöht die Bevölkerungskapazität auf allen bewohnbaren Planeten, abhängig von deren Größe; [[SZ_TINY]]: +2, [[SZ_SMALL]]: +4, [[SZ_MEDIUM]]: +6, [[SZ_LARGE]]: +8, [[SZ_HUGE]]: +10. - -The primary limiting factors on a planets population capacity are environmental desirability and space. By phasing basic infrastructure development into multiple dimensions, spatial limitations are all but eliminated, and under ideal environmental conditions, the maximum population of a planet can be greatly increased. - -Von allen bizzaren Kunststücken der angewandten Physik hat die Fähigkeit, den Raum in sich selbst zu falten, den direktesten und tiefgreifendsten Einfluss auf die Form und das Design von Gebäuden. Das Potential ist endlos, und das wortwörtlich: Flure biegen sich zurück an ihren Beginn oder werden so verdreht, dass ihre Decke der Boden wird. Darüber hinaus löst sich das Konzept des Städtewachstums in Wohlgefallen auf, da eine beliebige Anzahl Personen in einem willkürlich kleinen Volumen sicher in einer angrenzenden Existenzebene untergebracht werden können.''' - SHP_GAL_EXPLO Galaktische Erkundung @@ -5282,6 +5469,9 @@ Bringt den Sieg! SHIP_PART_UNLOCK_SHORT_DESC Schaltet Schiffsteile frei +SHIP_FUEL_IMPROVE_SHORT_DESC +Verbessert Treibstofftanks + SHIP_WEAPON_UNLOCK_SHORT_DESC Schaltet Schiffsbewaffnung frei @@ -5383,17 +5573,6 @@ Bioterroranlage GRO_BIOTERROR_DESC Ein Forschungszentrum erschaffen, um sicher die gefährlichsten Biologischen Waffen zu entwickeln, versteckt vor neugierigen Augen. Biologische Waffen wirken zu langsam um in einem offenen miliärischen Konflikt wirksam zu sein, als Terrorwaffe sind sie unerreicht, eine langsam wachsende Kontamination verbreitet Angst und Schrecken. Bilder von verzweifelten Personen, in Quarantäne gehalten durch ihre eigene Regierung, säen Verzweiflung jenseits der betroffenen Gebiete. Jedoch müssen größte Sicherheitsmaßnahmen eingehalten werden, wenn man solche Organismen entwickelt, um sicherzustellen, dass sie nicht versehentlich freigesetzt werden. Außerdem ist strikte Geheimhaltung notwendig, um nicht zu riskieren, dass diplomatische Beziehungen mit anderen Imperien und zur eigenen Bevölkerung leiden. -#GRO_CYBORG -#Cyborgs - -GRO_CYBORG_DESC -'''Erhöht die Maximale Bevölkerung von Planeten des Typs [[PE_HOSTILE]], abhängig von deren Größe; [[SZ_TINY]]: +2, [[SZ_SMALL]]: +4, [[SZ_MEDIUM]]: +6, [[SZ_LARGE]]: +8, [[SZ_HUGE]]: +10. - -A highly versatile fusion of organism and machine, capable of adapting to a tremendous variety of circumstances. Spontaneous generation of specialized organs and mechanically enhanced strength permit cyborgs to exist with ease in nearly any environment.''' - -#GRO_GAIA_TRANS -#Gaia Transformation - LRN_OBSERVATORY_I Observatorium @@ -5408,11 +5587,6 @@ Der Durchschnittsbürger verschwendet wortwörtlich sein Hirn, das eine der best LRN_STELLAR_TOMOGRAPHY Stellartomographie -LRN_STELLAR_TOMOGRAPHY_DESC -'''Increases target [[metertype METER_RESEARCH]] on planets with the Research focus per Population by 1, 0.75, 0.5, or 0.2 in systems with Black Hole stars, Neutron stars, Blue or white stars, and Yellow, Orange or Red stars, respectively. - -Ein Netzwerk spezialisierter Satelliten kann mit tomographischen Methoden die Vorgänge im Inneren eines Sterns rekonstruieren. Diese schwer herzustellenden Bedingungen, besonders die in exotischen Sterntypen, sind für diverse Forschungsprojekte ideal.''' - LRN_ENCLAVE_VOID Enklave der Leere @@ -5726,13 +5900,16 @@ SHP_DEUTERIUM_TANK Deuteriumtank SHP_DEUTERIUM_TANK_DESC -Schaltet das Schiffsteil [[shippart FU_DEUTERIUM_TANK]] frei. +Erhöht die Kapazität der Treibstofftanks etwas und schaltet das Schiffsteil [[shippart FU_RAMSCOOP]] frei. + +SHP_DEUTERIUM_TANK_EFFECT +Deuterium Treibstoff SHP_ANTIMATTER_TANK Antimaterietank SHP_ANTIMATTER_TANK_DESC -Schaltet das Schiffsteil [[shippart FU_ANTIMATTER_TANK]] frei. +Erhöht die Kapazität der Treibstofftanks ordentlich. SHP_ZERO_POINT Nullpunkt-Treibstoffgenerator @@ -5793,25 +5970,48 @@ Neue Erfahrungen und die Weiterentwicklung der Produktionsanlagen ermöglichen e ## BLD_EVACUATION -Evakuierung +Evakuierungssystem BLD_EVACUATION_DESC -Entfernt die Bevölkerung dieses Planeten über mehrere Runden. Wenn einen Ersatzplanet mit der gleichen Spezies und genügend Platz verfügbar ist, wird die Bevölkerung dorthin befördert. +Entfernt die [[metertype METER_POPULATION]] dieses Planeten über mehrere Runden gemäß dem [[encyclopedia EVACUATION_TITLE]]. Wenn einen Ersatzplanet mit der gleichen Spezies und genügend Platz verfügbar ist, wird die Bevölkerung dorthin befördert. BLD_OBSERVATORY Observatorium +BLD_OBSERVATORY_DESC +'''Enthüllt der gesamten Galaxis jede Runde einen zufälligen Stern. Sollte dieser Stern bereits entdeckt oder erkundet sein, wird kein neuer Stern enthüllt. + +Eine Beobachtungseinrichtung zur Lokalisierung und Kartierung von Sternen, insbesondere solche, die über das Sternstraßennetzwerk erreichbar sind.''' + BLD_CULTURE_ARCHIVES Kulturelle Archive +BLD_CULTURE_ARCHIVES_DESC +'''Erhöht [[metertype METER_TARGET_RESEARCH]] um 5 und [[metertype METER_TARGET_INDUSTRY]] um die Hälfte der [[metertype METER_POPULATION]] des Planeten. + +Speichert das über tausende von Jahren angesammelte Wissen dieses Planeten. Verbessert Produktivität in vielerlei Hinsicht.''' + BLD_CULTURE_LIBRARY Kulturelle Bibliothek +BLD_CULTURE_LIBRARY_DESC +'''Erhöht [[metertype METER_TARGET_RESEARCH]] um 5. + +Speichert das über tausende von Jahren angesammelte Wissen dieses Planeten. Sollte die [[metertype METER_POPULATION]] der Eingeborenen dieses Planeten auf null fallen, wird dieses Gebäude zerstört.''' + +BLD_AUTO_HISTORY_ANALYSER +Automatischer Geschichtsauswerter + +BLD_AUTO_HISTORY_ANALYSER_DESC +'''Kann nur auf Planeten gebaut werden, welche [[buildingtype BLD_CULTURE_ARCHIVES]] besitzen. Erhöht [[metertype METER_TARGET_RESEARCH]] um 5. + +Ein automatisiertes Forschungszentrum, welches mit enormer Rechenleistung die digitalen Archive durchforstet, um Muster, fachübergreifende Parallelen sowie verlorene oder unvollstandige Einblicke zu finden.''' + BLD_IMPERIAL_PALACE Imperialer Palast BLD_IMPERIAL_PALACE_DESC -'''Increases [[metertype METER_SUPPLY]] line range by 1, [[metertype METER_CONSTRUCTION]] by 20, [[metertype METER_DEFENSE]] by 5, and [[metertype METER_TROOPS]] by 3. Also sets the owner's Capital for the empire. +'''Erhöht Reichweite der [[metertype METER_SUPPLY]]slinie um 1, [[metertype METER_CONSTRUCTION]] um 20, [[metertype METER_DEFENSE]] um 5 sowie [[metertype METER_TROOPS]] um 3. Markiert die Heimatwelt des Imperiums. Der imperiale Palast spiegelt die Macht und das Ansehen eines Imperiums wider, und fungiert als Steuerzentrale über die Besitztümer des Imperiums.''' @@ -5819,365 +6019,1637 @@ BLD_SHIPYARD_BASE Einfache Werft BLD_SHIPYARD_BASE_DESC -Orbitale Produktionsanlage für den Bau interstellarer militärischer Raumschiffe. Diese Werft muss existieren, um Schiffe auf diesem Planeten produzieren zu können. Sie kann durch den Bau zusätzlicher Gebäude im selben System aufgerüstet werden. Es können keine Werftaufrüstungen gebaut werden, ohne daß diese Werft verfügbar ist. +Orbitale Produktionsanlage für den Bau interstellarer militärischer Raumschiffe. Diese Werft muss existieren, um Schiffe auf diesem Planeten produzieren zu können. Sie kann durch den Bau zusätzlicher Gebäude im selben System aufgerüstet werden. Es können keine Werftaufrüstungen gebaut werden, bevor diese Werft verfügbar ist. BLD_SHIPYARD_ORBITAL_DRYDOCK Orbitales Trockendock -BLD_SHIPYARD_ORBITAL_DRYDOCK_DESC -'''Dies ist eine Aufrüstung der [[buildingtype BLD_SHIPYARD_BASE]]. Diese wird für die Herstellung aller größeren Schiffshüllen und weitere Werftaufrüstungen benötigt. Diese Erweiterung bietet auch die Fähigkeit beschädigte Schiffe in einer einzigen Runde zu reparieren. +BLD_SHIPYARD_CON_NANOROBO +Nanobot-Verarbeitungseinheit -When a large interstellar vessel is being constructed, it is not practical to move large parts up and down space elevators for orbital assembly. Rather, it is far more practical to fabricate the parts and assemble them in the same orbital facility, thus eliminating the need to transport particularly massive ship parts from the surface of the planet.''' +BLD_SHIPYARD_CON_NANOROBO_DESC +'''Dies ist eine Aufrüstung für [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] Sie ermöglicht die Herstellung des [[shiphull SH_NANOROBOTIC]]s und, zusammen mit anderen Gebäuden, des [[shiphull SH_LOGISTICS_FACILITATOR]]s. -BLD_SHIPYARD_ORG_CELL_GRO_CHAMB -Zellwachstum Kammer +Die Herstellung von Millionen winziger Wartungsroboter und ihrer Verbindung zu einer zentralen KI benötigt weit umfangreichere Produktionsanlagen, als die eines einfachen Robotikrumpfes. Dieses Gebäude ist in der Lage, fortschrittlichere Robotikrümpfe zu konstruieren.''' -BLD_SHIPYARD_ORG_CELL_GRO_CHAMB_DESC -'''Das ist eine Verbesserung vom [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] und ermöglicht die Entwicklung von [[shiphull SH_PROTOPLASMIC]]s, [[shiphull SH_SYMBIOTIC]]s und [[shiphull SH_BIOADAPTIVE]]s. Das Vorhandensein dieser Verbesserung und die [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] auf dem Planeten erlaubt die Entwicklung des [[shiphull SH_ENDOSYMBIOTIC]] und [[shiphull SH_SENTIENT]]. +BLD_SHIPYARD_CON_GEOINT +Geo-Integrierte Anlage -Eine Werft Verbesserung die darauf zielt, die Größe von einzelligen Organismen um wirklich massive Proportionen zu erhöhen.''' +BLD_SHIPYARD_CON_GEOINT_DESC +'''Dies ist eine Aufrüstung für [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] Sie ermöglicht die Herstellung des [[shiphull SH_TITANIC]]s, des [[shiphull SH_SELF_GRAVITATING]]s und, zusammen mit anderen Gebäuden, des [[shiphull SH_LOGISTICS_FACILITATOR]]s. -BLD_BIOTERROR_PROJECTOR -Bioterror Angriffsbasis +Wenn die Anforderungen einer Schiffswert die Möglichkeiten der Planetenoberfläche übersteigt, so macht es nur Sinn, selbige Oberfläche aus der Gleichung zu entfernen und die Werft in den Planeten selbst zu integrieren. Eine Anlage, die vom tiefen Untergrund bis hoch über die geosynchrone Umlaufbahn reicht, kann von allen geologischen Aspekten ihres Planeten gebrauch machen.''' -BLD_LIGHTHOUSE -Interstellarer Leuchtturm +BLD_SHIPYARD_CON_ADV_ENGINE +Fortgeschrittenes Maschinendock -BLD_INDUSTRY_CENTER -Industriezentrum +BLD_SHIPYARD_CON_ADV_ENGINE_DESC +'''Dies ist eine Aufrüstung für [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]]. Sie ermöglicht die Herstellung des [[shiphull SH_TRANSSPATIAL]]s, des [[shippart FU_TRANSPATIAL_DRIVE]]s und, zusammen mit anderen Gebäuden, des [[shiphull SH_LOGISTICS_FACILITATOR]]s. -BLD_INDUSTRY_CENTER_DESC -'''Gibt einen Bonus von 0.2 pro Bevölkerung auf [[metertype METER_INDUSTRY]] für alle über [[metertype METER_SUPPLY]] verbundenen Planeten mit Industrie. +Eine hochentwickelte Konstruktionsanlage speziell für den Bau des [[FU_TRANSPATIAL_DRIVE]]s.''' + +BLD_SHIPYARD_AST +Asteroidenverarbeitung + +BLD_SHIPYARD_AST_DESC +'''Dieses Gebäude wird für die Herstellung aller Asteroidenrümpfe sowie für weitere Aufrüstungen der Astroidenverarbeitung benötigt. Dieses Gebäude kann nur auf Asteroidengürteln gebaut werden, einschließlich [[encyclopedia OUTPOSTS_TITLE]]. + +Eine gewaltige Produktionsanlage, in der Asteroiden ausgehöhlt und für den Einsatz als Schiffsrumpf vorbereitet werden. Eine [[buildingtype BLD_SHIPYARD_BASE]] im gleichen System verarbeitet diese dann zu einem fertigen Rumpf.''' + +BLD_SHIPYARD_AST_REF +Asteroidenumwandlungsanlage + +BLD_SHIPYARD_AST_REF_DESC +'''Dies ist eine Aufrüstung für die [[buildingtype BLD_SHIPYARD_AST]]. Sie ermöglicht die Herstellung des [[shiphull SH_MINIASTEROID_SWARM]]s, des [[shiphull SH_SCATTERED_ASTEROID]]s und des [[shiphull SH_CRYSTALLIZED_ASTEROID]]s. Des Weiteren ermöglicht dieses Gebäude allen Werften, einschließlich derer in verbündeten Imperien, die Herstellung von [[shippart AR_ROCK_PLATE]] und [[shippart AR_CRYSTAL_PLATE]]. + +Eine komplexe Produktionsstätte, in der Asteroiden zertrümmert, verschweißt, zerlegt und zusammengesetzt werden, um sie für den Einsatz als Raumschiffkomponenten vorzubereiten.''' + +BLD_SHIPYARD_ORG_ORB_INC +Orbitalinkubator + +BLD_SHIPYARD_ORG_ORB_INC_DESC +'''Dies ist eine Aufrüstung für [[buildingtype BLD_SHIPYARD_BASE]]en und wird für die Herstellung aller organische Rümpfe sowie für alle zusammenhängenden Werftaufrüstungen benötigt. Dieses Gebäude ermöglicht die Herstellung des [[shiphull SH_ORGANIC]]s, des [[shiphull SH_SYMBIOTIC]]s and [[shiphull SH_STATIC_MULTICELLULAR]]s. + +Eine Schiffswert, die an die Bedürtnisse heranwachsender Weltallkreaturen angepasst ist. Damit die lebenden Schiffe wachsen und gedeihen können, müssen in der Werft die exakt richtigen Bedingungen herrschen. Daher führt Schaden an der Werft häufig zum Tod aller unfertigen Schiffe.''' + +BLD_SHIPYARD_ORG_CELL_GRO_CHAMB +Zellwachstumskammer + +BLD_SHIPYARD_ORG_CELL_GRO_CHAMB_DESC +'''Dies ist eine Aufrüstung des [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]]s und ermöglicht die Entwicklung des [[shiphull SH_PROTOPLASMIC]]s, des [[shiphull SH_SYMBIOTIC]]s und des [[shiphull SH_BIOADAPTIVE]]s. Das Vorhandensein dieser Aufrüstung und der [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] auf dem selben Planeten ermöglicht die Entwicklung des [[shiphull SH_ENDOSYMBIOTIC]]s und des [[shiphull SH_SENTIENT]]s. + +Eine spezialisierte Werftaufrüstung, die die Größe einzelliger Organismen auf enorme Ausmaße bringen kann.''' + +BLD_SHIPYARD_ORG_XENO_FAC +Xeno-Koordinierungseinrichtung + +BLD_SHIPYARD_ORG_XENO_FAC_DESC +'''Dies ist eine Aufrüstung des [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]]s und ermöglicht die Entwicklung des [[shiphull SH_ENDOMORPHIC]]s und [[shiphull SH_RAVENOUS]]s. Das Vorhandensein dieser Aufrüstung und der [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] auf dem selben Planeten ermöglicht die Entwicklung des [[shiphull SH_ENDOSYMBIOTIC]]s und des [[shiphull SH_SENTIENT]]s. + +Eine Einrichtung, die eine vollkommen symbiotische Beziehung zwischen Schiff und Besatzung schafft.''' + +BLD_SHIPYARD_ENRG_COMP +Energieverdichter + +BLD_SHIPYARD_ENRG_COMP_DESC +'''Dies ist eine Aufrüstung für [[buildingtype BLD_SHIPYARD_BASE]]en und wird für die Bildung aller Energierümpfe sowie für alle zusammenhängenden Werftaufrüstungen benötigt. + +Da Energie aus einem hochenergetischen Stern abgezapft werden muss, ist der Bau nur möglich in Systemen der Sternentypen [[STAR_BLUE]], [[STAR_WHITE]] oder [[STAR_BLACK]]. + +Eine hochspezialisierte Schiffswerft, in der Schiffskomponenten aus zusammengepresster Energie gebaut und montiert werden. Dies ist ein delikater Prozess, weshalb selbst geringer [[encyclopedia DAMAGE_TITLE]] an der Werft zu einer gewaltigen Explosion führt.''' + +BLD_SHIPYARD_ENRG_SOLAR +Sonnenbehälter + +BLD_SHIPYARD_ENRG_SOLAR_DESC +'''Dies ist eine Aufrüstung für den [[buildingtype BLD_SHIPYARD_ENRG_COMP]] und ermöglicht die Bildung des [[shiphull SH_SOLAR]]s. + +Da enorme Mengen Energie abgezapft werden müssen, ist der Bau nur in Systemen mit einem [[STAR_BLACK]] möglich. + +Allein die Herstellung einer Miniatursonne ist schon eine enorme Aufgabe. Die Herstellung von einer, an die sich Schiffsteile anbringen lassen, ist nur umso enormer.''' + +BLD_BIOTERROR_PROJECTOR +Bioterror-Verbreitungsbasis + +BLD_BIOTERROR_PROJECTOR_DESC +'''Verleiht der Kolonie den [[FOCUS_BIOTERROR]]-Fokus, welcher auf allen Feindplaneten, die nicht weiter als 4 Sternstraßensprünge entfernt sind, die [[metertype METER_POPULATION]] um 2 pro Runde reduziert, sofern das betroffene Imperium keine [[buildingtype BLD_GENOME_BANK]] hat. + +Diese verborgene Biowaffenbasis verwüstet alle feindlichen Planeten in Reichweite. Die Öffentlichkeit missbilligt solch ein Vorgehen aber und die Basis kann nur auf Planeten mit einem resonanten Mond errichtet werden. [[BUILDING_AVAILABLE_ON_OUTPOSTS]], sofern ein [[special RESONANT_MOON_SPECIAL]] vorhanden ist.''' + +BLD_LIGHTHOUSE +Interstellarer Leuchtturm + +BLD_LIGHTHOUSE_DESC +Reduziert die [[metertype METER_STEALTH]] aller Objekte im System um 30 und erhöht die [[metertype METER_SPEED]] aller verbündeten Schiffe innerhalb von 50uu um 20. [[BUILDING_AVAILABLE_ON_OUTPOSTS]] and unterstützt den Einsatz von [[buildingtype BLD_PLANET_DRIVE]]en. + +BLD_SCANNING_FACILITY +Scananlage + +BLD_SCANNING_FACILITY_DESC +'''Erhöht die [[metertype METER_DETECTION]] des Planeten um 75. + +Durch die Kombination von Langstreckenscannern und Computeranalyse kann ein größerer Bereich nach Objekten abgesucht werden.''' + +BLD_INDUSTRY_CENTER +Industriezentrum + +BLD_INDUSTRY_CENTER_DESC +'''Erhöht auf allen Planeten mit Industriefokus, die über die [[metertype METER_SUPPLY]]slinie verbunden sind, [[metertype METER_TARGET_INDUSTRY]] um 0,2 pro [[metertype METER_POPULATION]]. +[[NO_STACK_SUPPLY_CONNECTION_TEXT]] + +Die Verbesserung [[tech PRO_INDUSTRY_CENTER_II]] erhöht den Bonus auf das doppelte. + +Die Verbesserung [[tech PRO_INDUSTRY_CENTER_III]] erhöht den Bonus auf das dreifache.''' + +BLD_INTERSPECIES_ACADEMY +Spezies-InterDesign-Akademie + +BLD_INTERSPECIES_ACADEMY_DESC +'''Erhöht Vorratsbildung um 10, wenn dieser Planet auf Vorrat fokussiert ist. +Erhöht [[metertype METER_RESEARCH]] um 5, wenn dieser Planet auf Forschung fokussiert ist. + +Zum Entwerfen universell verwendbarer Werkzeuge ist ein Verständis der physischen Möglichkeiten anderer Spezies unabdingbar. + +Ein Imperium kann auf bis zu sechs Planeten Akademien unterhalten. Jede Akademie benötigt eine zufriedene [[metertype METER_POPULATION]] einer anderen Spezies und muss mindestens drei Sternstraßensprünge von den anderen Akademien entfernt sein.''' + +BLD_SPACE_ELEVATOR +Weltraumlift + +BLD_SPACE_ELEVATOR_DESC +'''Beseitigt die Auswirkungen der Planetengröße auf [[metertype METER_SUPPLY]] und erhöht [[METER_SUPPLY]] um 3. + +Die Nanokonstruktion eröffnet die Möglichkeit, Kabel zu erschaffen, welche zugfest genug sind, um über den geostationären Orbit zu reichen. Magnetisch am Kabel beförderte Fähren sind den teuren, ineffizienten und hochgefährlichen Raketenstarts überlegen. Ein enormes und nahezu unzerreissbares Kabel, das an einem Ende mit einem Satelliten im niedrigen geostationären Orbit verbunden ist, und an dessen Ende zum Planeten hin sich eine Startplattform befindet.''' + +BLD_GENOME_BANK +Genombank + +BLD_GENOME_BANK_DESC +'''Verleiht dem Imperium Imunität gegen Bioterror. + +Die größte jemals erfasste Datenbank. Die Genombank beinhaltet Genkarten der Genome eines jeden dem Imperium bekannten Organismus. Diese Datenbank ist von unschätzbarem Wert bei der Behandlung von Krankheiten.''' + +BLD_GAIA_TRANS +Gaia-Umwandlung + +BLD_GAIA_TRANS_DESC +'''Verleiht dem Planeten die Besonderheit [[special GAIA_SPECIAL]]. Diese erhöht [[metertype METER_TARGET_POPULATION]] in einer [[PE_GOOD]]en [[encyclopedia ENVIRONMENT_TITLE]], entsprechend der Planetengröße: +• [[SZ_TINY]] (+3) +• [[SZ_SMALL]] (+6) +• [[SZ_MEDIUM]] (+9) +• [[SZ_LARGE]] (+12) +• [[SZ_HUGE]] (+15) +Erhöht außerdem [[metertype METER_TARGET_HAPPINESS]] um 5. + +Durch einen zellenbasierten, denkenden, fast schon gottgleichen Supercomputer wird der Planet in eine Gaia-Welt umgewandelt - ein Monument des Lebens und der Harmonie, ein Wunder, gefeiert in der gesamten Galaxie. Die Bewohner dieser Welt sind Teil eines größeren Ganzen, in Denken und Handeln stets einander dienlich.''' + +BLD_COLLECTIVE_NET +Kollektives Gedankennetzwerk + +BLD_COLLECTIVE_NET_DESC +'''Erhöht [[metertype METER_TARGET_INDUSTRY]] auf Planeten mit Industriefokus um 0,5 pro [[metertype METER_POPULATION]] und [[metertype METER_TARGET_RESEARCH]] auf Planeten mit Forschungsfokus um 0,5 pro [[metertype METER_POPULATION]]. Sternstraßensprünge innerhalb von 200uu stören die Funktion dieses Gebäudes und negieren den Bonus. +[[NO_STACK_SUPPLY_CONNECTION_TEXT]] + +Durch die Übertragung des Geistes in den Cyberspace können unzählige Wesen vereint agieren und so Probleme lösen und Gedanken fassen, die einem einzelnen Wesen nicht möglich wären.''' + +BLD_GATEWAY_VOID +Portal der Leere + +BLD_GATEWAY_VOID_DESC +'''Das vollständige Sperren eines ganzen Systems stellt eine effektive Verteidigungsmaßnahme dar. Alle Schiffe, die dieses System betreten und nicht sofort weiterfliegen, werden vernichtet und alle Kolonien im System werden zu [[encyclopedia OUTPOSTS_TITLE]] reduziert. Die [[metertype METER_STEALTH]] aller Objekte im System steigt um 1000, womit sie praktisch unsichtbar wären. + +Ein dimensionaler Riss, welche die zerstörerische Macht des Geists der Leere auf eine einzelne Region konzentrieren kann.''' + +BLD_ENCLAVE_VOID +Enklave der Leere + +BLD_ENCLAVE_VOID_DESC +'''Erhöht [[metertype METER_TARGET_RESEARCH]] auf allen Planeten um 0,75 pro [[metertype METER_POPULATION]]. +Weitere Instanzen dieses Gebäudes haben keinen zusätzlichen Effekt. + +Die Bevölkerung eines ganzen Planeten wird genetisch auf den Geist der Leere ausgerichtet und widmet sich ganz der Aufgabe, seine Weisheit dem Imperium mitzuteilen. Die Mitglieder der Enklave der Leere werden als Priester erachtet und als Vermittler höherer Weisheit. In allen wichtigen Angelegenheiten des Imperiums wird ihr Rat eingeholt. Botschafter der Enklave können auch ausgesandt werden, um bei Forschungsprojekten des Imperiums mitzuwirken.''' + +BLD_ART_BLACK_HOLE +Künstliches schwarzes Loch + +BLD_ART_BLACK_HOLE_DESC +Durch Überladung der Schwerkraft bei gleichbleibender Energie können Sterne zum Kollaps gebracht werden, was ein [[STAR_BLACK]] erzeugt. Dies funktioniert jedoch nur bei [[STAR_RED]]en Sternen, da der interne Druck heißerer Sterne zu hoch ist, um überwunden zu werden. Dieser Prozess kann auch von einem [[encyclopedia OUTPOSTS_TITLE]] aus durchführt werden. + +BLD_BLACK_HOLE_COLLAPSER +Schwarzes-Loch-Kollabierer + +BLD_BLACK_HOLE_COLLAPSER_DESC +Erschafft einen [[fieldtype FLD_SUBSPACE_RIFT]] mit 100uu Radius um das Gebäude herum. + +BLD_HYPER_DAM +Hyperspatialdamm + +BLD_SOL_ACCEL +Solarbeschleuniger + +BLD_SOL_ACCEL_DESC +Beschleunigt die natürlichen Prozesse eines Sterns, um ihn künstlich zu altern. Der Stern wird um eine Stufe älter, gemäß der folgenden Reihenfolge: [[STAR_BLUE]], [[STAR_WHITE]], [[STAR_YELLOW]], [[STAR_ORANGE]], [[STAR_RED]]. Dieser Prozess kann auch von einem [[encyclopedia OUTPOSTS_TITLE]] aus durchgeführt werden. + +BLD_SOL_ORB_GEN +Sonnenorbitgenerator + +BLD_SOL_ORB_GEN_DESC +'''Erhöht [[metertype METER_TARGET_INDUSTRY]] auf via [[metertype METER_SUPPLY]] verbundenen Planeten proportional zur [[metertype METER_POPULATION]], abhängig von der Farbe des Sterns: +* [[STAR_BLUE]] oder [[STAR_WHITE]] (x0,4) +* [[STAR_YELLOW]] oder [[STAR_ORANGE]] (x0,2) +* [[STAR_RED]] (x0,1) +[[NO_STACK_SUPPLY_CONNECTION_TEXT]] Der höchste Bonus ist gültig. + +Ein elektrischer Generator in niedriger Umlaufbahn, welcher aus der Sonne, die er umkreist, Energie gewinnt. Da unterschiedliche Sterne unterschiedliche Mengen Energie abgeben, hängt die Effizienz des Generators vom Typ des Sterns ab. [[BUILDING_AVAILABLE_ON_OUTPOSTS]].''' + +BLD_CLONING_CENTER +Kloncenter + +BLD_TERRAFORM +Terraformation + +BLD_TERRAFORM_DESC +Wandelt den Planeten in eine Welt um, die der bevorzugten [[encyclopedia ENVIRONMENT_TITLE]] der Einwohnerspezies einen Schritt näher ist. + +BLD_TERRAFORM_REVERT +Terraform-Umkehr + +BLD_TERRAFORM_REVERT_DESC +Wandelt den Planeten einen Schritt in Richtung seiner ursprünglichen [[encyclopedia ENVIRONMENT_TITLE]]s um. + +BLD_REMOTE_TERRAFORM +Fern-Terraformation + +BLD_REMOTE_TERRAFORM_DESC +Wandelt einen Planeten in eine Welt um, die der bevorzugten [[encyclopedia ENVIRONMENT_TITLE]] der Einwohnerspezies einen Schritt näher ist. Dieser Prozess kann auch von einem [[encyclopedia OUTPOSTS_TITLE]] aus durchgeführt werden. + +BLD_NEST_ERADICATOR +Nestbeseitiger + +BLD_NEST_ERADICATOR_DESC +Entfernt ein [[encyclopedia KRAKEN_NEST_SPECIAL]], [[encyclopedia SNOWFLAKE_NEST_SPECIAL]] oder [[encyclopedia JUGGERNAUT_NEST_SPECIAL]] von dem Planeten. [[BUILDING_AVAILABLE_ON_OUTPOSTS]]. + +BLD_NEUTRONIUM_EXTRACTOR +Neutroniumextrahierer + +BLD_NEUTRONIUM_FORGE +Neutroniumschmiede + +BLD_NEUTRONIUM_SYNTH +Neutroniumsynthesierer + +BLD_NEUTRONIUM_SYNTH_DESC +'''Gewährt dem Imperium Zugriff auf Neutronium, welches einer [[buildingtype BLD_NEUTRONIUM_FORGE]] die Herstellung von Schiffsbauteilen wie der [[shippart AR_NEUTRONIUM_PLATE]] ermöglicht. Der Bau eines [[buildingtype BLD_NEUTRONIUM_EXTRACTOR]] wird somit überflüssig. + +Ein antikes Gebäude, das Neutronium herstellen kann.''' + +BLD_CONC_CAMP +Konzentrationslager + +BLD_CONC_CAMP_DESC +'''Reduziert [[metertype METER_POPULATION]] um 3 pro Zug, erhöht [[metertype METER_TARGET_INDUSTRY]] um das fünffache der [[metertype METER_POPULATION]] und reduziert [[metertype METER_TARGET_HAPPINESS]] auf null. + +Die Einführung einer besseren Bewohnerspezies setzt das Verschwinden der ungewollten voraus. Durch ein planetenumfassendes Netzwerk von Lagern, in denen sich Insassen wortwörtlich zu Tode arbeiten, kann dieses Ziel schnell und effizient realisiert werden.''' + +BLD_CONC_CAMP_REMNANT +Lagerruinen + +BLD_CONC_CAMP_REMNANT_DESC +Obwohl die Behörden den Einsatz der [[buildingtype BLD_CONC_CAMP]] leugnen, wissen die Einwohner, was hier passiert ist. + +BLD_BLACK_HOLE_POW_GEN +Schwarzes-Loch-Generator + +BLD_BLACK_HOLE_POW_GEN_DESC +'''Erhöht auf allen via [[metertype METER_SUPPLY]] verbundenen Planeten mit Industriefokus [[metertype METER_TARGET_INDUSTRY]] um 1 per [[metertype METER_POPULATION]]. [[NO_STACK_SUPPLY_CONNECTION_TEXT]] -Die [[tech PRO_INDUSTRY_CENTER_II]] Aufrüstung verdoppelt den Bonus. +[[BUILDING_AVAILABLE_ON_OUTPOSTS]], setzt aber ein [[STAR_BLACK]] im System voraus.''' + +BLD_PLANET_DRIVE +Planetarer Sternenantrieb + +BLD_PLANET_DRIVE_DESC +'''Ermöglicht einem Planeten die Reise zu anderen Systemen. Um einen erfolgreiche Passage zu gewährleisten, wird ein [[buildingtype BLD_LIGHTHOUSE]] innerhalb von 200uu des Zielsystems vorausgesetzt. Andernfalls besteht eine Wahrscheinlichkeit von 50%, das der Planet zerstört wird. Selbst im Idealfall ist die Reise aber noch hochgefährlich und nur die Hälfte der [[metertype METER_POPULATION]] wird den Transit überleben, unabhängig von Vorhandensein eines Leuchtturms. [[BUILDING_AVAILABLE_ON_OUTPOSTS]], benötigt aber eine Kolonie, um in Betrieb genommen zu werden. + +Da manuelles Steuern nicht möglich ist, wird eine [[buildingtype BLD_PLANET_BEACON]] oder ein Schiff mit einer [[shippart SP_PLANET_BEACON]] am Zielort benötigt. Das Gebäude zerstört sich anschließend selbst, um eine spätere Weiterreise zu erlauben. + +Um den Antrieb zu nutzen, muss vorher im benachbarten Zielsystem eine [[buildingtype BLD_PLANET_BEACON]] oder eine [[shippart SP_PLANET_BEACON]] platziert werden. Dann wird der Fokus des Planeten auf Planetenantrieb gesetzt, um den Transit einzuleiten. Eine Bake kann nicht im selben Zug zum Einsatz kommen, in dem sie fertiggebaut wurde beziehungsweise eingetroffen ist.''' + +BLD_PLANET_BEACON +Planetarbake + +BLD_PLANET_BEACON_DESC +'''Kennzeichnet das Ziel für den [[buildingtype BLD_PLANET_DRIVE]]. [[BUILDING_AVAILABLE_ON_OUTPOSTS]].''' + +BLD_ART_PLANET +Künstlicher Planet + +BLD_ART_PLANET_DESC +'''Wandelt einen Asteroidengürtel oder Gasriesen in einen künstlichen Planeten um. Aus [[PT_ASTEROIDS]] werden [[SZ_TINY]]e [[PT_BARREN]]e Planeten und aus [[PT_GASGIANT]]n werden [[SZ_HUGE]]e [[PT_BARREN]]e Planeten. [[ARTIFICIAL_PLANET_PROCESS_LOCATION]]''' + +BLD_ART_FACTORY_PLANET +Künstliche Fabrikwelt + +BLD_ART_FACTORY_PLANET_DESC +Wandelt einen Asteroidengürtel oder Gasriesen in einen künstlichen Planeten um und bevölkert ihn mit [[species SP_EXOBOT]]s. Aus [[PT_ASTEROIDS]] werden [[SZ_TINY]]e [[PT_BARREN]]e Planeten und aus [[PT_GASGIANT]]n werden [[SZ_HUGE]]e [[PT_BARREN]]e Planeten. [[ARTIFICIAL_PLANET_PROCESS_LOCATION]] + +BLD_ART_PARADISE_PLANET +Künstliche Paradieswelt + +BLD_ART_MOON +Künstlicher Mond + +BLD_ART_MOON_DESC +'''Formt einen künstlichen Mond mit identischer Umlaufzeit und Rotationsperiode. Verleiht dem Planeten die Besonderheit [[special RESONANT_MOON_SPECIAL]]. + +Dies kann nicht auf einem [[PT_GASGIANT]]n oder Asteroidengürtel gebaut werden. + +Während natürliche Satelliten für Planetenkörper keineswegs ungewöhnlich sind, sind Monde, deren Umlaufzeit ihrer Rotationsperiode gleicht, äußerst selten. Ein solcher Satellit hätte eine dem Planeten stets abgewandte Seite, auf der sich riesige Bauten, die sonst vom Planeten aus leicht zu erkennen wären, vollkommen heimlich errichten ließen, außer Sicht der ahnungslosen Einwohner. Schiffe im Orbit des Planeten könnten sich außerdem leichter vor Angreifern verbergen.''' + +BLD_SOL_REJUV +Solarverjüngerer + +BLD_SOL_REJUV_DESC +Versorgt einen Stern mit zusätzlichem Brennstoff, um ihn künstlich zu verjüngern. Der Stern wird um eine Stufe jünger, gemäß der folgenden Reihenfolge: [[STAR_RED]], [[STAR_ORANGE]], [[STAR_YELLOW]], [[STAR_WHITE]], [[STAR_BLUE]]. Dieser Prozess kann auch von einem [[encyclopedia OUTPOSTS_TITLE]] aus durchgeführt werden. + +BLD_PLANET_CLOAK +Planetares Tarngerät + +BLD_PLANET_CLOAK_DESC +'''Verleiht dem Planeten einen hohen Bonus auf [[metertype METER_STEALTH]]. + +EIne Tarnvorrichtung, die stark genug ist, ganze Welten zu verbergen.''' + +BLD_SPATIAL_DISTORT_GEN +Raumstörungserzeuger + +BLD_SPATIAL_DISTORT_GEN_DESC +Ändert die dimensionalen Eigenschaften angrenzender Sternstraßen. Feindschiffe, die sich auf das System zu bewegen, werden bis zu ihrer Ankunft jeden Zug um 40uu zurückgedrängt. Wird durch den Raumstörungsfokus aktiviert. + +BLD_STARGATE +Sternentor + +BLD_STARGATE_DESC +'''Schafft eine Abkürzung durch die Raumzeit zwischen diesem und anderen [[BLD_STARGATE]]en im Imperium. Der Energieverbrauch des Tores, während es verwendet wird, ist hoch genug, um den ganzen Planeten zu lähmen. Aufgrund von Quantumschwankungen ist es unmöglich, den Ankunftsort eines Schiffes zu bestimmen, während mehr als ein Tor empfangsbereit ist. + +Um das Tor zu nutzen, muss der Fokus des Startplaneten auf [[FOCUS_STARGATE_SEND]] und der Fokus des Zielplaneten auf [[FOCUS_STARGATE_RECEIVE]] gestellt werden.''' + +BLD_GAS_GIANT_GEN +Gasriesengenerator + +BLD_STARLANE_BORE +Sternstraßenbohrer + +BLD_STARLANE_BORE_DESC +Erschafft eine neue Sternstraße zum nächtgelegen möglichen System. Die Straße kann keine existierenden Straßen überschneiden oder zu nahe an anderen Systemen vorbei gehen. + +BLD_STARLANE_NEXUS +Sternstraßenkonnex + +BLD_STARLANE_NEXUS_DESC +Erschafft neue Sternstraßen zu umliegenden Systemen, sofern die entstehenden Straßen keine existierenden Straßen überschneiden oder zu nahe an anderen Systemen vorbei gehen würden. + +BLD_XENORESURRECTION_LAB +Xeno-Reanimierungslabor + +BLD_XENORESURRECTION_LAB_DESC +Sammelt Proben ausgestorbener Spezies, die in [[special ANCIENT_RUINS_SPECIAL]] gefunden wurden. Ermöglicht die Kolonisierung von via [[metertype METER_SUPPLY]] verbundener Planeten mit ausgestorbenen Spezies. Kann nur auf Planeten gebaut werden, in denen in Ruinen gut erhaltene Leichen ausgestorbener Spezies ausgegraben wurden. + +BLD_COLONY_BASE +Siedlungsbasis + +BLD_COLONY_BASE_DESC +Erstellt ein [[predefinedshipdesign SD_COLONY_BASE]], welches zur Besiedlung eines Planeten im gleichen System genutzt werden kann. Kann nur auf Planeten gebaut werden, die eine Bevölkerung von 3 oder mehr haben. + +BLD_EXPERIMENTOR_OUTPOST +Experimentor-Außenposten + +BLD_EXPERIMENTOR_OUTPOST_DESC +Erin Außenposten der Experimentoren, zuständig für höchst komplexe Experimente. Dient zusätzlich als Tarnvorrichtung, um potentielle Störenfriede abzuhalten. + +BLD_NOVA_BOMB_ACTIVATOR +Novabombenauslöser + +BLD_NOVA_BOMB_ACTIVATOR_DESC +Aktiviert alle [[shippart SP_NOVA_BOMB]]n in bis zu einem Sternensprung Entfernung. + +BLD_SCRYING_SPHERE +Hellsichtskugel + +BLD_SCRYING_SPHERE_DESC +Macht alle Planeten sichtbar, die ebenfalls eine [[BLD_SCRYING_SPHERE]] haben. + +BLD_COL_ABADDONI +Abaddoni-Kolonie + +BLD_COL_ABADDONI_DESC +[[BLD_COL_PART_1]] [[species SP_ABADDONI]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_ABADDONI]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_BANFORO +Banforo-Kolonie + +BLD_COL_BANFORO_DESC +[[BLD_COL_PART_1]] [[species SP_BANFORO]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_BANFORO]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_CHATO +Chato-Kolonie + +BLD_COL_CHATO_DESC +[[BLD_COL_PART_1]] [[species SP_CHATO]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_CHATO]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_CRAY +Cray-Kolonie + +BLD_COL_CRAY_DESC +[[BLD_COL_PART_1]] [[species SP_CRAY]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_CRAY]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_DERTHREAN +Derthrean-Kolonie + +BLD_COL_DERTHREAN_DESC +[[BLD_COL_PART_1]] [[species SP_DERTHREAN]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_DERTHREAN]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_EAXAW +Eaxaw-Kolonie + +BLD_COL_EAXAW_DESC +[[BLD_COL_PART_1]] [[species SP_EAXAW]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_EAXAW]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_EGASSEM +Egassem-Kolonie + +BLD_COL_EGASSEM_DESC +[[BLD_COL_PART_1]] [[species SP_EGASSEM]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_EGASSEM]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_ETTY +Etty-Kolonie + +BLD_COL_ETTY_DESC +[[BLD_COL_PART_1]] [[species SP_ETTY]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_ETTY]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_FURTHEST +Furthest-Kolonie + +BLD_COL_FURTHEST_DESC +[[BLD_COL_PART_1]] [[species SP_FURTHEST]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_FURTHEST]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_FULVER +Fulver-Kolonie + +BLD_COL_FULVER_DESC +[[BLD_COL_PART_1]] [[species SP_FULVER]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_FULVER]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_GEORGE +George-Kolonie + +BLD_COL_GEORGE_DESC +[[BLD_COL_PART_1]] [[species SP_GEORGE]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_GEORGE]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_GYSACHE +Gysache-Kolonie + +BLD_COL_GYSACHE_DESC +[[BLD_COL_PART_1]] [[species SP_GYSACHE]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_GYSACHE]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_HAPPY +Happybirthday-Kolonie + +BLD_COL_HAPPY_DESC +[[BLD_COL_PART_1]] [[species SP_HAPPY]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_HAPPY]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_HHHOH +Hhhoh-Kolonie + +BLD_COL_HHHOH_DESC +[[BLD_COL_PART_1]] [[species SP_HHHOH]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_HHHOH]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_HUMAN +Menschenkolonie + +BLD_COL_HUMAN_DESC +[[BLD_COL_PART_1]] [[species SP_HUMAN]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_HUMAN]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_KILANDOW +Kilandow-Kolonie + +BLD_COL_KILANDOW_DESC +[[BLD_COL_PART_1]] [[species SP_KILANDOW]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_KILANDOW]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_KOBUNTURA +Kobuntura-Kolonie + +BLD_COL_KOBUNTURA_DESC +[[BLD_COL_PART_1]] [[species SP_KOBUNTURA]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_KOBUNTURA]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_LAENFA +Laenfa-Kolonie + +BLD_COL_LAENFA_DESC +[[BLD_COL_PART_1]] [[species SP_LAENFA]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_LAENFA]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_MISIORLA +Misiorla-Kolonie + +BLD_COL_MISIORLA_DESC +[[BLD_COL_PART_1]] [[species SP_MISIORLA]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_MISIORLA]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_MUURSH +Mu Ursh-Kolonie + +BLD_COL_MUURSH_DESC +[[BLD_COL_PART_1]] [[species SP_MUURSH]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_MUURSH]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_PHINNERT +Phinnert-Kolonie + +BLD_COL_PHINNERT_DESC +[[BLD_COL_PART_1]] [[species SP_PHINNERT]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_PHINNERT]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_REPLICON +Replicon-Kolonie + +BLD_COL_REPLICON_DESC +[[BLD_COL_PART_1]] [[species SP_REPLICON]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_REPLICON]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_SCYLIOR +Scylior-Kolonie + +BLD_COL_SCYLIOR_DESC +[[BLD_COL_PART_1]] [[species SP_SCYLIOR]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_SCYLIOR]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_SETINON +Setinon-Kolonie + +BLD_COL_SETINON_DESC +[[BLD_COL_PART_1]] [[species SP_SETINON]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_SETINON]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_SILEXIAN +Silexian-Kolonie + +BLD_COL_SILEXIAN_DESC +[[BLD_COL_PART_1]] [[species SP_SILEXIAN]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_SILEXIAN]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_SLY +Sly-Kolonie + +BLD_COL_SLY_DESC +[[BLD_COL_PART_1]] [[species SP_SLY]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_SLY]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_SSLITH +Sslith-Kolonie + +BLD_COL_SSLITH_DESC +[[BLD_COL_PART_1]] [[species SP_SSLITH]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_SSLITH]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_TAEGHIRUS +Tae Ghirus-Kolonie + +BLD_COL_TAEGHIRUS_DESC +[[BLD_COL_PART_1]] [[species SP_TAEGHIRUS]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_TAEGHIRUS]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_TRITH +Trith-Kolonie + +BLD_COL_TRITH_DESC +[[BLD_COL_PART_1]] [[species SP_TRITH]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_TRITH]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_UGMORS +Ugmors-Kolonie + +BLD_COL_UGMORS_DESC +[[BLD_COL_PART_1]] [[species SP_UGMORS]]-Kolonie. [[BLD_COL_PART_2]] [[species SP_UGMORS]]-Kolonie [[BLD_COL_PART_3]]. + +BLD_COL_EXOBOT +Exobot-Kolonie + +BLD_COL_EXOBOT_DESC +Dieses Gebäude kann nur auf einem [[encyclopedia OUTPOSTS_TITLE]] errichtet werden und erweitert den [[OUTPOSTS_TITLE]] in eine [[species SP_EXOBOT]]-Kolonie. Die Exobots werden als Teil der Kolonie vor Ort gebaut und müssen daher nicht von einer anderen Exobot-Kolonie bereitgestellt werden. Die Kolonie muss zum Bau aber dennoch durch einen bewohnten Planeten des Imperiums versorgt werden und ist außerdem teurer als andere Kolonien. + +BLD_COL_SUPER_TEST +Supertester-Kolonie + +BLD_COL_SUPER_TEST_DESC +[[BLD_COL_PART_1]] Supertester-Kolonie. [[BLD_COL_PART_2]] Supertester-Kolonie [[BLD_COL_PART_3]]. + + + +## +## Hull and ship part description templates +## + +# %1% ship part capacity (fuel, troops, colonists, fighters). +PART_DESC_CAPACITY +Fassungsvermögen: %1% + +# %1% ship part strength (other). +PART_DESC_STRENGTH +Stärke: %1% + +# %1% ship part strength (shield). +PART_DESC_SHIELD_STRENGTH +Schildstärke: %1% + +# %1% ship part damage done (direct fire weapons). +# %2% number of shots done per attack. +PART_DESC_DIRECT_FIRE_STATS +'''[[encyclopedia DAMAGE_TITLE]]: %1% +Kadenz: %2%/Runde''' + +# %1% number of deployable fighters. +# %2% ship part damage done (fighters). +PART_DESC_HANGAR_STATS +'''Fliegerkapazität: %1% +[[encyclopedia DAMAGE_TITLE]]: %2%''' + + + +## +## Ship parts +## + +FT_WEAPON_1 +Jägerbewaffnung + +FT_WEAPON_1_DESC +Prototyp-Waffensystem für Jagdflieger. Nicht als Schiffsbewaffnung geeignet. + +FT_BAY_1 +Startrampe + +FT_BAY_1_DESC +Ein Dock, aus dem Jagdflieger abheben können. + +FT_HANGAR_0 +Lockvogelhangar + +FT_HANGAR_0_FIGHTER +Lockvogel + +FT_HANGAR_0_DESC +Lagersystem für unbewaffnete Köderflieger. + +FT_HANGAR_1 +Abfangjägerhangar + +FT_HANGAR_1_FIGHTER +Abfangjäger + +FT_HANGAR_1_DESC +Lagersystem für minimal bewaffnete Kampfflieger. + +FT_HANGAR_2 +Jagdfliegerhangar + +FT_HANGAR_2_FIGHTER +Jagdflieger + +FT_HANGAR_2_DESC +Lagersystem für leicht bewaffnete Kampfflieger. + +FT_HANGAR_3 +Jagdbomberhangar + +FT_HANGAR_3_FIGHTER +Jagdbomber + +FT_HANGAR_3_DESC +Lagersystem für moderat bewaffnete Kampfflieger. + +FT_HANGAR_4 +Kampfbomberhangar + +FT_HANGAR_4_FIGHTER +Kampfbomber + +FT_HANGAR_4_DESC +Lagersystem für schwer bewaffnete Kampfflieger. + +FT_HANGAR_KRILL +Krillschwarm + +FT_HANGAR_KRILL_DESC +Krillschwarm + +FT_BAY_KRILL +Krillschwarm + +FT_BAY_KRILL_DESC +Wenn ein ausreichend großer Krillschwarm Raumschiffen begegnet, verlassen einzelne Krill den Schwarm, um anzugreifen. + +SR_WEAPON_0_1 +Flugabwehrkanone + +SR_WEAPON_0_1_DESC +'''Verursacht an Schiffen nur geringfügigen [[encyclopedia DAMAGE_TITLE]], ist aber kostengünstig und effektiv gegen Jagdflieger. + +[[SR_WEAPON_0_1]]n erhalten 1 zusätzlichen Schuss für jeden Level in der Pilotenfähigkeit der Spezies, die sie konstruiert hat (-1 Schuss für schlechte Piloten).''' + +SR_WEAPON_1_1 +Massenkatapult + +SR_WEAPON_2_1 +Laserwaffe + +SR_WEAPON_3_1 +Plasmakanone + +SR_WEAPON_4_1 +Todesstrahl + +SR_SPINAL_ANTIMATTER +Antimateriekanone + +SR_SPINAL_ANTIMATTER_DESC +Ein großes, rückenmontiertes Massenkatapult, welches mit Antimaterie feuert. + +SR_JAWS +Kiefer + +SR_JAWS_DESC +Maul eines Weltraummonsters, dass Schiffe beschädigen kann. Die größten Monster können sogar ganze Flotten verschlingen. + +SR_SPINES +Klaue + +SR_SPINES_DESC +Gliedmaß eines Weltraummonsters, kann mit nur einem Schlag schweren Schaden verursachen. + +SR_TENTACLE +Tentakel + +SR_TENTACLE_DESC +Gliedmaß eines Kraken, das Schiffe beschädigen kann. + +SR_PLASMA_DISCHARGE +Plasmaausstoß + +SR_PLASMA_DISCHARGE_DESC +Plasmaangriff eines Weltraummonsters, kann die meisten Panzerungen schmelzen. + +SR_ICE_BEAM +Riesenlaser + +SR_ICE_BEAM_DESC +Schusswaffe, die in den Körper eines Monsters integriert wurde. + +SR_ION_CANNON +Ionenkanone + +SR_ION_CANNON_DESC +Akzeptable Durchschlagskraft und Masse. + +PD_PULSE_LASER +Impulslaser + +PD_PULSE_LASER_DESC +Kurze Reichweite, hohe Feuerrate. + +PD_PHASOR +Phaser + +PD_PHASOR_DESC +Schnelle, starke Waffe zur Punktverteidigung. + +PD_PARTICLE_BEAM +Partikelstrahl + +PD_PARTICLE_BEAM_DESC +Ultimative Waffe für Punktverteidigung. + +FI_INTERCEPTOR +Abfangjäger + +FI_INTERCEPTOR_DESC +Zwölf schnelle Abfangjäger, die Raketen und feindliche Jäger abwehren. + +FI_BOMBER +Jagdbomber + +FI_BOMBER_DESC +Sechs Jagdbomber, die auf Schiffe und Planeten feuern können. + +FI_RECON +Aufklärungsjäger + +FI_RECON_DESC +Zwölf schnelle, unbewaffnete Jäger zur Aufklärung. + +FI_BIOINTERCEPTOR +Bio-Abfangjäger + +FI_BIOINTERCEPTOR_DESC +Sechsunddreißig lebende Abfangjäger, die Raketen und feindliche Jäger abwehren. Deutlich leistungsfähiger als gewöhnliche Abfangjäger. [[BLD_SHIPYARD_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED_ANY_SYSTEM]] + +FI_BIOBOMBER +Bio-Jagdbomber + +FI_BIOBOMBER_DESC +Achtzehn Jagdbomber, die auf Schiffe und Planeten feuern können. Deutlich leistungsfähiger als gewöhnliche Jagdbomber. [[BLD_BIONEURAL_REQUIRED_ANY_SYSTEM]] + +LR_NUCLEAR_MISSILE +Atomrakete + +LR_NUCLEAR_MISSILE_DESC +Hohe Reichweite und akzeptable Durchschlagskraft. + +LR_SPECTRAL_MISSILE +Spektralrakete + +LR_SPECTRAL_MISSILE_DESC +Eine schnelle Tarnrakete. + +LR_NEUTRONIUM_PLATE_NUC_MIS +Neutroniumüberzogene Atomrakete + +LR_NEUTRONIUM_PLATE_NUC_MIS_DESC +Schwer gepanzerte Atomrakete. + +LR_NEUTRONIUM_PLATE_SPEC_MIS +Neutroniumüberzogene Spektralrakete + +LR_NEUTRONIUM_PLATE_SPEC_MIS_DESC +Schwer gepanzerte Spektralrakete. + +LR_ANTIMATTER_TORPEDO +Antimaterietorpedo + +LR_ANTIMATTER_TORPEDO_DESC +Eine schnelle Langstreckenrakete, angetrieben durch den [[tech SHP_SPACE_FLUX_DRIVE]] und ausgestattet mit einem mächtigen Antimaterie-Sprengkopf. [[BLD_SHIPYARD_CON_ADV_ENGINE_REQUIRED_ANY_SYSTEM]] + +LR_PLASMA_TORPEDO +Plasmatorpedo + +LR_PLASMA_TORPEDO_DESC +Eine schnelle Langstreckenrakete mit exzellenter [[metertype METER_STEALTH]], angetrieben durch den [[tech SHP_TRANSSPACE_DRIVE]] und ausgestattet mit einem verheerenden Plasmasprengkopf. [[BLD_SHIPYARD_CON_ADV_ENGINE_REQUIRED_ANY_SYSTEM]] + +DT_DETECTOR_1 +Optischer Scanner + +DT_DETECTOR_1_DESC +Kurzstreckenaufklärung. + +DT_DETECTOR_2 +Aktives Radar + +DT_DETECTOR_2_DESC +Mittelstreckenaufklärung. + +DT_DETECTOR_3 +Neutronen Scanner + +DT_DETECTOR_3_DESC +Langstreckenaufklärung. + +DT_DETECTOR_4 +Sensoren + +DT_DETECTOR_4_DESC +Überlangstreckenaufklärung. + +FU_ENGINE_PART_LIMIT +Schiffe können nur ein Triebwerk haben. + +FU_IMPROVED_ENGINE_COUPLINGS +Verbesserte Triebwerkkopplung + +FU_IMPROVED_ENGINE_COUPLINGS_DESC +'''Bessere Bauteile bedeuten bessere Leistung. + +- Erhöht [[metertype METER_SPEED]] um 20. +- [[FU_ENGINE_PART_LIMIT]]''' + +FU_N_DIMENSIONAL_ENGINE_MATRIX +N-Dimensionale Triebwerkmatrix + +FU_N_DIMENSIONAL_ENGINE_MATRIX_DESC +'''Durch Forschung am N-Dimensionalen Subraum konnte eine effizientere Triebwerkmatrix enteickelt werden. + +- Erhöht [[metertype METER_SPEED]] um 40. +- [[FU_ENGINE_PART_LIMIT]]''' + +FU_SINGULARITY_ENGINE_CORE +Singularitätstriebkern + +FU_SINGULARITY_ENGINE_CORE_DESC +'''Eine kleinere Variante des [[buildingtype BLD_HYPER_DAM]]s zum Einsatz auf Schiffen. Steigert die Stromerzeugung immens. + +- Erhöht [[metertype METER_SPEED]] um 80. +- [[FU_ENGINE_PART_LIMIT]]''' + +FU_TRANSPATIAL_DRIVE +Zwischenraumantrieb + +FU_TRANSPATIAL_DRIVE_DESC +'''Ein unglaublich starker Antrieb, der die Raumzeit krümmt. + +- Erhöht [[metertype METER_SPEED]] um 60 +- Erhöht [[metertype METER_STEALTH]] um 40 +- [[NO_STACK_STEALTH_SHIP_PARTS]] +- [[FU_ENGINE_PART_LIMIT]]''' + +FU_BASIC_TANK +Zusatztank + +FU_BASIC_TANK_DESC +Erhöht die [[metertype METER_FUEL]]kapazität, was einen zusätzlichen Sprung ermöglicht. Weiterentwicklungen erlauben weitere Sprünge pro Tank. + +FU_ZERO_FUEL +Nullpunkt-Treibstoffgenerator + +FU_ZERO_FUEL_DESC +Sammelt Energie aus Nullpunktgeneration und wandelt sie in [[metertype METER_FUEL]] um. + +FU_RAMSCOOP +Bussardkollektor + +FU_RAMSCOOP_DESC +Erzeugt langsam [[metertype METER_FUEL]] in Sternensystemen, indem es den Wasserstoff im interplanetaren Raum sammelt. + +ST_CLOAK_1 +Elektromagnetische Dämpfung + +ST_CLOAK_1_DESC +'''Schwache Tarnung (+20) + +Verbessert die [[metertype METER_STEALTH]] des Schiffes, auf dem es installiert wurde, indem es die elektromagnetische Abstrahlung der Schiffssysteme dämpft. Kann nur kleine Emissionsmengen kompensieren und wird von vorhandenen Schiffsteilen, die hohe Strahlungsmengen abgeben, überladen. [[NO_STACK_STEALTH_SHIP_PARTS]]''' + +ST_CLOAK_2 +Absorptionsfeld + +ST_CLOAK_2_DESC +'''Moderate Tarnung (+40) + +Verbessert die [[metertype METER_STEALTH]] des Schiffes, auf dem es installiert wurde, indem es das Schiff mit einem Absorptionsfeld umgibt es und damit quasi in einen idealen schwarzen Körper umwandelt. [[NO_STACK_STEALTH_SHIP_PARTS]]''' + +ST_CLOAK_3 +Dimensionsmantel + +ST_CLOAK_3_DESC +'''Gute Tarnung (+60) + +Verbessert die [[metertype METER_STEALTH]] des Schiffes, auf dem es installiert wurde, indem es das Schiff in einem Dimensionsriss verstackt. [[NO_STACK_STEALTH_SHIP_PARTS]]''' + +ST_CLOAK_4 +Phasenmantel + +ST_CLOAK_4_DESC +'''Sehr gute Tarnung (+80) + +Verbessert die [[metertype METER_STEALTH]] des Schiffes, auf dem es installiert wurde, indem es die Frequenzen aller Materiewellen moduliert und sie der Umgebungsstrahlung angleicht. [[NO_STACK_STEALTH_SHIP_PARTS]]''' + +AR_STD_PLATE +Standardpanzerplatten + +AR_STD_PLATE_DESC +Erhöht die [[metertype METER_STRUCTURE]] des Schiffsrumpfs. Standardpanzerplatten bieten eine nur mäßige Erweiterung der Widerstandsfähigkeit. + +AR_ZORTRIUM_PLATE +Zortriumpanzerplatten + +AR_ZORTRIUM_PLATE_DESC +Erhöht die [[metertype METER_STRUCTURE]] des Schiffsrumpfs. Bietet höheren Widerstand als [[shippart AR_STD_PLATE]]. + +AR_DIAMOND_PLATE +Diamantpanzerplatten + +AR_DIAMOND_PLATE_DESC +Erhöht die [[metertype METER_STRUCTURE]] des Schiffsrumpfs. Bietet höheren Widerstand als [[shippart AR_ZORTRIUM_PLATE]]. + +AR_XENTRONIUM_PLATE +Xentroniumpanzerplatten + +AR_XENTRONIUM_PLATE_DESC +Erhöht die [[metertype METER_STRUCTURE]] des Schiffsrumpfs. Extrem starke und robuste Panzerplatten. + +AR_ROCK_PLATE +Gesteinpanzerplatten + +AR_ROCK_PLATE_DESC +Erhöht die [[metertype METER_STRUCTURE]] des Schiffsrumpfs. Eine billige, robuste Alternative zu Legierungen. [[BLD_SHIPYARD_AST_REF_REQUIRED_ANY_SYSTEM_SHIP_PARTS_TEXT]] + +AR_CRYSTAL_PLATE +Kristallpanzerplatten + +AR_CRYSTAL_PLATE_DESC +Erhöht die [[metertype METER_STRUCTURE]] des Schiffsrumpfs. Eine äußerst robuste Panzerung, mit modernen Kristallisierungstechniken hergestellt. [[BLD_SHIPYARD_AST_REF_REQUIRED_ANY_SYSTEM_SHIP_PARTS_TEXT]] + +AR_NEUTRONIUM_PLATE +Neutroniumpanzerplatten + +AR_NEUTRONIUM_PLATE_DESC +Erhöht die [[metertype METER_STRUCTURE]] des Schiffsrumpfs. Die stärkste und widerstandsfähigste Panzerung. [[MACRO_NEUTRONIUM_BUILDINGS]] + +AR_PRECURSOR_PLATE +Vorläuferpanzerplatten + +AR_PRECURSOR_PLATE_DESC +Erhöht die [[metertype METER_STRUCTURE]] des Schiffsrumpfs. Bemerkenswert wirksame Panzerung aus unbekanntem Material. + +SH_DEFENSE_GRID +Schutzfeld + +SH_DEFENSE_GRID_DESC +'''Verleiht einen einfachen [[metertype METER_SHIELD]], der erlittenen [[encyclopedia DAMAGE_TITLE]] durch schwächere Waffen absorbiert oder zumindest deutlich verringert. + +[[NO_STACK_SHIELDS_SHIP_PARTS]]''' + +SH_DEFLECTOR +Deflektorschild + +SH_DEFLECTOR_DESC +'''Verleiht einen verbesserten [[metertype METER_SHIELD]], der erlittenen [[encyclopedia DAMAGE_TITLE]] durch mittlere Waffen absorbiert oder zumindest deutlich verringert. + +[[NO_STACK_SHIELDS_SHIP_PARTS]]''' + +SH_PLASMA +Plasmaschild + +SH_PLASMA_DESC +'''Verleiht einen starken [[metertype METER_SHIELD]], der erlittenen [[encyclopedia DAMAGE_TITLE]] durch fortschrittliche Waffen absorbiert oder zumindest deutlich verringert. + +[[NO_STACK_SHIELDS_SHIP_PARTS]]''' + +SH_BLACK +Schwarzschild + +SH_BLACK_DESC +'''Der stärkste [[metertype METER_SHIELD]], kann nur von den mächtigsten Waffen durchdrungen werden. Entwicklung und Herstellung kosten entsprechend. + +[[NO_STACK_SHIELDS_SHIP_PARTS]]''' + +SH_MULTISPEC +Multispectraler Schild + +SH_MULTISPEC_DESC +'''Mächtiger [[metertype METER_SHIELD]], der das Schiff sogar im Inneren von Sternen schützt. Erhöht [[metertype METER_STEALTH]] beim Aufenthalt in Sternsystemen ohne [[STAR_BLACK]] oder [[STAR_NEUTRON]] und lässt das Schiff im Kampf Sterne betreten. + +[[NO_STACK_SHIELDS_SHIP_PARTS]]''' + +SH_ROBOTIC_INTERFACE_SHIELDS +Robotikinterface: Schild + +CO_COLONY_POD +Besiedlungsmodul + +CO_COLONY_POD_DESC +'''Grundlegende Einrichtungen für Siedler, um die Reise zu einem neuen Planeten zu überleben. Ermöglicht es einem Schiff, neue Welten zu besiedeln. + +[[COLONY_SHIP_PARTS_MIN_POP]] [[COLONY_SHIP_PARTS_UPKEEP_COST]]''' + +CO_SUSPEND_ANIM_POD +Kälteschlaf-Besiedlungsmodul + +CO_SUSPEND_ANIM_POD_DESC +'''Siedler werden während der Reise im Kälteschlaf gehalten, was die Notwendigkeit der Nahrungsversorgung beseitigt und die Anzahl der Kolonisten, die transportiert werden können, drastisch erhöht. + +[[COLONY_SHIP_PARTS_MIN_POP]] [[COLONY_SHIP_PARTS_UPKEEP_COST]]''' + +CO_OUTPOST_POD +Außenpostenmodul + +CO_OUTPOST_POD_DESC +'''Ermöglicht die Gründung unbemannter Stationen auf unbewohnbaren Planeten. [[encyclopedia OUTPOSTS_TITLE]] haben keine [[metertype METER_POPULATION]] und produzieren normalerweise keine Ressourcen, können aber die Umgebung überwachen und bilden [[metertype METER_SUPPLY]]slinien, sofern bestimmte Technologien erforscht sind. [[OUTPOSTS_TITLE]] können zu Kolonien erweitert werden. + +[[COLONY_SHIP_PARTS_UPKEEP_COST]]''' + +GT_TROOP_POD +Bodentruppenkapsel + +GT_TROOP_POD_DESC +'''Trägt zwei Einheiten Bodentruppen samt Ausrüstung, die auf einem Planeten eingesetzt werden kann. Können nur auf Planeten mit mindestens 2 [[metertype METER_TROOPS]] gebaut werden. + +[[TROOP_POD_OPERATION_TEXT]]''' + +GT_TROOP_POD_2 +Cyborg-Bodentruppenkapsel + +GT_TROOP_POD_2_DESC +'''Trägt vier Einheiten von kybernetisch verbesserter Bodentruppen samt Ausrüstung, die auf einem Planeten eingesetzt werden kann. Können nur auf Planeten mit mindestens 4 [[metertype METER_TROOPS]] gebaut werden. + +[[TROOP_POD_OPERATION_TEXT]]''' + +SP_PLANET_BEACON +Mobile Planetarbake + +SP_PLANET_BEACON_DESC +Kennzeichnet das Ziel für den [[buildingtype BLD_PLANET_DRIVE]]. Zum Einsatz muss das Schiff einen Zug lang seine Position halten und der Planet muss in einem verbundenen System sein. + +SP_DISTORTION_MODULATOR +Verzerrungsmodulator + +SP_DISTORTION_MODULATOR_DESC +Unterscheidet zwischen natürlichen geometrischen Unregelmäßigkeiten und Feindschiffen durch Manipulation der von nahen Objekten verursachten Raumzeitverzerrungen. Reduziert [[metertype METER_STEALTH]] aller Objekte im System um 20. Weitere [[SP_DISTORTION_MODULATOR]] im gleichen System haben keinen weiteren Effekt. + +SP_CLOUD +Wolkengenerator + +SP_ASH +Aschegenerator + +SP_DIM +Dimensionsspalter + +SP_VOID +Leere-Entlader + +SP_DEATH_SPORE +Todessporen + +SP_DEATH_SPORE_DESC +Eine biologische Waffe [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_ORGANIC_POP]]. + +SP_BIOTERM +Bioterminierer + +SP_BIOTERM_DESC +Eine mächtige biologische Waffe [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_ORGANIC_POP]]. + +SP_EMP +Elektromagnetischer Impulsgenerator + +SP_EMP_DESC +Eine elektrische Waffe [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_ROBOTIC_POP]]. + +SP_EMO +Elektromagnetischer Überladungsgenerator + +SP_EMO_DESC +Eine mächtige elektrische Waffe [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_ROBOTIC_POP]]. + +SP_SONIC +Schallschockwelle + +SP_SONIC_DESC +Eine Vibrationswaffe [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_LITHIC_POP]]. + +SP_GRV +Schwerkraftimpulsgenerator + +SP_GRV_DESC +Eine mächtige Vibrationswaffe [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_LITHIC_POP]]. + +SP_DARK_RAY +Finsterstrahl + +SP_DARK_RAY_DESC +Eine negative Energie-Waffe [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_PHOTOTROPHIC_POP]]. + +SP_VOID_SHADOW +Leereschatten + +SP_VOID_SHADOW_DESC +Eine mächtige negative Energie-Waffe [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_PHOTOTROPHIC_POP]]. + +SP_CHAOS_WAVE +Chaoswelle + +SP_NOVA_BOMB +Novabombe + +SP_NOVA_BOMB_DESC +Eine Waffe, die ganze Systeme auslöschen kann. Wird ausgelöst durch ein Signal von einem [[buildingtype BLD_NOVA_BOMB_ACTIVATOR]] in einem System, dass höchstens einen Sternstraßensprung entfernt ist. + +SP_KRILL_SPAWNER +Krillerzeuger + +SP_KRILL_SPAWNER_DESC +Erzeugt [[predefinedshipdesign SM_KRILL_1]] in Systemen mit unbesetzten [[PT_ASTEROIDS]], die noch kein Krillmonster beinhalten. Erhöht auf unbewaffneten Schiffen [[metertype METER_STEALTH]] substanziell. ([[NO_STACK_STEALTH_SHIP_PARTS]]) + +SP_SOLAR_CONCENTRATOR +Solarkonzentrator + +SP_SOLAR_CONCENTRATOR_DESC +Nutzt den phototropischen Effekt von Schiffen mit [[encyclopedia HULL_LINE_ORGANIC]], um [[tech SHP_WEAPON_2_1]]n zu verstärken. Die Effizienz hängt von der Helligkeit des örtlichen Sterns ab. Zu erwarten ist ein Schadensbonus zwischen 1,5 und 4,5. Die Erforschung von [[tech SHP_SOLAR_CONNECTION]]-Technologie wird empfohlen, um den Effekt zu maximieren. + +SHP_SOLAR_CONNECTION +Solarnetz + +SHP_SOLAR_CONNECTION_DESC +Eine Erweiterung für den [[shippart SP_SOLAR_CONCENTRATOR]]. Erlaubt es der Besatzung, ein Energienetz zwischen Schiffen des selben Imperiums anzulegen. Das [[SHP_SOLAR_CONNECTION]] sammelt zusätzlichen Photonen, die gleichmäßig zwischen allen Schiffen mit [[encyclopedia HULL_LINE_ORGANIC]] aufgeteilt werden. Theoretisch kann die Intensität von [[tech SHP_WEAPON_2_1]]n im Idealfall um bis zu 130% erhöht werden. Realistischere Schätzungen prognostizieren eine Steigerung um 80% bei 100 Konzentratoren, bei 20 Konzentratoren eine Steigerung um 50%. Die dünnen [[SHP_SOLAR_CONNECTION]] reißen beim Eintritt in eine Sternstraße, der Effekt endet kurz darauf. + +SHP_SOLAR_CONNECTION_SHORT_DESC +Bildet ein Energienetz zwischen [[SP_SOLAR_CONCENTRATOR]]en. + +SHP_ORGANIC_WAR_ADAPTION +Organische Kriegsanpassung + +SHP_ORGANIC_WAR_ADAPTION_DESC +Schiffe mit [[encyclopedia HULL_LINE_ORGANIC]] können Photonenenergie sammeln und sie an andere Schiffssysteme weiterleiten, um sie zu verstärken. + +SHP_ORGANIC_WAR_ADAPTION_SHORT_DESC +Erhöht die Stärke von [[SR_WEAPON_2_1]] abhängig vom Stern. + +PR_MOBILE_ASSEMBLER +Mobile Schiffswerft + +PR_MOBILE_ASSEMBLER_DESC +Ermöglicht den Bau von Schiffen abseits von Planeten. + +RS_AUTOLAB +Automatisches Labor + +RS_AUTOLAB_DESC +Generiert Forschung. + +RS_AUTOFACTORY +Automatische Fabrik + +RS_AUTOFACTORY_DESC +Generiert Produktion. + +RS_AUTOTRADER +Automatischer Händler + +RS_AUTOTRADER_DESC +Generiert Handel. + + +## +## Ship hulls +## + +SH_ROBOTIC +Robotikrumpf + +SH_ROBOTIC_DESC +'''Ein vollkommen automatischer Rumpf, dessen Funktiosweise den Prinzipien der [[tech SHP_MIL_ROBO_CONT]] entspricht. Hat vier externe sowie einen intenen Steckplatz und repariert jede Runde 2 Punkte [[metertype METER_STRUCTURE]], sofern das Schiff nicht in einen Kampf verwickelt ist. + +[[metertype METER_STEALTH]] ist durchschnittlich, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist durchschnittlich. + +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]]''' + +SH_SPATIAL_FLUX +Raumflussrumpf + +SH_SPATIAL_FLUX_DESC +'''Dieser kleine Rumpf kann dank des [[tech SHP_SPACE_FLUX_DRIVE]]s mit guter Geschwindigkeit über weite Distanzen reisen. Er hat allerdings nur zwei externe Steckplätze und nur geringe [[metertype METER_STRUCTURE]]. + +Beginnt mit einer [[metertype METER_STEALTH]] von 15. Der Wert erhöht sich um 10 im Stillstand, aber um 30 pro Zug wenn in Transit. Technologische Verbesserungen wie z.B. [[tech SPY_STEALTH_PART_1]] erhöhen [[METER_STEALTH]] um je 10 Punkte. + +[[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist durchschnittlich. + +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]]''' + +SH_SPACE_FLUX_BUBBLE +Raumflussblasenrumpf + +SH_SPACE_FLUX_BUBBLE_DESC +'''Dieser kleine Rumpf kann dank des [[tech SHP_SPACE_FLUX_DRIVE]]s mit guter Geschwindigkeit über weite Distanzen reisen. Er hat allerdings nur einen internen Steckplatz und nur geringe [[metertype METER_STRUCTURE]]. + +Beginnt mit einer [[metertype METER_STEALTH]] von 15. Der Wert erhöht sich um 10 im Stillstand, aber um 30 pro Zug wenn in Transit. Technologische Verbesserungen wie z.B. [[tech SPY_STEALTH_PART_1]] erhöhen [[METER_STEALTH]] um je 10 Punkte. + +[[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist durchschnittlich. + +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]]''' + +SH_SELF_GRAVITATING +Eigengravitierender Rumpf + +SH_SELF_GRAVITATING_DESC +'''Dieser Rumpf ist gewaltig in Größe wie auch in Stärke. Mit sechs externen, zwei internen und sogar einem Kernsteckplatz ist dieser Rumpf einer der fortschrittlichsten überhaupt. Er ist außerdem sehr robust, mit hoher [[metertype METER_STRUCTURE]]. + +[[metertype METER_STEALTH]] is gering, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist durchschnittlich. + +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED]]''' -Die [[tech PRO_INDUSTRY_CENTER_III]] Aufrüstung erhöht den Bonus auf das dreifache des Grundbonuses.''' +SH_NANOROBOTIC +Nanorobotischer Rumpf -#BLD_MEGALITH -#Megalith +SH_NANOROBOTIC_DESC +'''Dieser große automatisierter Rumpf verwendet Millionen von Nanorobotern, um sich selbst zu reparieren. Er hat sechs externe und einen internen Steckplatz. Zwischen Kämpfen können die Nanobots durch schnelle Reparaturen durchführen und jede Runde die [[metertype METER_STRUCTURE]] des Schiffes um ihren derzeitigen Wert wiederherstellen. -BLD_SPACE_ELEVATOR -Weltraumlift +[[metertype METER_STEALTH]] ist durchschnittlich, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist durchschnittlich. -BLD_SPACE_ELEVATOR_DESC -'''Doubles [[metertype METER_SUPPLY]] line range on the planet on which it is built. +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]], eine [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] und eine [[buildingtype BLD_SHIPYARD_CON_NANOROBO]] vorhanden sein.''' -Die Nanokonstruktion eröffnet die Möglichkeit Kabel zu erschaffen, welche zugfest genug sind, um über den geostationären Orbit zu reichen. Magnetisch am Kabel beförderte Fähren sind den teuren, ineffizienten und hochgefährlichen Raketenstarts überlegen. Ein enormes und nahezu unzerreissbares Kabel, das an einem Ende mit einem Satelliten im niedrigen geostationären Orbit verbunden ist, und an dessen Ende zum Planeten hin sich eine Startplattform befindet.''' +SH_TITANIC +Titanenrumpf -BLD_GENOME_BANK -Genombank +SH_TITANIC_DESC +'''Nur durch meisterhafte Ingenieurskunst lässt sich ein Rumpf dieser Größe überhaupt zum Bewegen zu bringen. Er hat sechzehn externe, drei interne und einen Kernsteckplatz und sehr hohe [[metertype METER_STRUCTURE]]. Die enorme Größe des [[SH_TITANIC]] macht ein Verstecken nahezu unmöglich, selbst mit fortschrittlicher Tarntechnologie. -BLD_GENOME_BANK_DESC -'''Gibt dem Imperium Imunität gegen Bioterror. +[[metertype METER_STEALTH]] ist sehr gering, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist durchschnittlich. -Die größte jemals erfasste Datenbank. Die Genombank beinhaltet Genkarten der Genome eines jeden dem Imperium bekannten Organismus. Diese Datenbank ist von unschätzbarem Wert bei der Behandlung von Krankheiten.''' +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED]]''' -BLD_GAIA_TRANS_DESC -'''Increases max Population according to planet size; Tiny: 3, Small: 6, Medium: 9, Large: 12, Huge: 15. +SH_TRANSSPATIAL +Zwischenraumrumpf -Convert a planet into a Gaia world by way of a sentient, almost god-like, cell-based computer program. This planet is wondrous to behold and famous throughout the known galaxy as a celebration of life and harmony. The inhabitants of this world think and act as though part of a larger organism, serving its needs simultaneously with their own.''' +SH_TRANSSPATIAL_DESC +'''Ein experimenteller Rumpf, primär zum Testen neuer Technologien; er hat einen externen und einen Kernsteckplatz und sehr geringe [[metertype METER_STRUCTURE]]. Der [[SH_TRANSSPATIAL]] wurde zusammen mit dem [[shippart FU_TRANSPATIAL_DRIVE]] entwickelt, mit dem er sich als höchst effektiver Späher einsetzen lässt. -BLD_COLLECTIVE_NET -Kollektives Gedankennetzwerk +[[metertype METER_STEALTH]] ist hoch, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist durchschnittlich. -BLD_COLLECTIVE_NET_DESC -'''Increases [[metertype METER_INDUSTRY]] on planets with the Industry focus by 0.5 per Population, and [[metertype METER_RESEARCH]] on Planets with the Research focus by 0.5 per Population. Sternstraße travel within 500 uus will disrupt this building's effectiveness and eliminate these bonuses. +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]], eine [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] und ein [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]] vorhanden sein.''' -Durch die Übertragung eines Geistes in den Cyberspace können unzählige Wesen vereint agieren und so Probleme lösen und Gedanken fassen, die einem einzelnen Wesen nicht möglich wären.''' +SH_LOGISTICS_FACILITATOR +Logistikvermittler -BLD_ENCLAVE_VOID -Enklave der Leere +SH_LOGISTICS_FACILITATOR_DESC +'''Flaggschiff zur Organisation von Besatzungen und anderen Objekten inmitten von Gefechten. Verfügt über sieben externe, zwei interne und einen Kernsteckplatz. Die eingebaute Flotte getarnter Frachter ermöglicht den Transport von Personal und Material während eines Kampfes, so dass alle verbündeten Schiffe sich auch in Zügen, in denen sie an einem Kampf teilgenommen haben, reparieren könnenn; in Zügen zwischen Kämpfen stellen alle Schiffe ihre [[metertype METER_STRUCTURE]] um ihren aktuellen Betrag wieder her, bis sie ganz repariert sind. -BLD_ENCLAVE_VOID_DESC -'''Increases target [[metertype METER_RESEARCH]] on all planets with the Research focus by 0.75 per Population. +[[metertype METER_STEALTH]] ist durchschnittlich, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist durchschnittlich. -Die Bevölkerung eines ganzen Planeten wird genetisch auf den Geist der Leere ausgerichtet und widmet sich ganz der Aufgabe, seine Weisheit dem Imperium mitzuteilen. Die Mitglieder der Enklave der Leere werden als Priester erachtet und als Vermittler höherer Weisheit. In allen wichtigen Angelegenheiten des Imperiums wird ihr Rat eingeholt. Botschafter der Enklave können auch ausgesandt werden, um bei Forschungsprojekten des Imperiums mitzuwirken.''' +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]], eine [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], eine [[buildingtype BLD_SHIPYARD_CON_NANOROBO]], eine [[buildingtype BLD_SHIPYARD_CON_GEOINT]] und ein [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]] vorhanden sein.''' -BLD_ART_BLACK_HOLE -Künstliches schwarzes Loch +SH_ASTEROID +Asteroidenrumpf -BLD_HYPER_DAM -Hyperspatialdamm +SH_ASTEROID_DESC +'''Gebaut aus einem Asteroiden mittlerer Größe und recht kostengünstig. Geräumiger als ein [[shiphull SH_BASIC_LARGE]], mit vier externen und zwei internen Steckplätzen. -BLD_CONC_CAMP -Arbeitslager +[[metertype METER_STEALTH]] ist durchschnittlich, steigt aber deutlich in Systemen mit Asteroidengürteln und im Kampf beim Verstecken in Asteroidengürteln. [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. -BLD_PLANET_DRIVE -Planetary Sternstraße Drive +[[BLD_SHIPYARD_BASE_AST_REQUIRED]]''' -BLD_ART_PLANET -Künstlicher Planet +SH_SMALL_ASTEROID +Kleiner Asteroidenrumpf -BLD_ART_MOON -Künstlicher Mond +SH_SMALL_ASTEROID_DESC +'''Gebaut aus einem kleinen Asteroiden. Verfügt über nur einen externen und einen internen Steckplatz. -BLD_COLONY_BASE -Siedlungsbasis +[[metertype METER_STEALTH]] ist durchschnittlich, steigt aber deutlich in Systemen mit Asteroidengürteln und im Kampf beim Verstecken in Asteroidengürteln. [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. -BLD_COLONY_BASE_DESC -Erstellt ein [[predefinedshipdesign SD_COLONY_BASE]], welches zur Besiedlung eines Planeten im gleichen System genutzt werden kann. Kann nur auf Planeten gebaut werden, die eine Bevölkerung von 3 oder mehr haben. +[[BLD_SHIPYARD_BASE_AST_REQUIRED]]''' +SH_HEAVY_ASTEROID +Schwerer Asteroidenrumpf -## -## Hull and ship part description templates -## +SH_HEAVY_ASTEROID_DESC +'''Gebaut aus einem riesigen Asteroiden. Verfügt über sechs externe und drei interne Steckplätze. -# %1% hull base starlane speed. -# %2% hull base fuel capacity. -# %3% hull base starlane speed. -# %4% hull base structure value. -#HULL_DESC -#'''[[metertype METER_SPEED]]: %1% -#[[metertype METER_FUEL]]: %2% -#[[metertype METER_STRUCTURE]]: %4%''' +[[metertype METER_STEALTH]] ist durchschnittlich, steigt aber deutlich in Systemen mit Asteroidengürteln und im Kampf beim Verstecken in Asteroidengürteln. [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. -# %1% ship part capacity (fuel, troops, colonists, fighters). -PART_DESC_CAPACITY -Fassungsvermögen: %1% +[[BLD_SHIPYARD_BASE_AST_REQUIRED]]''' -# %1% ship part strength (other). -PART_DESC_STRENGTH -Stärke: %1% +SH_CAMOUFLAGE_ASTEROID +Getarnter Asteroidenrumpf -# %1% ship part strength (shield). -PART_DESC_SHIELD_STRENGTH -Schildstärke: %1% +SH_CAMOUFLAGE_ASTEROID_DESC +'''Entworfen, um im Aussehen einem echten Asteroiden zu gleichen. Verfügt über vier interne Steckplätze, aber keine externen. -# %1% ship part damage done (direct fire weapons). -# %2% number of shots done per attack. -PART_DESC_DIRECT_FIRE_STATS -Angriffsschaden: %1% +[[metertype METER_STEALTH]] ist hoch und steigt zusätzlich in Systemen mit Asteroidengürteln und im Kampf beim Verstecken in Asteroidengürteln. [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. +[[BLD_SHIPYARD_BASE_AST_REQUIRED]]''' -## -## Ship parts -## +SH_SMALL_CAMOUFLAGE_ASTEROID +Kleiner Getarnter Asteroidenrumpf -FT_WEAPON_1 -Jägerbewaffnung +SH_SMALL_CAMOUFLAGE_ASTEROID_DESC +'''Verwendet auf der Außenseite die gleichen Bauteile wie der reguläre Getarnte Asteroidenrumpf, allerdings in deutlich kleinerer Ausführung, um seinen [[metertype METER_STEALTH]]-Bonus nicht zu verlieren. Verfügt über einen externen und einen internen Steckplatz. -SR_WEAPON_1_1 -Massenkatapult +[[metertype METER_STEALTH]] ist sehr hoch und steigt zusätzlich in Systemen mit Asteroidengürteln und im Kampf beim Verstecken in Asteroidengürteln. [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. -SR_WEAPON_1_1_DESC -Der Massenkatapult, die Grundbewaffnung. +[[BLD_SHIPYARD_BASE_AST_REQUIRED]]''' -SR_WEAPON_2_1 -Laserwaffe +SH_AGREGATE_ASTEROID +Zusammengesetzter Asteroidenrumpf -SR_WEAPON_2_1_DESC -Der Laser, eine leistungsfähigere Schiffswaffe als der Massenkatapult. +SH_AGREGATE_ASTEROID_DESC +'''Ein massiver Rumpf, geformt die Kombination mehrerer großer Asteroiden. Verfügt über fünfzehn externe und vier interne Steckplätze. -SR_WEAPON_3_1 -Plasmakanonen +[[metertype METER_STEALTH]] ist durchschnittlich, steigt aber deutlich in Systemen mit Asteroidengürteln und im Kampf beim Verstecken in Asteroidengürteln. [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. -SR_WEAPON_3_1_DESC -Die Plasmakanone, eine leistungsfähigere Schiffswaffe als der Laser. +[[BLD_SHIPYARD_BASE_AST_REQUIRED]] [[BLD_SHIPYARD_AST_REF_REQUIRED]]''' -SR_WEAPON_4_1 -Todesstrahl +SH_MINIASTEROID_SWARM +Schwarmasteroidenrumpf -SR_WEAPON_4_1_DESC -Der Todesstrahl, die ultimative Schiffswaffe. +SH_MINIASTEROID_SWARM_DESC +'''Gebaut aus einem kleinen Asteroiden, von dem etliche Mini-Asteroiden abgebrochen wurden, die wiederum vom Rumpf aus gesteuert werden. Der Schwarm schützt das eigentliche Schiff und verleiht so einen Bonus auf [[metertype METER_SHIELD]]. Der Rumpf verfügt über zwei externe Steckplätze, aber keine internen. [[metertype METER_STRUCTURE]] ist sehr gering. -SR_ION_CANNON -Ionenkanone +[[metertype METER_STEALTH]] ist sehr hoch und steigt zusätzlich in Systemen mit Asteroidengürteln und im Kampf beim Verstecken in Asteroidengürteln. [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. -SR_ION_CANNON_DESC -Akzeptable Durchschlagskraft und Masse +[[BLD_SHIPYARD_BASE_AST_REQUIRED]] [[BLD_SHIPYARD_AST_REF_REQUIRED]]''' -PD_PULSE_LASER -Impulslaser +SH_SCATTERED_ASTEROID +Streuasteroidenrumpf -PD_PULSE_LASER_DESC -Kurze Reichweite, hohe Feuerrate +SH_SCATTERED_ASTEROID_DESC +'''Flaggschiff, gebaut aus mehreren großen Asteroiden, von denen einige zum eigentlichen Rumpf kombiniert wurden und andere in Mini-Asteroiden zerbrochen wurden, die wiederum vom Rumpf aus gesteuert werden. Diese Asteroiden schützen nicht nur das Schiff selbst, sondern alle verbündeten Schiffe im System und verleihen ihnen allen einen Bonus auf [[metertype METER_SHIELD]]. Dieser Rumpf verfügt über fünfzehn externe und vier interne Steckplätze. [[metertype METER_STRUCTURE]] ist hoch. -LR_NUCLEAR_MISSILE -Atomare Rakete +[[metertype METER_STEALTH]] ist durchschnittlich, steigt aber deutlich in Systemen mit Asteroidengürteln und im Kampf beim Verstecken in Asteroidengürteln. [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. -LR_NUCLEAR_MISSILE_DESC -Hohe Reichweite und akzeptable Durchschlagskraft +[[BLD_SHIPYARD_BASE_AST_REQUIRED]] [[BLD_SHIPYARD_AST_REF_REQUIRED]]''' -DT_DETECTOR_1 -Optischer Scanner +SH_CRYSTALLIZED_ASTEROID +Kristallisierter Astroidenrumpf -DT_DETECTOR_1_DESC -Kurzstreckenaufklärung. +SH_CRYSTALLIZED_ASTEROID_DESC +'''Gebaut aus einem Asteroiden mittlerer Größe, welcher mit speziellen Kristallisierungstechniken erhärtet wurde. Wie der reguläre [[shiphull SH_ASTEROID]] hat dieser Rumpf vier externe und zwei interne Steckplätze. [[metertype METER_STRUCTURE]] ist extrem hoch. -DT_DETECTOR_2 -Aktives Radar +[[metertype METER_STEALTH]] ist durchschnittlich, steigt aber deutlich in Systemen mit Asteroidengürteln und im Kampf beim Verstecken in Asteroidengürteln. [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. -DT_DETECTOR_2_DESC -Mittelstreckenaufklärung. +[[BLD_SHIPYARD_BASE_AST_REQUIRED]] [[BLD_SHIPYARD_AST_REF_REQUIRED]]''' -DT_DETECTOR_3 -Neutronen Scanner +SH_ORGANIC +Organischer Rumpf -DT_DETECTOR_3_DESC -Langstreckenaufklärung. +SH_ORGANIC_DESC +'''Ein lebender Rumpf mit drei externen und einem internen Steckplatz. +Organisches Wachstum: Startet mit 5 [[metertype METER_STRUCTURE]], aber wächst um 5 [[metertype METER_STRUCTURE]] über 25 Züge. -DT_DETECTOR_4 -Sensoren +[[metertype METER_STEALTH]] ist durchschnittlich, [[metertype METER_DETECTION]] ist gering und [[metertype METER_SPEED]] ist hoch. -DT_DETECTOR_4_DESC -Überlangstreckenaufklärung. +Eine billiger, vielseitiger Rumpf, der verschiedene Rollen ausfüllen kann, aber mit fortschreitender Technologie voraussichtlich obsolet wird. Mit geringer [[METER_DETECTION]] ist der Rumpf für Aufklärer weniger geeignet als für Kriegsschiffe. -FU_BASIC_TANK -Zusatz Treibstofftank +[[LIVING_HULL_AUTO_REGEN]] -FU_BASIC_TANK_DESC -Erhöht die [[metertype METER_FUEL]]kapazität, was einen zusätzlichen Sprung erlaubt. +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED]]''' -FU_DEUTERIUM_TANK -Deuterium-Tank +SH_STATIC_MULTICELLULAR +Statischer Mehrzellerrumpf -FU_DEUTERIUM_TANK_DESC -Kleine Steigerung der Schiffsreichweite durch eine verbesserte Treibstoffkapazität. Sperrig und verwundbar durch Beschuss. +SH_STATIC_MULTICELLULAR_DESC +'''Obwohl er organisch ist, lebt dieser Rumpf nicht, da er durch Vielzellenguss hergestellt wurde. Die Flexibilität des Innenraums und fehlende Notwendigkeit interner Organe erhöht das Fassungsvermögen, aber ohne lebende Systeme ist eine natürliche Erholung nicht möglich. Der Rumpf hat drei externe und zwei interne Steckplätze. -FU_ANTIMATTER_TANK -Antimaterietank +[[metertype METER_STEALTH]] ist durchschnittlich, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist hoch. -FU_ANTIMATTER_TANK_DESC -Moderate Steigerung der Schiffsreichweite durch eine verbesserte Treibstoffkapazität. Kompakt und geringe Masse. +Eine billiger, vielseitiger Rumpf, der verschiedene Rollen ausfüllen kann, aber in keiner heraussticht. Dieser Rumpf regeneriert weder [[metertype METER_STRUCTURE]], noch [[metertype METER_FUEL]] zwischen Kämpfen. -FU_ZERO_FUEL -Nullpunkt-Treibstoffgenerator +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED]]''' -ST_CLOAK_1 -Elektromagnetische Dämpfung +SH_ENDOMORPHIC +Endomorphischer Rumpf -ST_CLOAK_1_DESC -'''Weak Cloaking (+20) +SH_ENDOMORPHIC_DESC +'''Ein halblebendiger Rumpf mit vier externen und zwei internen Steckplätzen. +Organisches Wachstum: Startet mit 5 [[metertype METER_STRUCTURE]], aber wächst um 15 [[metertype METER_STRUCTURE]] über 30 Züge. -Verbessert die [[metertype METER_STEALTH]] des Schiffes auf dem es installiert wurde, indem es die Elektromagnetische Abstrahlung der Schiffssysteme dämpft. Kann nur kleine Emissionsmengen kompensieren, und wird von vorhandenen Schiffsteilen die hohe Strahlungsmengen abgeben überladen. [[metertype METER_STEALTH]]-causing ship parts do not stack.''' +[[metertype METER_STEALTH]] ist durchschnittlich, [[metertype METER_DETECTION]] ist hoch und [[metertype METER_SPEED]] ist hoch. -AR_STD_PLATE -Standardpanzerplatten +Hat Potential als Kriegsschiff. -AR_STD_PLATE_DESC -Erhöht die [[metertype METER_STRUCTURE]] der Schiffshülle. Standardpanzerplatten bieten nur mäßige Erweiterung zur Widerstandsfähigkeit eines Schiffsrumpfes. +Dieser Rumpf regeneriert weder [[metertype METER_STRUCTURE]], noch [[metertype METER_FUEL]] zwischen Kämpfen. -AR_ZORTRIUM_PLATE -Zortrium Panzerplatten +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_REQUIRED]]''' -AR_ZORTRIUM_PLATE_DESC -Erhöht die [[metertype METER_STRUCTURE]] der Schiffshülle. Stärkere Abwehr als [[shippart AR_STD_PLATE]]. +SH_SYMBIOTIC +Symbiotischer Rumpf -AR_DIAMOND_PLATE -Diamant Panzerplatten +SH_SYMBIOTIC_DESC +'''Ein lebender Rumpf mit zwei externen und zwei internen Steckplätzen. Steht mit seiner Besatzung in einer symbiotischen Beziehung, was [[metertype METER_STEALTH]] und Geschwindigkeit erhöht. +Organisches Wachstum: Startet mit 10 [[metertype METER_STRUCTURE]], aber wächst um 10 [[metertype METER_STRUCTURE]] über 50 Züge. -AR_DIAMOND_PLATE_DESC -Erhöht die [[metertype METER_STRUCTURE]] der Schiffshülle. Stärkere Abwehr als [[shippart AR_ZORTRIUM_PLATE]]. +[[metertype METER_STEALTH]] ist hoch, [[metertype METER_DETECTION]] ist hoch und [[metertype METER_SPEED]] ist hoch. -AR_XENTRONIUM_PLATE -Xentronium Panzerplatten +Mit wenigen externen Steckplätzen ist dieser Rumpf für die Frontlinie ungeeignet, aber die internen Steckplätze, [[METER_DETECTION]] und [[METER_STEALTH]] verleihen ihm Potential als Aufklärer oder Plünderer. -AR_XENTRONIUM_PLATE_DESC -Erhöht die [[metertype METER_STRUCTURE]] der Schiffshülle. Äußerst starke und haltbare Panzerplatten. +[[LIVING_HULL_AUTO_REGEN]] -AR_ROCK_PLATE -Gestein Panzerplatten +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED]]''' -AR_CRYSTAL_PLATE -Kristall Panzerplatten +SH_PROTOPLASMIC +Protoplasmischer Rumpf -AR_NEUTRONIUM_PLATE -Neutronium Panzerplatten +SH_PROTOPLASMIC_DESC +'''Ein lebender Rumpf mit zwei externen und drei internen Steckplätzen +Organisches Wachstum: Startet mit 5 [[metertype METER_STRUCTURE]], aber wächst um 25 [[metertype METER_STRUCTURE]] über 50 Züge. -AR_PRECURSOR_PLATE -Vorläufer Panzerplatten +[[metertype METER_STEALTH]] ist sehr hoch, [[metertype METER_DETECTION]] ist hoch und [[metertype METER_SPEED]] ist hoch. -SH_DEFENSE_GRID -Verteidigungsperimeter +Mit wenigen externen Steckplätzen ist dieser Rumpf für die Frontlinie ungeeignet, aber die internen Steckplätze, [[METER_DETECTION]] und [[METER_STEALTH]] verleihen ihm Potential als Aufklärer oder Plünderer. -SH_DEFLECTOR -Deflektorschild +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_CELL_GRO_CHAMB_REQUIRED]]''' -CO_COLONY_POD -Besiedlungsmodul +SH_ENDOSYMBIOTIC +Endosymbiotischer Rumpf -CO_COLONY_POD_DESC -'''Grundlegende Einrichtungen für Siedler um die Reise zu einem neuen Planeten zu überleben. Ermöglicht es einem Schiff neue Welten zu besiedeln. +SH_RAVENOUS +Gefräßiger Rumpf -Alle Siedlungschiffstypen benötigen auf dem Planet, wo sie gebaut werden sollen, eine Bevölkerung von mindestens 3.''' +SH_BIOADAPTIVE +Bio-Adaptiver Rumpf -CO_SUSPEND_ANIM_POD -Kälteschlaf Besiedlungsmodul +SH_SENTIENT +Empfindungsfähiger Rumpf -CO_SUSPEND_ANIM_POD_DESC -'''Siedler werden während der Reise im Kälteschlaf gehalten, was die Notwendigkeit zur Versorgung beseitigt und die Anzahl der Kolonisten, die transportiert werden können, drastisch erhöht. +SH_SENTIENT_DESC +'''Ein ichbewusstes organisches Flaggschiff mit exzellenten analytischen Fähigkeiten und immenser Speicherkapazität, mit der es verbündete Schiffe anleitet und ihnen im Gefecht Informationen und taktische Hinweise zukommen lässt. Dadurch erhöht sich bei verbündeten Begleitschiffen [[metertype METER_STEALTH]] und [[metertype METER_DETECTION]]. Der Rumpf hat sechs externe, drei interne und einen Kernsteckplatz. +Organisches Wachstum: Startet mit 12 [[metertype METER_STRUCTURE]], aber wächst um 45 [[metertype METER_STRUCTURE]] über 45 Züge. -Alle Siedlungschiffstypen benötigen auf dem Planet, wo sie gebaut werden sollen, eine Bevölkerung von mindestens 3.''' +[[metertype METER_STEALTH]] ist sehr hoch, [[metertype METER_DETECTION]] ist sehr hoch und [[metertype METER_SPEED]] ist hoch. -GT_TROOP_POD -Bodentruppen Kapsel +[[LIVING_HULL_AUTO_REGEN]] -GT_TROOP_POD_DESC -Trägt eine Brigade Boden[[metertype METER_TROOPS]], samt Ausrüstung, die auf einen Planeten eingesetzt werden kann. +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED]]''' +SH_COMPRESSED_ENERGY +Pressenergierumpf -## -## Ship hulls -## +SH_COMPRESSED_ENERGY_DESC +'''Ein schneller Rumpf aus verdichteter Energie mit einem einzigen externen Steckplatz. -SH_ROBOTIC -Robotische Hülle +[[metertype METER_STRUCTURE]] is gering, [[metertype METER_STEALTH]] ist sehr hoch, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist sehr hoch. + +[[BLD_SHIPYARD_BASE_ENRG_COMP_THREE_STARS_REQUIRED]]''' + +SH_ENERGY_FRIGATE +Energiefregatte + +SH_ENERGY_FRIGATE_DESC +'''Kombiniert verdichtete Energie und Kriegsschifftechnologie zu einem schnellen, aber zerbrechlichen Angriffschiff. + +[[metertype METER_STRUCTURE]] und [[metertype METER_STEALTH]] sind durchschnittlich, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist sehr hoch. + +[[BLD_SHIPYARD_BASE_ENRG_COMP_THREE_STARS_REQUIRED]]''' + +SH_FRACTAL_ENERGY +Fraktalenergierumpf + +SH_FRACTAL_ENERGY_DESC +'''Obwohl dieser Rumpf klein ist, ermöglicht seine fraktale Oberfläche das Anbringen von weit mehr Bauteilen als auf glatten Oberflächen. Der Rumpf hat vierzehn externe Steckplätze, aber keine internen. + +[[metertype METER_STRUCTURE]] ist durchschnittlich, [[metertype METER_STEALTH]] ist gering, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] sit sehr hoch. + +[[BLD_SHIPYARD_BASE_ENRG_COMP_TWO_STARS_REQUIRED]]''' + +SH_QUANTUM_ENERGY +Quantenenergierumpf + +SH_QUANTUM_ENERGY_DESC +'''Dieser Rumpf vergrößert Energieschwankungen, um sich selbst zu verstärken. Er hat sieben externe und drei interne Steckplätze. + +[[metertype METER_STRUCTURE]] ist durchschnittlich, [[metertype METER_STEALTH]] ist gering, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist sehr hoch. + +[[BLD_SHIPYARD_BASE_ENRG_COMP_TWO_STARS_REQUIRED]]''' + +SH_SOLAR +Solarrumpf + +SH_SOLAR_DESC +'''Dieses mächtige Flaggschiff ist im Grunde eine Miniatursonne und ist daher eine exzellente [[metertype METER_FUEL]]- und Lichtquelle. Alle freundlichen Schiffe füllen außerhalb von Kämpfen ihren [[METER_FUEL]] vollständig auf, Feindschiffe im System verlieren an [[metertype METER_STEALTH]]. Dieser Rumpf hat sechzehn externe, vier interne und einen Kernsteckplatz und damit ein größeres Fassungsvermögen als jeder andere Rumpf. + +[[metertype METER_STRUCTURE]] ist hoch, [[metertype METER_STEALTH]] ist sehr gering, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist sehr hoch. Das Schiff ist außerdem in der Lage, sich in Sternen zu verstecken. In Systemem mit einem Stern (ausschließlich [[STAR_BLACK]] oder [[STAR_NEUTRON]]) erhält das Schiff perfekte [[metertype METER_STEALTH]] auf der Karte und, wenn es sich versteckt, im Gefecht. + +Die Konstruktion dieses Rumpfes benötigt immense Mengen an Energie, die aus Partikel-Antipartikel-Kollisionen aus dem Ereignishorizint schwarzer Löcher gewonnen werden muss. Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]], ein [[buildingtype BLD_SHIPYARD_ENRG_COMP]] und ein [[buildingtype BLD_SHIPYARD_ENRG_SOLAR]] vorhanden sein.''' SH_BASIC_SMALL Kleiner Standardrumpf SH_BASIC_SMALL_DESC -'''Ein kleiner interstellarer Standardrumpf. Verglichen mit anderen Standardrümpfen, kann er einen Sprung mehr ausführen. +'''Ein kleiner interstellarer Standardrumpf. Hat nur einen externen Steckplatz, kann dafür sehr effizient Sternensprünge durchführen. + +[[metertype METER_STEALTH]] ist durchschnittlich, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist durchschnittlich. -Ein Planet benötigt eine [[buildingtype BLD_SHIPYARD_BASE]], um ein solches Schiff zu bauen.''' +[[BLD_SHIPYARD_BASE_REQUIRED]]''' SH_BASIC_MEDIUM Mittelgroßer Standardrumpf SH_BASIC_MEDIUM_DESC -'''Ein mittelgroßer Schiffsrumpf für interstellare Reisen. +'''Ein mittelgroßer Schiffsrumpf für interstellare Reisen. Verfügt über zwei externe und einen internen Steckplatz. -Ein Planet benötigt eine [[buildingtype BLD_SHIPYARD_BASE]], um ein solches Schiff zu bauen.''' +[[metertype METER_STEALTH]] ist durchschnittlich, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. -SH_STANDARD +[[BLD_SHIPYARD_BASE_REQUIRED]]''' + +SH_BASIC_LARGE Großer Basisrumpf -SH_STANDARD_DESC -'''Ein großer Schiffsrumpf für interstellare Reisen. +SH_BASIC_LARGE_DESC +'''Ein großer Schiffsrumpf für interstellare Reisen. Verfügt über drei externe und einen internen Steckplatz. -Ein Planet benötigt eine [[buildingtype BLD_SHIPYARD_BASE]], um ein solches Schiff zu bauen.''' +[[metertype METER_STEALTH]] ist durchschnittlich, [[metertype METER_DETECTION]] ist durchschnittlich und [[metertype METER_SPEED]] ist gering. -SH_COLONY_BASE -Siedlungsbasis Rumpf +[[BLD_SHIPYARD_BASE_REQUIRED]]''' + +SHP_XENTRONIUM_HULL +Xentroniumgepanzerter Rumpf + +SH_XENTRONIUM +Xentroniumrumpf + +SH_XENTRONIUM_DESC +'''Hauptsächlich aus Xentronium konstruiert. Trotz seiner geringen Größe hat dieser Rumpf extrem hohe [[metertype METER_STRUCTURE]]. Verfügt über vier externe und einen Kernsteckplatz. + +[[metertype METER_STEALTH]] ist durchschnittlich und [[metertype METER_DETECTION]] ist durchschnittlich.''' -SH_COLONY_BASE_DESC -Ein Rumpf konzipiert zum Gründen einer Siedlung auf einem anderen Planet dieses Systems. Er kann nicht zwischen Systeme navigieren oder sich schnell im System bewegen. +SH_COLONY_BASE +Siedler-Basisrumpf SH_FLOATER_BODY -Gebrechlicher Körper +Schwimmerkörper SH_FLOATER_BODY_DESC -Kleiner gebrechlicher Körper, der keinen nennenswerten Schäden standhält. +Kleiner gebrechlicher Körper, der keinem nennenswerten [[encyclopedia DAMAGE_TITLE]] standhält. SH_TREE_BODY -Immobiler Körper +Baumkörper SH_TREE_BODY_DESC -Starrer baumförmigen Körper, der nicht in der Lage ist, sich zwischen den Systemen zu bewegen. +Starrer baumförmigen Körper, der nicht in der Lage ist, sich zwischen Systemen zu bewegen. SH_STRONG_MONSTER_BODY -Kräftiger Körper +Drachenkörper SH_STRONG_MONSTER_BODY_DESC -Großer starker Körper mit furchterregenden natürlichen Waffen und starker natürlicher Rüstung. +Großer, kräftiger Körper mit furchterregenden natürlichen Waffen und starker natürlicher Panzerung. SH_GUARD_MONSTER_BODY -Wache Rumpf +Wächterrumpf SH_GUARD_MONSTER_BODY_DESC Ein starker Rumpf für Monster der Wächterklasse. +SH_GUARD_0_BODY +Wartungsrumpf + +SH_GUARD_0_BODY_DESC +Ein einfacher Rumpf für Wartungsschiffe. + +SH_GUARD_1_BODY +Wacherumpf + +SH_GUARD_1_BODY_DESC +Ein einfacher Rumpf für Monster der Wächterklasse. + +SH_GUARD_3_BODY +Aufseherrumpf + +SH_GUARD_3_BODY_DESC +Ein mächtiger Rumpf für Monster der Wächterklasse. + SH_KRILL_1_BODY Krillkörper 1 @@ -6218,7 +7690,7 @@ SH_KRAKEN_1_BODY Larvalkrakenkörper SH_KRAKEN_1_BODY_DESC -Ein gewaltiges mittelgewichtiges Weltraummonster. +Ein gewaltiges leichtgewichtiges Weltraummonster. SH_KRAKEN_2_BODY Krakenkörper @@ -6230,34 +7702,321 @@ SH_KRAKEN_3_BODY Großer Krakenkörper SH_KRAKEN_3_BODY_DESC -Ein gewaltiges mittelgewichtiges Weltraummonster. +Ein gewaltiges schwergewichtiges Weltraummonster. + +SH_WHITE_KRAKEN_BODY +Weißkrakenkörper + +SH_WHITE_KRAKEN_BODY_DESC +Der prähistorische weiße Vorfahr des Krakenmonsters. SH_BLACK_KRAKEN_BODY Schwarzkrakenkörper +SH_BLACK_KRAKEN_BODY_DESC +Ein monströser schwarzer Kraken, der eine unnatürliche Aura ausstrahlt. + +SH_SNOWFLAKE_1_BODY +Kleiner Schneeflockenkörper + +SH_SNOWFLAKE_1_BODY_DESC +Ein gefährliches leichtgewichtiges Weltraummonster. + +SH_SNOWFLAKE_2_BODY +Schneeflockenkörper + +SH_SNOWFLAKE_2_BODY_DESC +Ein gefährliches leichtgewichtiges Weltraummonster. + +SH_SNOWFLAKE_3_BODY +Großer Schneeflockenkörper + +SH_SNOWFLAKE_3_BODY_DESC +Ein gefährliches leichtgewichtiges Weltraummonster. + +SH_PSIONIC_SNOWFLAKE_BODY +Psionischer Schneeflockenkörper + +SH_PSIONIC_SNOWFLAKE_BODY_DESC +Ein psychisch begabtes Weltraummonster. + +SH_JUGGERNAUT_1_BODY +Kleiner Kolosskörper + +SH_JUGGERNAUT_1_BODY_DESC +Ein beeindruckendes schwergewichtiges Weltraummonster. + +SH_JUGGERNAUT_2_BODY +Kolosskörper + +SH_JUGGERNAUT_2_BODY_DESC +Ein beeindruckendes schwergewichtiges Weltraummonster. + +SH_JUGGERNAUT_3_BODY +Großer Kolosskörper + +SH_JUGGERNAUT_3_BODY_DESC +Ein beeindruckendes schwergewichtiges Weltraummonster. + +SH_BLOATED_JUGGERNAUT_BODY +Aufgeblähter Kolosskörper + +SH_BLOATED_JUGGERNAUT_BODY_DESC +Ein widernatürlich großes Weltraummonster. + +SH_NEBULOUS_BODY +Nebliger Körper + +SH_EXP_OUTPOST_HULL +Experimentoren-Außenpostenrumpf + +SH_EXP_OUTPOST_HULL_DESC +Ein Rumpf, mit dem die Experimentoren Versuchsausrüstung in andere Galaxien befördern. + +SH_COSMIC_DRAGON_BODY +Kosmischer Drachenkörper + +SH_COSMIC_DRAGON_BODY_DESC +Ein fürchterlicher Weltraumdrache, so groß wie ein kleiner Planet. + +SH_DAMPENING_CLOUD_BODY +Wolkenkörper + +SH_DAMPENING_CLOUD_BODY_DESC +Eine kosmische Wolke aus hochenergetischen Partikeln. + +## +## Monsters Macros +## + +SM_KRILL_MACRO_1 +Raumkrill sind kleine, insektenähnliche Organismen, welche sich weit entfernt von jedem Gravitationsfeld von Staub und Felsen ernähren. Obwohl als Individuum einfach, kommunizieren Krill durch kohärentes Licht, was der Schwarm zur Koordination und zur Berechnung von Flugbahnen nutzt + +SM_KRILL_MACRO_2 +Die hohe Anzahl von Ressourcen mit niedrigem Gravitationsfeld innerhalb eines Asteroidengürtels bieten Krill perfekte Bedingungen, um sich schnell zu vermehren. + +SM_GUARD_MACRO +von den Vorläufern dazu programmiert, das System gegen Eindringlinge zu verteidigen + +SM_KRAKEN_ENVIRONMENT +Bevorzugte Umbebung: Gasriesen. Krakennester finden sich für gewöhnlich im Orbit von Gasriesen, und in Systemen mit Gasriesen können Kraken heranreifen. + +SM_SNOWFLAKE_ENVIRONMENT +Bevorzugte Umbebung: Kleine Planeten. Schneeflockennester finden sich für gewöhnlich im Orbit von kleinen Planeten, und in Systemen mit kleinen Planeten können Schneeflocken heranreifen. + +SM_JUGGERNAUT_ENVIRONMENT +Bevorzugte Umbebung: Asteroiden. Kolossnester finden sich für gewöhnlich in Asteroidengürteln, und in Systemen mit Asteroidengürteln können Kolosse heranreifen. + ## ## Buildings macros ## +BUILDING_AVAILABLE_ON_OUTPOSTS +Dieses Gebäude kann auch auf [[encyclopedia OUTPOSTS_TITLE]] errichtet werden + +NO_STACK_SUPPLY_CONNECTION_TEXT +Weitere Gebäude dieser Art innerhalb der gleichen [[metertype METER_SUPPLY]]slinie haben keinen zusätzlichen Effekt. + +MACRO_NEUTRONIUM_BUILDINGS +Um Neutronium nutzen zu können, braucht das Imperium einen [[buildingtype BLD_NEUTRONIUM_EXTRACTOR]] in einem [[STAR_NEUTRON]]system oder Zugriff aud einen [[buildingtype BLD_NEUTRONIUM_SYNTH]], sowie eine [[buildingtype BLD_NEUTRONIUM_FORGE]] auf dem Planeten, auf dem Neutronium verwendet werden soll. + +ARTIFICIAL_PLANET_PROCESS_LOCATION +Dieser Prozess muss in einem [[encyclopedia OUTPOSTS_TITLE]] auf einem Gasriesen oder einem Asteroidengürtel durchgeführt werden. + +BLD_COL_PART_1 +Dieses Gebäude kann nur auf einem [[encyclopedia OUTPOSTS_TITLE]] errichtet werden und erweitert den [[OUTPOSTS_TITLE]] in eine + +BLD_COL_PART_2 +Die Mindestbauzeit variiert abhängig vom Abstand in Sternstraßensprüngen zur nächstgelegenen + +BLD_COL_PART_3 +des Imperiums; ein größerer Abstand führt zu längerer Bauzeit. Diese Varianz lässt sich durch Technologien rezduieren, die schnellere Kolonisierungsschiffe ermöglichen, wie z.B. Triebwerke und schnellere Rümpfe. + + +# Macro keys formatting for ship designs, hulls or parts: +# BLD_SHIPYARD_TYPE1_TYPE2_REQUIRED (required building type(s) owned by +# empire only in the same system) +# ANY_SYSTEM (building owned in any system by empire or ally) +# Any other key formatting is self-explanatory (e.g. LIVING_HULL_AUTO_REGEN) + +## +## Ship design/hull macros +## + +# Keys used in Predefined Ship Designs and Ship Hulls sections + +BLD_SHIPYARD_BASE_REQUIRED +Zum Bau muss vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]] vorhanden sein. + +BLD_SHIPYARD_BASE_AST_REQUIRED +Zum Bau muss vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]] vorhanden sein. Ein Asteroidengürtel mit [[buildingtype BLD_SHIPYARD_AST]] im gleichen System wird vorrausgesetzt. + +BLD_SHIPYARD_AST_REF_REQUIRED +Eine [[buildingtype BLD_SHIPYARD_AST_REF]] im gleichen System wird ebenfalls benötigt. + +BLD_SHIPYARD_AST_REF_REQUIRED_ANY_SYSTEM +Zur Herstellung wird eine [[buildingtype BLD_SHIPYARD_AST_REF]] in diesem oder einem verbündeten Imperium benötigt. + +BLD_SHIPYARD_BASE_ENRG_COMP_THREE_STARS_REQUIRED +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]] und ein [[buildingtype BLD_SHIPYARD_ENRG_COMP]] vorhanden sein. Aufgrund des hohen Energiebedarfs ist der Bau nur in Systemen mit einem [[STAR_BLUE]]en oder [[STAR_WHITE]]en Stern oder einem schwarzem Loch möglich. + +BLD_SHIPYARD_BASE_ENRG_COMP_TWO_STARS_REQUIRED +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]] und ein [[buildingtype BLD_SHIPYARD_ENRG_COMP]] vorhanden sein. Aufgrund des immensen Energiebedarfs ist der Bau nur in Systemen mit einem [[STAR_BLUE]]en Stern oder einem schwarzen Loch möglich. + +BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]] und ein [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] vorhanden sein. + +BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]], ein [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] und eine [[buildingtype BLD_SHIPYARD_CON_GEOINT]] vorhanden sein. + +LIVING_HULL_AUTO_REGEN +Lebende Rümpfe stellen zwischen Gefechten [[metertype METER_STRUCTURE]] und [[metertype METER_FUEL]] wieder her. + +BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]] und ein[[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] vorhanden sein. + +BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_REQUIRED +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]], ein [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] und eine [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] vorhanden sein. + +MIN_POPULATION_THREE_REQUIRED +Zum Bau muss vor Ort eine [[metertype METER_POPULATION]] von mindestens drei vorhanden sein + +BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_CELL_GRO_CHAMB_REQUIRED +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]], ein [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]]und eine [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] vorhanden sein. + +BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED +Zum Bau müssen vor Ort eine [[buildingtype BLD_SHIPYARD_BASE]], ein [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], eine [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] und eine [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] vorhanden sein. + +# Other than Requirements + +SHIPDESIGN_DETECTION_RESEARCH_TIPS +Weitere [[metertype METER_RESEARCH]] zur Verbesserung der [[metertype METER_DETECTION]] würde bessere Konstruktionen ermöglichen. + +SHIPDESIGN_MILLIONS_COLONIZATION_CAPACITY +welches Millionen von Bürgern sicher zu neuen Kolonien transportieren kann + +SHIPDESIGN_MANY_MILLIONS_COLONIZATION_CAPACITY +welches viele Millionen von Bürgern sicher zu neuen Kolonien transportieren kann + +SHIPDESIGN_COLONIZATION_CAPACITY_SAME_SYSTEM +Kolonie auf einem bewohnbaren Planeten im selben System + +SHIPDESIGN_OUTPOSTS_CAPACITY +zur Gründung eines [[encyclopedia OUTPOSTS_TITLE]] auf einem unbewohnbaren Planeten + +SHIPDESIGN_NO_TRAVEL +Dieses Schiff kann keine Sternstraßen bereisen. + +SHIPDESIGN_PLANET_INVASION +[[metertype METER_TROOPS]]n und Ausrüstung zum Einmarsch auf feindliche Planeten + + +## +## Ship part/tech application macros +## + +# Keys used in Ship Parts and Technology Application sections + +BLD_SHIPYARD_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED_ANY_SYSTEM +Zur Herstellung werden eine [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] und eine [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] in diesem oder einem verbündeten Imperium benötigt. + +BLD_BIONEURAL_REQUIRED_ANY_SYSTEM +Zur Herstellung wird eine Bioneurale Anpassungseinheit in diesem oder einem verbündeten Imperium benötigt. + +BLD_SHIPYARD_CON_ADV_ENGINE_REQUIRED_ANY_SYSTEM +Zur Herstellung wird ein [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]] in diesem oder einem verbündeten Imperium benötigt. + +NO_STACK_STEALTH_SHIP_PARTS +Nur der höchste Tarnungsbonus aller Schiffskomponenten ist gültig. + +BLD_SHIPYARD_AST_REF_REQUIRED_ANY_SYSTEM_SHIP_PARTS_TEXT +Zur Herstellung wird eine [[buildingtype BLD_SHIPYARD_AST_REF]] auf einem Asteroidengürtel in diesem oder einem verbündeten Imperium benötigt. + +NO_STACK_SHIELDS_SHIP_PARTS +[[metertype METER_SHIELD]] reduziert mit jedem Treffer erlittenen [[encyclopedia DAMAGE_TITLE]] entsprechend ihrer Stärke. Jedes Schiff kann nur einen aktiven Schildgenerator haben, weitere Generatoren haben keinen zusätzlichen Effekt. + +COLONY_SHIP_PARTS_MIN_POP +Kolonisierungsschiffe können nur auf Planeten mit einer [[metertype METER_POPULATION]] von mindestens 3 gebaut werden. + +COLONY_SHIP_PARTS_UPKEEP_COST +Je weiter das Imperium wächst, destso größer werden dessen Unterhaltungskosten, was sich in höheren Preisen für dieses Bauteil widerspiegelt. + +TROOP_POD_OPERATION_TEXT +''' * Die Truppen können nur für eine einzige Invasion genutzt werden. + * Die Transportschiffe führen beim Einmarsch eine harte Landung aus und sind anschließend unbrauchbar.''' + +SHIP_WEAPON_GRADUALLY_REDUCE +zur graduellen Auslöschung + +SHIP_WEAPON_QUICKLY_REDUCE +zur schnellen Auslöschung + +ENEMY_PLANET_ORGANIC_POP +der organischen [[metertype METER_POPULATION]] von Feindplaneten + +ENEMY_PLANET_ROBOTIC_POP +der robotischen [[metertype METER_POPULATION]] von Feindplaneten + +ENEMY_PLANET_LITHIC_POP +der lithischen [[metertype METER_POPULATION]] von Feindplaneten + +ENEMY_PLANET_PHOTOTROPHIC_POP +der phototropen [[metertype METER_POPULATION]] von Feindplaneten + +ENEMY_PLANET_ANY_POP +jeglicher [[metertype METER_POPULATION]] von Feindplaneten + ## ## Tech macros ## +COLONY_BUILDING_TIME_DECREASE +Unter anderem reduziert diese Technologie den nötigen Zeitaufwand, um neue Kolonien in größerer Entfernung zur relevanten Spezies zu gründen. + ## ## Growth macros ## -#GROWTH_SPECIAL_POPULATION_ORGANIC_INCREASE -#[[encyclopedia ORGANIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] [[ORGANIC_SPECIES_TITLE]]. +GROWTH_SPECIAL_POPULATION_ORGANIC_INCREASE +[[encyclopedia ORGANIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] organischer [[METER_POPULATION]]. + +GROWTH_SPECIAL_POPULATION_ROBOTIC_INCREASE +[[encyclopedia ROBOTIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] robotischer [[METER_POPULATION]]. + +GROWTH_SPECIAL_POPULATION_LITHIC_INCREASE +[[encyclopedia LITHIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] lithischer [[METER_POPULATION]]. + +GROWTH_SPECIAL_POPULATION_INCREASE +'''wird auf diesem Planeten begünstigt. Eine entsprechende Bewohnerspezies erhält [[metertype METER_TARGET_POPULATION]], abhängig von der Planetengröße: +• [[SZ_TINY]] (+1) +• [[SZ_SMALL]] (+2) +• [[SZ_MEDIUM]] (+3) +• [[SZ_LARGE]] (+4) +• [[SZ_HUGE]] (+5) +Die [[encyclopedia ENVIRONMENT_TITLE]] hat hierauf keine Auswirkung. -#GROWTH_SPECIAL_POPULATION_ROBOTIC_INCREASE -#[[encyclopedia ROBOTIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] [[ROBOTIC_SPECIES_TITLE]]. +Wenn dieser Planet [[encyclopedia GROWTH_FOCUS_TITLE]] hat, gilt obengenannter Bonus für alle via [[metertype METER_SUPPLY]] verbundenen Welten mit''' -#GROWTH_SPECIAL_POPULATION_LITHIC_INCREASE -#[[encyclopedia LITHIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] [[LITHIC_SPECIES_TITLE]]. +GROWTH_SPECIAL_INDUSTRY_BOOST +Erhöht [[metertype METER_TARGET_INDUSTRY]] um 0,2 pro [[metertype METER_POPULATION]], solange dieser Planet Industriefokus hat. + +GROWTH_SPECIALS_ENTRY_LIST +'''Mansche Besonderheiten wirken sich nur auf organische Spezies aus, manche nur auf robotische und manche nur auf lithische; Für andere [[encyclopedia METABOLISM_TITLE]] gibt es derzeit keine zutreffenden Wachstumsbesonderheiten. + +Besonderheiten für [[encyclopedia ORGANIC_SPECIES_TITLE]]: +[[special FRUIT_SPECIAL]] | [[special SPICE_SPECIAL]] | [[special PROBIOTIC_SPECIAL]] + +Besonderheiten für [[encyclopedia ROBOTIC_SPECIES_TITLE]]: +[[special MONOPOLE_SPECIAL]] | [[special SUPERCONDUCTOR_SPECIAL]] | [[special POSITRONIUM_SPECIAL]] + +Besonderheiten für [[encyclopedia LITHIC_SPECIES_TITLE]]: +[[special MINERALS_SPECIAL]] | [[special ELERIUM_SPECIAL]] | [[special CRYSTALS_SPECIAL]] ''' + +#GROWTH_SPECIAL_LABEL +#Planet %1% [[TT_SPECIAL]] ## @@ -6584,12 +8343,6 @@ Empfang der Nachricht bestätigt. OPTIONS_PAGE_HOTKEYS Tastaturkürzel -HOTKEYS_Z_COMBAT -Kampffenster - -HOTKEYS_MAP -Kartenfenster - HOTKEYS_GENERAL Allgemeine Kürzel @@ -6735,6 +8488,5 @@ Ewiger September #[[SHIP_NAME_UNSORTED_MYTHOLOGY]] #[[SHIP_NAME_DEVS]]''' -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/en.txt b/default/stringtables/en.txt index 9453f9e038d..5cd1611457e 100644 --- a/default/stringtables/en.txt +++ b/default/stringtables/en.txt @@ -1,4 +1,4 @@ -English +English # This is the English String Table file for FreeOrion # @@ -8,7 +8,7 @@ English # make any stringtable keys beginning with "FUNCTIONAL_". # New Sitreps priorities should be added to # `default/customizations/common_user_customizations.txt`. - +# # semi-randomly collected characters to force font code page loading # 肛門オーガズム # åřžßąłżęЗыдШит @@ -267,6 +267,10 @@ Balance CONTENT Content +MULTIPLAYER +Multiplayer + + ## ## Major errors ## @@ -276,6 +280,14 @@ ERROR_SOUND_INITIALIZATION_FAILED Check log files for more detailed messages. ''' +OPENGL_VERSION_LOW_TITLE +OpenGL Version Low + +OPENGL_VERSION_LOW_TEXT +'''The OpenGL version on this system is less than 2.0 + +FreeOrion may crash while starting.''' + # Used as a prefix for the FORMAT_LIST_[1-MANY]_ITEMS translation entries. FORMAT_LIST_DEFAULT_PLURAL_HEADER There are: @@ -328,6 +340,28 @@ FORMAT_LIST_MANY_ITEMS %1% %2%, %3%, %4%, %5%, %6%, %7%, %8%, %9%, %10%, %11% ... +## +## Build Projects +## + +PROJECT_BT_STOCKPILE +Stockpile Transfer + +PROJECT_BT_STOCKPILE_SHORT_DESC +Transfers PP into the imperial stockpile + +PROJECT_BT_STOCKPILE_DESC +'''PP allocated to the transfer project are added to the imperial stockpile. The amount to transfer and how many times the transfer should repeat can be specified. + +Only PP that are produced in the same supply-group (and none from the imperial stockpile) are allocated to the stockpile transfer project. + +Surplus PP (PP not allocated to anything on the queue) at this or any other location will be transferred to the stockpile with or without a stockpile transfer project on the queue. PP allocated to a stockpile transfer are sent to the stockpile, even if there are additional items later in the production queue. + +For example: An empire wants to draw PP from the stockpile in a secondary supply-group, in which there is insufficient PP being generated. To do this, the stockpile needs to be filled from the primary supply-group. The empire has 100 PP industry in the primary supply-group, its stockpile use limit is 10 PP and it wants to have 10 PP always available in the stockpile. The build queue contains items on planets in the primary supply-group which need 120 PP per turn, so all available PP would be used. +To be able to use the PP in the secondary supply-group, the empire adds to the top of the queue a 10x Stockpile Transfer project on a planet in the primary supply-group. +Because the Stockpile Transfer project is above other primary supply-group projects in the build queue, it is fully funded and will increase the stockpile by 10 PP on the next turn. To ensure a continuing transfer each turn, the repetition of the project is set to 99 times.''' + + ## ## Predefined Ship Designs (located in default/scripting/ship_designs/) ## @@ -390,13 +424,13 @@ SD_LARGE_MARK_1 Cruiser Ms SD_LARGE_MARK1_DESC -Cruiser equipped for long range independent action with improved lasers and armor. [[BLD_SHIPYARD_BASE_REQUIRED]] +Cruiser equipped for long range independent action with improved weapons and armor. [[BLD_SHIPYARD_BASE_REQUIRED]] SD_LARGE_MARK_2 Cruiser Lz SD_LARGE_MARK2_DESC -Cruiser equipped for long range independent action . [[BLD_SHIPYARD_BASE_REQUIRED]] +Cruiser equipped for long range independent action. [[BLD_SHIPYARD_BASE_REQUIRED]] SD_LARGE_MARK_3 Destroyer Ms @@ -608,13 +642,13 @@ SM_TREE Dyson Forest SM_TREE_DESC -A "forest" made up of numerous space-born "trees" with filamentous branches. The trees multiply and align themselves in a bubble at the proper distance around their host star. Dyson Forests are a hazard to navigation, and as time passes, the forest expands and becomes harder to eradicate. If not destroyed, they will periodically send out a seed through the starlanes to colonize other star systems. A [[SM_TREE]] seed is known as a [[predefinedshipdesign SM_FLOATER]], and may be difficult to detect. +A "forest" made up of numerous space-born "trees" with filamentous branches. The trees multiply and align themselves in a bubble at the proper distance around their host star. Dyson Forests are a hazard to navigation, and as time passes, the forest expands and becomes harder to eradicate. If not destroyed, they will periodically send out a seed through the starlanes to colonize other star systems. A [[SM_TREE]] seed is known as a [[predefinedshipdesign SM_FLOATER]], and may be difficult to detect. SM_FLOATER Floater SM_FLOATER_DESC -A gas-filled bulbous sac drifting through space, sent as a seed by a [[predefinedshipdesign SM_TREE]] to found a new forest around other stars. It is generally advisable to destroy [[SM_FLOATER]]s as soon as possible. However, they are small and difficult to detect; it will generally require the [[tech SPY_DETECT_2]] technology to do so. +A gas-filled bulbous sac drifting through space, sent as a seed by a [[predefinedshipdesign SM_TREE]] to found a new forest around other stars. It is generally advisable to destroy [[SM_FLOATER]]s as soon as possible. However, they are small and difficult to detect; it will generally require the [[tech SPY_DETECT_2]] technology to do so. SM_DRAGON Vacuum Dragon @@ -694,7 +728,7 @@ Black Kraken SM_BLACK_KRAKEN_DESC '''A powerful, unnatural-seeming space monster. -Black Kraken are bioengineered space monsters, incredibly tough, with dangerous weapons and high [[metertype METER_STEALTH]]. A powerful fleet and good [[encyclopedia DETECTION_TITLE]] will be needed to track them down. They seek out planets with visible buildings and attack their population.''' +Black Kraken are bioengineered space monsters, incredibly tough, with dangerous weapons and high [[metertype METER_STEALTH]]. A powerful fleet and good [[encyclopedia DETECTION_TITLE]] will be needed to track them down. They seek out planets with visible buildings and attack their [[metertype METER_POPULATION]].''' SM_SNOWFLAKE_1 Small Snowflake @@ -726,7 +760,7 @@ Psionic Snowflake SM_PSIONIC_SNOWFLAKE_DESC '''A monster with the power to neutralize the minds of organic beings. -Psionic Snowflakes are bioengineered space monsters with dangerous weapons and the ability to take control of ships with [[encyclopedia ORGANIC_SPECIES_TITLE]] crews. A powerful fleet will be needed to fight them. They seek out and attack enemy space ships, forcing crews vulnerable to psychic attack to abandon their empire. They are also able to directly attack the population of planets.''' +Psionic Snowflakes are bioengineered space monsters with dangerous weapons and the ability to take control of ships with [[encyclopedia ORGANIC_SPECIES_TITLE]] crews. A powerful fleet will be needed to fight them. They seek out and attack enemy space ships, forcing crews vulnerable to psychic attack to abandon their empire. They are also able to directly attack the [[metertype METER_POPULATION]] of planets.''' SM_JUGGERNAUT_1 Small Juggernaut @@ -758,7 +792,7 @@ Bloated Juggernaut SM_BLOATED_JUGGERNAUT_DESC '''A massive, sickly-looking space monster. -Bloated Juggernauts are bioengineered space monsters, incredibly tough, with dangerous weapons and high [[metertype METER_STEALTH]]. A powerful fleet and good [[encyclopedia DETECTION_TITLE]] will be needed to track them down. They seek out planets with visible buildings and attack their population.''' +Bloated Juggernauts are bioengineered space monsters, incredibly tough, with dangerous weapons and high [[metertype METER_STEALTH]]. A powerful fleet and good [[encyclopedia DETECTION_TITLE]] will be needed to track them down. They seek out planets with visible buildings and attack their [[metertype METER_POPULATION]].''' SM_CLOUD Space Cloud @@ -893,6 +927,9 @@ The server is not responding. SERVER_LOST The connection to the server has been lost. +LOCAL_SERVER_ALREADY_RUNNING_ERROR +Can't start server. A local server is already running. + PLAYER_DISCONNECTED Player %1% no longer has a connection to the server. @@ -919,6 +956,9 @@ Error when reading config.xml file. Using default options. UNABLE_TO_READ_PERSISTENT_CONFIG_XML Error when attempting to read the optional persistent_config.xml file (this is expected if it does not exist). +UNABLE_TO_WRITE_PERSISTENT_CONFIG_XML +Error when writing persistent_config.xml file. + UNABLE_TO_WRITE_SAVE_FILE Error when writing save file. @@ -938,10 +978,10 @@ ABORT_SAVE_AND_EXIT Exit FreeOrion without saving. EMPIRE_NOT_FOUND_CANT_HANDLE_ORDERS -You do not control an empire and cannot issue orders. +No empire is being controlled by this client; orders cannot be issued. ORDERS_FOR_WRONG_EMPIRE -Orders were sent for an empire you do not control. +Orders were sent for an empire that this client does not control. SERVER_ALREADY_HOSTING_GAME This server is already hosting a game. @@ -958,16 +998,77 @@ Universe generation completed with errors. See log files for detailed error mess SERVER_TURN_EVENTS_ERRORS Python scripted turn events executed with errors. See log files for detailed error messages. Game can continue, but gameplay will probably be impaired. +SERVER_ALREADY_PLAYING_GAME +The server is not currently accepting players because a game is being played. + ERROR_PYTHON_AI_CRASHED Python AI for %1% crashed. ERROR_PLAYER_NAME_ALREADY_USED Player name %1% already in use. +ERROR_WRONG_PASSWORD +Password wrong for player name %1%. + +ERROR_CLIENT_TYPE_NOT_ALLOWED +Client type forbidden. + +ERROR_NOT_ENOUGH_AI_PLAYERS +Not enough AI players. + +ERROR_TOO_MANY_AI_PLAYERS +Too many ai players. + +ERROR_NOT_ENOUGH_HUMAN_PLAYERS +Not enough human players. + +ERROR_TOO_MANY_HUMAN_PLAYERS +Too many human players. + +ERROR_NOT_ENOUGH_CONNECTED_HUMAN_PLAYERS +Not enough connected human empire players. + +ERROR_TOO_MANY_UNCONNECTED_HUMAN_PLAYERS +Too many unconnected human empire players. + +ERROR_CONNECTION_WAS_REPLACED +Your connection was replaced. + +ERROR_NONPLAYER_CANNOT_CONCEDE +Only Players can concede. + +ERROR_CONCEDE_DISABLED +Concede is disabled. + +ERROR_CONCEDE_EXCEED_COLONIES +Cannot concede; empire controls too many colonies. + +ERROR_CONCEDE_LAST_HUMAN_PLAYER +Cannot concede from a game with only one human player. + +ERROR_INCOMPATIBLE_VERSION +Server cannot properly read messages from your client, most likely due to incompatible game versions. + +ERROR_CHECKSUM_MISMATCH +Content checksum on the server and the client differs. + + ## ## Game Rules ## +RULE_PRODUCTION_QUEUE_FRONTLOAD_FACTOR +Turn PP overcommit + +RULE_PRODUCTION_QUEUE_FRONTLOAD_FACTOR_DESC +Percentage of PP overcommitment per item build turn to build in time. + +RULE_PRODUCTION_QUEUE_TOPPING_UP_FACTOR +Final PP overcommit + +RULE_PRODUCTION_QUEUE_TOPPING_UP_FACTOR_DESC +Percentage of PP overcommitment on final item build turn to build in time. + RULE_CHEAP_AND_FAST_TECH_RESEARCH Cheap and Fast Techs @@ -992,11 +1093,17 @@ Combat Rounds RULE_NUM_COMBAT_ROUNDS_DESC How many rounds of combat occur each turn. +RULE_AGGRESSIVE_SHIPS_COMBAT_VISIBLE +Aggressive Ships Combat Visible + +RULE_AGGRESSIVE_SHIPS_COMBAT_VISIBLE_DESC +Ships in fleets that are set to control their system start combat visible to all empires, regardless of their stealth and detection levels. + RULE_RESEED_PRNG_SERVER Random Reseeding RULE_RESEED_PRNG_SERVER_DESC -Random number generation is re-seeded frequently to give more predictable and reproducible values. +With reseeding, replaying the same save or using the same settings and seed is not expected to produce the same random results. This is possibly preferable if players want to be sure that none of them can predict future random results based on previous random results or if a player wants to 're-roll' the results of a battle or random effect when re-playing a turn, and not get the same results as a previous attempt. RULE_SHIP_SPEED_FACTOR Ship Speed Scaling @@ -1040,6 +1147,18 @@ Enable Experimentors RULE_ENABLE_EXPERIMENTORS_DESC Enables Experimentors during galaxy generation +RULE_ENABLE_SUPER_TESTER +Enable Super-Tester Takeover + +RULE_ENABLE_SUPER_TESTER_DESC +Enables [[BLD_SUPER_TEST]] building + +RULE_STOCKPILE_IMPORT_LIMITED +Stockpile Import Limited + +RULE_STOCKPILE_IMPORT_LIMITED_DESC +Enables limits on stockpile import per turn + RULE_TEST_INT Test Integer @@ -1052,19 +1171,116 @@ Test String RULE_TEST_STRING_DESC Test +RULE_STARLANES_EVERYWHERE +Starlanes Everywhere + +RULE_STARLANES_EVERYWHERE_DESC +Starlanes are generated between every pair of systems, regardless of geometry. Lanes may still be removed during a game, however. Lanes are also not rendered on the galaxy map. + +RULE_HABITABLE_SIZE_TINY +Tiny planets Habitable size + +RULE_HABITABLE_SIZE_SMALL +Small planets Habitable size + +RULE_HABITABLE_SIZE_MEDIUM +Medium planets Habitable size + +RULE_HABITABLE_SIZE_LARGE +Large planets Habitable size + +RULE_HABITABLE_SIZE_HUGE +Huge planets Habitable size + +RULE_HABITABLE_SIZE_ASTEROIDS +Asteroids Habitable size + +RULE_HABITABLE_SIZE_GASGIANT +Gas Giants Habitable size + +RULE_HABITABLE_SIZE_DESC +Value used to adjust for [[METER_POPULATION]] changes on a planet of this size. + +RULE_ALLOW_CONCEDE +Allow Conceding + +RULE_ALLOW_CONCEDE_DESC +Allows empires to concede from a game. This eliminates the empire's assets from the universe. The game continues with the remaining players. + +RULE_CONCEDE_COLONIES_THRESHOLD +Maximum Colonies When Conceding + +RULE_CONCEDE_COLONIES_THRESHOLD_DESC +Empires with more than this number of colonies may not concede. + +RULE_THRESHOLD_HUMAN_PLAYER_WIN +Max human player winners + +RULE_THRESHOLD_HUMAN_PLAYER_WIN_DESC +Maximum number of human players that could win together in multiplayer games if there are no other human player survivors. + +RULE_ONLY_ALLIANCE_WIN +Only allied players win + +RULE_ONLY_ALLIANCE_WIN_DESC +Allied players can win together if the number of human players in their alliance is no more than the maximum human player winners and there are no other human player survivors. + +RULE_SHIP_PART_BASED_UPKEEP +Use part-based upkeep + +RULE_SHIP_PART_BASED_UPKEEP_DESC +Calculate upkeep based on ship parts instead of ships themselves. + +RULE_ENABLE_ALLIED_REPAIR +Drydocks and Fleet Field Repair repair allied ships + +RULE_ENABLE_ALLIED_REPAIR_DESC +Allow Drydocks building and Fleet Field Repair tech to repair ships of allied empires. + +RULE_SHOW_DETAILED_EMPIRES_DATA +Show detailed empires data + +RULE_SHOW_DETAILED_EMPIRES_DATA_DESC +Show research and production, researched buildings, parts, and hulls of all empires. + +RULE_DIPLOMACY +Allowed diplomacy + +RULE_DIPLOMACY_DESC +Manages diplomatic restrictions between players. By default all interactions are allowed. + +RULE_DIPLOMACY_ALLOWED_FOR_ALL +All Allowed + +RULE_DIPLOMACY_FORBIDDEN_FOR_ALL +All Forbidden + ## ## Command-line and options database entries ## +COMMAND_LINE_NOT_FOUND +No options or sections found matching + COMMAND_LINE_USAGE -'''Usage: ''' +Usage: -h | --help [group name | option name] COMMAND_LINE_DEFAULT -'''Default: ''' +Default + +COMMAND_LINE_SECTIONS +Option Groups + +COMMAND_LINE_OPTIONS +Options OPTIONS_DB_HELP -Print this help message. +'''Print this help message. +Accepts an argument for partial or full option name. +Special arguments are provided for: +all - [[OPTIONS_DB_SECTION_ALL]] +raw - [[OPTIONS_DB_SECTION_RAW]]''' OPTIONS_DB_VERSION Print version and exit. @@ -1072,6 +1288,15 @@ Print version and exit. OPTIONS_DB_SINGLEPLAYER Start server in single player host mode. This only allows clients on localhost to connect. +OPTIONS_DB_HOSTLESS +Start server in hostless mode. Server accepts players in the multiplayer lobby, and returns to the lobby after the game session ends. + +OPTIONS_DB_SKIP_CHECKSUM +Skip comparing the resource directory checksums. This allows faster startup when client and server are on the same machine, using the same resource directory. + +OPTIONS_DB_TESTING +Linux only. AI will output log to console instead file for testing purpose. + OPTIONS_DB_GENERATE_CONFIG_XML Uses default settings, settings from any existing config.xml file, and settings given on the command line to generate a config.xml file. This will overwrite the current config.xml file, if it exists. @@ -1105,10 +1330,10 @@ OPTIONS_DB_UI_SAVE_DIALOG_COLUMNS Valid columns: time, turn, player, empire, systems, seed, galaxy_age, galaxy_shape, planet_freq, native_freq, specials_freq, starlane_freq''' OPTIONS_DB_UI_SAVE_DIALOG_COLUMN_WIDE_AS -If UI.save-file-dialog.[name].wide-as is set, the column will always be wide enough to contain the text there. +If ui.dialog.save.columns.[name].width.chars is set, the column will always be wide enough to contain the text there. OPTIONS_DB_UI_SAVE_DIALOG_COLUMN_STRETCH -If UI.save-file-dialog.[name].stretch is set, the column will get that stretch factor if visible. +If ui.dialog.save.columns.[name].stretch is set, the column will get that stretch factor if visible. OPTIONS_DB_GALAXY_MAP_GAS Render gassy substance around systems to give galaxy shape. May slow rendering on older systems. @@ -1116,6 +1341,9 @@ Render gassy substance around systems to give galaxy shape. May slow rendering o OPTIONS_DB_GALAXY_MAP_STARFIELDS Render star fields around systems. May slow rendering on older systems. +OPTIONS_DB_GALAXY_MAP_STARFIELDS_SCALE +Sets size of star fields around systems. + OPTIONS_DB_GALAXY_MAP_SCALE_LINE Show scale line for universe distance on galaxy map. @@ -1155,6 +1383,15 @@ Sets how far apart to render dots for fleet supply lines. OPTIONS_DB_FLEET_SUPPLY_LINE_DOT_RATE Sets how fast to render dots for fleet supply lines. +OPTIONS_DB_FLEET_EXPLORE_IGNORE_HOSTILE +Ignore hostile ships when a fleet is set to explore + +OPTIONS_DB_FLEET_EXPLORE_SYSTEM_ROUTE_LIMIT +Limits the number of fleets that test for a route to each system for fleets set to automatically explore. Lower limits may be faster but may produce less-optimized routing. A limit of -1 disables the limit. + +OPTIONS_DB_FLEET_EXPLORE_SYSTEM_KNOWN_MULTIPLIER +Multiplier to priority value of unexplored systems which have been previously seen by the empire. A value less than 1.0 will prefer known systems over unknown systems. + OPTIONS_DB_GALAXY_MAP_DETECTION_RANGE Toggles whether to show circles around objects to indicate their detection range on the galaxy map. @@ -1173,6 +1410,48 @@ Address to connect to when joining a multiplayer game. OPTIONS_DB_MP_PLAYER_NAME Player name to use when hosting or joining a multiplayer game. +OPTIONS_DB_MP_AI_MIN +Set the minimum number of ai players required to start a multiplayer game. + +OPTIONS_DB_MP_AI_MAX +Limit the number of AIs allowed in a multiplayer game. Set to -1 for unlimited. + +OPTIONS_DB_MP_HUMAN_MIN +Set the minimum number of human players required to start a multiplayer game. + +OPTIONS_DB_MP_HUMAN_MAX +Limit the number of human players allowed in a multiplayer game. Set to -1 for unlimited. + +OPTIONS_DB_MP_CONN_HUMAN_MIN +Minimum number of active human players, below which the server will stop the game being played. + +OPTIONS_DB_MP_UNCONN_HUMAN_MAX +Maximum number of players, who control non-eliminated empires, who may be unconnected from the server before it stops the game being played. Disabled if 0, meaning the server will never stop the game due to empire-playing players being unconnected. + +OPTIONS_DB_COOKIES_EXPIRE +Count of minutes after the cookie record will be considered expired. + +OPTIONS_DB_PUBLISH_STATISTICS +Enable sending empire staticstics to the player. + +OPTIONS_DB_PUBLISH_SEED +Enable sending galaxy seed to the player. + +OPTIONS_DB_FIRST_TURN_TIME +Absolute time point in format "2019-01-20 11:59:59" in UTC. If empty, first turn advance will happen after expiring interval. Requires fixed interval to be enabled. + +OPTIONS_DB_TIMEOUT_INTERVAL +Maximum interval in seconds between turn automatic advances. If 0, turn advances by timeout are disabled, and turns will only advance once all players have ended their turn. Automatic turn advance is also disabled after any player has won the game. + +OPTIONS_DB_TIMEOUT_FIXED_INTERVAL +Turns advance after fixed intervals, regardless of when and whether players have submitted turn orders. + +OPTIONS_DB_CLIENT_MESSAGE_SIZE_MAX +Limit of client message size in bytes. + +OPTIONS_DB_DROP_EMPIRE_READY +Drop empire's readiness on joining to the playing game. + OPTIONS_DB_UI_MAIN_MENU_X Position of the center of the intro screen main menu, as a portion of the application's total width. @@ -1380,6 +1659,24 @@ Sets the color of the scan-line shading over field icons. OPTIONS_DB_UI_PLANET_FOG_CLR Sets the color of the scan-line shading over planet graphics on the sidepanel. +OPTIONS_DB_UI_PLANET_STATUS_ICON_SIZE +Sets the size of the planet status icon. + +OPTIONS_DB_UI_PLANET_STATUS_ICON_TITLE +Planet status notification + +OPTIONS_DB_UI_PLANET_STATUS_ATTACKED +%1% was attacked on the previous turn. + +OPTIONS_DB_UI_PLANET_STATUS_CONQUERED +%1% has just been conquered. Happiness will probably be reduced. + +OPTIONS_DB_UI_PLANET_STATUS_UNHAPPY +The population on %1% is very unhappy. Neither [[metertype METER_INDUSTRY]] nor [[metertype METER_RESEARCH]] will increase. It can not be used as a source for settlers. + +OPTIONS_DB_UI_PLANET_STATUS_NO_SUPPLY +%1% has been cut off from supply. Industry generated on this planet can only be used here. + OPTIONS_DB_UI_SYSTEM_CIRCLES Toggles whether to draw circles around systems. @@ -1441,15 +1738,15 @@ OPTIONS_DB_UI_TOOLTIP_LONG_DELAY Sets UI tooltip pop-up delay for alternate pop-ups, in ms. OPTIONS_DB_UI_ENC_SEARCH_ARTICLE -When searching the pedia, check for matches in article contents. +When searching the pedia, check for matches in article contents. Search will be slower, but may find more matches. # Section title for options window logging OPTIONS_DB_UI_LOGGER_THRESHOLDS Logger thresholds OPTIONS_DB_UI_LOGGER_THRESHOLD_TOOLTIP -'''Each log output file (freeorion.log, freeoriond.log, and AI_x.log) collects log records from sources (sections of code). -Setting the threshold for a source will enable logs records of that level or higher for that source. +'''Each log output file (freeorion.log, freeoriond.log, and AI_x.log) collects log records from sources (sections of code). +Setting the threshold for a source will enable logs records of that level or higher for that source. Each process client, server, and ai has a general source and may have additional detailed sources (eg. combat resolution logs).''' # This is a label to designate the logger as the general or default logger for a process @@ -1469,11 +1766,14 @@ Sets delay between holding a mouse button and repeat clicks being generated OPTIONS_DB_MOUSE_REPEAT_INTERVAL Sets delay between repeat clicks while holding a moues button +OPTIONS_DB_DISPLAY_TIMESTAMP +Enables messages' timestamp in the chat windows. + OPTIONS_DB_UI_MULTIPLE_FLEET_WINDOWS If true, clicks on multiple fleet buttons will open multiple fleet windows at the same time. Otherwise, opening a fleet window will close any currently-open fleet window. OPTIONS_DB_UI_WINDOW_QUICKCLOSE -Close open fleet window(s) when you left-click on the main map. +Close fleet window(s) when the main map is left-clicked. OPTIONS_DB_UI_AUTO_REPOSITION_WINDOWS Toggles whether to automatically reposition windows when the application size changes. @@ -1509,6 +1809,9 @@ OPTIONS_DB_GAMESETUP_SEED '''The seed used for randomly generating the galaxy. Galaxies generated with the same settings and the same seed will be identical.''' +OPTIONS_DB_GAMESETUP_UID +The game UID for new games. Server-only option can be used for predictable game name on hostless servers. + OPTIONS_DB_GAMESETUP_STARS '''The approximate number of systems in the galaxy to be generated. @@ -1538,7 +1841,7 @@ When set to Low, most systems will typically have one or two starlanes. When set OPTIONS_DB_GAMESETUP_SPECIALS_FREQUENCY '''The proportion of planets and systems containing Special Features. -Some Special Features are protected by Guardians left by Precursor species—only setting Monster Frequency to None will prevent these appearing if the game contains specials. +Some Special Features are protected by Guardians left by Precursor species-only setting Monster Frequency to None will prevent these appearing if the game contains specials. ''' OPTIONS_DB_GAMESETUP_MONSTER_FREQUENCY @@ -1555,7 +1858,7 @@ Some natives can be technologically advanced and may create guard ships to prote OPTIONS_DB_GAMESETUP_AI_MAX_AGGRESSION '''The maximum aggression level for AI opponents. -Most AIs will be at the level set, some may be at the next level below, all should greet you and state their level in the messages window during the first turn.''' +Most AIs will be at the level set, some may be at the next level below, all should send greetings and state their level in the messages window during the first turn.''' OPTIONS_DB_GAMESETUP_EMPIRE_NAME Your empire's name. @@ -1593,12 +1896,18 @@ Adjusts the size of research screen controls, scaled with the text font size. OPTIONS_DB_SAVE_DIR The directory in which saved games are saved and from which they are loaded. +OPTIONS_DB_SERVER_SAVE_DIR +The publicly accessible directory for saved games on the server. + OPTIONS_DB_RESOURCE_DIR Sets the root directory for the game resource files (game content and data files). OPTIONS_DB_LOG_LEVEL Overrides all loggers' thresholds to this level at or above which log messages will be output. +OPTIONS_DB_LOG_FILE +Overrides default log file location. + OPTIONS_DB_LOGGER_FILE_SINK_LEVEL Sets the threshold at or above which log messages will be generated for the default source for this process. @@ -1609,28 +1918,34 @@ OPTIONS_DB_STRINGTABLE_FILENAME Sets the language-specific string table filename. OPTIONS_DB_AI_FOLDER_PATH -Sets the path for the directory containing the AI script files, for current execution only, relative to the Resource Directory; default is "AI". Intended to facilitate AI testing. +Sets the path for the directory containing the AI script files, for current execution only, relative to the Resource Directory; default is "AI". Intended to facilitate AI testing. OPTIONS_DB_AI_CONFIG -Is available to the AI via the freeorioninterface, is set for current execution only. Current expected use is to name an optional AI config file within the AI script folder; default is the empty string. Intended to facilitate AI testing. +Is available to the AI via the freeorioninterface, is set for current execution only. Current expected use is to name an optional AI config file within the AI script folder; default is the empty string. Intended to facilitate AI testing. OPTIONS_DB_AI_CONFIG_TRAIT_AGGRESSION_FORCED Boolean for AI testing which indicates if all AIs are forced to have the same Aggression Trait. OPTIONS_DB_AI_CONFIG_TRAIT_AGGRESSION_FORCED_VALUE -Forced value of the Aggression Trait. A value from 0 to 5. +Forced value of the Aggression Trait. A value from 0 to 5. OPTIONS_DB_AI_CONFIG_TRAIT_EMPIREID_FORCED Boolean for AI testing which indicates if all AIs are forced to have the same EmpireID Trait. OPTIONS_DB_AI_CONFIG_TRAIT_EMPIREID_FORCED_VALUE -Forced value of the EmpireID Trait. A value from 0 to 39. +Forced value of the EmpireID Trait. A value from 0 to 39. -OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER -If true, autosaves will occur during single-player games. +OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER_TURN_START +Enable single-player autosaves at turn start. -OPTIONS_DB_AUTOSAVE_MULTIPLAYER -If true, autosaves will occur during multiplayer games. +OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER_TURN_END +Enable single-player autosaves at turn end. + +OPTIONS_DB_AUTOSAVE_MULTIPLAYER_TURN_START +Enable multi-player autosaves at turn start. + +OPTIONS_DB_AUTOSAVE_MULTIPLAYER_TURN_END +Enable multi-player autosaves at turn end. OPTIONS_DB_AUTOSAVE_TURNS Sets the number of turns between autosaves. @@ -1638,6 +1953,18 @@ Sets the number of turns between autosaves. OPTIONS_DB_AUTOSAVE_LIMIT Sets the maximum number of autosave files to keep. +OPTIONS_DB_AUTOSAVE_GALAXY_CREATION +Enable autosave after the galaxy is created before any game play. + +OPTIONS_DB_AUTOSAVE_GAME_CLOSE +Enable autosave when resigning or closing the game. In multiplayer hostless mode, enable autosaves when the server closes a game due to player disconnection or when the server is terminated. + +OPTIONS_DB_AUTOSAVE_HOSTLESS +Enable autosave in hostless mode. + +OPTIONS_DB_AUTOSAVE_INTERVAL +Delay in seconds after the most recent turn start or autosave until the next autosave. Prevents losing player orders on server crash. 0 if disabled. + OPTIONS_DB_UI_MOUSE_LR_SWAP Swaps results of clicking left and right mouse buttons. @@ -1645,7 +1972,10 @@ OPTIONS_DB_MUSIC_VOLUME The volume (0 to 255) at which music should be played. OPTIONS_DB_QUICKSTART -Starts a new quick-start game, bypassing the main menu. +Starts a new quick-start game, bypassing the main menu in single-player or lobby in multi-player. In multi-player, it requires hostless mode and no restriction on the minimum number of connected players, because the server will start with no connected players. + +OPTIONS_DB_CONTINUE +Continues play from latest save, bypassing the main menu. OPTIONS_DB_AUTO_N_TURNS Hits the "Turn" button automatically on the first N turns (up to 400 turns, defaults to zero); useful for various testing particularly with --quickstart or --load, possibly also --auto-quit. @@ -1669,7 +1999,7 @@ OPTIONS_DB_UI_SITREP_ICONSIZE Sets the sitrep icon width and height; default 16 (min 12, max 64). OPTIONS_DB_LOAD -Loads the specified single-player save game. +Loads and starts the specified single-player or multi-player save game. In multi-player, it requires hostless mode and no restriction on the minimum number of connected players, because the server will start with no connected players. OPTIONS_DB_EFFECTS_THREADS_UI_DESC Specifies number of threads to use for effects processing in the user interface. More than one thread may lead to unpredictable crashes. @@ -1686,6 +2016,9 @@ Automatically quits once any turns specified by --auto-advance-n-turns are compl OPTIONS_DB_BINARY_SERIALIZATION Use Binary serialization for saving games. Binary serialization is faster to save and load, but may not be possible to load on a different operating system. +OPTIONS_DB_SERVER_BINARY_SERIALIZATION +The server will use Binary serialization for client-server interaction if the client's version matches the server's version. Binary serialization is faster to transfer, but may not be compatible between different operating systems and architectures. + OPTIONS_DB_XML_ZLIB_SERIALIZATION When saving games with XML serialization, compress most of the XML before writing the file. Compression substantially reduces save file sizes, but may make saves unloadable due to memory requirements to decompress the save data. @@ -1777,6 +2110,89 @@ OPTIONS_DB_NETWORK_MESSAGE_PORT Network port to use for client-server messaging. +## +## Option sections +## + +OPTIONS_DB_SECTION_ALL +All options + +OPTIONS_DB_SECTION_AUDIO +Audio + +OPTIONS_DB_SECTION_AUDIO_MUSIC +Background music + +OPTIONS_DB_SECTION_AUDIO_EFFECTS +Sound effects + +OPTIONS_DB_SECTION_AUDIO_EFFECTS_PATHS +Sound effect paths + +OPTIONS_DB_SECTION_EFFECTS +Effects + +OPTIONS_DB_SECTION_LOGGING +Logging + +OPTIONS_DB_SECTION_MISC +Miscellaneous + +OPTIONS_DB_SECTION_NETWORK +Network and server + +OPTIONS_DB_SECTION_RAW +All options with only raw description keys + +OPTIONS_DB_SECTION_RESOURCE +Game data resources + +OPTIONS_DB_SECTION_SAVE +Save game + +OPTIONS_DB_SECTION_SETUP +New game starting settings + +OPTIONS_DB_SECTION_UI +User interface + +OPTIONS_DB_SECTION_UI_COLORS +Colors + +OPTIONS_DB_SECTION_UI_CONTROL +Control (generic) + +OPTIONS_DB_SECTION_UI_HOTKEYS +Hotkeys + +OPTIONS_DB_SECTION_UI_MAP +Main map + +OPTIONS_DB_SECTION_UI_MAP_FLEET +Fleets (map) + +OPTIONS_DB_SECTION_UI_FLEET +Fleet window + +OPTIONS_DB_UI_WINDOW +Window (generic) + +OPTIONS_DB_SECTION_VERSION +Version + +OPTIONS_DB_SECTION_VIDEO +Video + +OPTIONS_DB_SECTION_VIDEO_FPS +Frames per second + +OPTIONS_DB_SECTION_VIDEO_FULLSCREEN +Fullscreen mode + +OPTIONS_DB_SECTION_VIDEO_WINDOWED +Windowed mode + + ## ## File dialog ## @@ -1842,6 +2258,9 @@ A: INTRO_WINDOW_TITLE FreeOrion Main Menu +INTRO_BTN_CONTINUE +Continue + INTRO_BTN_SINGLE_PLAYER Single Player @@ -1899,12 +2318,26 @@ HOST_GAME_BN Host a new game JOIN_GAME_BN -Join a game +Join a game as REFRESH_LIST_BN Refresh list +## +## Password Dialog +## + +AUTHENTICATION_WINDOW_TITLE +Authentication + +AUTHENTICATION_DESC +This player name requires authentication. Reminder! Password will be transfered plain-text. + +PASSWORD_LABEL +Password + + ## ## Multiplayer lobby ## @@ -1933,6 +2366,12 @@ Previous Player MULTIPLAYER_PLAYER_LIST_STARTING_SPECIES Starting Species +EDITABLE_GALAXY_SETTINGS +All Can Edit Settings + +EDITABLE_GALAXY_SETTINGS_DESC +All players can edit galaxy and AI settings. + NEW_GAME_BN New game @@ -1948,6 +2387,18 @@ Accept NOT_READY_BN Decline +# %1% is a turn progress phase +PLAYING_GAME +Playing game: %1% + +# %1% is a player name +PLAYER_ENTERED_GAME +%1% entered game + +# %1% is a player name +PLAYER_LEFT_GAME +%1% left game + ## ## Galaxy Setup Screen @@ -1975,7 +2426,7 @@ GSETUP_SEED Seed GSETUP_RANDOM_SEED -Generate a random seed +Toggle generation of a random seed GSETUP_STARS Systems @@ -2132,7 +2583,10 @@ GAME_MENU_LOAD Load Game GAME_MENU_RESIGN -Resign +Exit to Menu + +GAME_MENU_CONCEDE +Concede GAME_MENU_SAVE_FILES Save Game Files @@ -2141,7 +2595,19 @@ BUTTON_DISABLED Button Disabled SAVE_DISABLED_BROWSE_TEXT -The Save Game button is disabled, either because this is a multiplayer game in which you are neither Host nor Moderator, or because at the time this menu was opened at least one of the AIs had not yet finished submitting its moves for the turn (indicated by a green triangle status icon just to the left of its player-type icon in the Empires window). +'''The Save Game button is disabled. This may be be because: + +-This is a multiplayer game in which this client is neither Host nor Moderator. + +-When this menu was opened at least one of the AIs had not yet finished submitting its moves for the turn (indicated by a green triangle status icon just to the left of its player-type icon in the Empires window). + +-Another save is ongoing.''' + +GAME_MENU_REALLY_CONCEDE +Confirm conceding? This empire will be removed from the game. + +GAME_MENU_CONFIRM_NOT_READY +Confirm quit? Orders not finalized. ## @@ -2226,11 +2692,11 @@ Path: # %1% entered path to the save game file that should be deleted. SAVE_REALLY_DELETE -Are you sure you want to delete %1%? +Really delete %1%? # %1% entered path to the save game file that should be overwritten. SAVE_REALLY_OVERRIDE -Are you sure you want to override %1%? +Really overwrite %1%? ## @@ -2240,6 +2706,9 @@ Are you sure you want to override %1%? OPTIONS_TITLE Options +OPTIONS_PEDIA_SEARCH_ARTICLE_TEXT +Search Pedia Article Text + OPTIONS_MULTIPLE_FLEET_WNDS Multiple fleet windows @@ -2252,15 +2721,12 @@ Show side panel planets OPTIONS_AUTO_REPOSITION_WINDOWS Automatically reposition windows +OPTIONS_DISPLAY_TIMESTAMP +Show chat timestamp + OPTIONS_MISC_UI Miscellaneous UI Settings -OPTIONS_SINGLEPLAYER -Single player - -OPTIONS_MULTIPLAYER -Multiplayer - OPTIONS_AUTOSAVE_LIMIT Autosaves limit @@ -2279,6 +2745,9 @@ Font Sizes OPTIONS_FONT_TEXT Text +OPTIONS_FONT_BOLD_TEXT +Bold text + OPTIONS_FONT_TITLE Window titles @@ -2438,6 +2907,9 @@ Galaxy map gas rendering OPTIONS_GALAXY_MAP_STARFIELDS Galaxy map star fields rendering +OPTIONS_GALAXY_MAP_STARFIELDS_SCALE +Galaxy map star fields star size + OPTIONS_GALAXY_MAP_SCALE_LINE Galaxy distance scale line @@ -2675,6 +3147,9 @@ Resource Files OPTIONS_FOLDER_SAVE Save files +OPTIONS_SERVER_FOLDER_SAVE +Server Save files + OPTIONS_LANGUAGE_FILE Language files @@ -2721,7 +3196,50 @@ OPTIONS_USE_BINARY_SERIALIZATION Create Binary Save Files OPTIONS_USE_XML_ZLIB_SERIALIZATION -Use Compression for XML Save Files +Use Compression for XML Save Files + +OPTIONS_CREATE_PERSISTENT_CONFIG +Write Persistent Config File + +OPTIONS_CREATE_PERSISTENT_CONFIG_TOOLTIP_TITLE +Create Persistent Config (Advanced Feature) + +OPTIONS_CREATE_PERSISTENT_CONFIG_TOOLTIP_DESC +'''Saves the current config settings as persistent default settings. + +Such settings take priority over any settings in config.xml, but not the command line. + +Only stores settings that have been modified from default values. + +Any existing persistent config file is overwritten. + +Settings may not always be valid across versions. +The settings from a previously stored window may not be retained if is has not been open this game session. + +See pedia article [[CONFIG_GUIDE_TITLE]] for more info.''' + +OPTIONS_CREATE_PERSISTENT_CONFIG_SUCCESS +Successfully (re)created persistent_config.xml + +OPTIONS_CREATE_PERSISTENT_CONFIG_FAILURE +Unable to create persistent_config.xml, check log file for details. + +OPTIONS_CREATE_ALL_CONFIG_SUCCESS +Successfully wrote config.xml + +OPTIONS_CREATE_ALL_CONFIG_FAILURE +Unable to write config.xml, check log file for details. + +OPTIONS_WRITE_ALL_CONFIG +Write Complete Config File + +OPTIONS_CREATE_ALL_CONFIG_TOOLTIP_TITLE +Create Full Config (Intermediate Feature) + +OPTIONS_CREATE_ALL_CONFIG_TOOLTIP_DESC +'''Saves the current config settings in config.xml including all settings, even if not modified from their defaults. + +The existing options.xml is overwitten, but may be regenerated (with just non-default options) if another option is changed, including by repositioning some game windows or within the options window.''' ## @@ -2732,6 +3250,61 @@ Use Compression for XML Save Files MAP_BTN_TURN_UPDATE Turn %1% +# %1% current turn number. +MAP_BTN_TURN_UNREADY +Revise Orders %1% + +MAP_BTN_TURN_TOOLTIP +Start Turn + +MAP_BTN_TURN_TOOLTIP_DESC_SP +'''Starts turn processing with the following sequence: + +[[MAP_BTN_TURN_TOOLTIP_SEQUENCE_MACRO]] +''' + +MAP_BTN_TURN_TOOLTIP_DESC_MP +'''Send orders to the server. Turn processing will be executed with the following sequence: + +[[MAP_BTN_TURN_TOOLTIP_SEQUENCE_MACRO]] +''' + +MAP_BTN_TURN_TOOLTIP_DESC_OBS +Observers are unable to start turn processing. + +MAP_BTN_TURN_TOOLTIP_DESC_MOD +'''Starts turn processing even if some players have not sent their orders to the server. +Turn processing will be executed with the following sequence: + +[[MAP_BTN_TURN_TOOLTIP_SEQUENCE_MACRO]] +''' + +MAP_BTN_TURN_TOOLTIP_DESC_WAIT +Waiting for other players to send their orders. Click again to revise orders. + +MAP_BTN_TURN_TOOLTIP_SEQUENCE_MACRO +'''Order execution +Colonisation, invasion, bombardment, gifting, scrapping and fleet movement + +Combat resolution + +Production, Research and Growth +Effect execution, production of ships and buildings, unlocking of new technologies, population growth, update of supply propagation''' + +# %1% number of seconds +MAP_TIMEOUT_SECONDS +%1% seconds + +# %1% number of minutes +# %2% number of seconds +MAP_TIMEOUT_MINS_SECS +%1% mins %2% secs + +# %1% number of hours +# %2% number of minutes +MAP_TIMEOUT_HRS_MINS +%1% hrs %2% mins + # %1% number of frames currently displayed per second. MAP_INDICATOR_FPS %1% FPS @@ -2821,39 +3394,43 @@ MAP_BTN_MESSAGES_DESC MAP_PRODUCTION_TITLE Production -MAP_PROD_WASTED_TITLE -Unused Production - -# %1% number of production points generated. -# %2% number of production points currently not used for production. -MAP_PROD_WASTED_TEXT -'''Total Production : %1% -Wasted Production : %2% +MAP_PRODUCTION_WASTED_TITLE +Wasted Production +MAP_PROD_CLICK_TO_OPEN +''' Click here to open the production menu''' -RESOURCE_TT_USED -Used +MAP_RESEARCH_TITLE +Research + +MAP_RESEARCH_WASTED_TITLE +Wasted Research + +MAP_RES_CLICK_TO_OPEN +''' +Click here to open the research menu''' + +MAP_STOCKPILE_TITLE +Imperial Stockpile RESOURCE_TT_OUTPUT -Current Output +Output RESOURCE_TT_TARGET_OUTPUT Target Output -MAP_RESEARCH_TITLE -Research +RESOURCE_TT_USED +Used -MAP_RES_WASTED_TITLE -Unused Research +RESOURCE_TT_EXCESS +Excess -# %1% number of research points generated. -# %2% number of research points currently not used for research. -MAP_RES_WASTED_TEXT -'''Total Research : %1% -Wasted Research : %2% +RESOURCE_TT_TO_STOCKPILE +To Stockpile -Click here to open the research menu''' +RESOURCE_TT_WASTED +Wasted MAP_POPULATION_DISTRIBUTION Census @@ -2924,6 +3501,9 @@ Research Output TRADE_PRODUCTION Trade Output +STOCKPILE_GENERATION +Stockpile Capacity Generation + INDUSTRY_CONSUMPTION Industrial Consumption @@ -2933,6 +3513,9 @@ Research Consumption TRADE_CONSUMPTION Trade Consumption +STOCKPILE_USE +Stockpile Capacity Use + IMPORT_EXPORT_TOOLTIP Import / Export @@ -2990,8 +3573,9 @@ PL_TYPE_SIZE # %1% the planet size as text (small, medium, large, ...). # %2% type of the planet environment (desert, oceanic, terran, ...). # %3% suitability of the planet for a concrete species (adequate, hostile, ...). +# %4% species name for which suitability applies (Human, Trith, ...). PL_TYPE_SIZE_ENV -%1% %2% (%3%) +%1% %2% (%4%: %3%) PL_NO_VISIBILITY Planet is not currently visible. @@ -3028,11 +3612,17 @@ Latest known [[metertype METER_STEALTH]] is out of date. Actual stealth exceeds MENUITEM_SET_FOCUS Set focus -MENUITEM_ENQUEUE_BUILDING -Queue Building +MENUITEM_ENQUEUE_SHIPDESIGN_TO_TOP_OF_QUEUE +Add Ship to Top of Queue MENUITEM_ENQUEUE_SHIPDESIGN -Queue Ship +Add Ship to Queue + +MENUITEM_ENQUEUE_BUILDING_TO_TOP_OF_QUEUE +Add Building to Top of Queue + +MENUITEM_ENQUEUE_BUILDING +Add Building to Queue ## @@ -3397,6 +3987,9 @@ Split Ships Into Fleets For Each Design FW_ORDER_DISMISS_SENSOR_GHOST Dismiss Sensor Ghost +FW_ORDER_DISMISS_SENSOR_GHOST_ALL +Dismiss All Sensor Ghosts Here + ORDER_SHIP_SCRAP Scrap Ship @@ -3427,6 +4020,7 @@ Give Planet To... ORDER_CANCEL_GIVE_PLANET Cancel Giving Planet + ## ## Fleet Button ## @@ -3440,6 +4034,7 @@ This fleet is currently being blockaded by a hostile fleet. It is unable to move FB_TOOLTIP_BLOCKADE_MONSTER This monster fleet is blockaded by an empire fleet. When monsters are blockaded by empire fleets, they are prevented from moving. + ## ## Moderator ## @@ -3476,9 +4071,18 @@ Diplomacy PEACE Peace +AT_PEACE_WITH +At peace with + WAR War +AT_WAR_WITH +At war with + +ALLIED_WITH +Allied with + WAR_DECLARATION Declare War @@ -3602,13 +4206,31 @@ PRODUCTION_INFO_EMPIRE %2% %1% PRODUCTION_INFO_TOTAL_PS_LABEL -Total Available Points +Available Points PRODUCTION_INFO_WASTED_PS_LABEL -Wasted Points +Excess + +PRODUCTION_INFO_STOCKPILE_PS_LABEL +Stockpiled Points + +PRODUCTION_INFO_STOCKPILE_USE_MAX_LABEL +Maximum Use + +PRODUCTION_INFO_STOCKPILE_USE_PS_LABEL +Stockpile Use -PRODUCTION_INFO_LOCAL_PS_LABEL -Points Available Here +STOCKPILE_LABEL +Stockpile + +STOCKPILE_USE_LABEL +Stockpile Use + +STOCKPILE_USE_LIMIT +Stockpile Use Limit + +STOCKPILE_CHANGE_LABEL +Next Turn Stockpile ## @@ -3657,6 +4279,10 @@ Available PRODUCTION_WND_AVAILABILITY_UNAVAILABLE Unavailable +PRODUCTION_WND_AVAILABILITY_OBSOLETE_AND_UNAVAILABLE +'''Obsolete +Unavailable''' + PRODUCTION_WND_REDUNDANT Redundant @@ -3711,10 +4337,10 @@ It also allows for some interaction with the [[encyclopedia MAP_WINDOW_ARTICLE_T (default position: top left) The Production Summary panel shows available Production Points. -Total Available Points relates to the PP available across your entire empire. -Points Available Here are the maximum available for the selected system. This may be less than the total available when all of your empire's systems are not [[metertype METER_SUPPLY]] connected. -Wasted Points will be lost, if not allocated before ending the turn. - +If a system is selected, there are two columns shown. The left column shows production points across your entire empire. The right column gives production points for the selected system (but is absent if no system is selected). +Available Points relates to the PP available for production. The right value are the maximum available production points for the selected system. This may be less than the total available when all of your empire's systems are not [[metertype METER_SUPPLY]] connected. +Stockpile relates to the PP available for production using the imperial stockpile. Production items are enabled to use PP from the Imperial Stockpile by default and have to be explicitly disabled from doing so. +PP not allocated to anything on the build queue will automatically be stored in the imperial stockpile. Producible Items panel @@ -3737,7 +4363,7 @@ These orders may be rearranged in the queue by dragging and dropping to a new po Right clicking on an order will show a menu for moving, removing, or pausing an order (as well as other possible actions, such as Show in Pedia ). When removing an order any previous progress on it is lost, there is no credit returned or given to other orders. Pausing an order prevents any progress on it until the order is resumed. -For ships, an additional menu item is available to Rally to a system. Once the order completes, the ship(s) will have a waypoint assigned for the system rallied to. To set or change a Rally destination, select the desired system on the map, right click the Production Order, and select Rally to ... (where ... is the system name). +For ships, an additional menu item is available to Rally to a system. Once the order completes, the ship(s) will have a waypoint assigned for the system rallied to. To set or change a Rally destination, select the desired system on the map, right click the Production Order, and select Rally to ... (where ... is the system name). Production Order @@ -3760,7 +4386,7 @@ A Survey Ship costs 36 PP with a minimum of 3 turns (max 12 PP/turn). You queue another order for Survey Ship with a repetition of 2. (order #2) You queue another order for Survey Ship and set the quantity to 2. --Turn 2: Order #1 has completed 10 PP, 26 PP remain. 10 PP is projected for next turn. +-Turn 2: Order #1 has completed 10 PP, 26 PP remain. 10 PP is projected for next turn. (Completed PP / Remaining PP to complete -> Projected next turn) @@ -3832,6 +4458,10 @@ PRODUCTION_WND_PROGRESS PRODUCTION_QUEUE_ITEM_LOCATION At %1% +# %1% location where the item is produced. +PRODUCTION_QUEUE_ITEM_RALLIED_FROM_LOCATION +Rallying From %1% + # %1% location where the item is enqueued for production. PRODUCTION_QUEUE_ENQUEUED_ITEM_LOCATION Enqueued at %1% @@ -3842,6 +4472,18 @@ Can be produced here PRODUCTION_LOCATION_INVALID CANNOT be produced here +IMPERIAL_STOCKPILE +Imperial Stockpile + +PRODUCTION_QUEUE_ITEM_STOCKPILE_ENABLED +(Use of [[IMPERIAL_STOCKPILE]] is Enabled for this Item) + +ALLOW_IMPERIAL_PP_STOCKPILE_USE +Enable drawing from [[IMPERIAL_STOCKPILE]] for this item + +DISALLOW_IMPERIAL_PP_STOCKPILE_USE +Disable drawing from [[IMPERIAL_STOCKPILE]] for this item + # %1% name of the system this item production should be relocated to. PRODUCTION_QUEUE_RALLIED_TO Rallied to %1% @@ -3859,6 +4501,12 @@ Move to Queue Top MOVE_DOWN_QUEUE_ITEM Move to Queue Bottom +MOVE_UP_LIST_ITEM +Move to Top + +MOVE_DOWN_LIST_ITEM +Move to Bottom + SPLIT_INCOMPLETE Separate Current Repetition @@ -3866,10 +4514,19 @@ DUPLICATE Duplicate PRODUCTION_WND_TOOLTIP_PROD_COST -Production cost +Production cost: %1% + +PRODUCTION_WND_TOOLTIP_PROD_TIME_MINIMUM +Minimum production turns: %1% + +PRODUCTION_WND_TOOLTIP_LOCATION_DEPENDENT +Depends on location PRODUCTION_WND_TOOLTIP_PROD_TIME -Production time +Fully-funded production turns at %2%: %1% + +NO_PRODUCTION_HERE_CANT_PRODUCE +Insufficient production at %1% PRODUCTION_WND_TOOLTIP_PARTS Ship Parts @@ -3910,6 +4567,18 @@ Obsolete Design DESIGN_WND_UNOBSOLETE_DESIGN Un-Obsolete Design +DESIGN_WND_OBSOLETE_HULL +Obsolete Hull + +DESIGN_WND_UNOBSOLETE_HULL +Un-Obsolete Hull + +DESIGN_WND_OBSOLETE_PART +Obsolete Part + +DESIGN_WND_UNOBSOLETE_PART +Un-Obsolete Part + DESIGN_WND_DELETE_DESIGN Delete Design @@ -3934,6 +4603,9 @@ Saved DESIGN_WND_MONSTERS Monsters +DESIGN_WND_ALL +All Known + DESIGN_WND_PART_PALETTE_TITLE Ship Parts @@ -3967,26 +4639,37 @@ Begin a ship design by selecting its hull. DESIGN_INV_NO_NAME Design name cannot be empty. -DESIGN_KNOWN +DESIGN_WND_KNOWN Duplicates Existing Design # %1% name of the equivalent ship design. -DESIGN_KNOWN_DETAIL +DESIGN_WND_KNOWN_DETAIL This design is a duplicate of "%1%". -DESIGN_COMPONENT_CONFLICT +DESIGN_WND_RENAME_FINISHED +Rename Finished Design + +# %1% name of the equivalent ship design. +# %2% new name of the ship design. +DESIGN_WND_RENAME_FINISHED_DETAIL +'''Rename Finished design from +"%1%" +to +"%2%"''' + +DESIGN_WND_COMPONENT_CONFLICT Conflicting Design # %1% first conflicting ship part name. # %2% second conflicting ship part name. -DESIGN_COMPONENT_CONFLICT_DETAIL +DESIGN_WND_COMPONENT_CONFLICT_DETAIL A shipdesign cannot contain both %1% and %2%. DESIGN_WND_ADD_FINISHED Add Finished Design # %1% name of the new ship design. -DESIGN_WND_ADD_DETAIL_FINISHED +DESIGN_WND_ADD_FINISHED_DETAIL '''Add new design called "%1%" to finished designs.''' @@ -3996,7 +4679,7 @@ Update Finished Design # %1% previous name of the existing design. # %2% new name of the existing design. -DESIGN_WND_UPDATE_DETAIL_FINISHED +DESIGN_WND_UPDATE_FINISHED_DETAIL '''Replace Finished design "%1%" with new design @@ -4007,7 +4690,7 @@ DESIGN_WND_ADD_SAVED Add Saved Design # %1% name of the new ship design. -DESIGN_WND_ADD_DETAIL_SAVED +DESIGN_WND_ADD_SAVED_DETAIL '''Add a design called "%1%" to saved designs.''' @@ -4017,7 +4700,7 @@ Update Saved Design # %1% old name of the saved design. # %2% new name of the saved design. -DESIGN_WND_UPDATE_DETAIL_SAVED +DESIGN_WND_UPDATE_SAVED_DETAIL '''Replace saved design "%1%" with new design @@ -4036,10 +4719,19 @@ Toggle the Obsolete, Available and Unavailable filters to show more hulls ADD_FIRST_SAVED_DESIGN_QUEUE_PROMPT Select an existing Finished design, right click and select "[[DESIGN_SAVE]]" to create a saved design. +ALL_AVAILABILITY_FILTERS_BLOCKING_PROMPT +Toggle the Obsolete, Available and Unavailable filters to show more + +NO_SAVED_OR_DEFAULT_DESIGNS_ADDED_PROMPT +'''Select a hull, add parts and then click "[[DESIGN_WND_ADD_FINISHED]]" to create an available design. + +Neither the Saved Designs nor the Default Designs were added to the empire at game start. Options to add these designs at game start can be changed in Options->Other. Add the Saved Designs immediately with the context menu of any design under the Saved tab.''' + # %1% File path of unwritable file ERROR_UNABLE_TO_WRITE_FILE Unable to write file:\n"%1%" + ## ## Statistics ## @@ -4095,6 +4787,9 @@ Own Planets Depopulated PLANETS_INVADED Planets Invaded +TOTAL_POPULATION_STAT +Population + STATISTICS_TEST_1 Statistics Test 1 @@ -4111,14 +4806,14 @@ Statistics Test 3 # %1% name of the species. ENC_SPECIES_PLANET_TYPE_SUITABILITY_COLUMN1 -'''%1%: ''' +%1%: # %1% content of ENC_SPECIES_PLANET_TYPE_SUITABILITY_COLUMN1 expanded to match # column width. # %2% the planet environment for a species. # %3% the planet population capacity for a species. ENC_SPECIES_PLANET_TYPE_SUITABILITY -%1% %2% (%3%) +%1% %2% (%3%) # %1% name of the planet. ENC_SUITABILITY_REPORT_POSITIVE_HEADER @@ -4141,6 +4836,30 @@ Suitability is determined by distance from the preferred type of [[encyclopedia ENC_GRAPH [Game - Graphs] +USE_LINEAR_SCALE +Linear Scale + +USE_LOG_SCALE +Logarithmic Scale + +SHOW_SCALE +Show Scale + +HIDE_SCALE +Hide Scale + +SHOW_LINES +Plot Lines + +SHOW_POINTS +Plot Points + +SCALE_TO_ZERO +Scale to Zero + +SCALE_FREE +Scale Free + ENC_GALAXY_SETUP [Game - Galaxy] @@ -4154,6 +4873,7 @@ ENC_GALAXY_SETUP # %8% monster frequency in the current galaxy. # %9% natives frequency in the current galaxy. # %10% upper AI aggression level in this galaxy. +# %11% game UID ENC_GALAXY_SETUP_SETTINGS '''Seed: %1% Number of Systems: %2% @@ -4165,6 +4885,7 @@ Specials: %7% Monsters: %8% Natives: %9% AI Max Aggression: %10% +Game UID: %11% ''' ENC_GAME_RULES @@ -4201,13 +4922,13 @@ ENC_SPECIES Species ENC_SPECIES_DESC -Each species is classified according their [[encyclopedia METABOLISM_TITLE]]. +Each species is classified according their [[encyclopedia METABOLISM_TITLE]]. ENC_FIELD_TYPE Field Type ENC_SHIP_DESIGN -Ship Design +[Game - Ship Design] SHIPS_OF_DESIGN '''Ships of this Design: ''' @@ -4233,6 +4954,36 @@ Empire ID EMPIRE_METERS Meters: +RESEARCHED_TECHS +'''Researched Techs: ''' + +BEFORE_FIRST_TURN +Before First Turn + +TURN +Turn + +NO_TECHS_RESEARCHED +No Techs Researched + +AVAILABLE_PARTS +'''Available Parts: ''' + +NO_PARTS_AVAILABLE +No Parts Available + +AVAILABLE_HULLS +'''Available Hulls: ''' + +NO_HULLS_AVAILABLE +No Hulls Available + +AVAILABLE_BUILDINGS +'''Available Buildings: ''' + +NO_BUILDINGS_AVAILABLE +No Buildings Available + OWNED_PLANETS '''Owned Planets: ''' @@ -4299,7 +5050,7 @@ ENC_MONSTER [Game - Monster] ENC_MONSTER_TYPE -Monster Types +[Game - Monster Types] ENC_FLEET [Game - Fleets] @@ -4322,9 +5073,21 @@ ENC_HOMEWORLDS ENC_TEXTURES [Test - Textures] +ENC_POP_CENTER +Population Center + +ENC_PROD_CENTER +Production Center + +ENC_FIGHTER +Fighter + ENC_TEXTURE_INFO %4% -- %1% x %2% px @ %3% BPP +ENC_VECTOR_TEXTURE_INFO +%4% -- %1% x %2% px with %3% shapes + ENC_INDEX Encyclopedia Index @@ -4510,7 +5273,6 @@ pierced ENC_COMBAT_ATTACK_SIMPLE_STR %1% attacks %2% - # %1% number of repeated events. # %2% link to the attacking unit or empire. # %3% link to the attacking unit or empire. @@ -4532,6 +5294,15 @@ ENC_COMBAT_RECOVER_STR ENC_COMBAT_UNKNOWN_OBJECT Unknown +# %1% empire name +# %2% species name +# %3% fighter type name +ENC_COMBAT_EMPIRE_FIGHTER_NAME +%1% %2% %3% + +ENC_COMBAT_ROGUE +Rogue + # %1% number of combat round. ENC_ROUND_BEGIN ''' @@ -4567,7 +5338,7 @@ The ship %2% was destroyed # %1% unused # %2% name of empire ENC_COMBAT_INITIAL_STEALTH_LIST -%2% empire can not target: +%2% empire initially cannot detect: # %1 link to the attacking unit # %2 link to the defending unit. @@ -4605,7 +5376,7 @@ ENC_COMBAT_PLATFORM_TARGET_AND_DAMAGE ENC_COMBAT_PLATFORM_NO_DAMAGE_1_EVENTS %2% could not damage -# %1% number of targets +# %1% number of targets # %2% name of ship unable to damage targets ENC_COMBAT_PLATFORM_NO_DAMAGE_MANY_EVENTS %2% could not damage %1% targets: @@ -4724,9 +5495,15 @@ CATEGORY_GAME_CONCEPTS GROWTH_ARTICLE_SHORT_DESC Growth +STOCKPILE_ARTICLE_SHORT_DESC +Stockpile + OUTPOSTS_ARTICLE_SHORT_DESC Outpost +HOMEWORLDS_ARTICLE_SHORT_DESC +Homeworld + PRODUCTION_ARTICLE_SHORT_DESC Production @@ -4750,24 +5527,25 @@ Management ## -## Encyclopedia subcategories +## Encyclopedia articles in Guides category ## -# In Guides category - INTERFACE_TITLE *Interface* INTERFACE_TEXT Non-exhaustive list of FreeOrion Interface features: -# In Game Concepts category + +## +## Encyclopedia articles in Game Concepts category +## DIPLOMACY_TITLE Diplomacy DIPLOMACY_TEXT -Non-exhaustive list of Diplomacy features: +List of Diplomacy features: METABOLISM_TITLE Metabolism @@ -4803,10 +5581,14 @@ SYSTEM_BLOCKADE_TITLE System Blockade SYSTEM_BLOCKADE_TEXT -'''When an armed fleet set to aggressive mode arrives at a system, it will establish a system blockade against all empires it is at war with and that currently do not blockade the system. When a blockade is established, [[metertype METER_SUPPLY]] propagation is interrupted for the blockaded empire. Also, if the blockaded empire posesses colonies within the system, the production generated by these colonies can only be used on the planet it is generated on. If a combat is initiated concomitantly, effects that only work if no combat has occurred, such as orbital drydock repair and troop meter growth, are suspended for that turn. +'''When an armed fleet set to aggressive mode arrives at a system, it will establish a system blockade against all empires it is at [[encyclopedia WAR_TITLE]] with and that currently do not blockade the system. When a blockade is established, [[metertype METER_SUPPLY]] propagation is interrupted for the blockaded empire. + After a system blockade has been set up, all starlane entries in the blockaded system will be guarded by the blockading force, so if hostile forces enter the system, they can exit only through the starlane entry from which they arrived. However, if more fleets from the blockaded empire arrive by different starlane entries than the first one, these entries will become available as an exit as well. If all armed fleets owned by the blockaded empire are destroyed or leave the system before new fleets from this empire arrive, the previously opened exits will become closed again. + Allied fleets do not have a direct impact on supply blocks and starlane availability apart from helping with eliminating the blockading empire's forces. + If fleets from two or more hostile empires arrive at an unblockaded system at the same time, neither such empire will establish a system blockade over each other, although they will establish a blockade against all other hostile empires that arrive at a later time. + Space monsters can also establish blockades. However, if an empire fleet becomes blockaded by a space monster, the blockade will be lifted after combat has occured, and the empire fleet will be able to leave the system by any starlane exit, or establish a blockade against the monster in turn. Furthermore, if the blockaded fleet is armed and set to aggressive mode, supply propagation will be restored. If a space monster and an empire fleet arrive at the same time, the empire fleet will be able to act first and establish a blockade against the monster.''' @@ -4892,6 +5674,12 @@ METER_MAX_SUPPLY_VALUE_LABEL METER_MAX_SUPPLY_VALUE_DESC [[METER_SUPPLY_VALUE_DESC]] +METER_MAX_STOCKPILE_VALUE_LABEL +[[METER_STOCKPILE_VALUE_LABEL]] + +METER_MAX_STOCKPILE_VALUE_DESC +[[METER_STOCKPILE_VALUE_DESC]] + METER_MAX_TROOPS_VALUE_LABEL [[METER_TROOPS_VALUE_LABEL]] @@ -4902,7 +5690,12 @@ METER_POPULATION_VALUE_LABEL Population METER_POPULATION_VALUE_DESC -An abstract value for the size of a colony, relative to differing species. +'''The Population represents the size of a colony on a planet. Its maximum value depends on the [[encyclopedia ENC_SPECIES]] and how suitable the planet's [[encyclopedia ENVIRONMENT_TITLE]] is for that species. Many [[metertype METER_INDUSTRY]] and [[metertype METER_RESEARCH]] output bonuses are linked to the Population size: larger Population gives more bonuses. + +A Population of at least three and a minimum [[metertype METER_HAPPINESS]] of 5 is necessary for a colony to act as a source of settlers for colonizing other planets. A colony ship has a population of Colonists, which is determined by the [[encyclopedia PC_COLONY]] capacity of the ship design. When colonizing a planet, the number of Colonists aboard the ship will be the starting Population on the newly settled planet. + +The Target Population is the stable Population size a planet will approach, given the current factors influencing the planetary Population. Its value is connected to [[encyclopedia ENC_BUILDING_TYPE]]s built on the planet, [[encyclopedia ENC_TECH]] researched by the empire, [[encyclopedia ENC_SPECIAL]]s linked to the planet, and various other game effects. If these factors change, then the Target Population value is likely to shift. +''' METER_INDUSTRY_VALUE_LABEL Industry @@ -4910,7 +5703,9 @@ Industry METER_INDUSTRY_VALUE_DESC '''Industry represents the production and modification of physical goods. It is needed to produce buildings, space ships and other projects. -The Industry of a planet can be used for any production projects of [[metertype METER_SUPPLY]] line connected planets. Any un-allocated Industry is lost each turn. +The Industry of a planet can be used for any production projects of [[metertype METER_SUPPLY]] line connected planets. Unallocated Industry gets added to the Imperial Stockpile. + +The Target Industry is the stable Industry output level a planet will approach, given the current factors influencing the Industry resources. Its value is connected to [[encyclopedia ENC_BUILDING_TYPE]]s built on the planet, [[encyclopedia ENC_TECH]] researched by the empire, [[encyclopedia ENC_SPECIAL]]s linked to the planet, and various other game effects. If these factors change, then the Target Industry value is likely to shift. For information on the interface, see [[encyclopedia PRODUCTION_WINDOW_ARTICLE_TITLE]]. ''' @@ -4921,8 +5716,10 @@ Research METER_RESEARCH_VALUE_DESC '''The Research (or RP "Research Points") of all planets in the empire is combined to discover new [[encyclopedia ENC_TECH]]. -Any Research that cannot be spent on a tech is lost each turn. Each tech has a minimum number of turns required to research it. For example, if a tech that cost 15 RPs, and had a minimum time of 3 turns, 5 RPs is the maximum that could be put towards its completion per turn. -The tech at the top of the queue is given RPs first. Any remaining RP are then given to the next tech in the queue, and if there is still more remaining to the next-- and so on. You can re-prioritize the items in the queue by dragging the most important items closer to the top. +The Target Research is the stable Research output level a planet will approach, given the current factors influencing the Research resources. Its value is connected to [[encyclopedia ENC_BUILDING_TYPE]]s built on the planet, [[encyclopedia ENC_TECH]] researched by the empire, [[encyclopedia ENC_SPECIAL]]s linked to the planet, and various other game effects. If these factors change, then the Target Research value is likely to shift. + +Each tech has a minimum number of turns required to research it. For example, if a tech costs 15 RP and has a minimum time of 3 turns, only 5 RP may be allocated to the tech per turn. +The tech at the top of the queue is given RP first. Any remaining RP are then given to the next tech in the queue, and if there is still more remaining to the next-- and so on. Items can be re-prioritized in the queue by dragging the most important items closer to the top. If any RP are left over after giving to all techs in the queue, they are automatically allocated to the unlocked tech(s) with the fewest remaining RP to completion. Research does not require [[metertype METER_SUPPLY]] line connections to be used.''' @@ -4936,13 +5733,17 @@ METER_CONSTRUCTION_VALUE_LABEL Infrastructure METER_CONSTRUCTION_VALUE_DESC -The term Infrastructure refers to technical structures that support a colonized planet: energy, transportation, telecommunication and social services for the citizens. +'''The term Infrastructure refers to technical structures that support a colonized planet: energy, transportation, telecommunication and social services for the citizens. + +The Target Infrastructure is the stable Infrastructure level a planet will approach, given the current factors influencing the planetary Infrastructure. Its value is connected to [[encyclopedia ENC_BUILDING_TYPE]]s built on the planet, [[encyclopedia ENC_TECH]] researched by the empire, [[encyclopedia ENC_SPECIAL]]s linked to the planet, and various other game effects. If these factors change, then the Target Infrastructure value is likely to shift.''' METER_HAPPINESS_VALUE_LABEL Happiness METER_HAPPINESS_VALUE_DESC -The Happiness of a colony's population starts out low after initial colonization or after invasion, and may also be affected by other factors. A minimum Happiness of 5 is necessary for a colony to act as a source of settlers for colonizing other planets. +'''The Happiness of a colony's population starts out between 1 and 5 after initial colonization depending on how suitable the planet's [[encyclopedia ENVIRONMENT_TITLE]] is for that species. Happiness is reduced to 0 after invasion and may also be affected by other factors. A minimum Happiness of 5 is necessary for a colony to act as a source of settlers for colonizing other planets. Also [[metertype METER_INDUSTRY]] and [[metertype METER_RESEARCH]] only start growing if Happiness is at least 5. + +The Target Happiness is the stable Happiness level a planet's population will approach, given the current factors influencing the planetary Happiness. Its value is connected to [[encyclopedia ENC_BUILDING_TYPE]]s built on the planet, [[encyclopedia ENC_TECH]] researched by the empire, [[encyclopedia ENC_SPECIAL]]s linked to the planet, and various other game effects. If these factors change, then the Target Happiness value is likely to shift.''' METER_CAPACITY_VALUE_LABEL Stat - Primary @@ -4961,13 +5762,17 @@ METER_FUEL_VALUE_LABEL Fuel METER_FUEL_VALUE_DESC -Fuel allows ships to travel the starlanes outside of [[metertype METER_SUPPLY]] of your empire. Fuel is consumed for each starlane traversed outside of supply; stopping within [[metertype METER_SUPPLY]] lines will fully refuel a ship. If a ship remains stationary beyond friendly [[metertype METER_SUPPLY]] lines, fuel will slowly regenerate. The amount of fuel a [[encyclopedia ENC_SHIP_DESIGN]] has depends on the [[encyclopedia ENC_SHIP_HULL]] used, but it can also be increased using some [[encyclopedia ENC_SHIP_PART]]s. +'''Fuel allows ships to travel the starlanes outside of [[metertype METER_SUPPLY]] of your empire. Fuel is consumed for each starlane traversed outside of supply; stopping within [[metertype METER_SUPPLY]] lines will fully refuel a ship. If a ship remains stationary beyond friendly [[metertype METER_SUPPLY]] lines, fuel will slowly regenerate. + +The effective amount of fuel a Ship Design depends on the base fuel of the hull and additional fuel effects scaled by the [[encyclopedia FUEL_EFFICIENCY_TITLE]] of the [[encyclopedia ENC_SHIP_HULL]]. Fuel efficiency changes the effective capacity of fuel [[encyclopedia ENC_SHIP_PART]]s and will multiply any other fuel-modifying effects. + +The game will always show the effective fuel levels, after scaling for hull efficiency. Details about the efficiency effect can be seen in the mouse over.''' METER_SHIELD_VALUE_LABEL Shields METER_SHIELD_VALUE_DESC -'''The term Shields combines all kinds of protective force-fields. They are usually the first barrier incoming attacks have to overcome. There are two main types of shields: ship shields and planetary barrier shields, and both ships and planets have a Shields meter. The main difference between them is simply how they recharge -- ship shields are able to recharge instantly after every single shot fired at them, whereas planetary barrier shields take more time to recharge. The result of that seemingly small difference makes a very noticeable difference in combat, however. +'''The term Shields combines all kinds of protective force-fields. They are usually the first barrier incoming attacks have to overcome. There are two main types of shields: ship shields and planetary barrier shields, and both ships and planets have a Shields meter. The main difference between them is simply how they recharge -- ship shields are able to recharge instantly after every single shot fired at them, whereas planetary barrier shields take more time to recharge. The result of that seemingly small difference makes a very noticeable difference in combat, however. Ship shields: Reduce the [[encyclopedia DAMAGE_TITLE]] sustained by each hit by the strength rating of the shield part. A weapon can only penetrate a shield if its damage value is higher than the shield's strength. However, ship shields do not negate shot damage coming from [[encyclopedia FIGHTER_TECHS]], as fighters fire from within the shields. @@ -4996,9 +5801,19 @@ METER_SUPPLY_VALUE_DESC Supply lines are shown by coloring star-lanes with an empire's color. Planets connected by supply lines form a 'Resource Group' and can share physical resources. Production Points created on one planet can be used on any connected planet to build ships or structures. -A set of core supply starlanes for an empire (the set of least-jump starlanes connecting the resource-producing systems of each Resource Group) will have greater thickness than the non-core starlanes. If PP is currently being wasted within a Resource Group, the outer bands of the core starlanes will be a contrasting color. +A set of core supply starlanes for an empire (the set of least-jump starlanes connecting the resource-producing systems of each Resource Group) will have greater thickness than the non-core starlanes. If PP is currently being wasted within a Resource Group, the outer bands of the core starlanes will be a contrasting color. + +Supply lines can also be used to refill the [[metertype METER_FUEL]] supply of ships. -Supply lines can also be used to refill the [[metertype METER_FUEL]] supply of ships.''' +Supply lines will be obstructed by armed enemy ships belonging to empires with which an empire is at [[encyclopedia WAR_TITLE]]. At the opposite end of the spectrum, empires in an [[encyclopedia ALLIANCE_TITLE]] will share supply lines and all their associated benefits.''' + +METER_STOCKPILE_VALUE_LABEL +Stockpile + +METER_STOCKPILE_VALUE_DESC +'''The imperial stockpile capacity indicates an empire's ability to take in excess and to use stockpiled production points. An empire's total stockpiling capacity is the sum of all its planets' stockpile meters plus technology bonus. The total determines how many PP can be taken out of the stockpile each turn to be used for production. + +Species have various innate levels of stockpiling ability.''' METER_TROOPS_VALUE_LABEL Troops @@ -5006,7 +5821,11 @@ Troops METER_TROOPS_VALUE_DESC '''The term Troops combines all kinds of military forces capable of operating on a planet's surface. -The Troop meter on a planet represents the power of the defending ground forces. The invading force must send a more powerful force (i.e. a higher cumulative number) to conquer a planet. Ships must be built with special parts for carrying troops, and such troops cannot invade if the defending planet has active [[metertype METER_SHIELD]].''' +The Troop meter on a planet represents the power of the defending ground forces. The invading force must send a more powerful force (i.e. a higher cumulative number) to conquer a planet. + +Ships must be built with [[encyclopedia PC_TROOPS]] parts for carrying troops to use for invasion. + +Ships cannot invade a planets if it has active [[metertype METER_SHIELD]].''' METER_REBEL_TROOPS_VALUE_LABEL Rebel Troops @@ -5034,9 +5853,9 @@ Detection Range METER_DETECTION_VALUE_DESC '''The Detection Range represents the distance in uu that sensors can reach. An empire can only see objects that have a [[metertype METER_STEALTH]] value lower than or equal to its [[encyclopedia DETECTION_TITLE]] and are within Detection Range of a controlled space ship or planet. -Space ships and planets have a Detection Range meter. There is the option to display Detection Range circles (in uu) in the Galaxy Map section of the Options menu. To facilitate predictions of what would be within a Detection Range, there are also options to display a standard 'Map Scale Line' as well as a 'Map Scale Circle' centered on the currently selected system and indicating the same distance as the Map Scale Line. They are both responsive to the the map zoom level and can be toggled via hotkeys. +Space ships and planets have a Detection Range meter. There is the option to display Detection Range circles (in uu) in the Galaxy Map section of the Options menu. To facilitate predictions of what would be within a Detection Range, there are also options to display a standard 'Map Scale Line' as well as a 'Map Scale Circle' centered on the currently selected system and indicating the same distance as the Map Scale Line. They are both responsive to the the map zoom level and can be toggled via hotkeys. -Some objects or abilities may allow vision of certain areas outside of this range. Some examples are from species abilities of [[species SP_TRITH]] or [[species SP_GEORGE]], or from the effects from a [[buildingtype BLD_SCRYING_SPHERE]].''' +Some objects or abilities may allow vision of certain areas outside of this range. Some examples are from species abilities of [[species SP_TRITH]] or [[species SP_GEORGE]], or from the effects from a [[buildingtype BLD_SCRYING_SPHERE]].''' METER_SPEED_VALUE_LABEL Speed @@ -5050,17 +5869,41 @@ METER_DETECTION_STRENGTH_VALUE_LABEL METER_DETECTION_STRENGTH_VALUE_DESC [[DETECTION_TEXT]] +WAR_TITLE +War + +WAR_TEXT +War is the default diplomatic state between empires. Armed ships will enforce a [[encyclopedia SYSTEM_BLOCKADE_TITLE]] on enemy ships, obstruct enemy [[metertype METER_SUPPLY]] lines, impede enemy colonization, and engage in combat with enemy ships. Enemy planets may be subjected to invasion or bombardment. + PEACE_TITLE Peace PEACE_TEXT -Peace is the diplomatic state between empires in which they mutually agree not to attack each other's ships or planets, not to obstruct each other's [[metertype METER_SUPPLY]] lines, and not to impede colonization. +Peace is the diplomatic state between empires in which they mutually agree not to attack each other's ships or planets, not to enforce a [[encyclopedia SYSTEM_BLOCKADE_TITLE]] on each other's ships, not to allows ships to obstruct each other's [[metertype METER_SUPPLY]] lines, and not to impede colonization. Peace is the minimum level of diplomacy at which [[encyclopedia GIFTING_TITLE]] between empires is allowed. + +ALLIANCE_TITLE +Alliance + +ALLIANCE_TEXT +Alliance is the closest diplomatic state between empires. Allied empires share visibility and [[metertype METER_SUPPLY]] lines. Allied empires may share victory when the relevant Game Rule is set accordingly. + +GIFTING_TITLE +Gifting + +GIFTING_TEXT +Empires with sufficiently close dipomatic relations ([[encyclopedia PEACE_TITLE]] or closer), may gift planets or fleets to one another, provided the target empire has a presence (ship or planet) in the same system as the object being gifted. OUTPOSTS_TITLE Outpost OUTPOSTS_TEXT -Outposts are unmanned stations, which can be founded in places too hostile for a colony to survive. They have no population, and normally produce no resources. But they create [[metertype METER_SUPPLY]] (with the right tech) and provide vision. Colonies can be founded on planets that already have an Outpost. To build an Outpost, you need to put an [[shippart CO_OUTPOST_POD]] on a ship. Outposts are also formed when the population of a colony drops to zero for any reason. +Outposts are unmanned stations, which can be founded in places too hostile for a colony to survive. They have no [[metertype METER_POPULATION]], and normally produce no resources. But they create [[metertype METER_SUPPLY]] (with the right tech) and provide fuel and vision. Colonies can be founded on planets that already have an Outpost. Building an Outpost requires a ship with an [[shippart CO_OUTPOST_POD]]. Outposts are also formed when the Population of a colony drops to zero for any reason. + +HOMEWORLDS_TITLE +Homeworld + +HOMEWORLDS_TEXT +Homeworlds are the planets where species are situated when the game starts (note that a single species may have multiple homeworlds). The [[tech SPY_CUSTOM_ADVISORIES]] reveals their existence and number, but not their locations. Being a homeworld gives a planet several advantages, as long as it is still inhabited by the original species: the [[metertype METER_TARGET_POPULATION]], [[metertype METER_MAX_TROOPS]], and [[metertype METER_TARGET_HAPPINESS]] are all increased, and the [[encyclopedia GROWTH_FOCUS_TITLE]] is enabled (unless the species is [[encyclopedia SELF_SUSTAINING_SPECIES_CLASS]]) and increases [[metertype METER_TARGET_POPULATION]] of other colonies with the same species. FOCUS_TITLE Focus @@ -5076,11 +5919,9 @@ GROWTH_FOCUS_TITLE Growth Focus GROWTH_FOCUS_TEXT -'''The Growth Focus represents exporting rare materials and substances that can increase population of planets. Is only available in certain situations: on some homeworlds, and on planets with growth specials that can be exported. - -A planet focused on Growth increases the max Population only of other planets connected to it by [[metertype METER_SUPPLY]] lines. +'''The Growth Focus represents exporting rare materials and substances that can increase the [[metertype METER_TARGET_POPULATION]] of planets. Is only available in certain situations: on some homeworlds, and on planets with growth specials that can be exported. -A Homeworld focused on Growth only helps other planets of the same species. +A planet focused on Growth extends those benefits to other planets connected to it by [[metertype METER_SUPPLY]] lines, provided that the species of those other planets are of the right type, i.e., a Homeworld focused on Growth only helps other planets of the same species, and Growth Specials (and Growth Focus based on them) only help species with a respective type of [[encyclopedia METABOLISM_TITLE]]. The benefits from different types of applicable Growth Focus will stack with each other, but not from multiple sources of the identical specific type. [[GROWTH_SPECIALS_ENTRY_LIST]]''' @@ -5088,7 +5929,7 @@ TRADE_FOCUS_TITLE Trade Focus TRADE_FOCUS_TEXT -Trade doesn't do anything, yet. It is just a placeholder to show that more resources are planned. +Trade doesn't do anything. It is just a placeholder to show that more resources are planned. PROTECTION_FOCUS_TITLE Protection Focus @@ -5096,6 +5937,16 @@ Protection Focus PROTECTION_FOCUS_TEXT The Protection focus represents a planet wholly focused on defending itself. [[metertype METER_TROOPS]], Planetary [[metertype METER_DEFENSE]], and Planetary [[metertype METER_SHIELD]] are doubled. +STOCKPILE_FOCUS_TITLE +Stockpile Focus + +STOCKPILE_FOCUS_TEXT +'''The Stockpile focus increases the [[metertype METER_STOCKPILE]] of a planet. + +A Homeworld focused on Stockpile also helps other planets of the same species and increases the [[metertype METER_STOCKPILE]] there by an additional 0.04 per population. + +[[METER_STOCKPILE_VALUE_DESC]]''' + ARMOR_TITLE Armor Plating @@ -5106,7 +5957,7 @@ SLOT_TITLE Slot SLOT_TEXT -A slot is a type of ship mount on a [[encyclopedia ENC_SHIP_HULL]], which can hold certain types of [[encyclopedia ENC_SHIP_PART]]s, like weapons, shields, armor plating, engines and various other devices. There are three slot types: external, internal, and core slots. [[encyclopedia ENC_SHIP_PART]] descriptions will state what kind of slots they are compatible with; some are compatble with multiple slot types. This information is also shown graphically in the [[encyclopedia DESIGN_WINDOW_ARTICLE_TITLE]], where each type of slot in a hull is shown with a shape specific to its type. The [[encyclopedia ENC_SHIP_PART]]s in the Design Window are also shown with shapes that indicate which slots they are compatible with. In most cases a part shape will clearly match a particular slot-type shape. Parts that can fit into multiple slot types will have shapes that are visibly compatible with the shapes of those slot types. Larger (and expensive) hulls often have more slots available to be filled with parts. +A slot is a type of ship mount on a [[encyclopedia ENC_SHIP_HULL]], which can hold certain types of [[encyclopedia ENC_SHIP_PART]]s, like weapons, shields, armor plating, engines and various other devices. There are three slot types: external, internal, and core slots. [[encyclopedia ENC_SHIP_PART]] descriptions will state what kind of slots they are compatible with; some are compatble with multiple slot types. This information is also shown graphically in the [[encyclopedia DESIGN_WINDOW_ARTICLE_TITLE]], where each type of slot in a hull is shown with a shape specific to its type. The [[encyclopedia ENC_SHIP_PART]]s in the Design Window are also shown with shapes that indicate which slots they are compatible with. In most cases a part shape will clearly match a particular slot-type shape. Parts that can fit into multiple slot types will have shapes that are visibly compatible with the shapes of those slot types. Larger (and expensive) hulls often have more slots available to be filled with parts. DETECTION_TITLE Detection Strength @@ -5126,11 +5977,11 @@ Each [[encyclopedia ENC_SPECIES]] has its own environmental preferences, which d [[PE_UNINHABITABLE]] < [[PE_HOSTILE]] < [[PE_POOR]] < [[PE_ADEQUATE]] < [[PE_GOOD]] -The planet suitability report can be displayed by right-cliking on a planet in the system sidepanel. A green number following the suitability data indicates the max population value if the species colonizes the planet, whereas a red number indicates that the population will stay idle or will decrease, to finally perish if the species attempts to colonize the planet. +The planet suitability report can be displayed by right-cliking on a planet in the system sidepanel. A green number following the suitability data indicates the maximum [[metertype METER_POPULATION]] the planet could achieve if colonized by this species, whereas a red number indicates that the colony will be unviable, and Population will decrease over several turns until the Colonists have all died off and the colony is lost. When terraforming a planet (by [[metertype METER_RESEARCH]] or if the planet has the [[special GAIA_SPECIAL]] special) in order to better suit the species environmental preferences, the planet's original environment is modified by stages, until it finally reaches the [[PE_GOOD]] suitability for the species who wants to live on the planet. The terraforming stages can be checked on the suitability wheel displayed below. -Clockwise from top: [[PT_TERRAN]], [[PT_OCEAN]], [[PT_SWAMP]], [[PT_TOXIC]], [[PT_INFERNO]], [[PT_RADIATED]], [[PT_BARREN]], [[PT_TUNDRA]], [[PT_DESERT]] +Clockwise from top: [[PT_TERRAN]], [[PT_OCEAN]], [[PT_SWAMP]], [[PT_TOXIC]], [[PT_INFERNO]], [[PT_RADIATED]], [[PT_BARREN]], [[PT_TUNDRA]], [[PT_DESERT]] ''' @@ -5138,25 +5989,25 @@ ORGANIC_SPECIES_TITLE Organic Metabolism ORGANIC_SPECIES_TEXT -[[encyclopedia ORGANIC_SPECIES_CLASS]] species are more or less, 'life as we know it'. These species' max population can be increased when their connected homeworld is set on [[encyclopedia GROWTH_FOCUS_TITLE]], or when an Organic Growth Special is colonized, and focused on Growth. Organic Growth specials are marked with a little green 'O' in the corner. +[[encyclopedia ORGANIC_SPECIES_CLASS]] species are more or less, 'life as we know it'. These species' [[metertype METER_TARGET_POPULATION]] can be increased when their connected homeworld is set on [[encyclopedia GROWTH_FOCUS_TITLE]], or when an Organic Growth Special is colonized, and focused on Growth. Organic Growth specials are marked with a little green 'O' in the corner. LITHIC_SPECIES_TITLE Lithic Metabolism LITHIC_SPECIES_TEXT -[[encyclopedia LITHIC_SPECIES_CLASS]] species are silicon or mineral-based. These species' max population can be increased when their connected homeworld is set on [[encyclopedia GROWTH_FOCUS_TITLE]], or when a Lithic Growth Special is colonized, and focused on Growth. Lithic Growth specials are marked with a little grey 'L' in the corner. +[[encyclopedia LITHIC_SPECIES_CLASS]] species are silicon or mineral-based. These species' [[metertype METER_TARGET_POPULATION]] can be increased when their connected homeworld is set on [[encyclopedia GROWTH_FOCUS_TITLE]], or when a Lithic Growth Special is colonized, and focused on Growth. Lithic Growth specials are marked with a little grey 'L' in the corner. ROBOTIC_SPECIES_TITLE Robotic Metabolism ROBOTIC_SPECIES_TEXT -[[encyclopedia ROBOTIC_SPECIES_CLASS]] species are sapient machines. These species' max population can be increased when their connected homeworld is set on [[encyclopedia GROWTH_FOCUS_TITLE]], or when a Robotic Growth Special is colonized, and focused on Growth. Robotic Growth specials are marked with a little red 'R' in the corner. +[[encyclopedia ROBOTIC_SPECIES_CLASS]] species are sapient machines. These species' [[metertype METER_TARGET_POPULATION]] can be increased when their connected homeworld is set on [[encyclopedia GROWTH_FOCUS_TITLE]], or when a Robotic Growth Special is colonized, and focused on Growth. Robotic Growth specials are marked with a little red 'R' in the corner. PHOTOTROPHIC_SPECIES_TITLE Phototrophic Metabolism PHOTOTROPHIC_SPECIES_TEXT -'''[[encyclopedia PHOTOTROPHIC_SPECIES_CLASS]] species live mainly or entirely on sunlight. These species' max population is greatly influenced by the brightness of the local star and the planet size - the brighter or bigger, the better. Growth specials do not benefit them. +'''[[encyclopedia PHOTOTROPHIC_SPECIES_CLASS]] species live mainly or entirely on sunlight. These species' [[metertype METER_TARGET_POPULATION]] is greatly influenced by the brightness of the local star and the planet size - the brighter or bigger, the better. Growth specials do not benefit them. Population changes: @@ -5174,7 +6025,13 @@ SELF_SUSTAINING_SPECIES_TITLE Self-Sustaining Metabolism SELF_SUSTAINING_SPECIES_TEXT -[[encyclopedia SELF_SUSTAINING_SPECIES_CLASS]] species may be energy or crystalline beings or something even stranger. Their commonality is that they need no sustenance, so their max population is very high. While there are no specials that boost their growth, their max population is equal to a species that is provided with all three [[encyclopedia GROWTH_FOCUS_TITLE]] specials. +[[encyclopedia SELF_SUSTAINING_SPECIES_CLASS]] species may be energy or crystalline beings or something even stranger. Their commonality is that they need no sustenance, so their [[metertype METER_TARGET_POPULATION]] is very high. While there are no specials that boost their growth, their Target Population is equal to a species that is provided with all three [[encyclopedia GROWTH_FOCUS_TITLE]] specials. + +GASEOUS_SPECIES_TITLE +Gaseous Metabolism + +GASEOUS_SPECIES_TEXT +[[encyclopedia GASEOUS_SPECIES_CLASS]] species have metabolisms based on elements which are in gaseous state in an earth-like environment. Most researchers actually agree that complex life based on a gaseous metabolism is physically impossible. Search for life on the most probable habitat, gas giants, gets mostly neglected. Although gas giants are really huge, the habitable area is similar to a medium size planet. Also there exist no specials that boost the growth of gaseous species. TELEPATHIC_TITLE Telepathy @@ -5186,7 +6043,7 @@ XENOPHOBIC_SPECIES_TITLE Xenophobic XENOPHOBIC_SPECIES_TEXT -'''Xenophobic species will automatically divert some of their industrial efforts into harassing other species that are nearby. The affected nearby species will suffer an industrial penalty due to this harassment. A Xenophobic species of [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]] will also find the presence of alien species nearby to be disruptive to its natural balances (whether due to some direct effect or simply due to wasted energies being applied to harassing the alien species, has not been determined) resulting in a reduced maximum population. +'''Xenophobic species will automatically divert some of their industrial efforts into harassing other species that are nearby. The affected nearby species will suffer an industrial penalty due to this harassment. A Xenophobic species of [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]] will also find the presence of alien species nearby to be disruptive to its natural balances (whether due to some direct effect or simply due to wasted energies being applied to harassing the alien species, has not been determined) resulting in a reduced [[metertype METER_TARGET_POPULATION]]. Xenophobic species start with [[tech CON_CONC_CAMP]].''' @@ -5200,7 +6057,7 @@ EVACUATION_TITLE Evacuation Plan EVACUATION_TEXT -'''The [[buildingtype BLD_EVACUATION]] when built will remove the population of a planet. Each turn some of the population will be removed. If there are planets colonized by the same species with free space, and connected by [[metertype METER_SUPPLY]] lines, one will be randomly chosen and the population will move there. If there are no appropriate planets available, the population disappears into the vastness of space. +'''The [[buildingtype BLD_EVACUATION]] when built will remove the [[metertype METER_POPULATION]] of a planet. Each turn, some of the Population will be removed. If there are planets colonized by the same species with free space, and connected by [[metertype METER_SUPPLY]] lines, one will be randomly chosen and the Population will move there. If there are no appropriate planets available, the Population disappears into the vastness of space. During an evacuation, production drops to nothing. When the colony is empty, it reverts to an [[encyclopedia OUTPOSTS_TITLE]]. It still belongs to the same empire, and may be re-colonized once the [[buildingtype BLD_EVACUATION]] deletes itself.''' AI_LEVELS_TITLE @@ -5209,7 +6066,7 @@ AI Aggression Levels AI_LEVELS_TEXT '''AI aggression levels are meant to be roughly akin to difficulty levels, but not exactly, particularly in that they are differentiated in various aspects of risk-taking and aggression, with only some actual limitations being applied to the lower level ones. They can all still play pretty well, but the lower aggression ones may be a bit easier to manage, even if not necessarily easier to actually take over. The more aggressive ones do a bit better over the course of a game. -AIs rename their capital planet on turn 1, adding a prefix. Once you discover their home system you can determine their personality. +AIs rename their capital planet on turn 1, adding a prefix. Once their home system is discovered, their personality can be discerned. The current prefixes are: Beginner: @@ -5234,7 +6091,7 @@ DAMAGE_TITLE Damage DAMAGE_TEXT -'''Defines the base amount that a weapon will reduce the [[metertype METER_STRUCTURE]] of another ship (or the [[metertype METER_SHIELD]], [[metertype METER_DEFENSE]] and [[metertype METER_CONSTRUCTION]] of a planet), in one combat round (of which there are three in each turn of battle). If a target ship is equipped with [[metertype METER_SHIELD]], the actual damage caused by each hit will be the base damage reduced by the strength rating of the ship's [[metertype METER_SHIELD]]. +'''Defines the base amount that a weapon will reduce the [[metertype METER_STRUCTURE]] of another ship (or the [[metertype METER_SHIELD]], [[metertype METER_DEFENSE]] and [[metertype METER_CONSTRUCTION]] of a planet), in one combat round (of which there are three in each turn of battle). If a target ship is equipped with [[metertype METER_SHIELD]], the actual damage caused by each hit will be the base damage reduced by the strength rating of the ship's [[metertype METER_SHIELD]]. Note: [[encyclopedia FIGHTER_TECHS]] inflict damage ignoring [[metertype METER_SHIELD]] on ships, as fighters fire from within the enemy shields.''' @@ -5256,19 +6113,19 @@ Design Window DESIGN_WINDOW_ARTICLE_TEXT '''The [[DESIGN_WINDOW_ARTICLE_TITLE]] is for viewing or altering the designs of monsters or ships. -Designs and ship parts can be [[PRODUCTION_WND_AVAILABILITY_AVAILABLE]], [[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]] or [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]]. [[PRODUCTION_WND_AVAILABILITY_AVAILABLE]] designs and parts are unrestricted for use by the empire. [[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]] designs and parts are restricted in some way. Some unavailable designs and parts may be unlocked by researching associated [[encyclopedia ENC_TECH]]. [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]] designs are obsoleted/unobsoleted at will by the empire. [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]] designs will not appear in the Production Window and only appear in [[DESIGN_WINDOW_ARTICLE_TITLE]] when the "[[PRODUCTION_WND_AVAILABILITY_OBSOLETE]]" filter is toggled to display them. Ship parts can not be obsoleted. +Designs and ship parts can be [[PRODUCTION_WND_AVAILABILITY_AVAILABLE]], [[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]] or [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]]. [[PRODUCTION_WND_AVAILABILITY_AVAILABLE]] designs and parts are unrestricted for use by the empire. [[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]] designs and parts are restricted in some way. Some unavailable designs and parts may be unlocked by researching associated [[encyclopedia ENC_TECH]]. [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]] designs, hulls and parts are obsoleted/unobsoleted at will by the empire. [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]] designs will not appear in the [[encyclopedia PRODUCTION_WINDOW_ARTICLE_TITLE]] and only appear in [[DESIGN_WINDOW_ARTICLE_TITLE]] when the "[[PRODUCTION_WND_AVAILABILITY_OBSOLETE]]" filter is toggled to display them. There are four sub windows: [[DESIGN_WND_STARTS]], [[DESIGN_WND_PART_PALETTE_TITLE]], [[DESIGN_WND_MAIN_PANEL_TITLE]] and the [[MAP_BTN_PEDIA]]. -[[DESIGN_WND_STARTS]] has four tabs: empty [[DESIGN_WND_HULLS]], [[DESIGN_WND_FINISHED_DESIGNS]] designs, [[DESIGN_WND_SAVED_DESIGNS]] designs from previous games and [[DESIGN_WND_MONSTERS]] designs for inspection. Drag or double click hulls, designs or monsters to edit/examine them on the workbench. Designs can be drag-and-dropped within the list to change their order. +[[DESIGN_WND_STARTS]] has four tabs: empty [[DESIGN_WND_HULLS]], [[DESIGN_WND_FINISHED_DESIGNS]] designs, [[DESIGN_WND_SAVED_DESIGNS]] designs from previous games and [[DESIGN_WND_MONSTERS]] designs for inspection. Drag or double click hulls, designs or monsters to edit/examine them on the workbench. Designs and hulls can be drag-and-dropped within the list to change their order. -[[DESIGN_WND_FINISHED_DESIGNS]] designs appear in the same order as on the Production Window. The right click context menu provides several options. Obsolete/unobsolete designs with "[[DESIGN_WND_OBSOLETE_DESIGN]]". Ctrl click also obsoletes/unobsoletes designs. Delete designs with "[[DESIGN_WND_DELETE_DESIGN]]". Deleted designs can't be recovered. Ships already queued for production will continue to be built with the old obsolete/deleted design. Rename designs with "[[DESIGN_RENAME]]". Save designs to disk for future games with "[[DESIGN_SAVE]]". Toggle the "[[DESIGN_WND_ADD_ALL_DEFAULT_START]]" option to use/not use the default designs in new games. +[[DESIGN_WND_FINISHED_DESIGNS]] designs appear in the same order as on the Production Window. The right click context menu provides several options. Obsolete/unobsolete designs with "[[DESIGN_WND_OBSOLETE_DESIGN]]". Ctrl click also obsoletes/unobsoletes designs and hulls. Delete designs with "[[DESIGN_WND_DELETE_DESIGN]]". Deleted designs can't be recovered. Ships already queued for production will continue to be built with the old obsolete/deleted design. Rename designs with "[[DESIGN_RENAME]]". Save designs to disk for future games with "[[DESIGN_SAVE]]". Toggle the "[[DESIGN_WND_ADD_ALL_DEFAULT_START]]" option to use/not use the default designs in new games. -Use the [[DESIGN_WND_SAVED_DESIGNS]] designs right click context menu to copy a single or all saved designs to the [[DESIGN_WND_FINISHED_DESIGNS]] designs. Ctrl click also copies a single design to the [[DESIGN_WND_FINISHED_DESIGNS]] designs. Permanently remove a saved design with "[[DESIGN_WND_DELETE_SAVED]]". Check "[[OPTIONS_ADD_SAVED_DESIGNS]]" to automaitcally import all the [[DESIGN_WND_SAVED_DESIGNS]] designs at the start of a new game. +Use the [[DESIGN_WND_SAVED_DESIGNS]] designs right click context menu to copy a single or all saved designs to the [[DESIGN_WND_FINISHED_DESIGNS]] designs. Ctrl click also copies a single design to the [[DESIGN_WND_FINISHED_DESIGNS]] designs. Permanently remove a saved design with "[[DESIGN_WND_DELETE_SAVED]]". Check "[[OPTIONS_ADD_SAVED_DESIGNS]]" to automatically import all the [[DESIGN_WND_SAVED_DESIGNS]] designs at the start of a new game. -[[DESIGN_WND_PART_PALETTE_TITLE]] displays parts. Parts whose associated technology has been researched may be used in current designs. Other parts can not be used because either the associated technology has not been researched or the part is only available to monsters. +[[DESIGN_WND_PART_PALETTE_TITLE]] displays parts. Parts whose associated technology has been researched may be used in current designs. Other parts can not be used because either the associated technology has not been researched or the part is only available to monsters. The "[[PRODUCTION_WND_AVAILABILITY_AVAILABLE]]", "[[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]]" and "[[PRODUCTION_WND_AVAILABILITY_OBSOLETE]]" filters on [[DESIGN_WND_STARTS]] and [[DESIGN_WND_PART_PALETTE_TITLE]] toggle the display of parts that the empire can currently use, that are restricted or that the empire has chosen to hide. @@ -5277,13 +6134,13 @@ The [[DESIGN_WND_PART_PALETTE_TITLE]] window has filters to display/hide entire [[DESIGN_WND_MAIN_PANEL_TITLE]] is the workbench for the current design. -The current design may have internal, external and/or core [[encyclopedia SLOT_TITLE]]s. Drag and drop or double click parts from [[DESIGN_WND_PART_PALETTE_TITLE]] to add/remove them to/from the current design. +The current design may have internal, external and/or core [[encyclopedia SLOT_TITLE]]s. Drag and drop or double click parts from [[DESIGN_WND_PART_PALETTE_TITLE]] to add/remove them to/from the current design. Ctrl and drag and drop will add/remove/replace all parts of a similar type. Add a name and optionally a description to the design. Click "[[DESIGN_WND_ADD_FINISHED]]" or "[[DESIGN_WND_ADD_SAVED]]" to add the design to the beginning of the list of designs. -Click "[[DESIGN_WND_UPDATE_FINISHED]]" or "[[DESIGN_WND_UPDATE_SAVED]]" to replace the most recently selected [[DESIGN_WND_FINISHED_DESIGNS]] or [[DESIGN_WND_SAVED_DESIGNS]] design with a new design. "[[DESIGN_WND_UPDATE_FINISHED]]" or "[[DESIGN_WND_UPDATE_SAVED]]" is only active if this design was started from a design in the [[DESIGN_WND_FINISHED_DESIGNS]] or [[DESIGN_WND_SAVED_DESIGNS]] list respectively. "[[DESIGN_WND_UPDATE_FINISHED]]" only changes the design, any ships already queued for production or already produced will continue to use the old design. +Click "[[DESIGN_WND_UPDATE_FINISHED]]" or "[[DESIGN_WND_UPDATE_SAVED]]" to replace the most recently selected [[DESIGN_WND_FINISHED_DESIGNS]] or [[DESIGN_WND_SAVED_DESIGNS]] design with a new design. "[[DESIGN_WND_UPDATE_FINISHED]]" or "[[DESIGN_WND_UPDATE_SAVED]]" is only active if this design was started from a design in the [[DESIGN_WND_FINISHED_DESIGNS]] or [[DESIGN_WND_SAVED_DESIGNS]] list respectively. "[[DESIGN_WND_UPDATE_FINISHED]]" only changes the design, any ships already queued for production or already produced will continue to use the old design. ''' FLEET_MOVEMENT_ARTICLE_TITLE @@ -5304,11 +6161,11 @@ HIDDEN_SETTINGS_ARTICLE_TITLE Hidden Settings HIDDEN_SETTINGS__ARTICLE_TEXT -'''There are a number of User Interface (UI) settings and other game settings that can be changed by the user via the Options Menu; there are also a number of UI settings which do not appear in the Options pages, but which can be changed by the user by directly editing the config.xml file, located in the logging directory. Directions for identifying the location of this file on the various different platforms is available at the FreeOrion Wiki. +'''There are a number of User Interface (UI) settings and other game settings that can be changed by the user via the Options Menu; there are also a number of UI settings which do not appear in the Options pages, but which can be changed by the user by directly editing the config.xml file, located in the logging directory. Directions for identifying the location of this file on the various different platforms is available at the FreeOrion Wiki. In addition to a great many entries for the size/location of various UI elements, these 'Hidden Settings' include: -UI.design-pedia-dynamic : controls whether or not the design detail pedia page in the [[encyclopedia DESIGN_WINDOW_ARTICLE_TITLE]] will be dynamically updated as the design name is edited (can cause lag) +ui.window.design.pedia.title.dynamic.enabled : controls whether or not the design detail pedia page in the [[encyclopedia DESIGN_WINDOW_ARTICLE_TITLE]] will be dynamically updated as the design name is edited (can cause lag) show-fleet-eta : controls whether a fleet's Estimated Time of Arrival (ETA), in number of turns from the current turn, is shown in its Fleet Panel in the Fleet Window @@ -5318,7 +6175,7 @@ FIELDS_TITLE Interstellar Fields FIELDS_TEXT -There are several Field Types, each having a distinctive, somewhat diffuse, appearance. Right-clicking on them in the [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]] brings up a small popup menu identifying their type and providing an option to look up their details. +There are several Field Types, each having a distinctive, somewhat diffuse, appearance. Right-clicking on them in the [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]] brings up a small popup menu identifying their type and providing an option to look up their details. ## @@ -5329,7 +6186,7 @@ GENERAL_PEDIA_REFERENCE Information such as this is accessible via the "Pedia" [[encyclopedia ENC_INDEX]]. DETAILED_PEDIA_REFERENCE -[[GENERAL_PEDIA_REFERENCE]] The Pedia can be opened and closed via the purple icon bearing a question mark, in the upper right portion of the main [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]]. The Pedia can be navigated via hotlinks and via the arrows in the lower right corner of the Pedia window. +[[GENERAL_PEDIA_REFERENCE]] The Pedia can be opened and closed via the purple icon bearing a question mark, in the upper right portion of the main [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]]. The Pedia can be navigated via hotlinks and via the arrows in the lower right corner of the Pedia window. GREETINGS_GUIDE_TITLE **Greetings** @@ -5340,7 +6197,6 @@ Greetings Esteemed Entity, and Welcome to the FreeOrion Universe! GREETINGS_GUIDE_REFERENCE Newcomers to this universe may particularly wish to consult the [[encyclopedia QUICK_START_GUIDE_TITLE]]. - GREETINGS_GUIDE_TEXT '''[[GREETINGS_INTRO]] @@ -5356,7 +6212,7 @@ QUICK_START_GUIDE_TITLE **Quick Start Guide** QUICK_START_GUIDE_TEXT -'''This is a brief introduction to FreeOrion gameplay. This guide was just born recently, and is currently quite tiny but growing rapidly. A somewhat less brief Quick Play Guide is available at freeorion.org (current URL: freeorion.org/index.php/V0.4_Quick_Play_Guide ). +'''This is a brief introduction to FreeOrion gameplay. A somewhat less brief Quick Play Guide is available at freeorion.org (current URL: freeorion.org/index.php/V0.4_Quick_Play_Guide ). [[DETAILED_PEDIA_REFERENCE]] Important sections include [[encyclopedia CATEGORY_GAME_CONCEPTS]], [[encyclopedia CATEGORY_GUIDES]], and the [[encyclopedia ENC_METER_TYPE]] list. @@ -5390,7 +6246,7 @@ SitRep types only appear in the Filters list once an event has occurred that wou Ignoring is useful for hiding a reoccurring event, such as reminders to build a [[buildingtype BLD_GAS_GIANT_GEN]] at one planet. -Blocking is useful if you find some types of SitReps unhelpful, such as once you have seen all of the [[encyclopedia BEGINNER_HINTS]]. +Blocking is useful if some types of SitReps are unhelpful, such as once all of the [[encyclopedia BEGINNER_HINTS]] have been seen. ''' RESEARCH_TECH_GUIDE_TITLE @@ -5415,14 +6271,58 @@ ORBITAL_DRYDOCK_REPAIR_TITLE Orbital Drydock Repair ORBITAL_DRYDOCK_REPAIR_TEXT -'''The [[building BLD_SHIPYARD_ORBITAL_DRYDOCK]] is considered the best repair facility available, able to restore a ship to pristine condition. +'''The [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] is considered the best repair facility available, able to restore a ship to pristine condition. -When the planet [[metertype METER_HAPPINESS]] is below 15, repairs at this facility are not as efficient. If the planet drops below 5 happiness, the drydock is not able to perform any repairs that turn. +When the planet [[metertype METER_HAPPINESS]] is below 15, repairs at this facility are not as efficient. If the planet drops below 5 happiness, the drydock is not able to perform any repairs that turn. Repairs are delayed for one turn for ships just entering the system. Ships need time to dock, and the drydock needs time to prepare both the necessary tooling and the ship itself. Any combat in the system will disrupt repairs for that turn.''' +CONFIG_GUIDE_TITLE +Configuration Guide + +CONFIG_GUIDE_TEXT +'''Most configuration options for the game are stored and read from a single XML formatted file named config.xml. +If this file does not exist (or is for a different build or version), it will be (re)created when the client starts. +The configuration file is typically updated after a stored option is changed, but may be deferred until a later action (such as clicking Done or Apply). +Some of these options can be changed from the [[OPTIONS_TITLE]] menu, under the Main Menu. + +An option set in the persistent config file (if one exists) will override the value from config.xml. +An option set from a command line argument will override the value from both files. +config.xml will be changed to reflect any overridden values. + + +Persistent Config File: + +A persistent configuration file is an optional XML file named persistent_config.xml. +If this file exists, any settings it contains will override those from the main configuration file. +This may be useful when a different build of the game is launched, such as after updating to a new version. To help retain compatibility, it is preferred to keep as few options in this file as needed. + +A persistent configuration file can be created or updated on the [[OPTIONS_TITLE]] menu [[OPTIONS_PAGE_MISC]] tab by clicking the [[OPTIONS_CREATE_PERSISTENT_CONFIG]] button. +When using this button, there are a couple of known issues: +Some options may be included even though they were not changed by the user, as they were adjusted after their default value. After updating to a new version, it may be required to manually remove these options from the file to update their default setting. +Windows that have not been open in the current session may not have previously changed settings retained. It is advised to have a game running and open all desired windows at least once to initialize the settings before using this button. + +The persistent configuration file may be manually created if desired, but should never contain the "version.string" option. + + +Location of Config Files: + +Location in the filesystem is dependant on the OS definition of an environment variable. +On Windows platforms the variable is denoted by wrapping in % - ( %var% ). +On MacOSX and Linux platforms the variable is denoted by starting with ${ and ending with } - ( ${var} ). + +Windows: %APPDATA%\Roaming\FreeOrion + (e.g. C:\Users\username\AppData\Roaming\FreeOrion) + +MacOSX: ${HOME}/Library/Application Support/FreeOrion + (e.g. /Users/username/Library/Application Support/FreeOrion) + +Linux: ${XDG_CONFIG_HOME}/freeorion or if $XDG_CONFIG_HOME is not set: ${HOME}/.config/freeorion + (e.g. /home/username/.config/freeorion) +''' + ## ## Turn progress @@ -5476,6 +6376,30 @@ Received unexpected turn update from server. MESSAGES_PANEL_TITLE Messages +# %1% and %2% are color coded empire names +MESSAGES_WAR_DECLARATION +%1% and %2% are now at war. + +# %1% and %2% are color coded empire names +MESSAGES_PEACE_TREATY +%1% and %2% are now at peace. + +# %1% and %2% are color coded empire names +MESSAGES_ALLIANCE +%1% and %2% have entered an alliance. + +MESSAGES_HELP_COMMAND +'''/pedia [article]: open the specified article in the pedia +/pm [player] [message]: sends private message to specified player +/zoom [object]: zoom to the specified universe object (system, planet, ship, fleet or building) +''' + +MESSAGES_WHISPER +(Whispers) + +MESSAGES_INVALID +Invalid player or no message entered. + ## ## Players list @@ -5521,6 +6445,9 @@ Planets FLEETS_SUBMENU Fleet / Ships +PLANET_ENVIRONMENTS_SUBMENU +Environment For Species + ID ID @@ -5551,6 +6478,12 @@ Parts HULL Hull +ATTACK +Total Weapon Damage + +PRODUCTION_COST +Production Cost + OBJECT_TYPE Type @@ -5602,18 +6535,36 @@ Next System PREV_SYSTEM Previous System +ARRIVAL_STARLANE +Arrived From System + LAST_TURN_BATTLE_HERE Last Turn Battle Here LAST_TURN_ACTIVE_IN_BATTLE Last Turn In Battle +LAST_TURN_RESUPPLIED +Last Turn Resupplied + +LAST_TURN_COLONIZED +Last Turn Colonized + +LAST_TURN_CONQUERED +Last Turn Conquered + +LAST_TURN_ATTACKED_BY_SHIP +Last Turn Attacked By Ship + ARRIVED_ON_TURN Arrived on Turn SIZE_AS_DOUBLE Size As Number +HABITABLE_SIZE +Habitable Size + DISTANCE_FROM_ORIGINAL_TYPE Distance From Original Planet Type @@ -5732,6 +6683,7 @@ Any ## Situation report ## ## Format: Ideally always start with Location: with ship/fleet names near the end. +## SITREP_PANEL_TITLE Situation Report - Initial turn @@ -5777,6 +6729,12 @@ SITREP_WELCOME SITREP_WELCOME_LABEL Welcome +SITREP_SYSTEM_GOT_INCOMING_WARNING +At %system%: Next turn enemy ships arrive! + +SITREP_SYSTEM_GOT_INCOMING_WARNING_LABEL +Incoming Enemies + SITREP_GAS_GIANT_GENERATION_REMINDER %planet% is a prime location for a %buildingtype%, but none is being built there. @@ -5804,6 +6762,12 @@ SITREP_TECH_RESEARCHED SITREP_TECH_RESEARCHED_LABEL Tech Researched +SITREP_EMPIRE_TECH_RESEARCHED_DETECTION +The %empire% [[encyclopedia DETECTION_TITLE]] has upgraded to %rawtext:dstrength% + +SITREP_EMPIRE_TECH_RESEARCHED_DETECTION_LABEL +Empire Detection Tech Upgrade + SITREP_TECH_UNLOCKED %tech% has been unlocked and can now be researched. @@ -5828,6 +6792,12 @@ SITREP_BUILDING_TYPE_UNLOCKED SITREP_BUILDING_TYPE_UNLOCKED_LABEL Building Type Unlocked +SITREP_WEAPONS_UPGRADED +All %shippart% weapons on supplied ships have been upgraded to %tech%. Upgraded weapons do %rawtext:dam% extra damage per shot + +SITREP_WEAPONS_UPGRADED_LABEL +Weapon Tech Upgrade + SITREP_COMBAT_SYSTEM At %system%: there was %combat%. @@ -5921,17 +6891,17 @@ At %system%: a %shipdesign% was damaged. SITREP_UNOWNED_SHIP_DAMAGED_AT_SYSTEM_LABEL Unowned Ship Damaged -SITREP_PLANET_BOMBARDED_AT_SYSTEM -At %system%: the %empire% planet %planet% was bombarded. +SITREP_PLANET_ATTACKED_AT_SYSTEM +At %system%: the %empire% planet %planet% was attacked. -SITREP_PLANET_BOMBARDED_AT_SYSTEM_LABEL -Empire Planet Bombarded +SITREP_PLANET_ATTACKED_AT_SYSTEM_LABEL +Empire Planet Attacked -SITREP_UNOWNED_PLANET_BOMBARDED_AT_SYSTEM -At %system%: The planet %planet% was bombarded. +SITREP_UNOWNED_PLANET_ATTACKED_AT_SYSTEM +At %system%: The planet %planet% was attacked. -SITREP_UNOWNED_PLANET_BOMBARDED_AT_SYSTEM_LABEL -Unowned Planet Bombarded +SITREP_UNOWNED_PLANET_ATTACKED_AT_SYSTEM_LABEL +Unowned Planet Attacked SITREP_GROUND_BATTLE On %planet%: there was a ground battle. @@ -5975,6 +6945,18 @@ On %planet%: A new %species% colony has been established. SITREP_NEW_COLONY_ESTABLISHED_LABEL New Colony Established +SITREP_FLEET_GIFTED +%fleet% was gifted by %empire% + +SITREP_FLEET_GIFTED_LABEL +Fleet Gifted + +SITREP_PLANET_GIFTED +%planet% was gifted by %empire% + +SITREP_PLANET_GIFTED_LABEL +Planet Gifted + SITREP_FLEET_ARRIVED_AT_DESTINATION At %system%: %fleet% (%rawtext% ships) has arrived. @@ -6018,7 +7000,7 @@ SITREP_FOREIGN_FLEET_ARRIVED_AT_DESTINATION_LABEL Foreign Fleet Arrived SITREP_POP_THRESHOLD -On %planet%: The colony has reached sufficient size and [[metertype METER_HAPPINESS]] to be a settler source for colonizing other planets. +On %planet%: The colony has reached sufficient size and [[metertype METER_HAPPINESS]] to be a settler source for colonizing other planets. Also [[metertype METER_INDUSTRY]] and [[metertype METER_RESEARCH]] may start growing. SITREP_POP_THRESHOLD_LABEL Colonizing Threshold @@ -6149,6 +7131,12 @@ EFFECT_TERRAFORM EFFECT_TERRAFORM_LABEL Terraform +EFFECT_NEST_REMOVAL +On %planet%: a monster nest has been eradicated. + +EFFECT_NEST_REMOVAL_LABEL +Nest Eradication + EFFECT_STARLANE_BORE At %system%: a new starlane has opened linking to a nearby system. @@ -6167,6 +7155,24 @@ EFFECT_ART_PLANET EFFECT_ART_PLANET_LABEL Black Hole +INTERSPECIES_ACADEMY_LABEL +Species InterDesign Academy + +CONDITION_INTERSPECIES_ACADEMY_SPECIES_ALREADY_EXISTS +Academies of this planet's species already exist in the empire + +EFFECT_INTERSPECIES_ACADEMY +Additional effect by the %species% on %planet% joining the InterDesign design academy. Each design academy adds 0.05 to %rawtext%. + +EFFECT_INTERSPECIES_ACADEMY_LABEL +New InterDesign Academy + +EFFECT_INTERSPECIES_ACADEMY_DESTROY +Destroying fake duplicate design academy %planet%. + +EFFECT_INTERSPECIES_ACADEMY_DESTROY_LABEL +Duplicate Academy Destroyed + EFFECT_TAME_MONSTER_HATCHED At %planet%: a tamed %predefinedshipdesign% is ready for service. @@ -6299,6 +7305,12 @@ At %planet%: [[tech LRN_SPATIAL_DISTORT_GEN]] moved %fleet% back into %system%. EFFECT_FLEET_MOVED_TO_LABEL Fleet Moved Into +EFFECT_SHIP_REFUELED +At %system%: ship %ship% has sufficient fuel to resume starlane travel. + +EFFECT_SHIP_REFUELED_LABEL +Ship Refueled + HEAD_ON_A_SPIKE_MESSAGE The capital of the %empire% empire has fallen. @@ -6306,7 +7318,7 @@ HEAD_ON_A_SPIKE_MESSAGE_LABEL Empire Capital Captured CUSTOM_SITREP_INTRODUCTION -Welcome to the initial Situation Report Briefing. Pursuant to the %tech% Directive, all the busy empire servants endeavor to collect important information and send it back to %system%, where it is compiled into Situation Reports, such as this, providing notice of important events and other reminders selected by the empire leadership. +Welcome to the initial Situation Report Briefing. Pursuant to the %tech% Directive, all the busy empire servants endeavor to collect important information and send it back to %system%, where it is compiled into Situation Reports, such as this, providing notice of important events and other reminders selected by the empire leadership. # This message value must contain no spaces. Use underlines instead. CUSTOM_1 @@ -6335,6 +7347,9 @@ VICTORY! The %empire% has transcended our reality and wins a technological victo VICTORY_ALL_ENEMIES_ELIMINATED VICTORY! The %empire% is the last surviving empire. +VICTORY_FEW_HUMANS_ALIVE +VICTORY! The %empire% is one of the last surviving empires controlled by a human player. + VICTORY_EXPERIMENTOR_CAPTURE VICTORY! The %empire% has captured the powerful Experimentors and ended the fearsome threat plaguing the galaxy. @@ -6343,6 +7358,7 @@ Victory SITREP_EMPIRE_ELIMINATED The %empire% has been defeated! + SITREP_EMPIRE_ELIMINATED_LABEL Empire Eliminated @@ -6414,7 +7430,7 @@ BEGINNER_HINT_02 2: Each species has its own planetary [[encyclopedia ENVIRONMENT_TITLE]] preference. If you can conquer a planet with a different species, you can colonize other planets with them as well, allowing you to secure a system faster. Keep in mind that not all species can colonize and that some can not build ships. BEGINNER_HINT_03 -3: Ships can be repaired at a system that has a friendly [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] if there was no combat in the system last turn, and the planet has a [[metertype METER_HAPPINESS]] score of 5 or more. Ships will need to wait one turn in the system before repairs can begin. +3: Ships can be repaired at a system that has a friendly [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] if there was no combat in the system last turn, and the planet has a [[metertype METER_HAPPINESS]] score of 5 or more. Ships will need to wait one turn in the system before repairs can begin. BEGINNER_HINT_04 4: Systems that are connected by thick lines are [[metertype METER_SUPPLY]] line linked and they share [[metertype METER_INDUSTRY]] Production Points (PP). @@ -6435,10 +7451,10 @@ BEGINNER_HINT_09 9: [[predefinedshipdesign SD_COLONY_SHIP]]s take longer to build than [[predefinedshipdesign SD_OUTPOST_SHIP]]s, but they can settle planets outside of your [[metertype METER_SUPPLY]] range. BEGINNER_HINT_10 -10: With a system selected in the [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]], right click on a planets picture in the system side panel to access the Planet Suitability report. This will tell you what species from your empire will be able to successfully settle that planet. Numbers in green show the maximum population that the planet could achieve if colonized by that species, red numbers indicate that the species would die out if they attempted to colonize. +10: With a system selected in the [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]], right click on a planets picture in the system side panel to access the Planet Suitability report. This will tell you what species from your empire will be able to successfully settle that planet. Numbers in green show the maximum [[metertype METER_POPULATION]] that the planet could achieve if colonized by that species, red numbers indicate that the species would die out if they attempted to colonize. BEGINNER_HINT_11 -11: Maximum populations that planets can achieve can be boosted by researching various growth techs. Rare specials can also increase the maximum population for species with certain metabolisms, see [[encyclopedia GROWTH_FOCUS_TITLE]] for more info on these specials. +11: Maximum [[metertype METER_POPULATION]]s that planets can achieve can be boosted by researching various growth techs. Rare specials can also increase the [[metertype METER_TARGET_POPULATION]] for species with certain metabolisms, see [[encyclopedia GROWTH_FOCUS_TITLE]] for more info on these specials. BEGINNER_HINT_12 12: Be on the lookout for planets with [[encyclopedia ENC_SPECIAL]]s, they usually indicate some special resource on that planet that may benefit your entire empire if utilized properly. Right click on a special's icon to access its 'Pedia entry and find out more! @@ -6450,7 +7466,7 @@ BEGINNER_HINT_14 14: Research [[metertype METER_SUPPLY]] improvements, like the [[tech CON_ORBITAL_CON]], to increase the distance from your systems that your supply lines reach. BEGINNER_HINT_15 -15: Most species start out with a [[metertype METER_HAPPINESS]] of 1 when you capture their planet, and this will normally increase by 1 per turn. Look out for [[encyclopedia XENOPHOBIC_SPECIES_TITLE]] species though! They don't like being near other species and this will effect the happiness and other values of both them and the other nearby species. +15: Most species start out with a [[metertype METER_HAPPINESS]] of 0 when you capture their planet, and this will normally increase by 1 per turn. Look out for [[encyclopedia XENOPHOBIC_SPECIES_TITLE]] species though! They don't like being near other species and this will effect the happiness and other values of both them and the other nearby species. BEGINNER_HINT_16 16: You may want to hold off on researching [[tech LRN_PSIONICS]] until your empire includes a species with [[encyclopedia TELEPATHIC_TITLE]]. @@ -6462,7 +7478,7 @@ BEGINNER_HINT_18 18: [[encyclopedia FOCUS_TITLE]] - pay attention to the focus setting of new planets that are added to your empire, either from settling them or from conquering them. This setting controls if the planet focuses on industrial production or on research or on defense. Later in the game, more focus options may unlock as new [[encyclopedia ENC_TECH]] are researched, and different species may have different focus options available. BEGINNER_HINT_19 -19: A colony can be established on an [[encyclopedia OUTPOSTS_TITLE]] by constructing a colony building or by using a [[predefinedshipdesign SD_COLONY_SHIP]]. Most colony buildings require a [[metertype METER_SUPPLY]] connection to a planet with at least 5 [[metertype METER_HAPPINESS]], and 3 population. When a species is unlocked, such as when researching [[tech PRO_EXOBOTS]], this requirement for their colony building does not apply. +19: A colony can be established on an [[encyclopedia OUTPOSTS_TITLE]] by constructing a colony building or by using a [[predefinedshipdesign SD_COLONY_SHIP]]. Most colony buildings require a [[metertype METER_SUPPLY]] connection to a planet with at least 5 [[metertype METER_HAPPINESS]], and 3 [[metertype METER_POPULATION]]. When a species is unlocked, such as when researching [[tech PRO_EXOBOTS]], this requirement for their colony building does not apply. BEGINNER_HINT_20 20: Specific Situation Reports (like this one) can be temporarily ignored by right clicking on their icon and selecting one of the options. Reports of a certain type may be filtered out on a more permanent basis using the Filters menu at the bottom of this window. These beginner game hints are grouped as [[encyclopedia BEGINNER_HINTS]] in the filter list. @@ -6514,7 +7530,10 @@ Opinions of Empires: OPINIONS_OF_OTHER_SPECIES Opinions of Other Species: -# Species Pedia Categories + +## +## Encyclopedia articles in Species Types category +## LITHIC_SPECIES_CLASS Lithic @@ -6546,7 +7565,16 @@ Self-Sustaining SELF_SUSTAINING_SPECIES_CLASS_DESC List of [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]] species: -# Species Pedia Articles +GASEOUS_SPECIES_CLASS +Gaseous + +GASEOUS_SPECIES_CLASS_DESC +List of [[encyclopedia GASEOUS_SPECIES_TITLE]] species: + + +## +## Encyclopedia articles in Species category +## SP_HUMAN Human @@ -6569,7 +7597,8 @@ SP_SCYLIOR Scylior SP_SCYLIOR_GAMEPLAY_DESC -'''Three-tentacled, aquatic nautiloids. +''' +Three-tentacled, aquatic nautiloids. Prefer [[PT_OCEAN]] planets. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' @@ -6587,13 +7616,15 @@ The remnant of a once-mighty telepathic hive brain, referred to as 'the Mind', i History: Millennia ago an expedition of the precursors discovered the Mind controlling the whole planet with his telepathic powers. The Mind immediately reached out and began copying the knowledge of the scientists and their ship crew, but the scientists cut out most of the Mind's brain and took it with them as a sample. The rest survived and lusted to recover the memories that had been taken from it. The Mind, however, was immobile, and for the time being, had no nearby intelligent beings to act as it's hands. Enter the Scyliori. + ''' SP_GYSACHE Gysache SP_GYSACHE_GAMEPLAY_DESC -'''Cowardly, freakish sheep-like herbivores. +''' +Cowardly, freakish sheep-like herbivores. Prefer [[PT_SWAMP]] planets. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' @@ -6610,13 +7641,15 @@ The Gysache are always wary of predators and are easily alarmed. Their natural r Homeworld: Their homeworld has a thick atmosphere of nitrogen, ammonia and carbon dioxide. Most parts of it are moist but it lacks large oceans, only 5% of its surface is covered in water. The main topographical features are enormous high rocky mesas cut by mile deep canyons the bottoms of which contain the only standing water. + ''' SP_CHATO Chato SP_CHATO_GAMEPLAY_DESC -'''Sessile crystalline entities which ride the animal Gormoshk. +''' +Sessile crystalline entities which ride the animal Gormoshk. Prefer [[PT_TOXIC]] planets. [[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]] ''' @@ -6627,14 +7660,16 @@ The Chato'matou'Gormoshk are actually two species together, however it is not en Social Structure: In Chato'matou society, age is the most important factor as they are nearly immortal. Every command the elders issue is obeyed at once and to disobey an elder is the greatest crime in Chato'matou society. They developed a system to transmit signals without delay throughout an entire colony, enabling them to converse with near the speed of light. This greatly enhanced the species mental capabilities, as all new ideas and theories could be instantly processed throughout the colony. The Gormoshk are valued as mounts and well cared for under normal circumstances, however they are also seen mostly as animals. + ''' SP_TRITH Trith SP_TRITH_GAMEPLAY_DESC -'''Bereaved xenophobic telepaths. -Prefer [[PT_DESERT]] planets. +''' +Bereaved xenophobic telepaths. +Prefer [[PT_RADIATED]] planets. [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]], [[encyclopedia XENOPHOBIC_SPECIES_TITLE]] ''' @@ -6644,13 +7679,15 @@ The Trith are a mixture of matter and enormous psychic energies. Their shape is Social Structure: The Trith are a theocracy led by the priests of Quaran-Dor (The God of the Mind). They are united in the single goal of eliminating all other thinking species to silence the thought-noise in the universe. + ''' SP_HAPPY Happybirthday SP_HAPPY_GAMEPLAY_DESC -'''Abandoned, lonely underwater robots, programmed to sing "happy birthday" to themselves at regular intervals. +''' +Abandoned, lonely underwater robots, programmed to sing "happy birthday" to themselves at regular intervals. Prefer [[PT_OCEAN]] planets. [[encyclopedia ROBOTIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] ''' @@ -6671,13 +7708,16 @@ Colonization is slow and industry inefficient due to species-wide depression, so History: Sad. Whether it is the initial lonely quest to explore their homeworld, their abandonment by their creators, the interval war, or their quest into space to find friendship, their existence has been a sad, depressing and lonely one. + + ''' SP_HHHOH Hhhoh SP_HHHOH_GAMEPLAY_DESC -'''Huge, slow, multi-trunked Mammoths. +''' +Huge, slow, multi-trunked Mammoths. Prefer [[PT_TUNDRA]] planets. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' @@ -6689,13 +7729,16 @@ Hhhoh locomotion is performed by using their two, large front legs, while using Personality: Hhhohs describe themselves as social pragmatists. Elders are very much revered, some living close to 230 years. Their sloth-like disposition makes them aversive to any fast movements, as well as hasty decisions. + + ''' SP_EAXAW Eaxaw SP_EAXAW_GAMEPLAY_DESC -'''Evil amazonian xenophobic aggressive worms. +''' +Evil amazonian xenophobic aggressive worms. Prefer [[PT_TERRAN]] planets. [[encyclopedia ORGANIC_SPECIES_TITLE]], [[encyclopedia XENOPHOBIC_SPECIES_TITLE]] ''' @@ -6709,6 +7752,7 @@ Eaxaw are organized into matriarchal breeding groups. A female keeps a "harem" o History: A long time ago a derelict ship of the Precursors crashed on the Eaxaw homeworld. As the Eaxaw learned some of the ship's secrets they deduced that there were other intelligent species. Their great matriarchal council soon issued its greatest proclamation: "All of this must go!" + ''' SP_DERTHREAN @@ -6732,7 +7776,8 @@ SP_LAENFA Laenfa SP_LAENFA_GAMEPLAY_DESC -'''Sneaky, telepathic, sentient vines. +''' +Sneaky, telepathic, sentient vines. Prefer [[PT_OCEAN]] planets. [[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] ''' @@ -6743,20 +7788,41 @@ The Laenfa are long and vine-like, able to attach and detach into any number of Social Structure: The Laenfa have a unified culture due to their telepathy but because their telepathic communication weakens with distance, there is a large central vine that decides on most important decisions. This vine carries out its debates in secret but takes into account the voices of all Laenfa it is able to detect. The Laenfa carry out their duties with hive-like efficiency with each individual working for the collective goals of gaining knowledge of other species and protecting knowledge of themselves. + +''' + +SP_SLY +Sly + +SP_SLY_GAMEPLAY_DESC +'''Friendly overlooked gasbags. +Greatly prefer [[PT_GASGIANT]]s. +[[encyclopedia GASEOUS_SPECIES_TITLE]] +''' + +SP_SLY_DESC +'''Description: +The Sly live in gas giants. The gas giants are so foreign to most species in the universe that, for most of their history, they have been overlooked. +Not being able to build devices, they couldn't leave their homeworld. Recently, they were able to obtain necessary equipment and technology for spacefaring from traders in exchange for their knowledge of history. +On the right diet, Sly excrete refined fuel. When in system with a gas giant, a Sly-crewed ship will regenerate fuel. + +Social Structure: +The Sly are very long-lived and keep the songs alive in which the whole cultural knowledge is encoded. They are very open and curious towards the galaxy and the other species they are now finally able to encounter. ''' SP_LEMBALALAM Lembala'Lam + SP_LEMBALALAM_GAMEPLAY_DESC '''Egocentric, degenerated ancient reptiles that no longer reproduce. Prefer [[PT_DESERT]] Planets. [[encyclopedia ORGANIC_SPECIES_TITLE]] -Owning the Lembala grants the technologies: [[tech SPY_STEALTH_1]], [[tech SPY_STEALTH_2]] and [[tech GRO_LIFECYCLE_MAN]]. +Owning the Lembala grants the technologies: [[tech SPY_STEALTH_1]], [[tech SPY_STEALTH_2]] and [[tech GRO_LIFECYCLE_MAN]]. ''' SP_LEMBALALAM_DESC '''Description: -The Lembala'Lam are an ancient species in the evening of its existence. They had some apparent resemblance with reptiles such as snakes or geckos, but most of their original physiognomy was lost to evolution warped by their mechanically enhanced lifestyle. Without machines they would no longer be capable of locomotion or feeding. The ability to reproduce was lost completely, but most importantly they've lost the trust in each other. The last nail in the coffin for Lembala society was the invention of an automated system that transfers the mind of a dying Lembala into the next spare clone, of which every Lembala has hundreds safely hidden. Almost all mental and material ressources of the Lembala are used to assure their artificial immortality, leaving little to no room for real developement of their civilization. +The Lembala'Lam are an ancient species in the evening of its existence. They had some apparent resemblance with reptiles such as snakes or geckos, but most of their original physiognomy was lost to evolution warped by their mechanically enhanced lifestyle. Without machines they would no longer be capable of locomotion or feeding. The ability to reproduce was lost completely, but most importantly they've lost the trust in each other. The last nail in the coffin for Lembala society was the invention of an automated system that transfers the mind of a dying Lembala into the next spare clone, of which every Lembala has hundreds safely hidden. Almost all mental and material ressources of the Lembala are used to assure their artificial immortality, leaving little to no room for real developement of their civilization. Social Structure: The Lembala know each other for eons, but are suspicious of each and every one of the others. Their democratic government is forever paralyzed by long accepted stalemates. Due to paranoia and their lack of altruism even the most basic cooperation is rare and short lived. @@ -6872,7 +7938,8 @@ SP_PHINNERT Phinnert SP_PHINNERT_GAMEPLAY_DESC -'''Industrious Flying Monkeys. +''' +Industrious Flying Monkeys. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' @@ -6885,6 +7952,7 @@ Life is much shorter for Phinnerts than for most other sentient life, owing to t Homeworld: Their planet has low gravity and a very thick atmosphere. Its composition is unique as it has a low proportion of heavier metals, but a much higher proportion of other heavy elements, particularly noble gases. It also has very stable tectonic formations meaning that the surface of the crust is very flat, and so is mostly covered with shallow, standing water. The entire world is like a giant everglade. + ''' SP_REPLICON @@ -6901,13 +7969,12 @@ SP_REPLICON_DESC The Replicons look like a 3m tall robotic spider with wings. The wings are actually highly efficient solar panels that fold out to a relatively large area, the 'abdomen' is actually the 'industrial processing center' where replacement parts and new Replicons are forged. Social Structure: -Replicons have a relatively primitive social structure. They have a planetary government, because a few feudal lords discovered space travel as a convenient way of attacking people really far away. Their government is effectively feudal, due to some unusual effects that allowed them to reach space flight before achieving the nationstate/industrial revolution stage of development. Because of the wide variety of self-replicating machines on Cisarruj, most needs that are met by industry on other worlds are met by 'agriculture' or 'breeding' by Replicons. They end up without any actual 'Industrial Base' or even much of an understanding of technology other than what verious 'natural' parts and products can be used for. This means that their society is highly illiterate, most people passing on knowledge that their pappies taught them on how to skin a "creature's" titanium coat, just enough so that it could grow it back or how much uranium ore to feed a reprocessing reactor if it was giving bad aluminum. Most things that normal 'developed' societies would expect are only present in the lord's estates, the remainder of society looks like something out of a post-apocalyptic novel. +Replicons have a relatively primitive social structure. They have a planetary government, because a few feudal lords discovered space travel as a convenient way of attacking people really far away. Their government is effectively feudal, due to some unusual effects that allowed them to reach space flight before achieving the nationstate/industrial revolution stage of development. Because of the wide variety of self-replicating machines on Cisarruj, most needs that are met by industry on other worlds are met by 'agriculture' or 'breeding' by Replicons. They end up without any actual 'Industrial Base' or even much of an understanding of technology other than what various 'natural' parts and products can be used for. This means that their society is highly illiterate, most people passing on knowledge that their pappies taught them on how to skin a "creature's" titanium coat, just enough so that it could grow it back or how much uranium ore to feed a reprocessing reactor if it was giving bad aluminum. Most things that normal 'developed' societies would expect are only present in the lord's estates, the remainder of society looks like something out of a post-apocalyptic novel. Homeworld: Cisarruj is a heavily irradiated planet with a profound mystery about it. The planet is dominated by a wide variety of autonomous self replicating machines. There are recorded histories of who went to war with who when, and this is actually significant when dealing with the formation of the Cisarruj Empire. However, most simply reads like any other world's pre industrial history periods of powers large and small waxing and waning. The Mystery of the Replicons, however is in their prehistory. Did a sentient race assemble them as an experiment maybe, or were they some type of accident (an industrial development project left unsupervised) did their creators simply leave and forget about them, or did their creators meet a worse fate possibly at the hands of those beings they created. ''' - SP_EGASSEM Egassem @@ -6933,7 +8000,7 @@ Sslith SP_SSLITH_GAMEPLAY_DESC '''Flat, aquatic, pliable creatures. -Prefer [[PT_TUNDRA]] planets. +Prefer [[PT_OCEAN]] planets. [[encyclopedia ORGANIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] ''' @@ -7003,13 +8070,14 @@ SP_TRENCHERS Trenchers SP_TRENCHERS_GAMEPLAY_DESC -'''Enigmatic wheeled robots devoted to carving up their planet into the most elaborate curves and spirals. +''' +Enigmatic wheeled robots devoted to carving up their planet into the most elaborate curves and spirals. Prefer [[PT_BARREN]] planets. [[encyclopedia ROBOTIC_SPECIES_TITLE]] ''' SP_TRENCHERS_DESC -'''''' + SP_RAAAGH Raaagh @@ -7090,7 +8158,8 @@ SP_UGMORS Ugmors SP_UGMORS_GAMEPLAY_DESC -'''Lava-dwelling yeast piles, which form temporary golems. +''' +Lava-dwelling yeast piles, which form temporary golems. Prefer [[PT_INFERNO]] planets. [[encyclopedia LITHIC_SPECIES_TITLE]] ''' @@ -7108,6 +8177,7 @@ An Ugmor can take on many shapes, but all of them consist of rocks or sheets of Homeworld: The Ugmor homeworld is a rocky planet that rotates very quickly (once every 3 and a half hours). 30% of its surface is covered by seas of lava and liquid metals. + ''' SP_GISGUFGTHRIM @@ -7145,18 +8215,21 @@ SP_ABADDONI Abaddoni SP_ABADDONI_GAMEPLAY_DESC -'''A race of cave dwelling generally decent folk living in permanent slavery to an overprotective computer they created. +''' +A race of cave dwelling generally decent folk living in permanent slavery to an overprotective computer they created. Prefers [[PT_INFERNO]] planets. [[encyclopedia LITHIC_SPECIES_TITLE]] ''' SP_ABADDONI_DESC '''Description: + Abaddoni are shaped like a chicken egg on its side, roughly 0,8 meters tall and deep red with black spots. Their predominant feature is a large mouth starting at the point and extending back for a full third of the body. At the end of each of their 6 short crab like legs, is an inflatable sac which enables them to climb upside down. Because they grew up underground Abaddoni have no eyes, they mainly rely on smell and to a lesser extent sound and touch to navigate. Social Structure: The entire Abaddonnian species lives in a dictatorship ruled by a sentient artificial intelligence called Mother. This AI was created by the Abaddoni as a governmental assistant, but slowly took over. Now every newborn Abaddoni gets a wireless transmitter placed into the brain, for Mother to read their thoughts and being able to terminate them when necessary. There is no real social structure between the Abaddoni, they are all the same - slaves. + ''' SP_ACIREMA @@ -7230,6 +8303,32 @@ Reason for Staying: Volp-Uglush are now too integrated and ingrown. They now totally lack the independence and innovation needed to leave Hive and start a new colony. ''' +SP_FULVER +Fulver + +SP_FULVER_GAMEPLAY_DESC +'''Precognitive unsatisfied ants. +Prefer [[PT_TUNDRA]]s. +[[encyclopedia LITHIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] +''' + +SP_FULVER_DESC +'''Biology: +The Fulvers' appearance resembles that of large nervous ants. They possess quantum computing brains which effectively allows them to predict all possible near futures making them hard to surprise in combat. + +Name: +The precognition ability is also unique to the Fulvers species on the Fulver homeworld. As such a "Fulver" is "One who can see" your possible reactions. + +Personality: +Fulver prefer to concentrate on their strength and prefer to live from day to day. Fulver often favor the tools and decisions which allows for the highest number of future choices. Having a bad memory and no capability for long term future prediction they are bad at long term commitments like science and building up defensive structures. Feeling locked in by the limited number of choices they are restless and unsatisfied and constantly try to overcome their situation. + +Reason for Leaving: +The endless possibilities that space promises helped the Fulvers to concentrate and finally develop space-faring technology. Finally they are out going to the frontier and beyond not caring much for what they leave behind. + +Social Structure: +The focus on the near future prevents the Fulver from building long-lasting structures. Knowing each others possible choices and reacting to these leads to a society which could be best described as an informal anarchy. +''' + SP_FURTHEST Furthest @@ -7257,7 +8356,8 @@ SP_BANFORO Banforo SP_BANFORO_GAMEPLAY_DESC -'''Cute, sedulous star gazers. +''' +Cute, sedulous star gazers. Prefer [[PT_BARREN]] planets. [[encyclopedia LITHIC_SPECIES_TITLE]] ''' @@ -7271,6 +8371,7 @@ The Banforo are very social towards each other and share strong bonds. Resources Reason for Extinction: It hasn't been sufficiently clarified, but scientists suspect that a cosmic event made the environment unbearable for the Banforo for a time so long that their numbers were dwindling to ultimately zero. + ''' SP_KILANDOW @@ -7328,6 +8429,7 @@ SP_EXOBOT_DESC SP_ANCIENT_GUARDIANS Ancient Guardians + SP_ANCIENT_GUARDIANS_GAMEPLAY_DESC '''Guardian Robots constructed by a Precursor civilization aeons ago [[encyclopedia ROBOTIC_SPECIES_TITLE]] @@ -7382,29 +8484,29 @@ GAIA_SPECIAL Gaia GAIA_SPECIAL_DESC -'''Terraforms planets then increases max Population by planet size: +'''Terraforms planets then increases [[metertype METER_TARGET_POPULATION]] by planet size: • [[SZ_TINY]] (+3) • [[SZ_SMALL]] (+6) • [[SZ_MEDIUM]] (+9) • [[SZ_LARGE]] (+12) • [[SZ_HUGE]] (+15) -and increases max [[metertype METER_HAPPINESS]] by 5. +and increases [[metertype METER_TARGET_HAPPINESS]] by 5. -This planet has essentially been transformed into a living organism, allowing it to change and adapt to suit the needs of its population. If the world is a [[PE_GOOD]] [[encyclopedia ENVIRONMENT_TITLE]], a substantial population bonus is added. If it is not, then it will slowly terraform itself in stages to eventually reach [[PE_GOOD]] (NB: [[species SP_EXOBOT]] do not have a [[PE_GOOD]] environment so it cannot improve beyond [[PE_ADEQUATE]] for them).''' +This planet has essentially been transformed into a living organism, allowing it to change and adapt to suit the needs of its Population. If the world is a [[PE_GOOD]] [[encyclopedia ENVIRONMENT_TITLE]], a substantial Population bonus is added. If it is not, then it will slowly terraform itself in stages to eventually reach [[PE_GOOD]] (NB: [[species SP_EXOBOT]] do not have a [[PE_GOOD]] environment so it cannot improve beyond [[PE_ADEQUATE]] for them).''' WORLDTREE_SPECIAL World Tree WORLDTREE_SPECIAL_DESC -'''Increases [[metertype METER_SUPPLY]] by +1, [[metertype METER_DETECTION]] by +10, max Population by +1, and [[metertype METER_HAPPINESS]] on the planet that has the special by +5. Increases [[metertype METER_HAPPINESS]] on all other planets owned by the same empire by +1. +'''Increases [[metertype METER_SUPPLY]] by +1, [[metertype METER_DETECTION]] by +10, [[metertype METER_TARGET_POPULATION]] by +1, and [[metertype METER_TARGET_HAPPINESS]] on the planet that has the special by +5. Increases [[metertype METER_TARGET_HAPPINESS]] on all other planets owned by the same empire by +1. -A gigantic tree has grown so large that its crown has left the atmosphere of the planet. Scientists suspect that it has been planted by the Caretakers in ancient times.''' +A gigantic tree has grown so large that its crown has left the atmosphere of the planet. Scientists suspect that it was planted by the Caretakers in ancient times.''' ANCIENT_RUINS_DEPLETED_SPECIAL Ancient Ruins (Excavated) ANCIENT_RUINS_DEPLETED_SPECIAL_DESC -'''Increases [[metertype METER_RESEARCH]] on this planet by 1 per Population when Focus is set to Research. +'''Increases [[metertype METER_TARGET_RESEARCH]] on this planet by 1 per [[metertype METER_POPULATION]] when Focus is set to Research. This planet holds the ruins of an unknown, advanced ancient race. A valuable discovery has already been made here.''' @@ -7412,7 +8514,7 @@ ANCIENT_RUINS_SPECIAL Ancient Ruins ANCIENT_RUINS_SPECIAL_DESCRIPTION -'''Increases [[metertype METER_RESEARCH]] on this planet by 1 per Population when Focus is set to Research. +'''Increases [[metertype METER_TARGET_RESEARCH]] on this planet by 1 per [[metertype METER_POPULATION]] when Focus is set to Research. This planet holds the ruins of an unknown, advanced ancient race. The first empire with the tech [[tech LRN_XENOARCH]] to possess this planet receives one free advanced tech or uncovers a powerful building or ship. There's also a good chance that well preserved bodies of an extinct species will be found.''' @@ -7420,7 +8522,7 @@ EXTINCT_BANFORO_SPECIAL Banforo Remains EXTINCT_BANFORO_SPECIAL_DESC -On this planet well preserved bodies of the extinct species [[species SP_BANFORO]] were found. Building a [[buildingtype BLD_XENORESURRECTION_LAB]] here will allow you to build a [[buildingtype BLD_COL_BANFORO]] on [[metertype METER_SUPPLY]] connected planets. +On this planet, well preserved bodies of the extinct species [[species SP_BANFORO]] were found. Building a [[buildingtype BLD_XENORESURRECTION_LAB]] here will allow production of a [[buildingtype BLD_COL_BANFORO]] on [[metertype METER_SUPPLY]] connected planets. TECH_COL_BANFORO Banforo Resequencing @@ -7432,7 +8534,7 @@ EXTINCT_KILANDOW_SPECIAL Kilandow Remains EXTINCT_KILANDOW_SPECIAL_DESC -On this planet well preserved bodies of the extinct species [[species SP_KILANDOW]] were found. Building a [[buildingtype BLD_XENORESURRECTION_LAB]] here will allow you to build a [[buildingtype BLD_COL_KILANDOW]] on [[metertype METER_SUPPLY]] connected planets. +On this planet, well preserved bodies of the extinct species [[species SP_KILANDOW]] were found. Building a [[buildingtype BLD_XENORESURRECTION_LAB]] here will allow production of a [[buildingtype BLD_COL_KILANDOW]] on [[metertype METER_SUPPLY]] connected planets. TECH_COL_KILANDOW Kilandow Resequencing @@ -7444,7 +8546,7 @@ EXTINCT_MISIORLA_SPECIAL Misiorla Remains EXTINCT_MISIORLA_SPECIAL_DESC -On this planet well preserved bodies of the extinct species [[species SP_MISIORLA]] were found. Building a [[buildingtype BLD_XENORESURRECTION_LAB]] here will allow you to build a [[buildingtype BLD_COL_MISIORLA]] on [[metertype METER_SUPPLY]] connected planets. +On this planet, well preserved bodies of the extinct species [[species SP_MISIORLA]] were found. Building a [[buildingtype BLD_XENORESURRECTION_LAB]] here will allow production of a [[buildingtype BLD_COL_MISIORLA]] on [[metertype METER_SUPPLY]] connected planets. TECH_COL_MISIORLA Misiorla Resequencing @@ -7475,7 +8577,7 @@ ECCENTRIC_ORBIT_SPECIAL Eccentric Orbit ECCENTRIC_ORBIT_SPECIAL_DESC -'''Increases [[metertype METER_RESEARCH]] by 3, but [[metertype METER_SUPPLY]] is decreased by 2. +'''Increases [[metertype METER_TARGET_RESEARCH]] by 3, but [[metertype METER_SUPPLY]] is decreased by 2. This planet's orbit is very eccentric. It has a large distance between its closest and farthest approaches to its star. Total insolation varies significantly throughout the year. The varied conditions inhibit the supply the planet can provide, but are a beneficial platform for research.''' @@ -7483,20 +8585,20 @@ TIDAL_LOCK_SPECIAL Tidally Locked Rotation TIDAL_LOCK_SPECIAL_DESC -'''Increases [[metertype METER_INDUSTRY]] on this planet by 0.2 per Population when Focus is set to [[metertype METER_INDUSTRY]]. Decreases population (regardless of Focus) depending on the planet size: +'''Increases [[metertype METER_TARGET_INDUSTRY]] on this planet by 0.2 per [[metertype METER_POPULATION]] when Focus is set to [[metertype METER_INDUSTRY]]. Decreases [[metertype METER_TARGET_POPULATION]] (regardless of Focus) depending on the planet size: • [[SZ_TINY]] (-1) • [[SZ_SMALL]] (-2) • [[SZ_MEDIUM]] (-3) • [[SZ_LARGE]] (-4) • [[SZ_HUGE]] (-5) -This planet is tidally locked with the star it orbits. Its day and year are the same duration, and one half of the planet is perpetually day, the other perpetually night. Population growth is hampered, though industrial capacity benefits from the locally stable surface conditions.''' +This planet is tidally locked with the star it orbits. Its day and year are the same duration, and one half of the planet is perpetually day, the other perpetually night. Population growth is hampered, though industrial capacity benefits from the locally stable surface conditions.''' TEMPORAL_ANOMALY_SPECIAL Temporal Anomaly TEMPORAL_ANOMALY_SPECIAL_DESC -'''Increases [[metertype METER_RESEARCH]] on this planet by 5 per Population when Focus is set to [[metertype METER_RESEARCH]]. Decreases population (regardless of Focus) depending on the planet size: +'''Increases [[metertype METER_TARGET_RESEARCH]] on this planet by 5 per [[metertype METER_POPULATION]] when Focus is set to [[metertype METER_RESEARCH]]. Decreases [[metertype METER_TARGET_POPULATION]] (regardless of Focus) depending on the planet size: • [[SZ_TINY]] (-5) • [[SZ_SMALL]] (-10) • [[SZ_MEDIUM]] (-15) @@ -7518,7 +8620,7 @@ COMPUTRONIUM_SPECIAL Computronium Moon COMPUTRONIUM_SPECIAL_DESC -'''When a planet with a Computronium Moon is focused on [[metertype METER_RESEARCH]], all research focused planets in the empire gain 0.2 [[metertype METER_RESEARCH]] per Population. +'''When a planet with a Computronium Moon is focused on [[metertype METER_RESEARCH]], all research focused planets in the empire gain 0.2 [[metertype METER_TARGET_RESEARCH]] per [[metertype METER_POPULATION]]. Left by some vanished civilization, the entire moon - from surface to core - is a computing device of awesome power. It can complete the most complicated simulations and equations faster than they can be entered.''' @@ -7526,7 +8628,7 @@ HONEYCOMB_SPECIAL Honeycomb HONEYCOMB_SPECIAL_DESC -'''When a planet with a Honeycomb is focused on [[metertype METER_INDUSTRY]], all industry focused [[metertype METER_SUPPLY]] connected planets gain 0.5 [[metertype METER_INDUSTRY]] per Population. +'''When a planet with a Honeycomb is focused on [[metertype METER_INDUSTRY]], all industry focused [[metertype METER_SUPPLY]] connected planets gain 0.5 [[metertype METER_TARGET_INDUSTRY]] per [[metertype METER_POPULATION]]. The Honeycomb is a planet that was prepared as a stash by the Builders in ancient times. Almost all other planets in surrounding systems were stripped of valuable materials and then dismantled. The materials were sorted and stored separately in gigantic tubes embedded into the planets crust. The sprectrum ranges from rare and valuable elementals to hard to produce molecules and a vast array of energy sources. What the Builders wanted to create with this treasure or why they didn't use it in the end, is unclear.''' @@ -7534,7 +8636,7 @@ PHILOSOPHER_SPECIAL Philosopher Planet PHILOSOPHER_SPECIAL_DESC -'''As long as the Philosopher Planet is uninhabited, all inhabited planets in the same system have their [[metertype METER_RESEARCH]] increased by 5. [[metertype METER_CONSTRUCTION]] on the Philosopher Planet is decreased by 20. +'''As long as the Philosopher Planet is uninhabited, all inhabited planets in the same system have their [[metertype METER_TARGET_RESEARCH]] increased by 5. [[metertype METER_TARGET_CONSTRUCTION]] on the Philosopher Planet is decreased by 20. The lone remnant of a planet-sized space wandering species has found his final rest orbiting in this system like a normal planet. Having lived for thousands of years he lost the will and ability to travel and resorts to pondering about the mysteries of the universe he has seen and learned about. Eccentric and slightly demented he refuses to communicate with anything that isn't the size and shape of his kin, leaving scientists no choice but to ask questions via the planet they are living on. Understandably colonizing the Philosopher Planet will disturb him gravely and he will fall silent. Construction on his surface is difficult as is to be expected from a living being.''' @@ -7560,7 +8662,7 @@ JUGGERNAUT_NEST_SPECIAL Juggernaut Nest JUGGERNAUT_NEST_SPECIAL_DESC -At some point young juggernauts will rise from this place. With knowledge of [[tech SHP_DOMESTIC_MONSTER]], these mega-fauna could be domesticated. +At some point young juggernauts will rise from this place. With knowledge of [[tech SHP_DOMESTIC_MONSTER]], these mega-fauna could be domesticated. KRAKEN_IN_THE_ICE_SPECIAL Kraken in the Ice @@ -7586,7 +8688,7 @@ CONC_CAMP_MASTER_SPECIAL Pall of Death CONC_CAMP_MASTER_SPECIAL_DESC -A visitor may not see [[buildingtype BLD_CONC_CAMP]] operating on this planet, but they have been operating relentlessly and exact a harsh toll on the population; a Pall of Death can be discerned by sensitive beings. +A visitor may not see [[buildingtype BLD_CONC_CAMP]] operating on this planet, but they have been operating relentlessly and exact a harsh toll on the [[metertype METER_POPULATION]]; a Pall of Death can be discerned by sensitive beings. CONC_CAMP_SLAVE_SPECIAL Psychic Fatigue @@ -7749,6 +8851,10 @@ Building OBJ_SHIP Ship +# Note: the OBJ_SHIP and OBJ_BUILDING entries are used for the build item type of ships and buildings. +BUILD_ITEM_TYPE_PROJECT +Project + # Universe object types OBJ_FLEET Fleet @@ -7933,6 +9039,13 @@ Growth FOCUS_INDUSTRY Industry +# Focus types +FOCUS_STOCKPILE +Stockpile + +FOCUS_STOCKPILE_DESC +Allows stockpile techs to boost stockpile withdrawal capacity + # Focus types FOCUS_RESEARCH Research @@ -8041,6 +9154,10 @@ Max Defense METER_MAX_SUPPLY Max Supply +# Meter types +METER_MAX_STOCKPILE +Max Stockpile + # Meter types METER_MAX_TROOPS Max Troops @@ -8097,6 +9214,10 @@ Defense METER_SUPPLY Supply +# Meter types +METER_STOCKPILE +Stockpile + # Meter types METER_TROOPS Troops @@ -8212,6 +9333,10 @@ building BT_SHIP ship +# Build types +BT_STOCKPILE +stockpiling + # Resource types INVALID_RESOURCE_TYPE invalid resource type @@ -8290,7 +9415,7 @@ PC_ARMOUR Armor PC_ARMOUR_DESC -Armor parts increase the [[metertype METER_STRUCTURE]] of a ship, allowing a ship to sustain more damage before being destroyed. +Armor parts increase the [[metertype METER_STRUCTURE]] of a ship, allowing a ship to sustain more [[encyclopedia DAMAGE_TITLE]] before being destroyed. # Ship part classes PC_TROOPS @@ -8300,7 +9425,10 @@ PC_TROOPS_DESC '''Troop parts increase the [[metertype METER_TROOPS]] on-board, which can be used to invade enemy planets. To successfully invade a planet, the total number of [[metertype METER_TROOPS]] from all invading ships must exceed the total defending (ties are awarded to the defender). -An invading ship is destroyed as part of the landing operation (any additional parts on the ship are lost as well).''' + +When invading a planet, the troop ships selected in the fleet window will be used for the invasion. If no troop ships are selected, troop ships will be automatically selected from the available ships and based on the number of defending troops. + +An invading ship is destroyed as part of the landing operation. All parts on the ship are lost with the ship.''' # Ship part classes PC_DETECTION @@ -8338,11 +9466,11 @@ Colonization PC_COLONY_DESC '''Colony parts integrate with a ship to provide the necessary equipment to establish ownership on a new planet. -These parts may also increase the [[metertype METER_POPULATION]] on a ship. -When colonizing a planet with a populated colony ship, a colony for the [[encyclopedia ENC_SPECIES]] is established, starting with the same population that was aboard the ship. +These parts may also increase the [[metertype METER_POPULATION]] on a ship (referred to as Colonists). +When colonizing a planet with a populated colony ship, a colony for the [[encyclopedia ENC_SPECIES]] is established, starting with the same Population that was aboard the ship. -A ship with no population will still have enough personnel to establish an [[encyclopedia OUTPOSTS_TITLE]]. -Outposts may construct a colony for any species in the empire within supply range, provided the source meets other criteria. +A ship with no Population will still have enough personnel to establish an [[encyclopedia OUTPOSTS_TITLE]]. +Outposts may construct a colony for any species in the empire within [[metertype METER_SUPPLY]] range, provided the source meets other criteria. The combination of outpost ship and local construction of a colony is cheaper than producing a populated colony ship. When a ship colonizes a planet, it is consumed in the process and can not be used again. Any weapons or other parts on the ship will be decommissioned as well.''' @@ -8366,7 +9494,7 @@ PC_BOMBARD Bombardment PC_BOMBARD_DESC -'''Bombardment parts allow a ship to bombard an enemy planet, typically reducing the population on the planet by the capacity value of any applicable bombardment parts on board. +'''Bombardment parts allow a ship to bombard an enemy planet, typically reducing the [[metertype METER_POPULATION]] on the planet by the capacity value of any applicable bombardment parts on board. The majority of bombardment parts only affect one [[encyclopedia METABOLISM_TITLE]].''' @@ -8399,23 +9527,58 @@ Production parts integrate with a ship to form a [[OBJ_PROD_CENTER]], allowing t INVALID_VISIBILITY invalid visibility -# Visibility levels -VIS_NO_VISIBILITY -Invisible +# Visibility levels +VIS_NO_VISIBILITY +Invisible + +# Visibility levels +VIS_BASIC_VISIBILITY +Basic + +# Visibility levels +VIS_PARTIAL_VISIBILITY +Partial + +# Visibility levels +VIS_FULL_VISIBILITY +Full + +# Path types +PATH_BINARY +'''''' + +# Path types +PATH_RESOURCE +'''''' + +# Path types +PATH_PYTHON +'''''' + +# Path types +PATH_DATA_ROOT +'''''' + +# Path types +PATH_DATA_USER +'''''' + +# Path types +PATH_CONFIG +'''''' -# Visibility levels -VIS_BASIC_VISIBILITY -Basic +# Path types +PATH_SAVE +'''''' -# Visibility levels -VIS_PARTIAL_VISIBILITY -Partial +# Path types +PATH_TEMP +'''''' -# Visibility levels -VIS_FULL_VISIBILITY -Full -# Ship hull categories +## +## Ship hull categories +## HULL_LINE_GENERIC Hull Line: Generic @@ -8515,9 +9678,6 @@ local condition candidate DESC_VAR_ROOT_CANDIDATE root condition candidate -DESC_STATISTIC -statistic - DESC_STAT_TYPE stat type @@ -8764,8 +9924,17 @@ last turn a battle occurred here DESC_COMPLEX Complex Variable -DESC_VARIABLE_NAME -Variable Name +DESC_VAR_JUMPSBETWEEN +starlane jumps between systems containing objects with ids %1% and %2% + +DESC_STATISTIC +Statistic + +DESC_VAR_COUNT +the number of objects%2% + +DESC_VAR_IF +0, or 1 if there is any object%2% # FOCS variable reference description text. # %1% reference type of the FOCS variable. @@ -9106,7 +10275,7 @@ DESC_DESIGN_HAS_PART_NOT # matching a part class. # %3% textual description of the ship part class. DESC_DESIGN_HAS_PART_CLASS -''' that has a part of class %1%''' +''' that contains between %1% and %2% parts of class %3%''' # %1% textual description of the lower limit for the number of ship parts # matching a part class. @@ -9114,7 +10283,7 @@ DESC_DESIGN_HAS_PART_CLASS # matching a part class. # %3% textual description of the ship part class. DESC_DESIGN_HAS_PART_CLASS_NOT -''' that does not have a part of class %1%''' +''' that does not contain between %1% and %2% parts of class %3%''' # %1% name of the ship design. DESC_PREDEFINED_SHIP_DESIGN @@ -9326,11 +10495,130 @@ DESC_ORDERED_BOMBARDED DESC_ORDERED_BOMBARDED_NOT ''' that is not being bombarded by an object %1%''' +# %1% content type description, eg. species, building, focus, tech +# %2% name of specific scripted content, eg. Human, Imperial Palace, Industry Focus, The Physicsal Brain +DESC_LOCATION +''' that is matched by the location condition of %1% %2%''' + +# %1% content type description, eg. species, building, focus, tech +# %2% name of specific scripted content, eg. Human, Imperial Palace, Industry Focus, The Physicsal Brain +DESC_LOCATION_NOT +''' that is not matched by the location condition of %1% %2%''' + +# %1% content type description, eg. species, building, focus, tech +# %2% name of specific scripted content, eg. Human, Imperial Palace, Industry Focus, The Physicsal Brain +DESC_COMBAT_TARGET +''' that is matched by the combat targets condition of %1% %2%''' + +# %1% content type description, eg. species, building, focus, tech +# %2% name of specific scripted content, eg. Human, Imperial Palace, Industry Focus, The Physicsal Brain +DESC_COMBAT_TARGET_NOT +''' that is not matched by the combat targets condition of %1% %2%''' + +# AND Descriptions for expressions 'AND [ A B C ]' +DESC_AND_BEFORE_OPERANDS +''' [''' + DESC_AND_BETWEEN_OPERANDS -''' and''' +, and + +DESC_AND_AFTER_OPERANDS +''' ]''' + +DESC_AND_BEFORE_SINGLE_OPERAND +'''''' + +DESC_AND_AFTER_SINGLE_OPERAND +'''''' + +# NOT AND Descriptions for expressions 'NOT AND [ A B C ]' +# We actually describe the the logically equivalent expression ( (not A) or (not B) or (not C) ) +# Note: the operands (not A), (not B).. are handled in the descriptions of A, B.. respectively +DESC_NOT_AND_BEFORE_OPERANDS +[[DESC_NOT_OR_BEFORE_OPERANDS]] + +DESC_NOT_AND_BETWEEN_OPERANDS +[[DESC_NOT_OR_BETWEEN_OPERANDS]] + +DESC_NOT_AND_AFTER_OPERANDS +[[DESC_NOT_OR_AFTER_OPERANDS]] + +# Description for 'not ( AND [ A ] )' expressions +# Actually instead we describe 'OR [ not A ]' and leave the description of negation to A +DESC_NOT_AND_BEFORE_SINGLE_OPERAND +[[DESC_OR_BEFORE_SINGLE_OPERAND]] + +DESC_NOT_AND_AFTER_SINGLE_OPERAND +[[DESC_OR_AFTER_SINGLE_OPERAND]] + +# OR Descriptions: for expressions of the form ( A or B or C ) +DESC_OR_BEFORE_OPERANDS +''' (either''' DESC_OR_BETWEEN_OPERANDS -''' or''' +, or + +DESC_OR_AFTER_OPERANDS +''' )''' + +DESC_OR_BEFORE_SINGLE_OPERAND +'''''' + +DESC_OR_AFTER_SINGLE_OPERAND +'''''' + +# NOT OR Descriptions; for expressions of the form 'NOT OR [ A B C ]' +# We actually describe the logically equivalent expression 'AND [ (NOT A) (NOT B) (NOT C) ]' +# Note: (NOT A), (NOT B).. are handled in the descriptions of A, B.. respectively +DESC_NOT_OR_BEFORE_OPERANDS +[[DESC_AND_BEFORE_OPERANDS]] + +DESC_NOT_OR_BETWEEN_OPERANDS +[[DESC_AND_BETWEEN_OPERANDS]] + +DESC_NOT_OR_AFTER_OPERANDS +[[DESC_AND_AFTER_OPERANDS]] + +# for expressions of the form 'NOT OR [ A ]' we describe instead 'AND [ NOT A ]' +# Note: (NOT A) is handled in the description of A +DESC_NOT_OR_BEFORE_SINGLE_OPERAND +[[DESC_AND_BEFORE_SINGLE_OPERAND]] + +DESC_NOT_OR_AFTER_SINGLE_OPERAND +[[DESC_AND_AFTER_SINGLE_OPERAND]] + +# OrderedAlternativesOf Descriptions for expressions 'OrderedAlternativesOf [ A B C ]' +DESC_ORDERED_ALTERNATIVES_BEFORE_OPERANDS +that matches the first matching condition { + +DESC_ORDERED_ALTERNATIVES_BETWEEN_OPERANDS +, else + +DESC_ORDERED_ALTERNATIVES_AFTER_OPERANDS +''' }''' + +DESC_ORDERED_ALTERNATIVES_BEFORE_SINGLE_OPERAND +{ + +DESC_ORDERED_ALTERNATIVES_AFTER_SINGLE_OPERAND +(iff it also has non-matches)} + +# Expressions like 'NOT OrderedAlternativesOf [ A B C ]' +# Note: returned matches rely on (NOT A), (NOT B).. and are handled in the descriptions of A, B.. respectively +DESC_NOT_ORDERED_ALTERNATIVES_BEFORE_OPERANDS +that matches the first matching conditions' non-matching candidates: { + +DESC_NOT_ORDERED_ALTERNATIVES_BETWEEN_OPERANDS +, else + +DESC_NOT_ORDERED_ALTERNATIVES_AFTER_OPERANDS +''' }''' + +DESC_NOT_ORDERED_ALTERNATIVES_BEFORE_SINGLE_OPERAND +{ + +DESC_NOT_ORDERED_ALTERNATIVES_AFTER_SINGLE_OPERAND +(iff it also has non-matches)} # %1% textual description of the lower turn limit. # %2% textual description of the upper turn limit. @@ -9658,7 +10946,7 @@ LRN_ALGO_ELEGANCE Algorithmic Elegance LRN_ALGO_ELEGANCE_DESC -'''Increases Target [[metertype METER_RESEARCH]] on all Research focused planets by 0.1 per Population. +'''Increases [[metertype METER_TARGET_RESEARCH]] on all Research focused planets by 0.1 per [[metertype METER_POPULATION]]. With greater and greater difficulty of data analysis problems, traditional measures of algorithm performance become less useful due to the limitations of irreducible complexity. At this stage, other measures of algorithm form and function become significant; aesthetically and metaphorically, the elegance of the solution must be optimized.''' @@ -9682,7 +10970,7 @@ LRN_ARTIF_MINDS Nascent Artificial Intelligence LRN_ARTIF_MINDS_DESC -'''Increases Target [[metertype METER_RESEARCH]] on all planets by 2. +'''Increases [[metertype METER_TARGET_RESEARCH]] on all planets by 2. While traditional computers have nearly unfathomable intelligence and computational abilities, they lack the essential qualities of self-awareness, consciousness or sentience. While these traits remain elusive, reasonable facsimiles may be produced. These investigations open new avenues of research into cognitive sciences and novel metaparadigms. @@ -9751,7 +11039,7 @@ LRN_QUANT_NET Quantum Networking LRN_QUANT_NET_DESC -'''Increases target [[metertype METER_RESEARCH]] on all planets with the Research focus by 0.5 per Population. +'''Increases [[metertype METER_TARGET_RESEARCH]] on all planets with the Research focus by 0.5 per [[metertype METER_POPULATION]]. A massive, empire-wide data network able to transfer information instantaneously between any two places, rather than via the lengthy and indirect path of starlanes. This advancement greatly facilitates research throughout the empire. @@ -9773,7 +11061,7 @@ GRO_PLANET_ECOL Planetary Ecology GRO_PLANET_ECOL_DESC -'''Increases the max population of [[PE_GOOD]] and [[PE_ADEQUATE]] planets by +1. This bonus is not cumulative with [[tech GRO_SYMBIOTIC_BIO]]. +'''Increases the [[metertype METER_TARGET_POPULATION]] of [[PE_GOOD]] and [[PE_ADEQUATE]] planets by +1. This bonus is not cumulative with [[tech GRO_SYMBIOTIC_BIO]]. Agriculture and medical science can drastically increase survivability, allowing great increases in population and food production. Eventually, new limits to growth arise in the carrying capacity of a planet, and the interconnected web of life that supports it. Understanding natural ecology and its interaction to stresses permits the system to be modified and enhanced, and its benefits reaped for generations to come.''' @@ -9793,7 +11081,7 @@ GRO_SYMBIOTIC_BIO Symbiotic Biology GRO_SYMBIOTIC_BIO_DESC -Increases the max Population of [[PE_GOOD]], [[PE_ADEQUATE]], and [[PE_POOR]] planets by planet size: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). +Increases the [[metertype METER_TARGET_POPULATION]] of [[PE_GOOD]], [[PE_ADEQUATE]], and [[PE_POOR]] planets by planet size: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). GRO_GENETIC_MED Genetic Medicine @@ -9805,7 +11093,7 @@ GRO_LIFECYCLE_MAN Lifecycle Manipulation GRO_LIFECYCLE_MAN_DESC -'''In addition to the ship part, this tech also increases the population of new colonies produced with colony buildings to 3. +'''In addition to the ship part, this tech also increases the [[metertype METER_POPULATION]] of new colonies produced with colony buildings to 3. Through chemical manipulation and cryonics, life processes may be halted without terminating them permanently. Beings in this state are preserved indefinitely, consume no resources and require very little storage - not living - space. With this technique, colonists can be transported much more efficiently, and the amount of colonists on a freshly built colony as well as the capacity of colony ships is greatly increased. @@ -9821,7 +11109,7 @@ GRO_XENO_GENETICS Xenological Genetics GRO_XENO_GENETICS_DESC -'''Increases the max Population on planets, depending on the planet suitability and planet size: +'''Increases the [[metertype METER_TARGET_POPULATION]] on planets, depending on the planet suitability and planet size: • [[PE_ADEQUATE]] or [[PE_POOR]] planet: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10) • [[PE_HOSTILE]] planet: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5) @@ -9842,7 +11130,7 @@ GRO_XENO_HYBRIDS Xenological Hybridization GRO_XENO_HYBRIDS_DESC -'''Increases the max Population on planets, depending on the planet suitability and planet size: +'''Increases the [[metertype METER_TARGET_POPULATION]] on planets, depending on the planet suitability and planet size: • [[PE_POOR]] planet: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5) • [[PE_HOSTILE]] planet: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10) @@ -9867,17 +11155,78 @@ GRO_ENERGY_META Pure-Energy Metabolism GRO_ENERGY_META_DESC -'''Increases max [[metertype METER_FUEL]] on all ships by 2, max [[metertype METER_DEFENSE]] on all planets by 5, max [[metertype METER_SHIELD]] on all planets by 5, target [[metertype METER_INDUSTRY]] on all planets with the Industry focus by 0.2 per Population, and target [[metertype METER_RESEARCH]] on all planets with the Research focus by 0.5 per Population. +'''Increases max [[metertype METER_FUEL]] on all ships by 1 (before scaling by [[encyclopedia FUEL_EFFICIENCY_TITLE]]), max [[metertype METER_DEFENSE]] on all planets by 5, max [[metertype METER_SHIELD]] on all planets by 5, [[metertype METER_TARGET_INDUSTRY]] on all planets with the Industry focus by 0.2 per [[metertype METER_POPULATION]], and [[metertype METER_TARGET_RESEARCH]] on all planets with the Research focus by 0.5 per [[metertype METER_POPULATION]]. While transforming an entire population into beings of pure energy would be impractical, certain tasks are better suited to individuals able to manifest themselves non-corporeally. Ship's crews for example, would no longer have to waste space with crew quarters or rations storage. Beings with greater energy channeling abilities would be able to enhance planetary shields and defense, and resource production would be generally enhanced. The ultimate stage of physical development is to transcend the physical altogether. Without bodies, individuals and society may move, grow and exist freely, unhindered by unnecessary physical limitations and inefficient metabolic processes that waste energy supporting homeostasis.''' +PRO_TRANS_DESIGN +Transcendent Design + +PRO_TRANS_DESIGN_DESC +'''Transcending the limitations of the designing species, the most creative and effective from the different species in the empire start to work together to build universally usable goods. +Building [[buildingtype BLD_INTERSPECIES_ACADEMY]] on different planets ensures the practice spreads in the empire. + +The imperial stockpile service uses the outcome to prepare goods which can be used by any citizen.''' + +IMPERIAL_STOCKPILE_SHORT_DESC +Allows for Imperial Production Stockpiling + +PRO_PREDICTIVE_STOCKPILING +Predictive Stockpiling + +PRO_PREDICTIVE_STOCKPILING_DESC +'''Knowing where demand for certain goods will be in future allows to stack the items up in time. +The imperial stockpile service has a fleet for distributing raw materials to places where important projects are predicted to be started. +This task is difficult as the service has to predict when demand happens, where it happens, what materials and goods are demanded and also who the users will be. + +Unused PP will automatically be transferred to the Imperial Production Stockpile. +An item on the production queue draws automatically on the Imperial Stockpile if the item has not been explicitly disabled from doing so. + +This is the basic technology for the imperial stockpile, which increases the [[metertype METER_STOCKPILE]] of stockpile-focused planets by 1.''' + +PRO_GENERIC_SUPPLIES +Generic Supplies + +PRO_GENERIC_SUPPLIES_DESC +'''Through coordination of supply chains, materials, and designs, Generic Supplies are created which can easily be decomposited and reused for most industrial products. + +Generic Supplies can be created a long time before the demand occurs. Supply packages usually include bootstrap devices which construct the facilities for constructing new products from the Generic Supplies. +Reaching this state of sophistication, Generic Supplies can be used to construct new technology products which were not available at the time the supplies were produced. + +This frees the imperial stockpile service from having to know which goods will be demanded exactly. Predicting the location and amount is enough. + +This is an upgrade technology for the imperial stockpile. The [[metertype METER_STOCKPILE]] is increased by 2, plus an additional 0.01 per [[metertype METER_POPULATION]], plus an additional 3 for each stockpiling-focused planet. +These improvements are cumulative with that provided by [[tech PRO_PREDICTIVE_STOCKPILING]].''' + +PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY +Interstellar Entanglement Factory + +PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY_DESC +'''Quantum entanglement of factory control rooms allows the operation of machines remotely from other star systems. +Decoupling workforce and the means of production helps to produce just in time and increases efficiency of the imperial stockpile service. + +This is an upgrade technology for the imperial stockpile. The [[metertype METER_STOCKPILE]] is increased by 6 plus an additional 0.02 per [[metertype METER_POPULATION]]. +These improvements are cumulative with those provided by [[tech PRO_PREDICTIVE_STOCKPILING]], [[tech PRO_GENERIC_SUPPLIES]] and [[tech PRO_VOID_PREDICTION]]. + +This also unlocks the [[buildingtype BLD_STOCKPILING_CENTER]] which additionally increases the [[metertype METER_STOCKPILE]] by 0.1 per [[metertype METER_INDUSTRY]].''' + +PRO_VOID_PREDICTION +Void Prediction + +PRO_VOID_PREDICTION_DESC +'''While classic prediction always relies on statistical knowledge of the past, studying the chaotic wisdom of the void leads to robust predictions of first-time and freak occurences. +For the imperial stockpile service, void prediction is the quintessential tool to deliver the right goods even before the need occurs. + +This is an upgrade technology for the imperial stockpile. The [[metertype METER_STOCKPILE]] is increased by 0.2 per [[metertype METER_POPULATION]]. +These improvements are cumulative with those provided by [[tech PRO_PREDICTIVE_STOCKPILING]], [[tech PRO_GENERIC_SUPPLIES]] and [[tech PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY]].''' + PRO_MICROGRAV_MAN Microgravity Industry PRO_MICROGRAV_MAN_DESC -'''Increases [[metertype METER_INDUSTRY]] by +5 on all Industry-focused colonies in system with an asteroid belt outposts or colonies. Additional claimed asteroid belts do not provide additional benefit. +'''Increases [[metertype METER_TARGET_INDUSTRY]] by +5 on all Industry-focused colonies in systems with asteroid belt outposts or colonies. Additional claimed asteroid belts do not provide additional benefit. Mineral resource extraction from sub-planetoid-sized bodies involves challenges and provides opportunities quite distinct from those of full-fledged planets. Lacking sufficient mass to self-liquefy and differentiate, asteroids can provide much easier access to some heavier elements, and lack of a gravity can make extraction for use in space much more efficient. Conversely, the limited size of each source asteroid requires fully portable self-sufficient extraction facilities. As well, the challenges of any microgravity environment must be overcome, requiring redesign of many traditional methods. @@ -9887,7 +11236,7 @@ PRO_ROBOTIC_PROD Robotic Production PRO_ROBOTIC_PROD_DESC -'''Increases target [[metertype METER_INDUSTRY]] on all planets with the Industry focus by 0.1 per Population. +'''Increases [[metertype METER_TARGET_INDUSTRY]] on all planets with the Industry focus by 0.1 per [[metertype METER_POPULATION]]. Exobots, as they become commonly known, are a marvel of form meets function. They are robots designed to fulfill nearly any manufacturing job, in fields as wide ranging as electronics to large-scale construction. Easily mass produced, they serve best on colonies that place a high priority on industry. Every world focused primarily on industry receives a team of Exobots, greatly increasing industrial production. @@ -9895,6 +11244,7 @@ Although high level initial design and initial facility setup still require acti PRO_EXOBOTS Exobots + PRO_EXOBOTS_DESC Allows the construction of [[species SP_EXOBOT]] colonies optimized for industry on [[PE_HOSTILE]] planets. @@ -9902,7 +11252,7 @@ PRO_FUSION_GEN Fusion Generation PRO_FUSION_GEN_DESC -'''Increases target [[metertype METER_INDUSTRY]] on planets with the Industry focus by 0.2 per Population. +'''Increases [[metertype METER_TARGET_INDUSTRY]] on planets with the Industry focus by 0.2 per [[metertype METER_POPULATION]]. Fusion plants, while expensive to produce and maintain, easily pay for themselves on industrialized worlds with ever increasing power demands. Reliance on old fission based nuclear plants will gradually become a thing of the past. @@ -9926,7 +11276,7 @@ PRO_SENTIENT_AUTOMATION Adaptive Automation PRO_SENTIENT_AUTOMATION_DESC -'''Increases target [[metertype METER_INDUSTRY]] on all planets by 5. +'''Increases [[metertype METER_TARGET_INDUSTRY]] on all planets by 5. By producing automated factories that operate without direct supervision, it is possible to increase industrial output in an entirely automated facility. These factories can be built and maintained on any planet, regardless of focus, providing a bonus to industry on all worlds. @@ -9964,6 +11314,12 @@ Neutronium Extraction PRO_NEUTRONIUM_EXTRACTION_DESC The core of a [[STAR_NEUTRON]] star consists of degenerate neutron matter, compressed by gravity to a density far greater than the heaviest normal matter. This exotic material has numerous applications once extracted from the star, allowing production of otherwise impossible structures and ship parts. +CON_OUTPOST +Outposts + +CON_OUTPOST_DESC +The technology necessary for establishing [[encyclopedia OUTPOSTS_TITLE]] on planets and asteroid belts. + CON_ARCH_PSYCH Architectural Psychology @@ -9994,7 +11350,7 @@ CON_FRC_ENRG_STRC Force-Energy Structures CON_FRC_ENRG_STRC_DESC -'''Increases the growth of resource meters below target value to 3 per turn, and the decay of resource meters above target value to 5 per turn. +'''Increases the growth of resource and infrastructure meters below target value to 3 per turn, and the decay of resource meters above target value to 5 per turn. [[metertype METER_INDUSTRY]] and [[metertype METER_RESEARCH]] grow only if [[metertype METER_HAPPINESS]] is at least 5. The flexibility and versatility of force-energy structures permits a colonies buildings to be more easily re-tooled to suit new purposes, increasing the rate at which focus changes can take effect. @@ -10038,15 +11394,15 @@ CON_ORBITAL_HAB Orbital Habitation CON_ORBITAL_HAB_DESC -Increases Population capacity by planet size on all habitable planets: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). +Increases [[metertype METER_TARGET_POPULATION]] by planet size on all habitable planets: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). CON_NDIM_STRC N-Dimensional Structures CON_NDIM_STRC_DESC -'''Increases Population capacity by planet size on all habitable planets: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10). +'''Increases [[metertype METER_TARGET_POPULATION]] by planet size on all habitable planets: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10). -Also increases target [[metertype METER_CONSTRUCTION]] by 10 on populated. +Also increases [[metertype METER_TARGET_CONSTRUCTION]] by 10 on populated planets. The primary limiting factors on a planets population capacity are environmental desirability and space. By phasing basic infrastructure development into multiple dimensions, spatial limitations are all but eliminated, and under ideal environmental conditions, the maximum population of a planet can be greatly increased. @@ -10082,7 +11438,13 @@ SHP_SPACE_FLUX_DRIVE Spatial Flux Drive SHP_SPACE_FLUX_DRIVE_DESC -Ordinarily, propulsion in space is limited to either propellant or field propulsion. On relatively small scales however, it is possible to propel an object in space much like one could in water: by pushing backwards the medium in which the object is immersed. This requires the manipulation of space itself whereby the smallest units of space are collapsed, forcing the matter they once contained into adjacent units of space. This method of propulsion is very efficient, but dangerous on larger scales and easily detectable due to the dimensional effects. Further research into stealth technologies can reduce the dimensional disruption. +Ordinarily, propulsion in space is limited to either propellant or field propulsion. On relatively small scales however, it is possible to propel an object in space much like one could in water: by pushing backwards the medium in which the object is immersed. This requires the manipulation of space itself whereby the smallest units of space are collapsed, forcing the matter they once contained into adjacent units of space. This method of propulsion is very efficient, but dangerous on larger scales and easily detectable due to the dimensional effects. Further research into stealth technologies can reduce the dimensional disruption. Advanced research allows different hull layout than the [[tech SHP_SPACE_FLUX_BUBBLE]]. + +SHP_SPACE_FLUX_BUBBLE +Spatial Flux Bubble + +SHP_SPACE_FLUX_BUBBLE_DESC +Ordinarily, propulsion in space is limited to either propellant or field propulsion. On relatively small scales however, it is possible to propel an object in space much like one could in water: by pushing backwards the medium in which the object is immersed. This requires the manipulation of space itself whereby the smallest units of space are collapsed, forcing the matter they once contained into adjacent units of space. This method of propulsion is very efficient, but dangerous on larger scales and easily detectable due to the dimensional effects. Further research into stealth technologies can reduce the dimensional disruption. The simplest solution to building a vessel using the flux propulsion is the bubble drive, which covers a sphere-shaped hull. More complex solutions are possible with better tech [[tech SHP_SPACE_FLUX_DRIVE]]. SHP_CONTGRAV_MAINT Contra Gravitational Maintenance @@ -10217,11 +11579,13 @@ A hull constructed from pure energy, while far more versatile than a solid hull, SHP_KRILL_SPAWN Krill Spawner + SHP_KRILL_SPAWN_DESC Unlocks the [[shippart SP_KRILL_SPAWNER]] ship part, which causes wild space krill to appear in systems with asteroid belts. SPY_ROOT_DECEPTION Deception + SPY_ROOT_DECEPTION_DESC The art of deception is not natural to every species, but the ability to hide the truth can be most useful. @@ -10236,6 +11600,9 @@ Brings Victory! SHIP_PART_UNLOCK_SHORT_DESC Unlocks Ship Part +SHIP_FUEL_IMPROVE_SHORT_DESC +Improves Ships Fuel Tanks + SHIP_WEAPON_UNLOCK_SHORT_DESC Unlocks Ship Weapon @@ -10351,13 +11718,13 @@ CON_PALACE_EXCLUDE Palace Excluder CON_PALACE_EXCLUDE_DESC -Ensures that only one imperial palace will be present in an empire at a time. Make sure you scrap your old imperial palace before your new one is built. +Ensures that only one imperial palace will be present in an empire at a time. Make sure to scrap any old imperial palace before a new one is built. GRO_SUBTER_HAB Subterranean Habitation GRO_SUBTER_HAB_DESC -Increases the max Population on all planets by planet size: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). +Increases the [[metertype METER_TARGET_POPULATION]] on all planets by planet size: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). GRO_GENOME_BANK Genome Bank @@ -10375,7 +11742,7 @@ GRO_TERRAFORM Terraforming GRO_TERRAFORM_DESC -Changing the environmental type of a planet is a massive undertaking, the challenge of this process is to a large degree logistical. The precise method depends greatly on the type of planet to be changed and the desired result. However, in all cases much of the surface of the entire planet and its atmosphere needs to be converted to other types of molecules, vented into space or buried under the crust. As such, this process requires most of the planet to be accessible to the terraformers, and cannot be performed on a world with max population less than one. +Changing the environmental type of a planet is a massive undertaking, the challenge of this process is to a large degree logistical. The precise method depends greatly on the type of planet to be changed and the desired result. However, in all cases much of the surface of the entire planet and its atmosphere needs to be converted to other types of molecules, vented into space or buried under the crust. As such, this process requires most of the planet to be accessible to the terraformers, and cannot be performed on a world with [[metertype METER_TARGET_POPULATION]] less than one. GRO_TERRAFORM_SHORT_DESC Allows terraforming @@ -10399,8 +11766,8 @@ GRO_CYBORG Cyborgs GRO_CYBORG_DESC -'''Increases the max Population on [[PE_HOSTILE]] planets by planet size: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10). -Increases max [[metertype METER_TROOPS]] on all planets by 0.2 per Population. +'''Increases the [[metertype METER_TARGET_POPULATION]] on [[PE_HOSTILE]] planets by planet size: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10). +Increases max [[metertype METER_TROOPS]] on all planets by 0.2 per [[metertype METER_POPULATION]]. A highly versatile fusion of organism and machine, capable of adapting to a tremendous variety of circumstances. Spontaneous generation of specialized organs and mechanically enhanced strength permit cyborgs to exist with ease in nearly any environment.''' @@ -10413,6 +11780,15 @@ Theoretically, it is possible to completely predict the ecology of a single plan GRO_GAIA_TRANS_SHORT_DESC Allows Gaia Transformation +GRO_MEGA_ECO +Mega-Fauna Ecology + +GRO_MEGA_ECO_DESC +Study of the lifecycles of space-faring organisms suggests common patterns in widely varied species. It may be possible to exploit this knowledge to more effectively control these organisms by disrupting their metabolic and reproductive processes. + +GRO_MEGA_ECO_SHORT_DESC +Monster Nest Control + LRN_OBSERVATORY_I Observatory @@ -10429,7 +11805,7 @@ LRN_DISTRIB_THOUGHT Distributed Thought Computing LRN_DISTRIB_THOUGHT_DESC -'''Increases target [[metertype METER_RESEARCH]] on all planets with any focus by 0.1 per Population. +'''Increases [[metertype METER_TARGET_RESEARCH]] on all planets with any focus by 0.1 per [[metertype METER_POPULATION]]. The average citizen literally wastes his mind away, one of the finest computing devices in the universe. By tapping into the idle brain cycles of a sufficiently large population through a network of simple cybernetic transceivers, the research centers of a world can gain access to cheap and plentiful raw computing power.''' @@ -10437,7 +11813,7 @@ LRN_STELLAR_TOMOGRAPHY Stellar Tomography LRN_STELLAR_TOMOGRAPHY_DESC -'''Increases target [[metertype METER_RESEARCH]] on planets with the Research focus, per Population and depending on the star type: +'''Increases [[metertype METER_TARGET_RESEARCH]] on planets with the Research focus, per [[metertype METER_POPULATION]] and depending on the star type: * [[STAR_BLACK]] (x1) * [[STAR_NEUTRON]] (x0.75) * [[STAR_BLUE]] or [[STAR_WHITE]] (x0.5) @@ -10473,8 +11849,8 @@ LRN_PSY_DOM Psychogenic Domination LRN_PSY_DOM_DESC -'''Has a 10% chance of taking control of enemy ships with non-telepathic crews in the same system as a planet with domination focus, unless the enemy empire also knows the [[tech LRN_PSY_DOM]]. -Protects against [[predefinedshipdesign SM_PSIONIC_SNOWFLAKE]]s taking over your ships. +'''Has a 10% chance of taking control of enemy ships with non-telepathic crews in the same system as a planet with domination focus, unless the enemy empire also knows the [[tech LRN_PSY_DOM]]. Domination focus can only be set on planets colonized by telepathic species. +Reduces the chance that [[predefinedshipdesign SM_PSIONIC_SNOWFLAKE]]s take over your ships. It is difficult to force a conscious individual to submit to the total mental control of another entity against its will. By manipulating the timeframes involved however, the mental force exerted by a population over a period of hours may be brought to bear on a single individual in the span of a few seconds, creating an almost irresistible compulsion to join the unified mind.''' @@ -10597,7 +11973,7 @@ DEF_ROOT_DEFENSE Self Defense DEF_ROOT_DEFENSE_DESC -'''Increases max [[metertype METER_SHIELD]] by 1 on each owned planet. For species with basic defensive troops, increases max [[metertype METER_TROOPS]] on the planet by 0.2 per population. +'''Increases max [[metertype METER_SHIELD]] by 1 on each owned planet. Regenerates 1 [[metertype METER_SHIELD]] per turn, on turns when the planet has not been attacked in combat. For species with basic defensive troops, increases max [[metertype METER_TROOPS]] on the planet by 0.2 per [[metertype METER_POPULATION]]. The concept of 'self defense' is at the root of many avenues of technological advance.''' @@ -10611,7 +11987,7 @@ DEF_GARRISON_2 Defensive Militia Training DEF_GARRISON_2_DESC -Increases max [[metertype METER_TROOPS]] on all planets by 0.4 per population (modified by species defensive trait), and causes troops to regenerate by an additional 1 per turn. +Increases max [[metertype METER_TROOPS]] on all planets by 0.4 per [[metertype METER_POPULATION]] (modified by species defensive trait), and causes troops to regenerate by an additional 1 per turn. DEF_GARRISON_3 Planetary Fortification Network @@ -10623,7 +11999,7 @@ DEF_GARRISON_4 Planetary Guard Brigades DEF_GARRISON_4_DESC -Increases max [[metertype METER_TROOPS]] on all planets by 0.4 per population (modified by species defensive trait), and causes troops to regenerate by an additional 3 per turn in addition to that from [[DEF_GARRISON_3]] and [[DEF_GARRISON_2]]. +Increases max [[metertype METER_TROOPS]] on all planets by 0.4 per [[metertype METER_POPULATION]] (modified by species defensive trait), and causes troops to regenerate by an additional 3 per turn in addition to that from [[DEF_GARRISON_3]] and [[DEF_GARRISON_2]]. DEF_PLANET_CLOAK Planetary Cloaking Device @@ -10669,34 +12045,41 @@ Bombardment SHP_BOMBARD_DESC Allows development of planetary bombardment weapons. -##Fighter explanation, needs improving and a Pedia article writing + +## +## Fighter explanation, needs improving and a Pedia article writing +## + SHP_FIGHTERS_1 Fighters and Launch Bays SHP_FIGHTERS_1_DESC -'''Enables your empire to build ships using Fighter Hangars and Launch Bays. A ship can only have one type of Fighter Hangar on it but can have as many as you have slots for. Each [[encyclopedia PC_FIGHTER_HANGAR]] has a maximum capacity of fighters and those fighters will all have the same strength (which can be modified by species abilities and further research). +'''Enables an empire to build ships using Fighter Hangars and Launch Bays. A ship can only have one type of Fighter Hangar on it, but can have as many as there are available slots. Each [[encyclopedia PC_FIGHTER_HANGAR]] has a maximum capacity of fighters and those fighters will all have the same strength (which can be modified by species abilities and further research). A [[encyclopedia PC_FIGHTER_BAY]] can launch a number of fighters in each combat round. These fighters will then be both valid targets and able to attack ships and other fighters in subsequent combat rounds, but a single hit from any source will destroy a fighter. Unlike [[encyclopedia DAMAGE_TITLE]] inflicted by regular weapons, fighter damage ignores [[metertype METER_SHIELD]] on warships (they fire from within the shields). +Piloting traits affect [[FT_HANGAR_2]], [[FT_HANGAR_3]], and [[FT_HANGAR_4]] fighters in the same way: bad piloting reduces damage by 1 per fighter; good piloting increases damage by 1, great piloting by 2, and ultimate piloting by 3 per fighter shot. +Piloting traits do not affect [[FT_HANGAR_1]] fighters. + At the end of combat, carriers will collect any fighters that have survived. If a carrier is in [[metertype METER_SUPPLY]] immediately after movement is resolved, it will refill hangars to maximum.''' SHP_FIGHTERS_2 Laser Fighters SHP_FIGHTERS_2_DESC -Upgrades the fighters of all carriers within [[metertype METER_SUPPLY]] to have laser weaponry. [[encyclopedia DAMAGE_TITLE]] from fighters in a [[shippart FT_HANGAR_1]] is increased by 1, [[shippart FT_HANGAR_2]] is increased by 2 and [[shippart FT_HANGAR_3]] by 3. +Upgrades the fighters of all carriers within [[metertype METER_SUPPLY]] to have laser weaponry. [[encyclopedia DAMAGE_TITLE]] from fighters in a [[shippart FT_HANGAR_2]] is increased by 2, [[shippart FT_HANGAR_3]] by 3, and [[shippart FT_HANGAR_4]] by 6. For [[shippart FT_HANGAR_1]] fighters the capacity as well as the launch rate of the [[shippart FT_BAY_1]] is increased by 1. SHP_FIGHTERS_3 Plasma Fighters SHP_FIGHTERS_3_DESC -Upgrades the fighters of all carriers within [[metertype METER_SUPPLY]] to have plasma weaponry. [[encyclopedia DAMAGE_TITLE]] from fighters in a [[shippart FT_HANGAR_1]] is increased by 1, [[shippart FT_HANGAR_2]] is increased by 3 and [[shippart FT_HANGAR_3]] by 4. +Upgrades the fighters of all carriers within [[metertype METER_SUPPLY]] to have plasma weaponry. [[encyclopedia DAMAGE_TITLE]] from fighters in a [[shippart FT_HANGAR_2]] is increased by 2, [[shippart FT_HANGAR_3]] by 3, and [[shippart FT_HANGAR_4]] by 6. For [[shippart FT_HANGAR_1]] fighters the capacity as well as the launch rate of the [[shippart FT_BAY_1]] is increased by 1. SHP_FIGHTERS_4 Death Ray Fighters SHP_FIGHTERS_4_DESC -Upgrades the fighters of all carriers within [[metertype METER_SUPPLY]] to have death ray weaponry. [[encyclopedia DAMAGE_TITLE]] from fighters in a [[shippart FT_HANGAR_1]] is increased by 1, [[shippart FT_HANGAR_2]] is increased by 5 and [[shippart FT_HANGAR_3]] by 7. +Upgrades the fighters of all carriers within [[metertype METER_SUPPLY]] to have death ray weaponry. [[encyclopedia DAMAGE_TITLE]] from fighters in a [[shippart FT_HANGAR_2]] is increased by 2, [[shippart FT_HANGAR_3]] by 3, and [[shippart FT_HANGAR_4]] by 6. For [[shippart FT_HANGAR_1]] fighters the capacity as well as the launch rate of the [[shippart FT_BAY_1]] is increased by 1. SHP_WEAPON_1_2 Mass Driver 2 @@ -10788,6 +12171,23 @@ Death Ray 4 SHP_WEAPON_4_4_DESC Upgrades [[shippart SR_WEAPON_4_1]] parts on all ships within [[metertype METER_SUPPLY]] (or after entering it later). The improved weapon part has shot damage of 30. +SHP_WEAPON_ARC_DISRUPTOR_1 +Arc Disruptors + +SHP_WEAPON_ARC_DISRUPTOR_1_DESC +Unlocks the [[shippart SR_ARC_DISRUPTOR]], an automated weapon system doing low damage to multiple targets. The base weapon part has shot damage 2. + +SHP_WEAPON_ARC_DISRUPTOR_2 +Arc Disruptor 2 + +SHP_WEAPON_ARC_DISRUPTOR_2_DESC +Upgrades [[shippart SR_ARC_DISRUPTOR]] parts on all ships within [[metertype METER_SUPPLY]] (or after entering it later). The improved weapon part has shot damage of 4. + +SHP_WEAPON_ARC_DISRUPTOR_3 +Arc Disruptor 3 + +SHP_WEAPON_ARC_DISRUPTOR_3_DESC +Upgrades [[shippart SR_ARC_DISRUPTOR]] parts on all ships within [[metertype METER_SUPPLY]] (or after entering it later). The improved weapon part has shot damage of 7. SHP_ION_CANNON Ion Cannon @@ -10907,7 +12307,7 @@ Planetary Cloud Cover SPY_STEALTH_1_DESC '''Gives all planets and outposts in the empire the [[special CLOUD_COVER_SLAVE_SPECIAL]] special which increases the [[metertype METER_STEALTH]] of all planets and buildings by 20. -The research cost of this tech is reduced by the cost of researching [[tech SPY_STEALTH_PART_1]] if it has already been completed.''' +The [[metertype METER_RESEARCH]] cost of this tech is reduced by the cost of researching [[tech SPY_STEALTH_PART_1]] if it has already been completed.''' SPY_STEALTH_PART_1 Electromagnetic Dampening @@ -10921,7 +12321,7 @@ Planetary Ash Clouds SPY_STEALTH_2_DESC '''Gives all planets and outposts in the empire the [[special VOLCANIC_ASH_SLAVE_SPECIAL]] special which increases the [[metertype METER_STEALTH]] of all planets and buildings by 40. -The research cost of this tech is reduced by the cost of researching [[tech SPY_STEALTH_PART_2]] if it has already been completed.''' +The [[metertype METER_RESEARCH]] cost of this tech is reduced by the cost of researching [[tech SPY_STEALTH_PART_2]] if it has already been completed.''' SPY_STEALTH_PART_2 Radiation Absorbing @@ -10935,7 +12335,7 @@ Planetary Dimensional Cloak SPY_STEALTH_3_DESC '''Gives all planets and outposts in the empire the [[special DIM_RIFT_SLAVE_SPECIAL]] special which increases the [[metertype METER_STEALTH]] of all planets and buildings by 60. -The research cost of this tech is reduced by the cost of researching [[tech SPY_STEALTH_PART_3]] if it has already been completed.''' +The [[metertype METER_RESEARCH]] cost of this tech is reduced by the cost of researching [[tech SPY_STEALTH_PART_3]] if it has already been completed.''' SPY_STEALTH_PART_3 Dimensional Cloaking @@ -10955,12 +12355,12 @@ SPY_CUSTOM_ADVISORIES Empire Intelligence Agency SPY_CUSTOM_ADVISORIES_DESC -'''The Empire Intelligence Agency gathers and organizes information from many sources, including spies, common informers, and official chains of command reporting. The most important items of information are presented each turn as Situation Reports (SitReps). If a SitRep is repeated from turn to turn more often than you really need for remembering the information, you can ignore the SitRep for the current turn by double-clicking its icon, or for multiple turns via right-click menu on the SitRep's icon. +'''The Empire Intelligence Agency gathers and organizes information from many sources, including spies, common informers, and official chains of command reporting. The most important items of information are presented each turn as Situation Reports (SitReps). If a SitRep is repeated from turn to turn, more often than needed for remembering the information, it can be ignored for the current turn by double-clicking its icon, or for multiple turns via right-click menu on the SitRep's icon. Many of the SitReps are determined automatically, but additional custom SitReps can be specified by editing the file default/customizations/custom_sitreps.txt (Note: in a multiplayer game, the content files of the Server are the ones which determine what SitReps are sent to empires.) More particulars on the FreeOrion scripting language, such as for these custom SitReps, can be found at our wiki: http://www.freeorion.org/index.php/Free_Orion_Content_Script_(FOCS) -There are four preset (possibly translated) labels for use with custom sitreps, so that they get placement at the top of the SitRep window. More information on how to use the labels can be found on the wiki, and in the custom_sitreps.txt. +There are four preset (possibly translated) labels for use with custom sitreps, so that they get placement at the top of the SitRep window. More information on how to use the labels can be found on the wiki, and in the custom_sitreps.txt. These four labels are "[[CUSTOM_1]]" "[[CUSTOM_2]]" @@ -10996,13 +12396,19 @@ SHP_DEUTERIUM_TANK Deuterium Tank SHP_DEUTERIUM_TANK_DESC -Unlocks [[shippart FU_DEUTERIUM_TANK]] ship part. +Small upgrade to each of your [[shippart FU_BASIC_TANK]] ship parts. Increases [[metertype METER_FUEL]] capacity and thereby the number of jumps your ships can do without refueling. + +SHP_DEUTERIUM_TANK_EFFECT +Deuterium Tank Technology SHP_ANTIMATTER_TANK Antimatter Tank SHP_ANTIMATTER_TANK_DESC -Unlocks [[shippart FU_ANTIMATTER_TANK]] ship part. +Moderate upgrade to each of your [[shippart FU_BASIC_TANK]] ship parts. Increases [[metertype METER_FUEL]] capacity and thereby the number of jumps your ships can do without refueling. + +SHP_ANTIMATTER_TANK_EFFECT +Antimatter Tank Technology SHP_ZERO_POINT Zero-Point Fuel Generator @@ -11233,7 +12639,7 @@ BLD_EVACUATION Evacuation System BLD_EVACUATION_DESC -Remove population from this planet over several turns according to an [[encyclopedia EVACUATION_TITLE]]. If a suitable alternate planets are available, with the same species and sufficient extra capacity, population will move there. +Remove [[metertype METER_POPULATION]] from this planet over several turns according to an [[encyclopedia EVACUATION_TITLE]]. If a suitable alternate planets are available, with the same species and sufficient extra capacity, Population will move there. BLD_OBSERVATORY Observatory @@ -11247,7 +12653,7 @@ BLD_CULTURE_ARCHIVES Cultural Archives BLD_CULTURE_ARCHIVES_DESC -'''Increases target [[metertype METER_RESEARCH]] by 5 and target [[metertype METER_INDUSTRY]] by half the planet's Population. +'''Increases [[metertype METER_TARGET_RESEARCH]] by 5 and [[metertype METER_TARGET_INDUSTRY]] by half the planet's [[metertype METER_POPULATION]]. Stores the sum accumulated knowledge from thousands of years of life on this planet, improving many aspects of planetary productivity.''' @@ -11255,15 +12661,15 @@ BLD_CULTURE_LIBRARY Cultural Library BLD_CULTURE_LIBRARY_DESC -'''Increases target [[metertype METER_RESEARCH]] by 5. +'''Increases [[metertype METER_TARGET_RESEARCH]] by 5. -Stores the accumulated knowledge from thousands of years of life on this planet, giving a bonuses to research. This building will be destroyed when the population of the high tech natives reaches zero.''' +Stores the accumulated knowledge from thousands of years of life on this planet, giving a bonuses to research. This building will be destroyed when the [[metertype METER_POPULATION]] of the high tech natives reaches zero.''' BLD_AUTO_HISTORY_ANALYSER Automated History Analyser BLD_AUTO_HISTORY_ANALYSER_DESC -'''The Automated History Analyser can only be built on a planet with [[buildingtype BLD_CULTURE_ARCHIVES]]. Increases target [[metertype METER_RESEARCH]] by 5. +'''The Automated History Analyser can only be built on a planet with [[buildingtype BLD_CULTURE_ARCHIVES]]. Increases [[metertype METER_TARGET_RESEARCH]] by 5. An automated research center with enormous computing power that scours the digitalised cultural archives to come up with patterns, interdisciplinary parallels and lost or unfinished insights.''' @@ -11271,7 +12677,7 @@ BLD_IMPERIAL_PALACE Imperial Palace BLD_IMPERIAL_PALACE_DESC -'''Increases [[metertype METER_SUPPLY]] line range by 2, [[metertype METER_CONSTRUCTION]] by 20, [[metertype METER_DEFENSE]] by 5, and [[metertype METER_TROOPS]] by 6. Also sets the owner's Capital for the empire. +'''Increases [[metertype METER_SUPPLY]] line range by 2, [[metertype METER_TARGET_CONSTRUCTION]] by 20, [[metertype METER_DEFENSE]] by 5, and [[metertype METER_TROOPS]] by 6. Also sets the owner's Capital for the empire. Represents imperial power and prestige and functions as a center of control for the empire's holdings.''' @@ -11385,9 +12791,9 @@ BLD_BIOTERROR_PROJECTOR Bioterror Projection Base BLD_BIOTERROR_PROJECTOR_DESC -'''Gives the planet on which it is built the Bioterror focus, which decreases Population on enemy planets within 4 starlane jumps at a rate of 2 per turn, provided the enemy empire does not contain a Genome Bank. +'''Gives the planet on which it is built the Bioterror focus, which decreases [[metertype METER_POPULATION]] on enemy planets within 4 starlane jumps at a rate of 2 per turn, provided the enemy empire does not contain a Genome Bank. -This clandestine biological warfare base wreaks havoc on enemy planets in the vicinity. Such activities are not endorsed by the public however, and this facility can only be built on a planet with a resonant moon. Ineffective if there is a [[buildingtype BLD_GENOME_BANK]] building in the enemy empire. This building can also be built at an [[encyclopedia OUTPOSTS_TITLE]], provided it has a [[special RESONANT_MOON_SPECIAL]].''' +This clandestine biological warfare base wreaks havoc on enemy planets in the vicinity. Such activities are not endorsed by the public however, and this facility can only be built on a planet with a resonant moon. Ineffective if there is a [[buildingtype BLD_GENOME_BANK]] building in the enemy empire. [[BUILDING_AVAILABLE_ON_OUTPOSTS]], provided it has a [[special RESONANT_MOON_SPECIAL]].''' BLD_LIGHTHOUSE Interstellar Lighthouse @@ -11407,18 +12813,37 @@ BLD_INDUSTRY_CENTER Industrial Center BLD_INDUSTRY_CENTER_DESC -'''Initially provides a bonus to [[metertype METER_INDUSTRY]] on all [[metertype METER_SUPPLY]] line connected planets with Industry focus by 0.2 per Population. +'''Initially provides a bonus to [[metertype METER_TARGET_INDUSTRY]] on all [[metertype METER_SUPPLY]] line connected planets with Industry focus by 0.2 per [[metertype METER_POPULATION]]. [[NO_STACK_SUPPLY_CONNECTION_TEXT]] [[tech PRO_INDUSTRY_CENTER_II]] refinement doubles the bonus. [[tech PRO_INDUSTRY_CENTER_III]] refinement further increases the bonus to triple the base bonus.''' +BLD_INTERSPECIES_ACADEMY +Species InterDesign Academy + +BLD_INTERSPECIES_ACADEMY_DESC +'''Increases stockpile extraction on its planet by 10, if that planet has stockpiling focus. +Increases [[metertype METER_RESEARCH]] on its planet by 5, if that planet has research focus. + +Learning to design devices for use by different species is crucial for developing universally usable tools. + +Each empire may contain an academy on up to six planets with different species. To support an academy, a planet's [[metertype METER_POPULATION]] must be happy, and at least three starlane jumps away from the nearest existing academy.''' + +BLD_STOCKPILING_CENTER +Imperial Entanglement Center + +BLD_STOCKPILING_CENTER_DESC +'''Provides a bonus to [[metertype METER_STOCKPILE]] by 0.1 per [[metertype METER_INDUSTRY]]. + +Instead of using factories entangled directly between the production place and the workers planet, a central hub can connect any entangled factories with any entangled factory workers. This provides great flexibility in production location.''' + BLD_MEGALITH Megalith BLD_MEGALITH_DESC -'''This building may only be built in the owner empire's capital, where it further adds to the splendor of the [[buildingtype BLD_IMPERIAL_PALACE]]. All resource meters for the planet on which it is built are able to reach target in a single turn. This planet also receives an increase of 30 to [[metertype METER_CONSTRUCTION]]. Populated planets in the owning empire receive an increase of 1 to [[metertype METER_SUPPLY]] line range. Populated planets within two starlane jumps have their [[metertype METER_TROOPS]] increased by 10. +'''This building may only be built in the owner empire's capital, where it further adds to the splendor of the [[buildingtype BLD_IMPERIAL_PALACE]]. All resource meters for the planet on which it is built are able to reach target in a single turn. This planet also receives an increase of 30 to [[metertype METER_TARGET_CONSTRUCTION]]. Populated planets in the owning empire receive an increase of 1 to [[metertype METER_SUPPLY]] line range. Populated planets within two starlane jumps have their [[metertype METER_TROOPS]] increased by 10. The Megalith is an abnormally massive starscraper, kilometers in diameter, and an inspiration to architects empire-wide.''' @@ -11442,13 +12867,13 @@ BLD_GAIA_TRANS Gaia Transformation BLD_GAIA_TRANS_DESC -'''Adds the [[special GAIA_SPECIAL]] special to the planet which increases max Population for [[PE_GOOD]] [[encyclopedia ENVIRONMENT_TITLE]]s, according to planet size: +'''Adds the [[special GAIA_SPECIAL]] special to the planet which increases [[metertype METER_TARGET_POPULATION]] for [[PE_GOOD]] [[encyclopedia ENVIRONMENT_TITLE]]s, according to planet size: • [[SZ_TINY]] (+3) • [[SZ_SMALL]] (+6) • [[SZ_MEDIUM]] (+9) • [[SZ_LARGE]] (+12) • [[SZ_HUGE]] (+15) -and increases max [[metertype METER_HAPPINESS]] by 5. +and increases [[metertype METER_TARGET_HAPPINESS]] by 5. Convert a planet into a Gaia world by way of a sentient, almost god-like, cell-based computer program. This planet is wondrous to behold and famous throughout the known galaxy as a celebration of life and harmony. The inhabitants of this world think and act as though part of a larger organism, serving its needs simultaneously with their own.''' @@ -11456,7 +12881,7 @@ BLD_COLLECTIVE_NET Collective Thought Network BLD_COLLECTIVE_NET_DESC -'''Increases [[metertype METER_INDUSTRY]] on planets with the Industry focus by 0.5 per Population, and [[metertype METER_RESEARCH]] on Planets with the Research focus by 0.5 per Population. Starlane travel within 200 uu will disrupt this building's effectiveness and eliminate these bonuses. +'''Increases [[metertype METER_TARGET_INDUSTRY]] on planets with the Industry focus by 0.5 per [[metertype METER_POPULATION]], and [[metertype METER_TARGET_RESEARCH]] on Planets with the Research focus by 0.5 per [[metertype METER_POPULATION]]. Starlane travel within 200 uu will disrupt this building's effectiveness and eliminate these bonuses. [[NO_STACK_SUPPLY_CONNECTION_TEXT]] By transferring the mind into cyberspace, thousands of minds can act as one, solving problems and making breakthroughs that no single mind could.''' @@ -11465,7 +12890,7 @@ BLD_GATEWAY_VOID Gateway to the Void BLD_GATEWAY_VOID_DESC -'''The ability to close off a system almost entirely can be useful for an empire on the defensive. Any ships who enter this system without passing through on the same turn are destroyed, and all colonies within are immediately reduced to [[encyclopedia OUTPOSTS_TITLE]] status. The [[metertype METER_STEALTH]] of all objects within the system is increased by 1000, making them almost impossible to detect. +'''The ability to close off a system almost entirely can be useful for an empire on the defensive. Any ships that enter this system without passing through on the same turn are destroyed, and all colonies within are immediately reduced to [[encyclopedia OUTPOSTS_TITLE]] status. The [[metertype METER_STEALTH]] of all objects within the system is increased by 1000, making them almost impossible to detect. A dimensional tear capable of magnifying the destructive potential of the Void Mind in a localized region.''' @@ -11473,7 +12898,7 @@ BLD_ENCLAVE_VOID Enclave of the Void BLD_ENCLAVE_VOID_DESC -'''Increases target [[metertype METER_RESEARCH]] on all planets with the Research focus by 0.75 per Population. +'''Increases [[metertype METER_TARGET_RESEARCH]] on all planets with the Research focus by 0.75 per [[metertype METER_POPULATION]]. Multiple copies do not stack. The population of an entire planet is genetically attuned to the Void Mind and dedicated to channeling its wisdom to the empire. The denizens of the Void Enclave are regarded as priests, channels of higher thinking and wisdom. In all matters of importance to the empire, their advice is sought after. Emissaries from the Enclave can even be sent to assist in the empire's research projects.''' @@ -11494,7 +12919,7 @@ BLD_HYPER_DAM Hyperspatial Dam BLD_HYPER_DAM_DESC -'''For planets not connected to a [[buildingtype BLD_BLACK_HOLE_POW_GEN]], increases target [[metertype METER_INDUSTRY]] on [[metertype METER_SUPPLY]] line connected planets with the Industry focus by 1 per Population, but decreases Population on such planets by planet size: ([[SZ_TINY]] -1) ([[SZ_SMALL]] -2) ([[SZ_MEDIUM]] -3) ([[SZ_LARGE]] -4) ([[SZ_HUGE]] -5). +'''For planets not connected to a [[buildingtype BLD_BLACK_HOLE_POW_GEN]], increases [[metertype METER_TARGET_INDUSTRY]] on [[metertype METER_SUPPLY]] line connected planets with the Industry focus by 1 per [[metertype METER_POPULATION]], but decreases [[metertype METER_TARGET_POPULATION]] on such planets by planet size: ([[SZ_TINY]] -1) ([[SZ_SMALL]] -2) ([[SZ_MEDIUM]] -3) ([[SZ_LARGE]] -4) ([[SZ_HUGE]] -5). [[NO_STACK_SUPPLY_CONNECTION_TEXT]] A tear in the fabric of the universe, safely controlled and harnessed. Each planet may construct its own hyperspatial dams, but a single control center is required to ensure that the space-time continuum is not torn apart by the stresses. Additionally, all planets who use hyperspatial dams experience health problems, unless they are orbiting a [[STAR_BLACK]]. @@ -11511,9 +12936,9 @@ BLD_SOL_ORB_GEN Solar Orbital Generator BLD_SOL_ORB_GEN_DESC -'''Increases target [[metertype METER_INDUSTRY]] on [[metertype METER_SUPPLY]] line connected planets, per Population and depending on the star type: +'''Increases [[metertype METER_TARGET_INDUSTRY]] on [[metertype METER_SUPPLY]] line connected planets, per [[metertype METER_POPULATION]] and depending on the star type: * [[STAR_BLUE]] or [[STAR_WHITE]] (x0.4) -* [[STAR_YELLOW]] or [[STAR_ORANGE]] (x0.2) +* [[STAR_YELLOW]] or [[STAR_ORANGE]] (x0.2) * [[STAR_RED]] (x0.1) [[NO_STACK_SUPPLY_CONNECTION_TEXT]] The best bonus applies. @@ -11523,7 +12948,7 @@ BLD_CLONING_CENTER Cloning Center BLD_CLONING_CENTER_DESC -'''Increases the max Population on all planets by planet size: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). +'''Increases the [[metertype METER_TARGET_POPULATION]] on all planets by planet size: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). [[tech GRO_INDUSTRY_CLONE]] allows made-to-order populations to be produced industrially, significantly improving population.''' @@ -11545,11 +12970,17 @@ Remote Terraforming BLD_REMOTE_TERRAFORM_DESC Terraforms a world one step closer to the [[encyclopedia ENVIRONMENT_TITLE]] preference of the inhabiting species. This process can be enacted at an [[encyclopedia OUTPOSTS_TITLE]]. +BLD_NEST_ERADICATOR +Nest Eradicator + +BLD_NEST_ERADICATOR_DESC +Eradicates a [[encyclopedia KRAKEN_NEST_SPECIAL]], [[encyclopedia SNOWFLAKE_NEST_SPECIAL]], or [[encyclopedia JUGGERNAUT_NEST_SPECIAL]] from the planet it is built on. [[BUILDING_AVAILABLE_ON_OUTPOSTS]]. + BLD_NEUTRONIUM_EXTRACTOR Neutronium Extractor BLD_NEUTRONIUM_EXTRACTOR_DESC -'''Makes Neutronium available to the owning empire. Neutronium may be used at a [[buildingtype BLD_NEUTRONIUM_FORGE]] to enable construction of ships with Neutronium ship parts, like the [[shippart AR_NEUTRONIUM_PLATE]]. +'''Makes Neutronium available to the owning empire. Neutronium may be used at a [[buildingtype BLD_NEUTRONIUM_FORGE]] to enable construction of ships with Neutronium ship parts, like the [[shippart AR_NEUTRONIUM_PLATE]]. Before parts can be built using Neutronium, it must be extracted from a [[STAR_NEUTRON]] star. Neutronium extracted at this facility is transported to Neutronium Forges throughout the empire. [[BUILDING_AVAILABLE_ON_OUTPOSTS]].''' @@ -11573,9 +13004,11 @@ BLD_CONC_CAMP Concentration Camps BLD_CONC_CAMP_DESC -'''Decreases the planet's population at a rate of 3 per turn, increases target [[metertype METER_INDUSTRY]] by 5 times the planet's population and sets the target [[metertype METER_HAPPINESS]] to 0. +'''Decreases the planet's [[metertype METER_POPULATION]] at a rate of 3 per turn, increases [[metertype METER_TARGET_INDUSTRY]] by 2 times the planet's [[metertype METER_POPULATION]] and sets the [[metertype METER_TARGET_HAPPINESS]] to 0. + +If removed, leaves a [[buildingtype BLD_CONC_CAMP_REMNANT]] for several more turns. -Ridding a planet of an undesirable species allows the introduction of a more suited species. By creating a planet-wide network of concentration camps designed to work the citizens to death, this goal can be achieved quickly and efficiently. This building can be built on an [[encyclopedia OUTPOSTS_TITLE]].''' +Ridding a planet of an undesirable species allows the introduction of a more suited species. By creating a planet-wide network of concentration camps designed to work the citizens to death, this goal can be achieved quickly and efficiently.''' BLD_CONC_CAMP_REMNANT Concentration Camp Remnants @@ -11587,7 +13020,7 @@ BLD_BLACK_HOLE_POW_GEN Black Hole Power Generator BLD_BLACK_HOLE_POW_GEN_DESC -'''Increases [[metertype METER_INDUSTRY]] on all [[metertype METER_SUPPLY]] line connected planets with the Industry focus by 1 per Population. +'''Increases [[metertype METER_TARGET_INDUSTRY]] on all [[metertype METER_SUPPLY]] line connected planets with the Industry focus by 1 per [[metertype METER_POPULATION]]. [[NO_STACK_SUPPLY_CONNECTION_TEXT]] [[BUILDING_AVAILABLE_ON_OUTPOSTS]], but must be in a [[STAR_BLACK]] system.''' @@ -11596,7 +13029,7 @@ BLD_PLANET_DRIVE Planetary Starlane Drive BLD_PLANET_DRIVE_DESC -'''Allows a planet to move between star systems. An [[buildingtype BLD_LIGHTHOUSE]] is required within 200 uu of the target system to allow the planet to proceed safely. Otherwise, the planet will have a 50% chance of being destroyed in transit. This is a hazardous journey even if the planet survives however, and only half the population of a planet will live even with support from the interstellar lighthouse. [[BUILDING_AVAILABLE_ON_OUTPOSTS]], but cannot be used unless the planet is colonised. +'''Allows a planet to move between star systems. An [[buildingtype BLD_LIGHTHOUSE]] is required within 200 uu of the target system to allow the planet to proceed safely. Otherwise, the planet will have a 50% chance of being destroyed in transit. This is a hazardous journey even if the planet survives however, and only half the [[metertype METER_POPULATION]] of a planet will live even with support from the Interstellar Lighthouse. [[BUILDING_AVAILABLE_ON_OUTPOSTS]], but cannot be used unless the planet is colonised. Due to a lack of UI targeting, either a [[buildingtype BLD_PLANET_BEACON]] or a ship equipped with a [[shippart SP_PLANET_BEACON]] currently required on the other end of the starlane. This building will destroy itself immediately, so that the planet can move to a different target system later. @@ -11676,7 +13109,7 @@ BLD_GAS_GIANT_GEN Gas Giant Generator BLD_GAS_GIANT_GEN_DESC -'''This building can only be constructed at a [[PT_GASGIANT]] and gives a +10 [[metertype METER_INDUSTRY]] bonus to planets with the Industry focus in the same system. +'''This building can only be constructed at a [[PT_GASGIANT]] and gives a +10 [[metertype METER_INDUSTRY]] bonus to planets with the Industry focus in the same system. If the [[BLD_GAS_GIANT_GEN]] is built on an inhabited gas giant it gives only a +5 [[metertype METER_INDUSTRY]] bonus. Multiple copies in the same system do not stack. A power generator designed to harvest the energy of a [[PT_GASGIANT]].''' @@ -11697,7 +13130,7 @@ BLD_XENORESURRECTION_LAB Xenoresurrection Lab BLD_XENORESURRECTION_LAB_DESC -The Xenoresurrection Lab restores living samples of extinct species, allowing colonization of [[metertype METER_SUPPLY]] connected planets with the extinct species found in [[special ANCIENT_RUINS_SPECIAL]]. It can only be build on planets where the excavations of [[special ANCIENT_RUINS_SPECIAL]] have found well preserved bodies of an extinct species. +The Xenoresurrection Lab restores living samples of extinct species, allowing colonization of [[metertype METER_SUPPLY]] connected planets with the extinct species found in [[special ANCIENT_RUINS_SPECIAL]]. It can only be built on planets where the excavations of [[special ANCIENT_RUINS_SPECIAL]] have found well preserved bodies of an extinct species. BLD_COLONY_BASE Colony Base @@ -11777,6 +13210,12 @@ Furthest Colony BLD_COL_FURTHEST_DESC [[BLD_COL_PART_1]] [[species SP_FURTHEST]] colony. [[BLD_COL_PART_2]] Furthest colony [[BLD_COL_PART_3]]. +BLD_COL_FULVER +Fulver Colony + +BLD_COL_FULVER_DESC +[[BLD_COL_PART_1]] [[species SP_FULVER]] colony. [[BLD_COL_PART_2]] Fulver colony [[BLD_COL_PART_3]]. + BLD_COL_GEORGE George Colony @@ -11867,6 +13306,12 @@ Silexian Colony BLD_COL_SILEXIAN_DESC [[BLD_COL_PART_1]] [[species SP_SILEXIAN]] colony. [[BLD_COL_PART_2]] Silexian colony [[BLD_COL_PART_3]]. +BLD_COL_SLY +Sly Colony + +BLD_COL_SLY_DESC +[[BLD_COL_PART_1]] [[species SP_SLY]] colony. [[BLD_COL_PART_2]] Sly colony [[BLD_COL_PART_3]]. + BLD_COL_SSLITH Sslith Colony @@ -11910,12 +13355,16 @@ BLD_COL_SUPER_TEST_DESC # %1% hull base starlane speed. # %2% hull base fuel capacity. -# %3% hull base starlane speed. +# %3% hull base stealth. # %4% hull base structure value. +# %5% hull slots listing HULL_DESC '''[[metertype METER_SPEED]]: %1% [[metertype METER_FUEL]]: %2% -[[metertype METER_STRUCTURE]]: %4%''' +[[metertype METER_STEALTH]]: %3% +[[metertype METER_STRUCTURE]]: %4% + +%5%''' # %1% ship part capacity (fuel, troops, colonists, fighters). PART_DESC_CAPACITY @@ -11939,11 +13388,11 @@ PART_DESC_DIRECT_FIRE_STATS '''Shot [[encyclopedia DAMAGE_TITLE]]: %1% Shots per Attack: %2%''' -# %1% ship part damage done (fighters). -# %2% number of deployable fighters. +# %1% number of deployable fighters. +# %2% ship part damage done (fighters). PART_DESC_HANGAR_STATS -'''Shot [[encyclopedia DAMAGE_TITLE]]: %2% -Fighter Capacity: %1%''' +'''Fighter Capacity: %1% +Shot [[encyclopedia DAMAGE_TITLE]]: %2%''' ## @@ -11960,10 +13409,13 @@ FT_BAY_1 Launch Bay FT_BAY_1_DESC -Launch system for fighters. +Launch system for fighters. Launches [[FT_HANGAR_1_FIGHTER]] faster: Can launch all fighters in a single [[shippart FT_HANGAR_1]]. FT_HANGAR_0 -Hangar 0 +Decoy Hangar + +FT_HANGAR_0_FIGHTER +Decoy FT_HANGAR_0_DESC Storage system for unarmed decoy craft. @@ -11971,44 +13423,56 @@ Storage system for unarmed decoy craft. FT_HANGAR_1 Interceptor Hangar +FT_HANGAR_1_FIGHTER +Interceptor + FT_HANGAR_1_DESC -Storage system for minimally armed fighters. +Storage system for minimally armed fighters. Can attack enemy fighters only. Launches faster: A [[shippart FT_BAY_1]] can launch all fighters in a single [[shippart FT_HANGAR_1]]. FT_HANGAR_2 -Fighter Hangar +Strike Fighter Hangar + +FT_HANGAR_2_FIGHTER +Strike Fighter FT_HANGAR_2_DESC -Storage system for lightly armed fighters. +Storage system for lightly armed fighters. Can attack enemy ships and fighters. FT_HANGAR_3 Bomber Hangar +FT_HANGAR_3_FIGHTER +Bomber + FT_HANGAR_3_DESC -Storage system for moderately armed fighters. +Storage system for moderately armed fighters. Can attack enemy ships only. FT_HANGAR_4 -Hangar 4 +Heavy-Bomber Hangar + +FT_HANGAR_4_FIGHTER +Heavy-Bomber FT_HANGAR_4_DESC -Storage system for heavily armed fighters. +Storage system for heavily armed fighters. Can attack enemy ships and planets. -FT_KRILL +FT_HANGAR_KRILL Krill Swarm -FT_KRILL_DESC +FT_HANGAR_KRILL_DESC Krill Swarm. FT_BAY_KRILL Krill Swarm FT_BAY_KRILL_DESC -When Krill swarms are big enough, single Krills leave the swarm to attack ships the swarm encounters. +When Krill swarms are big enough, single Krill leave the swarm to attack ships the swarm encounters. SR_WEAPON_0_1 Flak Cannon SR_WEAPON_0_1_DESC -'''The flak cannon does negligible damage to most ships, but is inexpensive and effective at destroying fighters. +'''The flak cannon is inexpensive and effective at destroying fighters, but does not target ships or planets. Each flak cannon on a ship built by a species with the piloting trait will have an additional number of shots equal to 1 per piloting level (-1 shot for Bad Pilots).''' @@ -12016,7 +13480,7 @@ SR_WEAPON_1_1 Mass Driver SR_WEAPON_1_1_DESC -'''The Mass Driver, a basic weapon. +'''The Mass Driver, a basic weapon. Attacks ships and planets. Upgrading the weapon technology will increase the shot damage. @@ -12026,18 +13490,17 @@ SR_WEAPON_2_1 Laser Weapon SR_WEAPON_2_1_DESC -'''The Laser, a more powerful ship's weapon than the Mass Driver. +'''The Laser, a more powerful ship's weapon than the Mass Driver. Attacks ships and planets. Upgrading the weapon technology will increase the shot damage. -The weapon's [[encyclopedia DAMAGE_TITLE]] is a combination of the shot damage and any active modifiers. Each laser weapon on a ship built by a species with the piloting trait will do an additional 2 damage per piloting level (-2 for Bad Pilots). Laser weapon damage can also be enhanced on [[shiphull SH_ORGANIC]] ships with a [[shippart SP_SOLAR_CONCENTRATOR]]. -''' +The weapon's [[encyclopedia DAMAGE_TITLE]] is a combination of the shot damage and any active modifiers. Each laser weapon on a ship built by a species with the piloting trait will do an additional 2 damage per piloting level (-2 for Bad Pilots). Laser weapon damage can also be enhanced on [[shiphull SH_ORGANIC]] ships with a [[shippart SP_SOLAR_CONCENTRATOR]].''' SR_WEAPON_3_1 Plasma Cannons SR_WEAPON_3_1_DESC -'''The Plasma Cannon, a more powerful ship's weapon than the Laser. +'''The Plasma Cannon, a more powerful ship's weapon than the Laser. Attacks ships and planets. Upgrading the weapon technology will increase the shot damage. @@ -12047,17 +13510,29 @@ SR_WEAPON_4_1 Death Ray SR_WEAPON_4_1_DESC -'''The Death Ray, a more powerful ship's weapon than the Plasma Cannon. +'''The Death Ray, a more powerful ship's weapon than the Plasma Cannon. Attacks ships and planets. Upgrading the weapon technology will increase the shot damage. The weapon's [[encyclopedia DAMAGE_TITLE]] is a combination of the shot damage and any active modifiers. Each death ray on a ship built by a species with the piloting trait will do an additional 5 damage per piloting level (-5 for Bad Pilots).''' +SR_ARC_DISRUPTOR +Arc Disruptor + +SR_ARC_DISRUPTOR_DESC +'''The Arc Disruptor, a multi-shot ship weapon which has good long-term upgrade research options. + +Charged polaron capacitors generate a molecular disruption flux wave that arcs through space hitting several ships before dispersing. The disruption flux can be substantially deflected around ship shields. + +Upgrading the weapon technology will increase the shot damage. + +The weapon's [[encyclopedia DAMAGE_TITLE]] is a combination of the shot damage and any active modifiers. Piloting trait does not affect damage.''' + SR_SPINAL_ANTIMATTER Spinal Antimatter Cannon SR_SPINAL_ANTIMATTER_DESC -A huge spinal mount mass driver firing heavy projectiles made of antimatter. +A huge spinal mount mass driver firing heavy projectiles made of antimatter. Attacks ships and planets. SR_JAWS Jaws @@ -12248,19 +13723,7 @@ FU_BASIC_TANK Extra Fuel Tank FU_BASIC_TANK_DESC -Increases [[metertype METER_FUEL]] capacity, allowing an additional jump. - -FU_DEUTERIUM_TANK -Deuterium Tank - -FU_DEUTERIUM_TANK_DESC -Small increase to ship range by increasing [[metertype METER_FUEL]] capacity. Bulky and vulnerable to weapons fire. - -FU_ANTIMATTER_TANK -Antimatter Tank - -FU_ANTIMATTER_TANK_DESC -Moderate increase to ship range by increasing [[metertype METER_FUEL]] capacity. Compact and low mass. +Increases [[metertype METER_FUEL]] capacity, allowing additional jumps depending on [[encyclopedia FUEL_EFFICIENCY_TITLE]]. Later technologies further increase the number of additional jumps per tank. FU_ZERO_FUEL Zero-Point Fuel Generator @@ -12423,7 +13886,7 @@ CO_OUTPOST_POD Outpost Module CO_OUTPOST_POD_DESC -'''[[encyclopedia OUTPOSTS_TITLE]] modules allow you to establish unmanned station. These may be place on uninhabitable planets. They have no population, and normally produce no resources. But they provide vision create [[metertype METER_SUPPLY]] lines when the right tech is researched. Outposts can be upgraded to colonies. +'''[[encyclopedia OUTPOSTS_TITLE]] modules allow unmanned stations to be established. These may be placed on uninhabitable planets. They have no [[metertype METER_POPULATION]], and normally produce no resources, but they do provide vision and create [[metertype METER_SUPPLY]] lines when the right tech is researched. Outposts can be upgraded to colonies. [[COLONY_SHIP_PARTS_UPKEEP_COST]]''' @@ -12519,7 +13982,7 @@ SP_CHAOS_WAVE Chaos Wave SP_CHAOS_WAVE_DESC -A powerful weapon [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_ANY_POP]]. WARNING: Also kills [[special GAIA_SPECIAL]] specials. +A powerful weapon [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_ANY_POP]]. WARNING: Also kills [[special GAIA_SPECIAL]] specials. SP_NOVA_BOMB Nova Bomb @@ -12531,7 +13994,7 @@ SP_KRILL_SPAWNER Krill Spawner SP_KRILL_SPAWNER_DESC -Spawns [[predefinedshipdesign SM_KRILL_1]] in systems with unowned [[PT_ASTEROIDS]] which do not already contain a krill monster. When carried on an unarmed ship, the [[SP_KRILL_SPAWNER]] also provides substantial [[metertype METER_STEALTH]] (but will not stack with any other [[metertype METER_STEALTH]] - causing parts). +Spawns [[predefinedshipdesign SM_KRILL_1]] in systems with unowned [[PT_ASTEROIDS]] which do not already contain a krill monster. When carried on an unarmed ship, the [[SP_KRILL_SPAWNER]] also provides substantial [[metertype METER_STEALTH]] (but will not stack with any other [[metertype METER_STEALTH]] - causing parts). SP_SOLAR_CONCENTRATOR Solar Concentrator @@ -12608,6 +14071,18 @@ SH_SPATIAL_FLUX_DESC [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]]''' +SH_SPACE_FLUX_BUBBLE +Spatial Flux Bubble Hull + +SH_SPACE_FLUX_BUBBLE_DESC +'''This tiny hull is capable of decent speed over long distances due to the power of the [[tech SHP_SPACE_FLUX_BUBBLE]]. It has only one internal slots however, and its max [[metertype METER_STRUCTURE]] is low. + +[[metertype METER_STEALTH]] starts at 15, is increased by 10 when passive, but reduced by 30 on any turn it remains in transit. Technology advances such as [[tech SPY_STEALTH_PART_1]] allow crews to increase the stealth of the craft by 10 each. + +[[metertype METER_DETECTION]] is medium and [[metertype METER_SPEED]] is average. + +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]]''' + SH_SELF_GRAVITATING Self-Gravitating Hull @@ -12662,7 +14137,7 @@ SH_ASTEROID Asteroid Hull SH_ASTEROID_DESC -'''This hull is constructed from an average-sized asteroid and it is fairly inexpensive to build. A bit roomier than the [[shiphull SH_STANDARD]], it has four external slots and two internal slots. +'''This hull is constructed from an average-sized asteroid and it is fairly inexpensive to build. A bit roomier than the [[shiphull SH_BASIC_LARGE]], it has four external slots and two internal slots. [[metertype METER_STEALTH]] is medium but the hull gets a large bonus on the galaxy map when it is in a system with an asteroid belt, and in combat when it is hiding in an asteroid belt. [[metertype METER_DETECTION]] is medium and [[metertype METER_SPEED]] is low. @@ -12823,7 +14298,7 @@ Endosymbiotic Hull SH_ENDOSYMBIOTIC_DESC '''This living mono-cellular hull exists in a symbiotic relationship with its crew, suspending them in its cytoplasm and using them as organelles. -It has four external slots and three internal slots.4 +It has four external slots and three internal slots. The base hull is born with 5 [[metertype METER_STRUCTURE]], but grows an additional 15 structure over 30 turns. @@ -12936,7 +14411,7 @@ SH_BASIC_SMALL Basic Small Hull SH_BASIC_SMALL_DESC -'''A small, basic interstellar hull. It has only one external slot but can make an extra jump compared to other basic hulls. +'''A small, basic interstellar hull. It has only one external slot but is very efficient at starlane travel. [[metertype METER_STEALTH]] is medium, [[metertype METER_DETECTION]] is medium and [[metertype METER_SPEED]] is average. @@ -12952,10 +14427,10 @@ SH_BASIC_MEDIUM_DESC [[BLD_SHIPYARD_BASE_REQUIRED]]''' -SH_STANDARD +SH_BASIC_LARGE Basic Large Hull -SH_STANDARD_DESC +SH_BASIC_LARGE_DESC '''A large, basic interstellar hull, with three external slots and one internal slot. [[metertype METER_STEALTH]] is medium, [[metertype METER_DETECTION]] is medium and [[metertype METER_SPEED]] is low. @@ -12977,7 +14452,7 @@ SH_COLONY_BASE Colony Base Hull SH_COLONY_BASE_DESC -'''A hull designed to create a colony on another planet in system. It cannot move between systems or move quickly in-system and consequently has only one internal slot. +'''A hull designed to create a colony on another planet in system. It cannot move between systems or move quickly in-system and consequently has only three internal slots. [[metertype METER_STEALTH]] is medium and [[metertype METER_DETECTION]] is medium.''' @@ -13208,17 +14683,16 @@ BLD_COL_PART_3 owned by the empire, and will increase the farther away it is; the increase will be reduced by researching techs that enable faster colony ships such as engine parts and faster hulls -# Macro keys formatting for ship designs, hulls or parts: -# BLD_SHIPYARD_TYPE1_TYPE2_REQUIRED (required building type(s) owned by -# empire only in the same system) -# ANY_SYSTEM (building owned in any system by empire or ally) -# Any other key formatting is self-explanatory (e.g. LIVING_HULL_AUTO_REGEN) - ## ## Ship design/hull macros ## - -# Keys used in Predefined Ship Designs and Ship Hulls sections +## Macro keys formatting for ship designs, hulls or parts: +## BLD_SHIPYARD_TYPE1_TYPE2_REQUIRED (required building type(s) owned by +## empire only in the same system) +## ANY_SYSTEM (building owned in any system by empire or ally) +## Any other key formatting is self-explanatory (e.g. LIVING_HULL_AUTO_REGEN) +## Keys used in Predefined Ship Designs and Ship Hulls sections +## BLD_SHIPYARD_BASE_REQUIRED Can only be built at a location with a [[buildingtype BLD_SHIPYARD_BASE]]. @@ -13254,7 +14728,7 @@ BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_REQUIRED Can only be built at a location with a [[buildingtype BLD_SHIPYARD_BASE]], an [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], and a [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]]. MIN_POPULATION_THREE_REQUIRED -Can only be built at a location with a population of at least three +Can only be built at a location with a [[metertype METER_POPULATION]] of at least three BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_CELL_GRO_CHAMB_REQUIRED Can only be built at a location with a [[buildingtype BLD_SHIPYARD_BASE]], an [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], and a [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]]. @@ -13262,7 +14736,10 @@ Can only be built at a location with a [[buildingtype BLD_SHIPYARD_BASE]], an [[ BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED Can only be built at a location with a [[buildingtype BLD_SHIPYARD_BASE]], an [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], a [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] and a [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]]. -# Other than Requirements + +## +## Other than Requirements +## SHIPDESIGN_DETECTION_RESEARCH_TIPS Further [[metertype METER_RESEARCH]] aimed at improving [[metertype METER_DETECTION]] would allow for better designs. @@ -13289,8 +14766,8 @@ SHIPDESIGN_PLANET_INVASION ## ## Ship part/tech application macros ## - -# Keys used in Ship Parts and Technology Application sections +## Keys used in Ship Parts and Technology Application sections +## BLD_SHIPYARD_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED_ANY_SYSTEM This part can only be built if there is a [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] and a [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] somewhere in the empire or an ally's empire. @@ -13311,7 +14788,7 @@ NO_STACK_SHIELDS_SHIP_PARTS [[metertype METER_SHIELD]] reduce [[encyclopedia DAMAGE_TITLE]] sustained from each hit by their Shield Strength. There can only be one active shield generator on a ship, so shield parts do not stack. COLONY_SHIP_PARTS_MIN_POP -All Colony class ship parts require the planet at which they are built to have a population of at least three. +All Colony class ship parts require the planet at which they are built to have a [[metertype METER_POPULATION]] of at least three. COLONY_SHIP_PARTS_UPKEEP_COST The cost of this part increases as the empire expands to reflect the upkeep costs of a large empire. @@ -13327,19 +14804,19 @@ SHIP_WEAPON_QUICKLY_REDUCE which allows ships to quickly reduce ENEMY_PLANET_ORGANIC_POP -the Organic population of enemy planets +the Organic [[metertype METER_POPULATION]] of enemy planets ENEMY_PLANET_ROBOTIC_POP -the Robotic population of enemy planets +the Robotic [[metertype METER_POPULATION]] of enemy planets ENEMY_PLANET_LITHIC_POP -the Lithic population of enemy planets +the Lithic [[metertype METER_POPULATION]] of enemy planets ENEMY_PLANET_PHOTOTROPHIC_POP -the Phototrophic population of enemy planets +the Phototrophic [[metertype METER_POPULATION]] of enemy planets ENEMY_PLANET_ANY_POP -any population of enemy planets +any [[metertype METER_POPULATION]] of enemy planets ## @@ -13364,7 +14841,7 @@ GROWTH_SPECIAL_POPULATION_LITHIC_INCREASE [[encyclopedia LITHIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] [[LITHIC_SPECIES_TITLE]]. GROWTH_SPECIAL_POPULATION_INCREASE -'''species inhabiting this planet will have the maximum population boosted by planet size: +'''species inhabiting this planet will have the [[metertype METER_TARGET_POPULATION]] boosted by planet size: • [[SZ_TINY]] (+1) • [[SZ_SMALL]] (+2) • [[SZ_MEDIUM]] (+3) @@ -13375,10 +14852,10 @@ regardless of planetary [[encyclopedia ENVIRONMENT_TITLE]]. If this planet is set to the [[encyclopedia GROWTH_FOCUS_TITLE]], the bonus is applied to all [[metertype METER_SUPPLY]] connected worlds inhabited by species with''' GROWTH_SPECIAL_INDUSTRY_BOOST -If this planet is set to the Industry Focus, target [[metertype METER_INDUSTRY]] is increased by 0.2 per Population. +If this planet is set to the Industry Focus, [[metertype METER_TARGET_INDUSTRY]] is increased by 0.2 per [[metertype METER_POPULATION]]. GROWTH_SPECIALS_ENTRY_LIST -'''Growth Special planets focused on growth only help a single classification of species. Some specials function for organic species only, some for lithic species only, and some for robotic species only. +'''Some specials function for organic species only, some for lithic species only, and some for robotic species only; no other [[encyclopedia METABOLISM_TITLE]] currently has applicable Growth Specials. [[encyclopedia ORGANIC_SPECIES_TITLE]] specials: [[special FRUIT_SPECIAL]] | [[special SPICE_SPECIAL]] | [[special PROBIOTIC_SPECIAL]] @@ -13452,7 +14929,7 @@ ULTIMATE_DEFENSE_TROOPS_DESC +++ Ultimate Defensive Ground [[metertype METER_TROOPS]]: 300% ANCIENT_DEFENSE_TROOPS_DESC -+++ Ancient Defensive Ground [[metertype METER_TROOPS]]: 10 per population ++++ Ancient Defensive Ground [[metertype METER_TROOPS]]: 10 per [[metertype METER_POPULATION]] NO_OFFENSE_TROOPS_DESC −−− No Offensive Ground [[metertype METER_TROOPS]] @@ -13511,18 +14988,25 @@ GREAT_WEAPONS_DESC ULTIMATE_WEAPONS_DESC +++ Ultimate Pilots: Base damage per ship weapon increased by three levels. +GASEOUS_DESC +'''+ Live on Gas Giants ++ Refuel on Gas Giants 0.1''' + BAD_POPULATION_DESC -− Bad Population 75% +− Bad [[metertype METER_POPULATION]]: 75% AVERAGE_POPULATION_DESC -''' Average Population''' +''' Average [[metertype METER_POPULATION]]: 100%''' GOOD_POPULATION_DESC -+ Good Population 125% ++ Good [[metertype METER_POPULATION]]: 125% FIXED_LOW_POPULATION_DESC −− Maximum Population is fixed at 5 +VERY_BAD_SUPPLY_DESC +−− Very Bad [[metertype METER_SUPPLY]]: -1 + BAD_SUPPLY_DESC − Bad [[metertype METER_SUPPLY]]: no bonus @@ -13535,6 +15019,24 @@ GREAT_SUPPLY_DESC ULTIMATE_SUPPLY_DESC ++ Ultimate [[metertype METER_SUPPLY]]: +3 +NO_STOCKPILE_DESC +−− No [[metertype METER_STOCKPILE]] + +BAD_STOCKPILE_DESC +− Bad [[metertype METER_STOCKPILE]]: +0.01 per [[metertype METER_POPULATION]] + +AVERAGE_STOCKPILE_DESC +''' Average [[metertype METER_STOCKPILE]]: +0.02 per [[metertype METER_POPULATION]]''' + +GOOD_STOCKPILE_DESC ++ Good [[metertype METER_STOCKPILE]]: +0.06 per [[metertype METER_POPULATION]] + +GREAT_STOCKPILE_DESC +++ Great [[metertype METER_STOCKPILE]]: +0.2 per [[metertype METER_POPULATION]] + +ULTIMATE_STOCKPILE_DESC ++++ Ultimate [[metertype METER_STOCKPILE]]: +0.3 per [[metertype METER_POPULATION]] + GOOD_SHIP_SHIELD_DESC + Good ship [[metertype METER_SHIELD]]: +1 @@ -13559,20 +15061,35 @@ FAST_COLONIZATION_DESC SLOW_COLONIZATION_DESC − Slow Colonization: +20% time to build colonies +NO_FUEL_DESC +−−− Ships have zero [[metertype METER_FUEL]] + BAD_FUEL_DESC -− Bad maximum [[metertype METER_FUEL]]: - 1 +− Bad Maximum [[metertype METER_FUEL]]: -0.5 + +AVERAGE_FUEL_DESC +''' Average Maximum [[metertype METER_FUEL]]: +0''' + +GOOD_FUEL_DESC ++ Good Maximum [[metertype METER_FUEL]]: +0.5 + +GREAT_FUEL_DESC +++ Great Maximum [[metertype METER_FUEL]]: +1 + +ULTIMATE_FUEL_DESC ++++ Ultimate Maximum [[metertype METER_FUEL]]: +1.5 GREAT_ASTEROID_INDUSTRY_DESC -+ Good Asteroid Miners: + 0.2 [[metertype METER_INDUSTRY]] per population when Industry Focused in systems with owned asteroid belts. ++ Good Asteroid Miners: + 0.2 [[metertype METER_INDUSTRY]] per [[metertype METER_POPULATION]] when Industry Focused in systems with owned asteroid belts. LIGHT_SENSITIVE_DESC -− Light Sensitive: Population is reduced in systems with [[STAR_BLUE]] and to a lesser extend [[STAR_WHITE]] stars. +− Light Sensitive: [[metertype METER_POPULATION]] is reduced in systems with [[STAR_BLUE]] and to a lesser extend [[STAR_WHITE]] stars. TELEPATHIC_DETECTION_DESC + Telepathic Detection: can sense nearby populated planets. COMMUNAL_VISION_DESC - Communal Vision: shares visibility within the same species. +''' Communal Vision: shares visibility within the same species.''' ## @@ -13642,12 +15159,12 @@ Homeworld # %1% FIXME # %2% FIXME BAD_POPULATION_LABEL -%2% Bad Population 75%% +%2% Bad Population # %1% FIXME # %2% FIXME GOOD_POPULATION_LABEL -%2% Good Population 125%% +%2% Good Population # %1% FIXME # %2% FIXME @@ -13699,6 +15216,11 @@ GREAT_RESEARCH_LABEL ULTIMATE_RESEARCH_LABEL %2% Ultimate Research +# %1% FIXME +# %2% FIXME +VERY_BAD_SUPPLY_LABEL +%2% Very Bad Supply + # %1% FIXME # %2% FIXME BAD_SUPPLY_LABEL @@ -13719,6 +15241,61 @@ GREAT_SUPPLY_LABEL ULTIMATE_SUPPLY_LABEL %2% Ultimate Supply +# %1% FIXME +# %2% FIXME +BAD_STOCKPILE_LABEL +%2% Bad Stockpiling + +# %1% FIXME +# %2% FIXME +AVERAGE_STOCKPILE_LABEL +%2% Average Stockpiling + +# %1% FIXME +# %2% FIXME +GOOD_STOCKPILE_LABEL +%2% Good Stockpiling + +# %1% FIXME +# %2% FIXME +GREAT_STOCKPILE_LABEL +%2% Great Stockpiling + +# %1% FIXME +# %2% FIXME +ULTIMATE_STOCKPILE_LABEL +%2% Ultimate Stockpiling + +# %1% FIXME +# %2% FIXME +NO_FUEL_LABEL +%2% No Fuel + +# %1% FIXME +# %2% FIXME +BAD_FUEL_LABEL +%2% Bad Maximum Fuel + +# %1% FIXME +# %2% FIXME +AVERAGE_FUEL_LABEL +%2% Average Maximum Fuel + +# %1% FIXME +# %2% FIXME +GOOD_FUEL_LABEL +%2% Good Maximum Fuel + +# %1% FIXME +# %2% FIXME +GREAT_FUEL_LABEL +%2% Great Maximum Fuel + +# %1% FIXME +# %2% FIXME +ULTIMATE_FUEL_LABEL +%2% Ultimate Maximum Fuel + # %1% FIXME # %2% FIXME BAD_TROOPS_LABEL @@ -13753,15 +15330,25 @@ Megalith OUTPOST_TROOP_LABEL Outpost +# %1% FIXME +# %2% FIXME +NATIVE_PLANETARY_DETECTION_LABEL +%2% Native Planetary Detection + # %1% FIXME # %2% FIXME NATIVE_PLANETARY_DEFENSE_LABEL -%2% Planetary Defense +%2% Native Planetary Defense # %1% FIXME # %2% FIXME NATIVE_PLANETARY_SHIELDS_LABEL -%2% Planetary Shields +%2% Native Planetary Shields + +# %1% FIXME +# %2% FIXME +NATIVE_PLANETARY_TROOPS_LABEL +%2% Native Planetary Troops VERY_BRIGHT_STAR Very bright star @@ -13799,9 +15386,27 @@ Homeworld CAPITAL_LABEL Capital +BLD_STOCKPILING_CENTER_LABEL +Imperial Entanglement Center + CONCENTRATION_CAMPS_LABEL Concentration Camps +GENERIC_SUPPLIES_FIXED_BONUS_LABEL +[[PRO_GENERIC_SUPPLIES]] fixed bonus + +GENERIC_SUPPLIES_FOCUS_BONUS_LABEL +[[PRO_GENERIC_SUPPLIES]] focus-based bonus + +GENERIC_SUPPLIES_POPULATION_BONUS_LABEL +[[PRO_GENERIC_SUPPLIES]] population-based bonus + +INTERSTELLAR_ENTANGLEMENT_FACTORY_POPULATION_BONUS_LABEL +Interstellar Entanglement Factory population based bonus + +INTERSTELLAR_ENTANGLEMENT_FACTORY_FIXED_BONUS_LABEL +Interstellar Entanglement Factory fixed bonus + ORBITAL_HAB_LABEL Orbital Habitation @@ -13826,6 +15431,12 @@ Excellent Vision FOCUS_PROTECTION_LABEL Protection Focus +GASEOUS_LABEL +Gas Giant Environment + +MINDLESS_LABEL +Mindless Species + XENOPHOBIC_LABEL_SELF Xenophobic Frenzy (other species nearby) @@ -13865,15 +15476,64 @@ Cloak Cross-Interference TRANSPATIAL_CLOAK_INTERACTION Transpatial Drive - Cloak Interaction +FUEL_TITLE +Fuel + +FUEL_EFFICIENCY_TITLE +Fuel Efficiency + +FUEL_REFUEL_EFFICIENCY_TEXT +'''[[metertype METER_FUEL]] allows ships to travel the starlanes outside of [[metertype METER_SUPPLY]] of your empire. Fuel is consumed for each starlane traversed outside of supply; stopping within [[metertype METER_SUPPLY]] lines will fully refuel a ship. If a ship remains stationary beyond friendly [[metertype METER_SUPPLY]] lines, fuel will slowly regenerate. + +The base fuel capacity of a Ship Design depends on the [[encyclopedia ENC_SHIP_HULL]] used, but it can also be increased using some [[encyclopedia ENC_SHIP_PART]]s. + +Ship hulls have a great (400%), good (200%), normal (100%), or bad (60%) efficiency which determines the effectiveness of any additional fuel parts or other fuel-modifying effects. Fuel efficiency typically decreases with hull mass, so only very small ships have a high fuel efficiency and huge ships struggle outside of [[metertype METER_SUPPLY]]. The game will always show the effective fuel levels, after scaling for hull efficiency.''' + BASE_FUEL_REGEN_LABEL Stationary Fuel Regen +FUEL_EFFICIENCY_DESC +[[FUEL_EFFICIENCY_TITLE]] + +BAD_FUEL_EFFICIENCY_LABEL +Hull %2% -40%% [[encyclopedia FUEL_EFFICIENCY_TITLE]] + +AVERAGE_FUEL_EFFICIENCY_LABEL +Hull %2% +0%% [[encyclopedia FUEL_EFFICIENCY_TITLE]] + +GOOD_FUEL_EFFICIENCY_LABEL +Hull %2% +100%% [[encyclopedia FUEL_EFFICIENCY_TITLE]] + +GREAT_FUEL_EFFICIENCY_LABEL +Hull %2% +300%% [[encyclopedia FUEL_EFFICIENCY_TITLE]] + +HULL_DESC_BAD_FUEL_EFFICIENCY +[[encyclopedia FUEL_EFFICIENCY_TITLE]]: 60% + +HULL_DESC_AVERAGE_FUEL_EFFICIENCY +[[encyclopedia FUEL_EFFICIENCY_TITLE]]: 100% + +HULL_DESC_GOOD_FUEL_EFFICIENCY +[[encyclopedia FUEL_EFFICIENCY_TITLE]]: 200% + +HULL_DESC_GREAT_FUEL_EFFICIENCY +[[encyclopedia FUEL_EFFICIENCY_TITLE]]: 400% + +MAX_FUEL_LESS_THAN_ONE_DESC +Refueling Impossible + +MAX_FUEL_LESS_THAN_ONE_LABEL +Fuel Tank Too Small for Refuel (Max Fuel < 1.0) + SPATIAL_FLUX_MALUS Flux Interference SPATIAL_FLUX_BONUS Flux Drive Bonus +DYING_POPULATION_LABEL +Dying Population + ## ## Tags @@ -13903,6 +15563,9 @@ Self-Sustaining TELEPATHIC Telepathic +GASEOUS +Gaseous + ORBITAL Orbital @@ -14025,6 +15688,42 @@ Krill Spawner ## AI diplomacy strings and lists ## +# Newline separated list of polite alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_ACKNOWLEDGEMENTS_MILD_LIST +'''Nice to hear from you again. +Receipt of transmission acknowledged. +''' + +# Newline separated list of harsh alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_ACKNOWLEDGEMENTS_HARSH_LIST +'''If you cannot sustain your own ambitions, you are unworthy of our help. +Please find another benefactor to leech sustenance from. +''' + +# Newline separated list of polite positive alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_RESPONSES_YES_MILD_LIST +'''Our empires will benefit from mutual support and collaboration. +We are honoured to work more closely together towards our mutual success. +''' + +# Newline separated list of polite negative alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_RESPONSES_NO_MILD_LIST +'''We do not believe such an agreement would be to our mutual benefit at this time. +We cannot spare the resources that such an agreement would require of us. +''' + +# Newline separated list of harsh positive alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_RESPONSES_YES_HARSH_LIST +'''We reluctantly agree to support your futile efforts. +Perhaps we will find amusement from prolonging your hopeless struggles. +''' + +# Newline separated list of harsh negative alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_RESPONSES_NO_HARSH_LIST +'''We would never agree to share our resources with you! +You are wholely unworthy of such trust and support. +''' + # Newline separated list of polite peace proposal acknowlegements. AI_PEACE_PROPOSAL_ACKNOWLEDGEMENTS_MILD_LIST '''Nice to hear from you again. @@ -14040,7 +15739,7 @@ Oh, not another pathetic sob story, please! # Newline separated list of polite positive peace proposal acknowlegements. AI_PEACE_PROPOSAL_RESPONSES_YES_MILD_LIST '''We will be happy to trust your good intentions. -Whatever you say, Boss. Everyone says you are very trustworthy. +Whatever you say, Boss. Everyone says you are very trustworthy. ''' # Newline separated list of polite negative peace proposal acknowlegements. @@ -14051,7 +15750,7 @@ We regret to inform you that we are unable to comply with that request. # Newline separated list of harsh positive peace proposal acknowlegements. AI_PEACE_PROPOSAL_RESPONSES_YES_HARSH_LIST -'''Out of pity, we will agree to not attack you. For now. +'''Out of pity, we will agree to not attack you. For now. Feeble Beings! There would be little honor in vanquishing you. ''' @@ -14075,7 +15774,7 @@ You fools did not believe this peace would last, did you? # Newline separated pre-game acknowledgements. AI_PREGAME_ACKNOWLEDGEMENTS__LIST -'''I am meditating, awaiting instructions from God. Call again later. +'''I am meditating, awaiting instructions from God. Call again later. Whispers from the Void? How can that be? ''' @@ -14216,15 +15915,24 @@ AI_FIRST_TURN_GREETING_MSG608''' OPTIONS_PAGE_HOTKEYS Keyboard shortcuts -HOTKEYS_Z_COMBAT -Combat window +HOTKEYS_UI +User Interface -HOTKEYS_MAP +HOTKEYS_UI_MAP Map window +HOTKEYS_UI_MAP_FLEET +Fleets + +HOTKEYS_UI_MAP_SYSTEM +Systems + HOTKEYS_GENERAL General shortcuts +HOTKEYS_VIDEO +Video + HOTKEY_MAP_OPEN_CHAT Open chat window @@ -14336,6 +16044,9 @@ Select All HOTKEY_DESELECT Deselect +HOTKEY_AUTOCOMPLETE +Autocomplete Typed Word + HOTKEY_FOCUS_PREV_WND Previous Control @@ -14672,6 +16383,7 @@ Cor Cormack Corona Correctos +Coruscant Crawford Crius Crow @@ -14949,6 +16661,7 @@ Icarius Idril Idun Ike +Ileenium Iliad Ilios Ilium @@ -14957,6 +16670,7 @@ Imlach Imra Incedius Ingwaz +Interrobang Inti Inupiaq Ioram @@ -15099,6 +16813,7 @@ Lupi Lurga Lusitania Lyae +Lylat Lynx Lyonesse Lyot @@ -15510,6 +17225,7 @@ Skadi Skaro Skat Slipher +Slnko Slunce Smeg Soare @@ -17658,9 +19374,15 @@ SHIP_NAME_DEVS Aquataine Big Joe Cami +Cjkjvfnby +Dbenage-CX Dilvish Eleazar Geoff the Medio +LGM-Doyle +Morlic +O01eg +Ophiuchusagrrr PD Tyreth Tzlaine @@ -17692,6 +19414,5 @@ MONSTER_NAMES [[SHIP_NAME_UNSORTED_MYTHOLOGY]] [[SHIP_NAME_DEVS]]''' -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/es.txt b/default/stringtables/es.txt index 789a9b7bdf8..42a3eed9440 100644 --- a/default/stringtables/es.txt +++ b/default/stringtables/es.txt @@ -107,36 +107,12 @@ Corbeta I SD_SMALL_MARK1_DESC Pequeña y barata nave con catapulta electromagnéticap. -SD_SMALL_MARK_2 -Corbeta II - -SD_SMALL_MARK2_DESC -Pequeña y barata nave con catapulta electromagnéticap. - SD_MARK_1 Fragata I SD_MARK1_DESC Asequible fragata con catapulta electromagnética. -SD_MARK_2 -Fragata II - -SD_MARK2_DESC -Fragata mejorada con catapulta electromagnética. - -SD_MARK_3 -Fragata III - -SD_MARK3_DESC -Asequible fragata con laser. - -SD_MARK_4 -Fragata IV - -SD_MARK4_DESC -Fragata mejorada con laser. - SD_LARGE_MARK_1 Crucero I @@ -196,11 +172,6 @@ Flota de Batalla ## Status update messages ## -SERVER_WONT_START -'''El servidor no ha podido ser lanzado. - -Nota para usuarios de GNU/Linux: Se supone que el ejecutable del servidor esta en el directorio de trabajo. Si freeorion fue ejecutado desde otro lugar que no fuese el directorio donde se encuentra el binario, necesitaras salir y reiniciar desde ese directorio.''' - SERVER_TIMEOUT El servidor no responde @@ -236,9 +207,6 @@ Uso: COMMAND_LINE_DEFAULT Por defecto: -OPTIONS_DB_HELP -Imprime este mensaje de ayuda. - OPTIONS_DB_GENERATE_CONFIG_XML Usa todas las configuraciones a partir de cualquier fichero config.xml existente y las que se den por la terminal, para generar un fichero config.xml. Esto sobreescribe el actual config.xml, si existe. @@ -506,24 +474,6 @@ Establece el tamaño de los planetas mas pequeños en el panel lateral. OPTIONS_DB_UI_SIDEPANEL_PLANET_SHOWN Establece si se muestran renderizados o no los planetas, asteroides en el panel lateral. -OPTIONS_DB_GAMESETUP_STARS -Número de estrellas en la galaxia generadas. - -OPTIONS_DB_GAMESETUP_GALAXY_SHAPE -La forma de la galaxia que se generará. - -OPTIONS_DB_GAMESETUP_GALAXY_AGE -La edad de la galaxia que se generará. - -OPTIONS_DB_GAMESETUP_PLANET_DENSITY -El número de planetas por sistema que sera generado. - -OPTIONS_DB_GAMESETUP_STARLANE_FREQUENCY -El número de líneas estelares en la galaxia para ser generado. - -OPTIONS_DB_GAMESETUP_SPECIALS_FREQUENCY -La frecuencia de especiales que aparecen en la galaxia para ser generados. - OPTIONS_DB_GAMESETUP_EMPIRE_NAME El nombre usado en juego por tu imperio. @@ -533,12 +483,6 @@ El nombre usado en el juego para representar al jugador en el modo solitario. OPTIONS_DB_GAMESETUP_EMPIRE_COLOR El color usado en juego por tu imperio. -OPTIONS_DB_GAMESETUP_STARTING_SPECIES_NAME -La especie en tu mundo origen al inicio del juego. - -OPTIONS_DB_GAMESETUP_NUM_AI_PLAYERS -El número de oponentes IA que jugaran la partida. - OPTIONS_DB_UI_TECH_LAYOUT_HORZ_SPACING El espaciado horizontal a ser introducido entre tecnologías en el árbol tecnológico, en múltiplos del ancho de una sola teoría tecnológica. @@ -557,12 +501,6 @@ Establece el nivel arriba o abajo donde los mensajes de log son extraídos (nive OPTIONS_DB_STRINGTABLE_FILENAME Establece el archivo de tablas de cadenas de idioma. -OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER -Si es verdadero, auto guardar ocurrirá durante los juegos en solitario. - -OPTIONS_DB_AUTOSAVE_MULTIPLAYER -Si es verdadero, auto guardar ocurrirá durante los juegos multijugador. - OPTIONS_DB_AUTOSAVE_TURNS Establece el número de turnos que pasarán entre auto guardados. @@ -572,9 +510,6 @@ El volumen (de 0 a 255) en el que la música se reproduce. OPTIONS_DB_QUICKSTART Comienza un nuevo juego rápido, sin pasar por el menu. -OPTIONS_DB_CHECKED_GL_VERSION -Comprueba si la versión OpenGL del sistema ha sido revisada. Si es falso, algunas opciones de renderizado pueden ser alteradas dependiendo de la version GL, por lo que esta opción deberia estar activada. - OPTIONS_DB_LOAD Carga la partida guardada de un solo jugador especificada. @@ -869,12 +804,6 @@ Cierre rápido de ventanas OPTIONS_MISC_UI Ajustes Misceláneos UI -OPTIONS_SINGLEPLAYER -Un jugador - -OPTIONS_MULTIPLAYER -Multijugador - OPTIONS_AUTOSAVE_TURNS_BETWEEN Turnos entre guardados automáticos @@ -1725,37 +1654,17 @@ El imperio %empire% ha sido eliminado. ## Species ## -SP_GYSACHE -GYSACHE - -SP_GYSACHE_DESC -Cobardes Herbivoros. - -SP_TRITH_DESC -Desconsolados xenófobos telépatas. - ## ## Specials ## -GAIA_SPECIAL_DESC -'''Este planeta ha sido esencialmente transformado en un organismo viviente, permitiendo que cambie y se adapte para suplir las necesidades de su población. - -Dobla la agricultura objetivo del planeta, salud y población.''' - ANCIENT_RUINS_DEPLETED_SPECIAL Ruinas Antiguas -ANCIENT_RUINS_DEPLETED_SPECIAL_DESC -Este planeta guarda las ruinas de una antigua raza avanzada, que han sido olvidados en los libros de historia, triplicando la investigación en el planeta e incrementado la investigación en mundos enfocados a través del imperio por 1/20, no acumulable. - ANCIENT_RUINS_SPECIAL Ruinas Antiguas -ANCIENT_RUINS_SPECIAL_DESCRIPTION -En este planeta se encuentran las ruinas de una antigua raza, que han sido olvidadas en los libros de historia. Proporcionan bonificación a la investigación. - ECCENTRIC_ORBIT_SPECIAL Órbita Excéntrica @@ -1767,9 +1676,6 @@ La distancia de suministro desde este planeta se reduce en 1, pero la investigac RESONANT_MOON_SPECIAL Luna Resonante -RESONANT_MOON_SPECIAL_DESC -Este planeta tiene un satelite natural, cuyo periodo orbital esta en resonancia 1:1 con su periodo rotacional, causando un lado oscuro permanente que no se ve desde el planeta. Este lado oscuro es perfecto para emplazar infraestructuras clandestinas. - MINERALS_SPECIAL Rico en Minerales @@ -1968,10 +1874,6 @@ Tipo de Medidor Invalido METER_TARGET_POPULATION Población Objetivo -# Meter types -METER_TARGET_HEALTH -Salud Objetivo - # Meter types METER_TARGET_INDUSTRY Indústria Objetivo @@ -2008,10 +1910,6 @@ Defensa Máxima METER_POPULATION Población -# Meter types -METER_HEALTH -Salud - # Meter types METER_INDUSTRY Indústria @@ -2504,33 +2402,18 @@ Las estructuras y funciones del cerebro son determinadas. La electroquímica y n LRN_ALGO_ELEGANCE Algoritmia elegante -LRN_ALGO_ELEGANCE_DESC -Con cada vez mayor dificultad para el análisis de datos, las medidas tradicionales de la eficiencia de los algoritmos se volvieron inútiles debido a la irreductible complejidad. En ese punto, otras mediciones de las formas y funciones de los algoritmos cobran significado, estética y metafóricamente, la elegancia de la solución debe ser optimizada. - LRN_TRANSLING_THT Pensamiento Translingüístico -LRN_TRANSLING_THT_DESC -Las mentes inferiores luchan, o aceptan los limites del lenguaje que han aprendido. Las mentes adecuadas se limitan y se sienten obligadas por los conceptos que les han dado para expresarse. Las autenticas mentes superiores superan libremente los limites del lenguaje, formando y analizando pensamientos que rondan lo trascendental. Pero si las grandes mentes son aisladas y futiles, sin un lenguaje con el que expresar sus pensamientos, no podrán compartir su interior. - LRN_PSIONICS Psiónicos -LRN_PSIONICS_DESC -Mediante introspección o mejoras artificiales, el cerebro puede desarrollar habilidades para interactuar directamente con el universo que lo rodea, sobrepasando las limitaciones de un cuerpo físico. Poderes como la telepatía, empatía, clarividencia, pre-ciencia, telequinesis y psicoenergias reemplazan o superan a la biología mundana o a las tecnologías alternativas. Las aplicaciones de estos poderes, incluyendo el control mental, alteración personal y posesión tiene profundas implicaciones para las relaciones entre personas con y sin talento. - LRN_ARTIF_MINDS Mentes Artificiales -LRN_ARTIF_MINDS_DESC -Mientras que las computadores tradicionales tienen una inteligencia insondable y habilidades de computo, se echa en falta las cualidades esenciales de supervivencia, consciencia o sensibilidad. Con el desarrollo de verdaderas mentes artificiales, esas cantidades pueden ser sintetizadas, modificadas y alteradas. Estas investigaciones abren nuevas posibilidades para la investigación en ciencias cognitivas y nuevos metaparadigmas. - LRN_XENOARCH Xenoarqueología -LRN_XENOARCH_DESC -Los imperios contemporáneos, razas y civilizaciones no son los primeros o únicos seres que han vivido en la galaxia. Restos, ruinas y rumores de los ancianos pueden ser encontrados en los planetas desérticos y asteroides sin vida, o viajando por el espacio. Encontrarlos y descifrarlos tiene un gran potencial para revelar secretos, enseñar lecciones, o avisar sobre peligros del conocimiento antiguo. - LRN_GRAVITONICS Gravitónicos @@ -2546,9 +2429,6 @@ Tiempo atrás, teorías sencillas, describían subelementos de las cuatro fuerza LRN_FORCE_FIELD Campos de Fuerza Armónicos -LRN_FORCE_FIELD_DESC -Como el análisis del sonido, electromagnetismo, fuertes y débiles fuerzas pueden ser expresadas como superposiciones armónicas cuánticas de onda estática de partículas de fuerza-carga. Amplificando selectivamente estos armónicos, las fuerzas pueden ser controladas arbitrariamente para escudos, ataques, control o soporte. - LRN_MIND_VOID Mente del Vacío @@ -2570,15 +2450,9 @@ Anteriores teóricos de supercuerdas hablan de 10, 11 ó 26 universos dimensiona LRN_UNIF_CONC Conciencia Unificada -LRN_UNIF_CONC_DESC -La comunicación telepática entre individuos o interfaces mente-máquina solo permite lo mas básico, intercambios triviales y superficiales de pensamientos e ideas. Mentes verdaderamente unidas funcionan como una única consciencia, con las habilidades y conocimiento sumadas de las partes que los constituyen y actuando como una entidad única. Esto tiene un enorme potencial, pero también implica riesgos, en que la unión no puede buscar su propia destrucción, que es necesario para las mentes originales para ser recuperadas. Alternativamente, una de las mentes puede dominar a las demás, controlando o destruyendo las otras, no formando una unión armoniosa equivalente. - LRN_QUANT_NET Red Cuántica -LRN_QUANT_NET_DESC -Cada particula esta conectada inextricablemente en el nivel cuantico de cada particula con la que previamente ha interactuado. Viejas ideas mecánicas sobre la causa y el efecto no se aplican en este nivel y las particulas pueden “comunicarse” instantaneamente a través de grandes distancias. Cuando una completa comprensión de este fenomeno es adquirido, acciones selectivas a pequeña escala a una distancia entre dos puntos cualquiera puede conseguirse instantaneamente y los límites de velocidad relativistas no se aplican en la tranferencia de información. - LRN_TRANSCEND Transcendencia de Singularidad @@ -2588,9 +2462,6 @@ LRN_TRANSCEND_DESC GRO_PLANET_ECOL Ecología Planetaria -GRO_PLANET_ECOL_DESC -La agricultura y la ciencia médica pueden incrementar drasticamente la supervivencia, causando que el crecimiento de la población sea hiperexponencial por un tiempo. A veces, nuevos límites para crecer surgen de la capacidad de almacenamiento de un planeta, y el ecosistema que lo soporta. Comprendiendo la ecología natural y su interacción a extremos permite al sistema ser sostenido, y sus beneficios cosechados por las generaciones futuras. - GRO_GENETIC_ENG Ingeniería Genética @@ -2612,27 +2483,15 @@ La ingeniería tradicional genética altera códigos genéticos e implementa o a GRO_LIFECYCLE_MAN Manipulación del Ciclo de Vida -GRO_LIFECYCLE_MAN_DESC -La mayoría de organismos complejos progresan a través de estados psicológicos durante su vida, claramente separados por metamorfosis o por el envejecimiento. En muchos casos, uno o mas de estos estados son, al menos en un tiempo dado, mas útiles y deseables que otros. Con un delicado control hormonal o mecanismos mentales, es posible acelerar o frenar cada estado, permitiendo producir individuos desarrollados y funcionales en una fracción del tiempo natural, convirtiendolos efectivamente en inmortales. - GRO_XENO_GENETICS Geneticas Xenológicas -GRO_XENO_GENETICS_DESC -Anteriores estudios genéticos se veían limitados por las muestras de organismos naturales disponibles en los que basar sus modificaciones. Así mismo, estas modificaciones están restringidas en los limites de los mecanismos físicos del código genético guardado o la alteración y expresión en individuos desarrollados. Investigando los sistemas equivalentes en ecosistemas totalmente diferentes y organismos, grandes visiones o revelaciones pueden ocurrir, estimulando nuevos desarrollos en mas sistemas familiares genéticos. - GRO_NANOTECH_MED Medicina Nanotecnológica -GRO_NANOTECH_MED_DESC -Mientras que las alteraciones genéticas pueden reparar o deshacer los efectos de mutaciones no deseadas o para corregir desajustes bioquímicos, tratar mayores traumas físicos y defectos congénitos no genéticos requiere medidas correctivas físicas. La nanotecnología puede ser usada para cumplir esas tareas, con dos ventajas importantes: La corrección se puede llevar a cabo sin cirugía invasiva o riesgos secundarios asociados y el tiempo de reacción para el tratamiento se ve enormemente reducido, ya que las herramientas para hacer el trabajo están siempre en su sitio y listas para funcionar. - GRO_XENO_HYBRIDS Hybridación Xenológica -GRO_XENO_HYBRIDS_DESC -Independientemente de lo bien que se comprenda una habilidad mejorada, un sistema biológico esta limitado por su naturaleza inherente. Las estructuras y la psicología básica solo pueden ser alteradas en los confines biológicos de un solo planeta. Con la compresión de otras biologias, los dispares sistemas pueden ser fusionados, formando una biología híbrida, con habilidades potenciales y mayores rasgos que los de cualquiera de sus partes. - GRO_NANO_CYBERNET Cibernética Nanotecnológica @@ -2648,32 +2507,15 @@ La sensibilidad tal y como la conocemos siempre ha sido vista como inextricablem GRO_ENERGY_META Metabolismo de Pura Energía -GRO_ENERGY_META_DESC -El último estado de desarrollo físico es transcender a todo lo físico. Sin cuerpo, los individuos y la sociedad pueden moverse, crecer y existir libremente, sin obstáculos por culpa de innecesarios limites físicos y procesos metabólicos ineficientes que gastan la energía que soporta la homeostasis. - PRO_MICROGRAV_MAN Manufactura Microgravitacional -PRO_MICROGRAV_MAN_DESC -La micro-gravedad de planeta de órbita baja ofrece un gran potencial para nuevos diseños de instalaciones de manufactura, equipamiento y técnicas imposibles en la superficie de los planetas. Los sólidos pueden ser mantenidos en su sitio con un soporte mínimo. Líquidos y gases flotan libres, cambiando de forma solo por la tensión de la superficie, a menos que se manipulen. El equipamiento de la fábricas puede ser organizado en 3 dimensiones. Estas condiciones permiten grandes incrementos en la eficiencia y la productividad, si son explotados adecuadamente. - PRO_ROBOTIC_PROD Producción Robótica -PRO_ROBOTIC_PROD_DESC -A pesar de diseños iniciales e instalaciones de alto nivel, la puesta en marcha todavía requiere de supervisión activa. La supresión de los operadores en los procesos actuales de producción, elimina muchos problemas. Los robots trabajan continuamente sin perpetuas demandas de mayores compensaciones económicas. Los robots tienen una mayor tolerancia a las condiciones ambientales peligrosas en fábricas y no requieren espacio habitable adicional. Los robots llevan a cabo las tares asignadas rápidamente, o por lo menos tan bien como han sido instruidos. - -PRO_EXOBOTS_DESC -'''Exobots, como se les conoce comúnmente, son una maravilla del encuentro entre forma y función. Son robots que se diseñaron para cumplir casi cualquier trabajo industrial, en campos que van desde la electrónica a la construcción a gran escala. Fáciles de producir en masa, sirven mejor en las colonias que carecen de la infraestructura y fuerza de trabajo para dedicarse a fábricas especializadas. Cada colonia recibe un equipo estándar de exobots, aumentando su construcción durante las primeras fases del desarrollo. - -Incrementa la indústria objetivo en todos los planetas con el enfoque de Indústria por un quinto de la población del planeta y la minería objetivo en todos los planetas con enfoque de Minería por 10.''' - PRO_FUSION_GEN Generación de Fusión -PRO_FUSION_GEN_DESC -Reacciones termonucleares explosivas o incontroladas son relativamente simples de causar a escala, desde cabezas tácticas nucleares hasta hornos estelares supergigantes. La generación de energía controlable, estable y práctica a partir de la reacción es algo mas difícil. El proceso sigue siendo atractivo, debido a la fuente casi ilimitada de combustible y las potenciales operaciones libres de emisiones. - PRO_NANOTECH_PROD Producción Nanotecnológica @@ -2683,33 +2525,18 @@ Las técnicas tradicionales de producción estan fundamentalmente limitadas por PRO_ORBITAL_GEN Generación Orbital -PRO_ORBITAL_GEN_DESC -Cuerpos planetarios de órbita baja proveen numerosos beneficios para la generación de energía, en comparación con la superficie planetaria. La exposición directa a la radiación solar cercana sin atenuaciones atmosféricas y los órdenes de magnitud de grandes estructuras que pueden ser ensambladas en micro-gravedad para recogerlos, son ventajas significativas. La energía resultante puede ser usada en órbita o puede ser dirigida al planeta para soportar la indústria de la superficie. Así mismo, nuevos mecanismos de conversión de energía hacen que este disponible en órbita, incluyendo el acoplamiento de los campos magnéticos planetarios, intercambios momentáneos orbitales y calientamiento por fricción con las atmósferas planetarias, que son únicamente aplicables para aplicaciones especializadas. - PRO_SENTIENT_AUTOMATION Automatización Sensible -PRO_SENTIENT_AUTOMATION_DESC -Cuando el equipamiento se automatiza, es capaz de replicarse y mantenerse por si mismo sin soporte externo, excepto por los materiales primarios necesarios y un producto final solicitado o sugerido a ser producido. Cuando son controlados por una mente artificial, el sistema entero puede aprender, planear, reaccionar y adaptarse de manera autónoma y lo puede hacer sin las tradicionales ineficiencias necesarias para albergar y emplear trabajadores. - PRO_NDIM_ASSMB Ensamblaje N-Dimensional -PRO_NDIM_ASSMB_DESC -La geometría del espacio físico siempre ha sido un impedimento mayor para ensamblar objetos en el espacio. Los objetos no pueden ser colocados y los volúmenes no pueden ser cerrados y abiertos si su existencia se limita a tres dimensiones. Sobrepasando esta limitación, partes del objeto pueden ser desplegadas de la forma en dimensiones de bolsillo y las líneas continuas se pueden pasar alrededor de otras masas “fuera de eje”. Mecanismos similares son usados para ensamblar objetos en menores dimensiones con configuraciones que podrian ser imposibles sin vías de dimensiones superiores. - PRO_SINGULAR_GEN Generación de Singuralidad -PRO_SINGULAR_GEN_DESC -Cuando la materia es atraída hacia un singularidad por la curvatura intensa del espacio tiempo o por campos gravitacionales, la materia forma un disco de acreción y es comprimido y calentado inmensamente. Radiaciones de alta energía emitidas desde el disco se pueden capturar y aprovechar para generar electricidad. El horizonte del evento de la singularidad misma también emite radiación para aniquilar los pares virtuales de partícula anti-partícula que se forman cerca. Este método de generación eléctrica es significativamente mas delicado e inestable que el usado con el disco de acreción, pero produce incluso mas producción también. - PRO_ZERO_GEN Generación Punto Cero -PRO_ZERO_GEN_DESC -Cualquier mecanismo de generación de energía que requiere suministro de combustible esta limitado por la relativa relación masa-energía: no se puede producir mas energía que la masa equivalente consumida, incluso con una eficiencia perfecta. Al hacer uso de la energía inherente del vacío o energía Punto Cero, una fuente infinita de energía efectiva es posible. El porcentaje o electricidad de esta extracción es también teóricamente ilimitada, con limites solo en los medios para recolectar y usar la producción. - PRO_NEUTRONIUM_EXTRACTION Extracción de Neutronium @@ -2725,9 +2552,6 @@ La forma de una estructura o espacio inhabitado no es meramente guiado por las l CON_ORGANIC_STRC Estructura Orgánica -CON_ORGANIC_STRC_DESC -Edificios formados por minerales inanimados son funcionales y en ocasiones elegantes y los que se realizan con formas orgánicas muertas son simples y baratas. Las estructuras vivientes actuales, pueden crecer en su forma final sin trabajo adicional, así como cierto grado de auto reparación. Pueden también auto regular naturalmente sus medio ambientes internos o potencialmente adaptándose a variados y cambiantes ambientes cuando sea necesario durante o después de su crecimiento inicial. - CON_ARCH_MONOFILS Monofilamento Arquitectural @@ -2743,21 +2567,12 @@ Una vez ciertos umbrales de rendimiento son sobrepasados, medir las propiedades CON_FRC_ENRG_STRC Estructuras Fuerza-Energía -CON_FRC_ENRG_STRC_DESC -Cuando fuerzas y energía pueden ser manipuladas como se desee para proporcionar la misma funcionalidad que las superficies sólidas, no hay una necesidad esencial para tener materiales físicos encapsulando a los ocupantes de un edificio. Las estructuras compuestas enteramente de energía tienen muchas ventajas, incluyendo la simple reconfiguración, resistencia, portabilidad y nuevas posibilidades de diseño. - CON_CONTGRAV_ARCH Arquitectura Gravitacional Controlada -CON_CONTGRAV_ARCH_DESC -Controlando integramente la fuerza y orientación de fuerzas gravitacionales que actúan sobre una estructura existente, los diseños pueden simultáneamente exhibir la libertad de la ingravidez y la utilidad de definir arriba y abajo. La estructura misma, y el medio ambiente local que busca proporcionar, puede tomar cualquier forma en el espacio, extendiendose a grandes distancias, bucle sobre si mismo y haciendo interconexiones arbitrarias. Simultáneamente, los medio ambientes locales pueden administrarse con un remolcador útil, que pueden variar de o ser completamente diferentes de áreas adyacentes. Una versatilidad como esta permite nuevos diseños funcionales para la sociedad, manufactura y otros usos. - CON_GAL_INFRA Infraestructura Galáctica -CON_GAL_INFRA_DESC -La integración y el desarrollo de las infraestructuras en mundos con gran capacidad productiva en una red galáctica unificada, es uno de los mayores esfuerzos logísticos concebibles. Una vez completo, la funcionalidad del sistema unifica los mundos que lo componen. Transporte integrado, alojamiento, mantenimiento y herramientas de construcción pueden ser desplegados de forma fluida donde sea necesario para optimizar la asignación de recursos o ajustar cambios. - CON_ART_HEAVENLY Cuerpos Celestiales Artificiales @@ -2767,15 +2582,9 @@ Una gran masa de una pequeña luna o mas grande no es suficiente para concebir l CON_TRANS_ARCH Arquitectura Transcendental -CON_TRANS_ARCH_DESC -La conclusión de diseños unificados funcionales óptimos con elegancia en la forma. Una estructura transcendente expande e inspira las mentes de sus ocupantes u observadores en su forma, lugar, substancia e integración, sea a través de la escala imponente, énfasis en una idea perfecta, percepción o la falta de la misma o absoluta simplicidad y adecuación para un fin. - CON_NDIM_STRC Estructuras N-Dimensionales -CON_NDIM_STRC_DESC -De todos los rasgos bizarros de física aplicada, la habilidad para doblar el espacio a si mismo tiene la mas directa y profunda influencia en la forma y diseño estructural. El infinito potencial, literalmente como pasillos estan hechos para doblegarse sobre sus principios o del revés para que su cielo se convierta en suelo. Así mismo, el concepto de la expansión humana se convierte en algo anecdótico, ya que cualquier número de personas puede caber en un pequeño volumen arbitrario, a buen recaudo en un plano adyacente de existencia. - SHP_GAL_EXPLO Exploración Galáctica @@ -2785,15 +2594,9 @@ El descubrimiento de los viajes interestelares mediante las líneas estelares im SHP_INTSTEL_LOG Logística Interestelar -SHP_INTSTEL_LOG_DESC -Suministrar efectivamente naves, ordenando el movimiento general de flota e incluso organizando el movimiento de naves especificas en batalla requiere de una comprensión compleja de navegación y detección asi como los métodos de adquisición de información. - SHP_MIL_ROBO_CONT Control Robótico Militar -SHP_MIL_ROBO_CONT_DESC -El control robótico puede ser usado efectivamente para tareas como la agricultura, minería e indústria; la extensión natural de este concepto es desarrollar equipamiento robótico militar capaz de operar una nave independientemente. Esto permitirá una gran versatilidad en el diseño, ya que la necesidad de aparatosos sistemas de soporte de vida serian eliminados. Los cascos producidos con esta tecnología pueden también llevar a cabo reparaciones peligrosas sin preocuparse por la seguridad y limitaciones biológicas de ingenieros vivos. - SHP_SPACE_FLUX_DRIVE Motor Espacial Fluido @@ -2821,9 +2624,6 @@ Los dispositivos tradicionales de propulsión operan en objetos de relativamente SHP_TRANSSPACE_DRIVE Motor Trans-Espacial -SHP_TRANSSPACE_DRIVE_DESC -Incluso cuando las dimensiones espaciales son sesgadas por efectos relativistas, un objeto generalmente tiene una constante en las características del espacio-tiempo. Creando un dispositivo que altere las características del espacio-tiempo mismo usando efectos casi relativistas, sería posible crear un método rápido y sigiloso. - SHP_MIDCOMB_LOG Logística en Pleno Combate @@ -2887,15 +2687,9 @@ De manera habitual, la manipulación de energía requiere que tenga forma de mat SHP_QUANT_ENRG_MAG Magnificación Energética Cuántica -SHP_QUANT_ENRG_MAG_DESC -Debido a la incertidumbre cuántica, es posible para una partícula ganar arbitrariamente una pequeña cantidad de energía o para nuevas partículas para tener una existencia temporal. Detectando y magnificando estas fluctuaciones en la energía cuántica, sería posible incrementar tremendamente las capacidades de un casco de energía. - SHP_ENRG_BOUND_MAN Manipulación de Energía Límite -SHP_ENRG_BOUND_MAN_DESC -Tanto si son construidos, crecidos o adaptados desde una estructura pre-existente, las naves compuestas de materia deben tener una estructura lógica y coordinada para permitir incluso una utilidad básica. Aunque la energía pura no tiene esas restricciones. Esto permite a un casco de energía pura tener cualquier forma, aunque inútil desde un punto de vista material. - SHP_SOLAR_CONT Contención Solar @@ -2992,9 +2786,6 @@ En imperios particularmente expansivos, la necesidad de mover naves a lugares de CON_ART_PLANET Planeta Artificial -CON_ART_PLANET_DESC -Gigantes de gas y cinturones de asteroides ofrecen una oportunidad única de recursos, no solo como sitios donde adquirir valiosos - y a veces extramadamente raros - recursos, pero como materias primas dentro y fuera de si mismas para la producción de planetas artificiales. Estos planetas ofreceran nuevos centros de producción, sistemas previamente vacios u ocupados. Además, varias mejoras clave del planeta pueden ser modificadas o añadidas durante el proceso de construcción, permitiendo potencialmente especificar el tamaño y ambiente o ciertos especiales de planetas a ser añadidos. - CON_PALACE_EXCLUDE Palacio Excluidor @@ -3040,11 +2831,6 @@ Permite la terraformación remota GRO_CYBORG Ciborgs -GRO_CYBORG_DESC -'''Una versátil fusión de organismo y máquina, capaz de adaptarse a una tremenda variedad de circunstancias. La generación espontanea de órganos especializados y fuerza mecánicamente mejorada permite a los ciborgs existir con facilidad en cualquier ambiente. - -Incrementa la población que puede ser sostenida por planetas Hostil, Pobre y Adecuado a 5, 10, 15, 20 y 25 para planetas Diminuto, Pequeño, Medio, Grande y Gigante, respectivamente. La Salud en planetas Hostil y Pobre se incrementa por 10 y la Salud de planetas Adecuado por 5.''' - GRO_GAIA_TRANS Transformación de Gaia @@ -3069,25 +2855,12 @@ Diferentes tipos de estrella no solo tienen sus propios atributos distintivos y LRN_DISTRIB_THOUGHT Red Distribuida de Computadores -LRN_DISTRIB_THOUGHT_DESC -El ciudadano medio malgasta literalmente su mente, uno de los dispositivos de computación mas finos del universo. Al hacer uso de los ciclos de inactividad del cerebro sobre una gran masa de población mediante simples transreceptores cibernéticos, los centros de investigación del mundo tienen acceso a un poder puro y duro de computación barata y abundante. - LRN_STELLAR_TOMOGRAPHY Tomografía Estelar -LRN_STELLAR_TOMOGRAPHY_DESC -'''Una red de satelites especializados puede usar métodos de tomografia para reconstruir las actividades que ocurren en lo profundo de una estrella. De otra forma las costosas condiciones para producir son ideales para una variedad de investigaciones forzosas, con grandes beneficios para tipos de estrella exóticas. Proporciona +4 a la investigación por 10 de población en mundos enfocados en investigación primaria en sistemas con agujeros negros. Proporciona +3 a planetas en sistemas con estrellas neutrón. Proporciona +2 a planetas en sistemas con estrellas blancas o azules. Proporciona +1 a planetas con estrellas rojas, naranjas o amarillas. - -Multiplica la investigación objetivo de todos los planetas con enfoque en investigación r 2. 1.75, 1.5 o 1.25 en sistemas con estrellas Agujero Negro, Neutron, Azul o Blanco y Amarillo, Naranja o Roja, respectivamente.''' - LRN_SPATIAL_DISTORT_GEN Generador de Distorsión Espacial -LRN_SPATIAL_DISTORT_GEN_DESC -'''Manipulando las propiedades dimensionales de una línea estelar, sería posible plegarla selectivamente para que una nave en camino con una velocidad suficientemente baja pueda volver por donde vino, gastando su tiempo y combustible. - -Alternativamente, la nave puede ser enviada al azar a otra línea estelar adyacente al edificio.''' - LRN_GATEWAY_VOID Puerta al Vacío @@ -3109,11 +2882,6 @@ Colapsando con cuidado una estrella roja, un Agujero Negro puede ser formado. E LRN_PSY_DOM Dominación Psicogenica -LRN_PSY_DOM_DESC -'''Es difícil forzar una conciencia individual a someterse al control mental total de otros seres contra su voluntad. Manipulando las fases temporales involucradas, la fuerza mental ejercida por una población sobre un periodo de horas puede ser interpuesto para llevar en un solo individuo en segundos, creando una casi irresistible compulsión para juntar la mente unificada. - -Tiene un 10% de probabilidad de tomar el control de cualquier nave enemiga entre un salto de línea estelar de una colonia perteneciente al imperio, teniendo en cuenta que la nave no es propiedad de un imperio que también tenga esta tecnología.''' - MIND_CONTROL_SHORT_DESC Permite el Control Mental @@ -3232,9 +3000,6 @@ Todas las colonias empiezan el juego con una detección de nivel 20 y la parte d SPY_DETECT_2 Radar Activo -SPY_DETECT_2_DESC -Desbloquea la parte de nave del Radar Activo. - SPY_DETECT_3 Escaner de Neutrones @@ -3252,9 +3017,6 @@ Desbloquea la parte de nave Sensores e incrementa la detección de todos los pla SPY_STEALTH_1 Amortiguador Electromagnético -SPY_STEALTH_1_DESC -Desbloquea la parte de nave Amortiguador Electromagnético e incrementa la ocultación de todos los planetas, sistemas y edificios por 5. Esta bonificación no es acumulativa con las de otras tecnologías de ocultación. - SPY_STEALTH_2 Campo de Absorción @@ -3266,9 +3028,6 @@ Incrementa la ocultación de todos los planetas, sistemas y edificios por 30. E SPY_STEALTH_3 Camuflaje Dimensional -SPY_STEALTH_3_DESC -Desbloquea la parte de nave Camuflaje Dimensional e incrementa la salud de todos los planetas, sistemas y edificios por 55. Esta bonificación no es acumulativa con otras tecnologías de ocultación. - SPY_STEALTH_4 Camuflaje de Fase @@ -3281,13 +3040,13 @@ SHP_DEUTERIUM_TANK Tanque de Deuterio SHP_DEUTERIUM_TANK_DESC -Desbloquea la parte de nave del Tanque de Deuterio. +Pequeño incremento del alcance de la nave, incrementando la capacidad de combustible. Desbloquea la parte de nave del [[FU_RAMSCOOP]]. SHP_ANTIMATTER_TANK Tanque de Antimateria SHP_ANTIMATTER_TANK_DESC -Desbloquea la parte de nave Tanque de Antimateria. +Incremento moderado del alcance de la nave, incrementado su capacidad de combustible. SHP_ZORTRIUM_PLATE Armadura de Blindaje de Zortrium @@ -3334,9 +3093,6 @@ Esta nave insignia se construye a partir de asteroides grandiosos, algunos de lo SHP_ORG_HULL Casco Orgánico -SHP_ORG_HULL_DESC -Este es el tipo de casco orgánico mas básico: un casco viviente crecido a partir de la unidad de vida más básica y forjado en la batalla. Tiene tres espacios externos y uno interno. La ocultación es elevada y la velocidad media, pero su estructura es un poco baja. Este casco regenera estructura y combustible entre combates. - SHP_ENDOSYMB_HULL Casco Endosimbiótico @@ -3428,69 +3184,36 @@ Instalación de producción orbital para el ensamblaje de naves interestelares m BLD_SHIPYARD_ORBITAL_DRYDOCK Dique seco Orbital -BLD_SHIPYARD_ORBITAL_DRYDOCK_DESC -Instalaciones astilleras expandidas orbitales que permiten la producción de mas grandes diseños de cascos de naves que un astillero básico. - BLD_SHIPYARD_CON_NANOROBO Unidad de Proceso Nanorobotica -BLD_SHIPYARD_CON_NANOROBO_DESC -La producción de millones de nanorobots de mantenimiento y su conexión a una sola IA principal requiere mas extensivas instalaciones roboticas que la producción de cascos roboticos básicos. Este edificio es capaz de facilitar la construcción de cascos roboticos mas avanzados que la Unidad de Proceso Robotica. Esta es una mejora para el Dique Seco Orbital y permite la producción de Cascos NanoRoboticos y Cascos Roboticos. - BLD_SHIPYARD_CON_GEOINT Instalación Geointegrada -BLD_SHIPYARD_CON_GEOINT_DESC -Cuando las necesidades de un astillero son suficientemente grandes, ya no es práctico para el y el planeta en el que se construye seguir siendo entidades separadas. Es mas útil integrarlas en una unidad armoniosa, extendiendo el astillero por debajo de la superficie hasta una órbita geosincrona, haciendo total uso de todos los aspectos geologicos del planeta. Esta es una mejora para el Dique Seco Orbital y permite la producción de Cascos Titanicos y Cascos Auto-Gravitatorios. - BLD_SHIPYARD_CON_ADV_ENGINE Bahia de Ingeniería Avanzada -BLD_SHIPYARD_CON_ADV_ENGINE_DESC -Una extremadamente bien desarrollada instalación de ingeniería que se especializa en la producción del Motor Trans-Espacial, pero también es capaz de producir el Motor Espacial Fluido. Esta es una mejora para el Dique Seco Orbital y permite la construcción de Cascos Espaciales Fluidos y Cascos Trans-Espaciales. Además, esta mejora permite a cualquier astillero en el imperio o imperio aliado producir naves con Torpedos Antimateria y Torpedos de Plasma. - BLD_SHIPYARD_AST Procesador de Asteroide -BLD_SHIPYARD_AST_DESC -Un astillero dedicado con el proposito de cazar asteroides y usarlos como cascos de naves. Solo puede ser construido en un cinturon de asteroides. Esta es una mejora para el Astillero Básico y es requirido para la producción de todos los cascos de asteroide y las consiguientes mejoras para astilleros de asteroide. - BLD_SHIPYARD_AST_REF Procesador de Reformación de Asteroide -BLD_SHIPYARD_AST_REF_DESC -Una extensiva instalación dedicada a fusionar, forjar, deconstruir y reconstruir asteroides preexistentes para formar partes de nave y cascos. Esta es una mejora para el Astillero de Asteroide y permite la producción de Enjambres de Nano-Asteroide, Cascos de Asteroide Compuesto y Cascos de Nave Insignia de Asteroide Esparcido. Además, esta mejora permite a cualquier astillero en el imperio o imperio aliado para producir partes de nave con Armadura Blindada de Roca. - BLD_SHIPYARD_ORG_ORB_INC Incubador Orbital -BLD_SHIPYARD_ORG_ORB_INC_DESC -Un astillero diseñado para cumplir las necesidades de criaturas espaciales maduras. Las condiciones en el astillero deben cumplir las especificaciones exactas para que las naves puedan seguir con vida y desarrollarse. Cualquier nave viva sin acabar tiene posibilidades de ser destruida si el astillero es suficientemente dañado. Esta es una mejora para el Astillero Básico y es requerido para el desarrollo de todos los cascos orgánicos con la excepción de Casco Estáticos Multicelulares y la producción de todas las mejoras posteriores de astilleros orgánicos. - BLD_SHIPYARD_ORG_CELL_GRO_CHAMB Cámara de Crecimiento Celular -BLD_SHIPYARD_ORG_CELL_GRO_CHAMB_DESC -Una mejora de astillero especializado capaz de incrementar el tamaño de organismos monocelulares a proporciones verdaderamente masivas. Esta es una mejora para el Astillero Orgánico y permite el desarrollo de Cascos Protoplásmicos. La presencia de esta mejora y la mejora de Instalación Xenocoordinadora en el mismo planeta permite el desarrollo de Cascos Endosimbióticos. La presencia de estas dos mejoras en cualquier sitio del imperio o imperio aliado permite la producción de partes de nave Bio-Interceptor. - BLD_SHIPYARD_ORG_XENO_FAC Instalación Xenocoordinadora -BLD_SHIPYARD_ORG_XENO_FAC_DESC -Una instalación dedicada a crear una relación totalmente simbiótica entre la nave y su tripulación. Esta es una mejora para el Astillero Orgánico y permite el desarrollo de Cascos Simbióticos. La presencia de esta mejora y la mejora Cámara de Crecimiento Celular en el mismo planeta permite el desarrollo de Cascos Endosimbióticos. La presencia de estas dos mejoras en cualquier parte del imperio o imperio aliado permite la producción de partes de nave Bio-Interceptor. - BLD_SHIPYARD_ENRG_COMP Compresor de Energía -BLD_SHIPYARD_ENRG_COMP_DESC -Un astillero altamente especializado diseñado para crear y montar partes en grandes haces de energía densamente comprimida. Este es un proceso frágil y este astillero sufrira una majestuosa explosión si recibe aunque sea una pequeña cantidad de daño. Esta es una mejora para el Astillero Básico y es requerido para la formación de todos los cascos de energía y futuras mejoras de astillero de casco de energía. - BLD_SHIPYARD_ENRG_SOLAR Unidad de Contención Solar -BLD_SHIPYARD_ENRG_SOLAR_DESC -La tarea de crear un sol en miniatura es una fastuosa tarea por si misma. La producción de un cuerpo así en el que partes de nave puedan ser montadas con seguridad lo es más aún. Esta es una mejora para el Compresor de Energía y permite la formación de Cascos Solares. - BLD_BIOTERROR_PROJECTOR Base de Proyección Bioterror @@ -3508,13 +3231,6 @@ Decrementa la ocultación de todos los objetos en el mismo sistema por 45. Este BLD_INDUSTRY_CENTER Centro Industrial -BLD_INDUSTRY_CENTER_DESC -'''Inicialmente proporciona +5 a la indústria a 100 de distancia en línea directa de la instalación, no acumulable, en mundos enfocados en la indústria. - -[[tech PRO_INDUSTRY_CENTER_II]] aumenta la bonificación de refinamiento en +6 e incrementa la distancia en 200. - -[[tech PRO_INDUSTRY_CENTER_III]] aumenta la bonificación de refinamiento en +7 e incrementa la distancia en 300.''' - BLD_MEGALITH Megalito @@ -3526,9 +3242,6 @@ Este edificio establece la capital de dueño del imperio, por encima de cualquie BLD_SPACE_ELEVATOR Ascensor Espacial -BLD_SPACE_ELEVATOR_DESC -Un enorme y prácticamente irrompible cable atado a un satélite en la órbita baja geoestacionaria en un lado, y una plataforma lanzadera en el límite del planeta en el otro. El ascensor espacial aumenta el comercio del sistema entero. - BLD_GENOME_BANK Banco de Genomas @@ -3540,35 +3253,15 @@ Incrementa la agricultura objetivo en planetas enfocados en agricultura por una BLD_GAIA_TRANS Transformación de Gaia -BLD_GAIA_TRANS_DESC -'''Convierte un planeta en un mundo Gaia mediante un programa de ordenador basado en celulas sensibles, casi divinas. Este planeta maravilloso para la vista y famoso a través de la galaxia conocida como una celebración de la vida y la harmonia. Los habitantes de este mundo piensan y actuan como parte de un organismo mayor, sirviendo sus necesidades simultáneamente consigo mismo. - -Los planetas Gaia tienen una salud, agricultura y población doble.''' - BLD_COLLECTIVE_NET Red de pensamiento colectivo -BLD_COLLECTIVE_NET_DESC -'''Transfiriendo la mente en el ciberespacio, millares de mentes pueden actuar como una, resolviendo los problemas y haciendo posible descubrimientos que una sola mente no podría lograr. - -Incrementa la agricultura objetivo en planetas con enfoque en Agricultura por la mitad de la población del planeta, en planetas objetivo con enfoque Industrial por la mitad de la población del planeta, en planetas objetivo de minería con enfoque en Minería por 20 y planetas objetivo de investigación con enfoque en Investigación por la mitad. - -Viajes de Línea Estelar entre 500 uus crearan disrupción en la efectivada de este edificio y eliminar estas bonificaciones.''' - BLD_GATEWAY_VOID Puerta al Vacío -BLD_GATEWAY_VOID_DESC -Una brecha dimensional capaz de magnificar el potencial destructivo de la Mente del Vacío en una región localizada. Cualquier nave que entre en el sistema sin pasar a través del mismo turno son destruidas y todas las colonias son reducidas al estatus de puesto avanzado, con una población máxima de un decimo. Es imposible para cualquiera que este fuera del sistema que contenga este edificio ver lo que hay. - BLD_ENCLAVE_VOID Enclave del Vacío -BLD_ENCLAVE_VOID_DESC -'''La población de un planeta entero es perfilada genéticamente hacia la Mente del Vacío y dedicada a canalizar su sabiduría para el imperio. Los ciudadanos del Enclave del Vacío son considerados como sacerdotes, los canales de un pensamiento y sabiduría mas elevados. En todos los asuntos de importancia para el imperio, se busca su consejo. Los emisarios del Enclave pueden incluso ser enviados para participar en los proyectos de investigación del imperio. Este mundo nunca puede exceder la producción industrial o de minería por más de 30. - -Multiplica la investigación objetivo en todos los planetas con enfoque en Investigación por 1.5.''' - BLD_ART_BLACK_HOLE Agujero Negro Artificial @@ -3578,11 +3271,6 @@ Una singularidad construida en una órbita lejana de un sistema. Su poder sirve BLD_HYPER_DAM Dique Hiperespacial -BLD_HYPER_DAM_DESC -'''Un rasguño en la tela del universo, controlado y enjaulado de forma segura. El dique hiperespacial sirve como una fuente de energía casi ilimitada para el sector entero. - -Incrementa la indústria objetivo en planetas con enfoque en Indústria por la población del planeta y decrementa la salud en estos planetas por 5.''' - BLD_SOL_ACCEL Acelerador Solar @@ -3592,17 +3280,6 @@ Un Acelerador Solar incrementa la edad de una estrella artificialmente acelerand BLD_SOL_ORB_GEN Generador Solar Orbital -BLD_SOL_ORB_GEN_DESC -'''Un generador en una órbita solar baja diseñado para atrapar directamente la energía de una estrella. Ya que diferentes tipos de estrella producen diferentes cantidades de energía, la bonificación a la indústria del sistema depende del tipo de estrella. - -Estrellas Azul y Blanca proporcionan una bonificación igual a la población del planeta a la indústria objetivo con enfoque en Indústria en el mismo sistema. - -Estrellas Amarilla y Naranja proporcionan una bonificación igual a la mitad de la población del planeta a la indústria objetivo en planetas con enfoque en Indústria en el mismo sistema. - -Estrellas Roja proporcionan una bonificación igual a una cuarta parte de la población del planeta a la indústria objetivo en planetas con enfoque en Indústria en el mismo sistema. - -Este edificio puede ser construido en un puesto de avanzada.''' - BLD_CLONING_CENTER Centro de Clonación @@ -3634,15 +3311,9 @@ Permite la construcción de naves con partes de nave de Neutronio.''' BLD_NEUTRONIUM_FORGE Forja de Neutronio -BLD_NEUTRONIUM_FORGE_DESC -El proceso industrial de neutronio requiere una instalación especializada extensiva. Este edificio es requerido para construir cualquier nave con partes de neutronio. A diferencia de la mayoría de astilleros y mejoras, este edificio no puede ser construido en un puesto de avanzada. - BLD_NEUTRONIUM_SYNTH Sintetizador de Neutronio -BLD_NEUTRONIUM_SYNTH_DESC -Un antiguo edificio diseñado hace mucho tiempo para sintetizar neutronio. Un imperio con este edificio no necesita un Extractor de Neutronio para construir partes de nave de neutronio. - BLD_CONC_CAMP Campos de Concentración @@ -3654,9 +3325,6 @@ Decrementa la población del planeta a un ritmo de 3 por turno e incrementa la i BLD_BLACK_HOLE_POW_GEN Generador de Energía de Agujero Negro -BLD_BLACK_HOLE_POW_GEN_DESC -Este edificio proporciona una bonificación a la indústria máxima del doble de la población del planeta para todos los planetas en el imperio con enfoque en Indústria. Este edificio puede ser construido en un puesto de avanzada. - BLD_PLANET_DRIVE Motor Planetario de Línea Estelar @@ -3682,11 +3350,6 @@ Forma un planeta artificial a partir de un cinturón de asteroides o un gigante BLD_ART_MOON Luna Artificial -BLD_ART_MOON_DESC -'''Forma una luna artificial con un periodo orbital 1:1 - periodo de resonancia rotacional. Esto permite que la construcción de edificios especiales que solo pueden ser construidos con una luna resonante. - -Esto no puede ser construido en un Gigante de Gas o Cinturon de Asteroides.''' - BLD_SOL_REJUV Rejuvenecedor Solar @@ -3704,9 +3367,6 @@ Este edificio puede tener los efectos de un Replicador, un Dispositivo de Oculta BLD_PLANET_CLOAK Dispositivo de Ocultación Planetario -BLD_PLANET_CLOAK_DESC -Ajustando fases de partes de un planeta dentro y afuera en el espacio-tiempo, su ocultación puede ser incrementada masivamente. - BLD_SPATIAL_DISTORT_GEN Generador de Distorsión Espacial @@ -3716,16 +3376,9 @@ Manipulando las propiedades dimensionales de una línea estelar, sería posible BLD_STARGATE Puerta Estelar -BLD_STARGATE_DESC -'''Capaz de crear un atajo mediante el espacio-tiempo entre si mismo y cualquier otra puerta estelar que pertenezca al imperio. -Para activar la puerta estelar, establece el planeta al enfoque Puerta Estelar y construye un activador de puerta estelar en el planeta en el sistema origen y una baliza de puerta estelar en un planeta con enfoque Puerta Estelar en el sistema destino.''' - BLD_GAS_GIANT_GEN Generador de Gigante de Gas -BLD_GAS_GIANT_GEN_DESC -Un generador de energía diseñado para recolectar la energía de un Gigante de Gas. Este edificio solo puede ser construido en un gigante de gas y proporciona una bonificación a la indústria de los planetas con enfoque en Indústria en el mismo sistema de un cuarto de la población de los planetas. - BLD_COLONY_BASE Colonia Base @@ -3737,16 +3390,6 @@ Crea una nave Colonia Base que puede ser usada para colonizar otro planeta en el ## Hull and ship part description templates ## -# %1% hull base starlane speed. -# %2% hull base fuel capacity. -# %3% hull base starlane speed. -# %4% hull base structure value. -HULL_DESC -'''Velocidad de Línea Estelar: %1% -Capacidad de Combustible: %2% -Velocidad de Combate: %3% -Salud: %4%''' - # %1% ship part capacity (fuel, troops, colonists, fighters). PART_DESC_CAPACITY Capacidad: %1% @@ -3755,13 +3398,6 @@ Capacidad: %1% PART_DESC_STRENGTH Fuerza: %1% -# %1% ship part damage done (direct fire weapons). -# %2% number of shots done per attack. -PART_DESC_DIRECT_FIRE_STATS -'''Daño: %1% -Cadencia de Disparo: %2% disparos/turno -Alcance: %3%''' - ## ## Ship parts @@ -3875,18 +3511,6 @@ Sensores DT_DETECTOR_4_DESC Detección Definitiva -FU_DEUTERIUM_TANK -Tanque de Deuterio - -FU_DEUTERIUM_TANK_DESC -Pequeño incremento del alcance de la nave, incrementando la capacidad de combustible. Aparatoso y vulnerable a los disparos. - -FU_ANTIMATTER_TANK -Tanque Anti materia - -FU_ANTIMATTER_TANK_DESC -Incremento moderado del alcance de la nave, incrementado su capacidad de combustible. Compacto y con poca masa. - FU_ZERO_FUEL Generador de Combustible Punto Cero @@ -3896,27 +3520,15 @@ Genera energía usando generación Punto Cero y convirtiendolo en combustible us ST_CLOAK_1 Amortiguador Electromagnético -ST_CLOAK_1_DESC -Mejora la ocultación de la nave amortiguando emisiones electromagnéticas de los sistemas. Solo puede compensar una pequeña parte de las emisiones y no servirá con partes que produzcan grandes emisiones, si están presentes. - ST_CLOAK_2 Campo de Absorción -ST_CLOAK_2_DESC -Crea un campo de absorción alrededor de la nave, transformandola efectivamente en un cuerpo negro perfecto. - ST_CLOAK_3 Ocultación Dimensional -ST_CLOAK_3_DESC -Esconde este nave entre rizos dimensionales, incrementando enormemente su ocultación. - ST_CLOAK_4 Ocultación de Fase -ST_CLOAK_4_DESC -Modula la frecuencia de cualquier onda emitida desde la nave para parecer radiación de fondo. - AR_ZORTRIUM_PLATE Armadura Blindada de Zortrium @@ -3944,21 +3556,12 @@ Admirable defensa con masa moderada SH_DEFENSE_GRID Red de Defensa -SH_DEFENSE_GRID_DESC -Pobre defensa sin masa extra - SH_DEFLECTOR Escudo Deflector -SH_DEFLECTOR_DESC -Gran defensa con poca masa - SH_MULTISPEC Escudo Multi-Espectral -SH_MULTISPEC_DESC -Poderoso Escudo capaz de proteger una nave dentro de una estrella. Proporciona una alta bonificación a la ocultación en el mapa de galaxia cuando la nave esta en una estrella que no sea un Agujero Negro o una Estrella Neutron y permite a la nave a entrar en una estrella en combate. - CO_COLONY_POD Capsula Colonial @@ -3989,255 +3592,99 @@ Distingue irregularidades geométricas naturales de naves enemigas manipulando d SH_ROBOTIC Casco Robótico -SH_ROBOTIC_DESC -'''Este casco esta completamente automatizado y funciona bajo los principios del Control Robótico Militar. Como el casco standard y restaura su estructura entre combates, pero tiene una baja estructura máxima. - -Además de un Astillero Básico y un Dique Seco Orbital, o una Unidad de Proceso Robótica o una Unidad de Proceso Nanorobótica, es necesario para ser construido en el planeta.''' - SH_SPATIAL_FLUX Casco Espacial Fluido -SH_SPATIAL_FLUX_DESC -'''Este casco diminuto es capaz de alcanzar una inmensa velocidad debido a la potencia del Motor Espacial Fluido. Solo tiene dos espacios externos y su estructura máxima es baja, la ocultación es muy elevada, pero recibe una gran penalización cuando esta en movimiento o en combate en la mapa de galaxia. - -Además de un Astillero Básico y un Dique Seco Orbital, o una Bahía de Ingeniería o una Bahía Avanzada de Ingeniería es necesario para ser construido en el planeta.''' - SH_SELF_GRAVITATING Casco Auto-Gravitatorio -SH_SELF_GRAVITATING_DESC -'''Este casco es inmenso tanto en tamaño como en potencia, con ocho espacios externos y dos internos. Es bastante robusto, con una estructura máxima elevada, pero con una ocultación y velocidad penosas. - -Además de un Astillero Básico y un Dique Seco Orbital, o un Megaprocesador Militar o una Instalación Geointegrada es necesario para ser construido en el planeta.''' - SH_NANOROBOTIC Casco Nano-Robótico -SH_NANOROBOTIC_DESC -'''Este casco automatizado es grande y rápido y es capaz de auto repararse usando millones de nano-robots. Tiene seis espacios externos y uno interno, pero tiene una ocultación baja y es muy frágil, aunque regenera su estructura entre combates. - -Requiere un Astillero Básico, un Dique Seco Orbital y una Unidad de Proceso Nanorobótica en el planeta donde se construye.''' - SH_TITANIC Casco Titánico -SH_TITANIC_DESC -'''Este extraordinariamente gran casco requiere un poco de ingeniería incluso para moverse. Tiene dieciséis espacios externos y tres internos y su estructura máxima es muy elevada, pero a costa de la velocidad y ocultación. - -Requiere un Astillero Básico, un Dique Seco Orbital y una Instalación Geointegrada en el planeta donde se construye.''' - SH_TRANSSPATIAL Casco Trans-Espacial -SH_TRANSSPATIAL_DESC -'''Este casco es capaz de viajar oculto y a una velocidad increíble debido a su Motor Trans-Espacial. Solo tiene un slot externo y su estructura máxima es extremadamente baja. - -Requiere un Astillero Básico, un Dique Seco Orbital y una Bahía Avanzada de Ingeniería en el planeta donde se construye.''' - SH_LOGISTICS_FACILITATOR Facilitador Logístico -SH_LOGISTICS_FACILITATOR_DESC -'''Esta nave insignia organizadora esta diseñada para organizar y dirigir transferencias de tripulación y objetos en medio de la batalla. Tiene siete espacios externos y dos internos. Su velocidad es baja, pero su estructura máxima muy elevada. Además, su propia flota de cargueros ocultos permite la transferencia de material y personal durante la batalla, permitiendo que todas las naves amigas recuperar estructura durante el combate y recuperar completamente la estructura entre combates. - -Requiere un Astillero Básico, un Dique Seco Orbital una Unidad de Proceso Nanorobótica, una Instalación Geointegrada, una Bahía Avanzada de Ingeniería y Ensamblaje Logístico Militar para poder ser construido.''' - SH_ASTEROID Casco de Asteroide -SH_ASTEROID_DESC -'''Este casco se construye a partir de un asteroide de tamaño considerable. Mas espacioso que el casco standard, tiene cuatro espacios externos y dos internos. La velocidad y estructura son relativamente bajas, pero es muy barato de construir. Este casco recibe una gran bonificación a la ocultación en el mapa de galaxia cuando esta en un sistema con un cinturón de asteroides y en combate cuando se oculta en ellos. - -Requiere un Astillero Básico y un Procesador de Asteroide en el cinturón de asteroides donde se produce.''' - SH_SMALL_ASTEROID Casco de Asteroide Pequeño -SH_SMALL_ASTEROID_DESC -'''Este casco se construye a partir de un asteroide muy pequeño. Solo tiene un espacio externo y otro interno. La velocidad es muy buena, pero su estructura es muy baja. Este casco recibe una gran bonificación a la ocultación en el mapa de galaxia cuando esta en un sistema con un cinturón de asteroides y en combate cuando se oculta en ellos. - -Requiere un Astillero Básico y un Procesador de Asteroide en el cinturón de asteroides donde se produce.''' - SH_HEAVY_ASTEROID Casco de Asteroide Pesado -SH_HEAVY_ASTEROID_DESC -'''Este casco se construye a partir de un asteroide gigantesco. Tiene seis espacios externos y tres internos. La velocidad es muy baja, pero su estructura es moderadamente alta. Este casco recibe una gran bonificación a la ocultación en el mapa de galaxia cuando esta en un sistema con un cinturón de asteroides y en combate cuando se oculta en ellos. - -Requiere un Astillero Básico y un Procesador de Asteroide en el cinturón de asteroides donde se produce.''' - SH_CAMOUFLAGE_ASTEROID Casco de Asteroide Camuflado -SH_CAMOUFLAGE_ASTEROID_DESC -'''Este asteroide esta diseñado para asemejarse a un asteroide real. Tiene cuatro espacios internos y ninguno interno. La estructura es moderada, pero la velocidad es bastante baja. Este casco consigue una tremenda bonificación a la ocultación en el mapa de galaxia cuando esta en un sistema que contiene un cinturón de asteroides y en combate cuando se oculta en uno de ellos. - -Requiere un Astillero Básico y un Procesador de Asteroide en el cinturón de asteroides donde se produce.''' - SH_SMALL_CAMOUFLAGE_ASTEROID Casco de Asteroide Pequeño Camuflado -SH_SMALL_CAMOUFLAGE_ASTEROID_DESC -'''Este casco de asteroide camuflado es capaz de usar partes de nave de asteroide camufladas en su exterior, aunque debe ser construido con un tamaño menor para compensar y evitar perdidas en su bonificación de ocultación. Tiene un espacio exterior y uno interior. La velocidad es moderada, pero su estructura bastante baja. Este casco tiene un tremendo bonificador a su ocultación en el mapa de galaxia cuando esta en un sistema que contenga un cinturón de asteroides y en combate cuando se esconde detrás de uno. - -Requiere un Astillero Básico y un Procesador de Asteroide en el cinturón de asteroides donde se produce.''' - SH_AGREGATE_ASTEROID Casco de Asteroide Agregado -SH_AGREGATE_ASTEROID_DESC -'''Este casco masivo esta formado por la combinación de varios asteroides grandes. Tiene cincuenta espacios externos y cuatro internos. La estructura es buena, pero su velocidad es muy baja. Este casco recibe una gran bonificación a la ocultación en el mapa de galaxia cuando esta en un sistema con un cinturón de asteroides y en combate cuando se oculta en ellos. - -Requiere un Astillero Básico y un Procesador de Asteroide y una Instalación de Reformado de Asteroides en el cinturón de asteroides donde se produce.''' - SH_MINIASTEROID_SWARM Enjambre de Mini-Asteroides -SH_MINIASTEROID_SWARM_DESC -'''Este casco se construye a partir de un asteroide pequeño, donde muchos de los trozos se han dividido para formar numerosos asteroides diminutos, controlados por el casco principal. Estos asteroides protegen a la nave, otorgando una bonificación a sus escudos. Este casco tiene dos espacios externos pero ninguno interno. La estructura es muy baja, pero con una velocidad alta. Este casco recibe una gran bonificación a la ocultación en el mapa de galaxia cuando esta en un sistema con un cinturón de asteroides y en combate cuando se oculta en ellos. - -Requiere un Astillero Básico y un Procesador de Asteroide y una Instalación de Reformado de Asteroides en el cinturón de asteroides donde se produce.''' - SH_SCATTERED_ASTEROID Casco de Asteroide Esparcido -SH_SCATTERED_ASTEROID_DESC -'''Esta nave insignia se construye a partir de asteroides grandiosos, alguno de los cuales han sido combinados para formar el casco principal. Estos asteroides protegen a la nave principal, así como cualquier nave amiga que la acompañe, otorgando una bonificación a sus escudos para todas las naves que te pertenezcan y las naves aliadas en las cercanias. Como el casco de asteroide compuesto, tiene quince espacios externos y cuatro internos. La velocidad es muy baja, pero la estructura es elevada. Este casco recibe una gran bonificación a la ocultación en el mapa de galaxia cuando esta en un sistema con un cinturón de asteroides y en combate cuando se oculta en ellos. - -Requiere un Astillero Básico y un Procesador de Asteroide y una Instalación de Reformado de Asteroides en el cinturón de asteroides donde se produce.''' - SH_CRYSTALLIZED_ASTEROID Casco de Asteroide Cristalizado -SH_CRYSTALLIZED_ASTEROID_DESC -'''Este robusto casco se construye a partir de asteroides grandiosos, endurecido con técnicas avanzadas de cristalización. Como el Casco de Asteroide normal, tiene cuatro espacios externos y dos internos. La velocidad es algo baja, pero su estructura es extremadamente alta. Este casco recibe una gran bonificación a la ocultación en el mapa de galaxia cuando esta en un sistema con un cinturón de asteroides y en combate cuando se oculta en ellos. - -Requiere un Astillero Básico y un Procesador de Asteroide y un Módulo de Cristalización en el cinturón de asteroides donde se produce.''' - SH_ORGANIC Casco Orgánico -SH_ORGANIC_DESC -'''Este es el tipo de casco orgánico mas básico: un casco viviente crecido a partir de la unidad de vida más básica y forjado en la batalla. Tiene tres espacios externos y uno interno. La ocultación es elevada y la velocidad media, pero su estructura es un poco baja. Este casco regenera estructura y combustible entre combates. - -Requiere un Astillero Básico y un Incubador Orbital en el planeta en el que se construye.''' - SH_STATIC_MULTICELLULAR Casco Multicelular Estático -SH_STATIC_MULTICELLULAR_DESC -'''Aunque empezara siendo orgánico, este casco no tiene vida, producido mediante fundición multicelular. La versatilidad del interior y la falta de necesidad para órganos internos incrementa su capacidad, pero sin sistemas vivos para mantener la estructura es muy débil. Tiene tres espacios externos y dos internos. La ocultación es moderada y su velocidad buena, pero su estructura es muy baja. A diferencia de la mayoría de cascos orgánicos, no regenera ni la estructura ni el combustible entre combates. - -Requiere un Astillero Básico y un Ensamblaje Orgánico Industrial en el planeta en el que se construye.''' - SH_ENDOMORPHIC Casco Endomórfico -SH_ENDOMORPHIC_DESC -'''Este casco viviente contiene componentes multicelulares sin vida para un modularidad interna mayor. Este casco tiene cuatro espacios externos y tres internos. La ocultación es buena y la velocidad moderada, pero la estructura es baja. Este casco regenera estructura y combustible entre combates. - -Requiere un Astillero Básico, un Incubador Orbital y un Ensamblador Orgánico Industrial en el planeta en el que se construye.''' - SH_SYMBIOTIC Casco Simbiótico -SH_SYMBIOTIC_DESC -'''Este casco viviente existe en una relación simbiótica con su tripulación, incrementando su ocultación y velocidad. Tiene dos espacios externos y uno interno. La ocultación, velocidad y estructura son buenas. Este casco regenera estructura y combustible entre combates. - -Requiere un Astillero Básico, un Incubador Orbital y una Instalación de Xenocoordinación en el planeta en el que se construye.''' - SH_PROTOPLASMIC Casco Protoplásmico -SH_PROTOPLASMIC_DESC -'''Este casco monocelular suspende sus partes internas en un citoplasma en vez de montarlos en una estructura sólida interna. Tiene tres espacios externos e internos. La velocidad es moderada y su ocultación alta, pero su estructura es muy baja. Este casco regenera estructura y combustible entre combates. - -Requiere un Astillero Básico, un Incubador Orbital y una Cámara de Crecimiento Celular en el planeta en el que se construye.''' - SH_ENDOSYMBIOTIC Casco Endosimbiótico -SH_ENDOSYMBIOTIC_DESC -'''Este casco monocelular existe en una relación simbiótica con su tripulación, suspendiendolos en citoplasma y usando sus órganos. Tiene tres espacios externos y dos internos. La velocidad y ocultación son buenas y su estructura moderada. Este casco regenera estructura y combustible entre combates. - -Requiere un Astillero Básico, un Incubador Orbital, una Instalación de Xenocoordinación y una Cámara de Crecimiento Celular en el planeta en el que se construye.''' - SH_RAVENOUS Casco Famélico -SH_RAVENOUS_DESC -'''Este casco viviente surge de la sangre de sus hermanos. Tiene cuatro espacios externos y dos internos. La ocultación es baja, pero la velocidad y estructura elevadas. A diferencia de la mayoría de cascos orgánicos, el Casco Famélico solo regenera combustible entre combates. - -Requiere un Astillero Básico, un Incubador Orbital y una Cámara de Maduración Competitiva en el planeta en el que se construye.''' - SH_BIOADAPTIVE Casco Bio-Adaptivo -SH_BIOADAPTIVE_DESC -'''Este casco viviente tiene una mente y cuerpo en perfecta armonía, permitiendo que reaccione y se adapte a su entorno con una eficiencia sin parangón. Solo tiene dos espacios internos y externos, pero la ocultación, velocidad y estructura son muy elevados. Este casco regenera combustible y recupera completamente su estructura entre combates. - -Requiere un Astillero Básico, un Incubador Orbital y una Unidad de Modificación Bioneuronal en el planeta en el que se construye.''' - SH_SENTIENT Casco Sensible -SH_SENTIENT_DESC -'''Esta nave insignia consciente de si misma tiene poderosas habilidades analíticas y una tremenda capacidad de memorización, que le permite dirigir naves amigas y proporcionar información útil y tácticas sutiles en combate, otorgando una bonificación a la salud y detección para todas las naves amigas que la acompañen. Tiene seis espacios externos y tres internos. Tiene una velocidad baja, pero una ocultación y estructura buenas. Este casco regenera estructura y combustible entre combates. - -Requiere un Astillero Básico, un Incubador Orbital, una Cámara de Maduración Competitiva y una Unidad de Modificación Neuronal en el planeta en el que se construye.''' - SH_COMPRESSED_ENERGY Casco de Energía Comprimida -SH_COMPRESSED_ENERGY_DESC -'''Este casco rápido se compone enteramente de energía comprimida y solo tiene un espacio externo. La estructura es baja, pero su ocultación y velocidad son muy elevadas. Este casco requiere una gran cantidad de energía para ser construido y solo puede hacerse en un sistema con una estrella de tipo Blanca, Azul o Agujero Negro. - -Requiere un Astillero Básico y un Compresor de Energía en el planeta en el que se construye.''' - SH_FRACTAL_ENERGY Casco de Energía Fractal -SH_FRACTAL_ENERGY_DESC -'''Este casco, aunque pequeño, tiene una superficie fractal que le permite montar muchas mas partes que un casco standard. Tiene veinte espacios externos pero ninguno interno. La ocultación es muy baja, pero su estructura y velocidad son moderadas. Este casco requiere una enorme cantidad de energía para ser construida y solo puede hacerse en un sistema con una estrella de tipo Azul o Agujero Negro. - -Requiere un Astillero Básico, un Compresor de Energía y una Formación Enfocada de Energía Controlada en el planeta en el que se construye.''' - SH_QUANTUM_ENERGY Casco de Energía Cuántica -SH_QUANTUM_ENERGY_DESC -'''Este casco magnifica las fluctuaciones de energía cuántica para incrementar su propio poder. Tiene siete espacios externos y tres internos. La velocidad y estructura son muy elevadas, pero su ocultación muy baja. Este casco requiere una enorme cantidad de energía para ser construido y solo puede hacerse en un sistema con una estrella de tipo Azul o Agujero Negro. - -Requiere un Astillero Básico, un Compresor de Energía y una Formación Enfocada de Energía Controlada en el planeta en el que se construye.''' - SH_SOLAR Casco Solar -SH_SOLAR_DESC -'''Esta majestuosa nave insignia es esencialmente un sol en miniatura y como tal es una gran fuente de combustible y visibilidad. Todas las naves amigas recuperan completamente el combustible entre combates y todas las naves enemigas en la vecindad reciben una penalización a la ocultación. Este casco tiene 18 espacios externos y 9 internos - una mayor capacidad interna que cualquier otro casco. La estructura es extremadamente alta pero la velocidad y la ocultación son muy bajas. Aun así, esta nave tiene la habilidad de entrar en una estrella y esconderse, otorgando la ocultación perfecta en el mapa de galaxia cuando esta en un sistema con una estrella de cualquiera que no sea Agujero Negro o Neutrónica y en combate, cuando se esconde en esa estrella. Esta nave requiere una cantidad tremenda de energía para construirla y debe captar energía desde colisiones partícula-antipartícula en el evento del horizonte de un Agujero Negro en su formación. - -Requiere un Astillero Básico, un Compresor de Energía y una Unidad de Contención Solar en el planeta en el que se construye.''' - -SH_STANDARD +SH_BASIC_LARGE Casco Standard -SH_STANDARD_DESC -'''Casco interestelar básico. - -Requiere un Astillero Básico y un Dique Seco Orbital en el planeta en el que se construye.''' - SH_XENTRONIUM Casco de Xentronio -SH_XENTRONIUM_DESC -Un casco construido principalmente de xentronio. Aunque pequeño, tiene una extrema estructura máxima. - SH_COLONY_BASE Casco de Colonia Base -SH_COLONY_BASE_DESC -Un casco diseñado para crear una colonia en otro planeta del sistema. No puede moverse entre sistemas o de forma rápida dentro del sistema. - ## ## Buildings macros @@ -4288,6 +3735,5 @@ Un casco diseñado para crear una colonia en otro planeta del sistema. No puede ## Name lists ## -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/fi.txt b/default/stringtables/fi.txt index b4b1cab75a8..ccc8a991400 100644 --- a/default/stringtables/fi.txt +++ b/default/stringtables/fi.txt @@ -163,9 +163,6 @@ COMMAND_LINE_USAGE COMMAND_LINE_DEFAULT '''Oletus: ''' -OPTIONS_DB_HELP -Tulosta ohje. - OPTIONS_DB_GENERATE_CONFIG_XML Käyttää kaikkia asetuksia mistä tahansa olemassa olevasta config.xml tiedostosta sekä komentoriville syötettyjä tietoja luodakseen config.xml tiedoston. Tämä tiedosto korvaa mahdollisen jo olemassa olevan config.xml tiedoston. @@ -349,24 +346,6 @@ Asettaa tähtijärjestelmän sivupaneelin koon. OPTIONS_DB_UI_SIDEPANEL_PLANET_SHOWN Asettaa näytetäänkö renderöidyt planeetat / asteroidit sivupaneelissa. -OPTIONS_DB_GAMESETUP_STARS -Tähtien määrä luotavassa galaksissa. - -OPTIONS_DB_GAMESETUP_GALAXY_SHAPE -Luotavan galaksin muoto. - -OPTIONS_DB_GAMESETUP_GALAXY_AGE -Luotavan galaksin ikä. - -OPTIONS_DB_GAMESETUP_PLANET_DENSITY -Planeettojen määrä tähtijärjestelmässä luotavassa galaksissa. - -OPTIONS_DB_GAMESETUP_STARLANE_FREQUENCY -Tähtilinjojen määrä luotavassa galaksissa. - -OPTIONS_DB_GAMESETUP_SPECIALS_FREQUENCY -Erikoisuuksien esiintyminen luotavassa galaksissa. - OPTIONS_DB_GAMESETUP_EMPIRE_NAME Pelissä käyttämäsi imperiumin nimi. @@ -376,21 +355,9 @@ Pelissä käyttämäsi nimi. OPTIONS_DB_GAMESETUP_EMPIRE_COLOR Pelissä käyttämäsi imperiumin väri. -OPTIONS_DB_GAMESETUP_STARTING_SPECIES_NAME -Kotiplaneettasi laji pelin alussa. - -OPTIONS_DB_GAMESETUP_NUM_AI_PLAYERS -Tekoälyvastustajien määrä pelissä. - OPTIONS_DB_STRINGTABLE_FILENAME Asettaa käännöstiedoston. -OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER -Jos päällä peli tallentaa itsensä automaattisesti tietyin väliajoin yksinpelissä. - -OPTIONS_DB_AUTOSAVE_MULTIPLAYER -Jos päällä peli tallentaa itsensä automaattisesti tietyin väliajoin moninpelissä. - OPTIONS_DB_AUTOSAVE_TURNS Asettaa automaattitallennusten välillä kuluvan ajan (vuoroissa). @@ -696,12 +663,6 @@ Nopeasti suljettavat ikkunat* OPTIONS_MISC_UI Sekalaiset käyttöliittymäasetukset -OPTIONS_SINGLEPLAYER -Yksinpeli - -OPTIONS_MULTIPLAYER -Moninpeli - OPTIONS_AUTOSAVE_TURNS_BETWEEN Vuoroja automaattitallennusten välillä @@ -1521,9 +1482,6 @@ SITREP_EMPIRE_ELIMINATED SP_HUMAN Ihmiset -SP_HUMAN_DESC -Normi. - ## ## Specials @@ -1532,15 +1490,9 @@ Normi. ANCIENT_RUINS_SPECIAL Muinaisia raunioita -ANCIENT_RUINS_SPECIAL_DESCRIPTION -Tällä planeetalla on jäänteitä kehittyneestä muinaisesta sivilisaatiosta. Näiden jäänteiden tutkiminen antaa bonuksen tutkimukseen. - ECCENTRIC_ORBIT_SPECIAL Epäsäännöllinen kiertorata -ECCENTRIC_ORBIT_SPECIAL_DESC -Tämän planeetan kiertorata on hyvin epäsäännöllinen. Planeetan etäisyys tähdestä, jota se kiertää vaihtelee suuresti, ja näin ollen planeetan saama säteilymäärä ei ole tasainen vuoden ympäri. Vaihtelevat olosuhteet haittaavat infrastruktuurin kehittymistä, mutta tarjoavat hedelmällisen maaperän tutkimukselle. - MINERALS_SPECIAL Mineraalirikas @@ -1713,10 +1665,6 @@ vaihdanta METER_TARGET_POPULATION Tavoite väestö -# Meter types -METER_TARGET_HEALTH -Tavoite terveys - # Meter types METER_TARGET_INDUSTRY Tavoite teollisuus @@ -1753,10 +1701,6 @@ Max Puolustus METER_POPULATION Väestö -# Meter types -METER_HEALTH -Terveys - # Meter types METER_INDUSTRY Teollisuus @@ -2238,15 +2182,9 @@ Avaa ydinohjuksen, aluksen osa. SPY_DETECT_2 Aktiivitutka -SPY_DETECT_2_DESC -Avaa aktiivitutkan, aluksen osa. - SHP_DEUTERIUM_TANK Deuteriumtankki -SHP_DEUTERIUM_TANK_DESC -Avaa Deuteriumtankin, aluksen osa. - ## ## Technology refinement @@ -2265,16 +2203,6 @@ Laivatelakka ## Hull and ship part description templates ## -# %1% hull base starlane speed. -# %2% hull base fuel capacity. -# %3% hull base starlane speed. -# %4% hull base structure value. -HULL_DESC -'''Tähtilinjanopeus: %1% -Polttoainekapasiteetti: %2% -Taistelunopeus: %3% -Kestävyys: %4%''' - # %1% ship part capacity (fuel, troops, colonists, fighters). PART_DESC_CAPACITY Kapasiteetti: %1% @@ -2283,13 +2211,6 @@ Kapasiteetti: %1% PART_DESC_STRENGTH Vahvuus: %1% -# %1% ship part damage done (direct fire weapons). -# %2% number of shots done per attack. -PART_DESC_DIRECT_FIRE_STATS -'''Vahinko: %1% -Tulinopeus: %2% laukausta/vuoro -Kantama: %3%''' - ## ## Ship parts @@ -2313,18 +2234,9 @@ Aktiivitutka DT_DETECTOR_2_DESC Keskinkertainen havainnointikyky, suuri häiverangaistus alukselle -FU_DEUTERIUM_TANK -Deuteriumtankki - -FU_ANTIMATTER_TANK -Antimateriatankki - SH_DEFENSE_GRID Puolustusverkko -SH_DEFENSE_GRID_DESC -Heikko puolustus, joka ei tuo lisämassaa - ## ## Ship hulls @@ -2380,6 +2292,5 @@ Heikko puolustus, joka ei tuo lisämassaa ## Name lists ## -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/fr.txt b/default/stringtables/fr.txt index 7c4340df858..8bad9443d63 100644 --- a/default/stringtables/fr.txt +++ b/default/stringtables/fr.txt @@ -2,13 +2,13 @@ Français # This is the French String Table file for FreeOrion. # -# Traduction par alchemy, shivu, zareif, nilstilar et Ouaz. Traduction terminée le 12 Juillet 2015 et couramment maintenue/améliorée par Ouaz (http://github.com/Ouaz/freeorion). +# Traduction par alchemy, shivu, zareif, nilstilar et Ouaz. Traduction achevée le 12 Juillet 2015 et couramment maintenue/améliorée par Ouaz (http://github.com/Ouaz/freeorion). # # Notes: to avoid potential conflict with functional keys in other files, do not make # any stringtable keys beginning with "FUNCTIONAL_". # New Sitreps priorities should be added to # `default/customizations/common_user_customizations.txt`. - +# # semi-randomly collected characters to force font code page loading # 肛門オーガズム # åřžßąłżęЗыдШит @@ -52,7 +52,7 @@ NO Non # Used as a placeholder for unexpanded content in the combat log window. -#ELLIPSIS +#ELLIPSIS #... EMPIRE # translated @@ -71,7 +71,7 @@ ADD_AI_PLAYER Ajouter une IA OBSERVER -Observateur +Spectateur MODERATOR Modérateur @@ -158,7 +158,7 @@ Flotte # Name for a newly created monster-only fleet. # %1% represents a unique number. NEW_MONSTER_FLEET_NAME -Meute %1% +Horde %1% # Name for a newly created colony-only fleet. # %1% represents a unique number. @@ -267,6 +267,10 @@ BALANCE CONTENT Contenu +MULTIPLAYER +Multijoueurs + + ## ## Major errors ## @@ -276,6 +280,14 @@ ERROR_SOUND_INITIALIZATION_FAILED Consultez le fichier log pour le détail des messages d'erreur. ''' +OPENGL_VERSION_LOW_TITLE +Version OpenGL Insuffisante + +OPENGL_VERSION_LOW_TEXT +'''La version OpenGL sur ce système est inférieure à 2.0 + +FreeOrion peut s'interrompre inopinément durant le démarrage.''' + # Used as a prefix for the FORMAT_LIST_[1-MANY]_ITEMS translation entries. FORMAT_LIST_DEFAULT_PLURAL_HEADER Engagés : @@ -328,6 +340,28 @@ FORMAT_LIST_10_ITEMS #%1% %2%, %3%, %4%, %5%, %6%, %7%, %8%, %9%, %10%, %11% ... +## +## Build Projects +## + +PROJECT_BT_STOCKPILE +Transfert Stock + +PROJECT_BT_STOCKPILE_SHORT_DESC +Transfère les PP vers le stock impérial + +PROJECT_BT_STOCKPILE_DESC +'''Les PP alloués au projet de Transfert Stock sont ajoutés au Stock Impérial. La quantité à transférer et le nombre de répétitions du transfert peuvent être spécifiés. + +Seuls les PP produits au sein du même Groupe Ressource (soit aucun du Stock Impérial) sont alloués au projet de Transfert Stock. + +L'excédent de PP (PP non-alloués à un projet dans la file d'attente) sur cette planète ou tout autre emplacement, sera transféré vers le Stock, qu'un projet de Transfert Stock soit présent ou pas dans la file d'attente. Les PP alloués à un projet de Transfert Stock sont invariablement envoyés vers le Stock, même si d'autres items de production sont ajoutés ultérieurement à la file d'attente. + +Exemple: Un empire désire puiser des PP dans le Stock au sein d'un Groupe Ressource secondaire, car ce dernier ne génère pas un nombre de PP suffisant. Dans cette optique, le stock nécessite d'être approvisionné à partir du Groupe Ressource principal. L'empire dispose de 100 PP d'Industrie dans le Groupe Ressource principal, sa limite d'usage du Stock est de 10 PP et l'empire désire avoir 10 PP toujours disponible dans le Stock. La file d'attente de Production contient des items sur les planètes du Groupe Ressource principal nécessitant 120 PP par tour, ce qui signifie que tous les PP disponibles seront utilisés. +Afin de pouvoir utiliser les PP dans le Groupe Ressource secondaire, l'empire ajoute en tête de la file d'attente un projet de Transfert Stock d'une valeur de 10x sur une planète du Groupe Ressource principal. +Étant donné que le projet de Transfert Stock est placé au-dessus des autres projets dans la file d'attente du Groupe Ressource principal, le projet est entièrement financé et augmentera donc le Stock de 10 PP au tour suivant. Pour assurer un transfert continu à chaque tour, la valeur de répétition du projet est réglée sur 99 fois.''' + + ## ## Predefined Ship Designs (located in default/scripting/ship_designs/) ## @@ -338,10 +372,10 @@ Porteur d'Escorte SD_CARRIER_DESC Porteur conçu pour défendre les autres vaisseaux armés via le lancement d'escadrons d'Intercepteurs. -SD_FLAK +SD_CARRIER_2 Porteur de Flotte -SD_FLAK_DESC +SD_CARRIER_2_DESC Porteur conçu pour prendre part aux actions offensives de flotte, à l'aide d'escadrons de Bombardiers et de Canons Mitrailleurs. SD_SCOUT @@ -390,7 +424,7 @@ SD_LARGE_MARK_1 Croiseur Ms SD_LARGE_MARK1_DESC -Croiseur équipé de lasers améliorés et d'un blindage standard pour opérations indépendantes de longue portée. [[BLD_SHIPYARD_BASE_REQUIRED]] +Croiseur équipé d'un armement amélioré et d'un blindage standard pour opérations indépendantes de longue portée. [[BLD_SHIPYARD_BASE_REQUIRED]] SD_LARGE_MARK_2 Croiseur Lz @@ -426,7 +460,7 @@ SD_ROBO_FLUX_TROOPS Transport Flux d'Infanterie SD_ROBO_FLUX_TROOPS_DESC -Transporteur rapide d'infanterie +Transporteur rapide d'infanterie SD_ROBO_FLUX_TROOPS_HVY Transport Flux d'Infanterie Lourde @@ -694,7 +728,7 @@ Kraken Noir SM_BLACK_KRAKEN_DESC '''Un puissant Kraken Noir réputé comme surnaturel. -Les Kraken Noirs sont des monstres spatiaux issus de la bio-ingénierie, incroyablement redoutables, d'une grande [[metertype METER_STEALTH]], et munis d'armes dangereuses. Une flotte puissante et une bonne [[encyclopedia DETECTION_TITLE]] seront nécessaires pour en découdre avec ces organismes, ces derniers attaquant les populations des planètes où les structures et bâtiments sont facilement détectables.''' +Les Kraken Noirs sont des monstres spatiaux issus de la bio-ingénierie, incroyablement redoutables, d'une grande [[metertype METER_STEALTH]], et munis d'armes dangereuses. Une flotte puissante et une bonne [[encyclopedia DETECTION_TITLE]] seront nécessaires pour en découdre avec ces organismes, ces derniers visant en priorité les planètes où les structures et bâtiments sont facilement détectables et attaquant leur [[metertype METER_POPULATION]].''' SM_SNOWFLAKE_1 Flocon de Neige Juvénile @@ -726,7 +760,7 @@ Flocon de Neige Psionique SM_PSIONIC_SNOWFLAKE_DESC '''Un monstre capable de neutraliser l'esprit des êtres organiques. -Les Flocons de Neige Psioniques sont des monstres spatiaux issues de la bio-ingénierie, munis d'armes dangereuses, ayant la capacité de prendre le contrôle des astronefs abritant un équipage à [[encyclopedia ORGANIC_SPECIES_TITLE]]. Une flotte puissante sera nécessaire pour les combattre. Chassant et attaquant les vaisseaux spatiaux ennemis, les Flocons de Neige Psioniques forcent les équipages vulnérables à leurs attaques psychiques à abandonner leur empire, et sont également capables d'attaquer directement les populations planétaires.''' +Les Flocons de Neige Psioniques sont des monstres spatiaux issues de la bio-ingénierie, munis d'armes dangereuses, ayant la capacité de prendre le contrôle des astronefs abritant un équipage à [[encyclopedia ORGANIC_SPECIES_TITLE]]. Une flotte puissante sera nécessaire pour les combattre. Chassant et attaquant les vaisseaux spatiaux ennemis, les Flocons de Neige Psioniques forcent les équipages vulnérables à leurs attaques psychiques à abandonner leur empire, et sont également capables d'attaquer directement la [[metertype METER_POPULATION]] d'une planète.''' SM_JUGGERNAUT_1 Mastodonte Juvénile @@ -758,7 +792,7 @@ Mastodonte Déformé SM_BLOATED_JUGGERNAUT_DESC '''Un monstre spatial massif à l'allure repoussante. -Les Mastodontes Déformés sont des monstres spatiaux issus de la bio-ingénierie, incroyablement résistants, d'une grande [[metertype METER_STEALTH]], et munis d'armes dangereuses. Une flotte puissante et une bonne [[encyclopedia DETECTION_TITLE]] seront nécessaires pour en découdre avec ces organismes, ces derniers attaquant les populations des planètes où les structures et bâtiments sont facilement détectables.''' +Les Mastodontes Déformés sont des monstres spatiaux issus de la bio-ingénierie, incroyablement résistants, d'une grande [[metertype METER_STEALTH]], et munis d'armes dangereuses. Une flotte puissante et une bonne [[encyclopedia DETECTION_TITLE]] seront nécessaires pour en découdre avec ces organismes, ces derniers visant en priorité les planètes où les structures et bâtiments sont facilement détectables et attaquant leur [[metertype METER_POPULATION]].''' SM_CLOUD Nuage Spatial @@ -833,7 +867,7 @@ FLD_MOLECULAR_CLOUD Nuage Moléculaire FLD_MOLECULAR_CLOUD_DESC -Nuage diffus de molécules complexes pouvant perturber le champ de protection des astronefs, ces derniers voyant la solidité de leurs [[metertype METER_SHIELD]] diminuer de 15. +Nuage diffus de molécules complexes pouvant perturber le champ de protection des astronefs, ces derniers voyant la solidité de leurs [[metertype METER_SHIELD]] diminuer de 15. FLD_NEBULA_1 Nébuleuse @@ -893,6 +927,9 @@ Le serveur ne répond pas. SERVER_LOST La connexion au serveur a été perdue. +LOCAL_SERVER_ALREADY_RUNNING_ERROR +Impossible de démarrer le serveur. Un serveur local est déjà activé. + PLAYER_DISCONNECTED Le joueur %1% n'est plus connecté au serveur. @@ -919,8 +956,11 @@ Erreur de lecture du fichier config.xml. Les options par défaut seront utilisé UNABLE_TO_READ_PERSISTENT_CONFIG_XML Erreur de lecture du fichier optionnel persistent_config.xml (normal si ce dernier n'existe pas). +UNABLE_TO_WRITE_PERSISTENT_CONFIG_XML +Erreur lors de l'écriture du fichier persistent_config.xml. + UNABLE_TO_WRITE_SAVE_FILE -Erreur dans l'écriture du fichier de sauvegarde. +Erreur lors de l'écriture du fichier de sauvegarde. UNABLE_TO_READ_SAVE_FILE Erreur de lecture du fichier de sauvegarde. @@ -938,10 +978,10 @@ ABORT_SAVE_AND_EXIT Quitter FreeOrion sans sauvegarder. EMPIRE_NOT_FOUND_CANT_HANDLE_ORDERS -Vous ne contrôlez aucun empire et ne pouvez émettre aucun ordre. +Aucun empire n'est contrôlé par ce client; les ordres ne peuvent pas être exécutés. ORDERS_FOR_WRONG_EMPIRE -Des ordres ont été transmis à un empire que vous ne contrôlez pas. +Des ordres ont été transmis à un empire que ce client ne contrôle pas. SERVER_ALREADY_HOSTING_GAME Ce serveur héberge déjà une partie. @@ -958,16 +998,77 @@ L'Univers généré contient des erreurs. Consultez le fichier log pour le déta SERVER_TURN_EVENTS_ERRORS Les événements de tour scriptés contiennent des erreurs. Consultez le fichier log pour le détail des messages d'erreur. La partie peut continuer, mais le gameplay en sera probablement impacté. +SERVER_ALREADY_PLAYING_GAME +Le serveur n'accepte pas de nouveaux joueurs car une partie est actuellement en cours. + ERROR_PYTHON_AI_CRASHED L'IA Python pour %1% a rencontré une erreur fatale. ERROR_PLAYER_NAME_ALREADY_USED %1% : nom de joueur déjà utilisé. +ERROR_WRONG_PASSWORD +Mot de passe erroné pour le joueur %1%. + +ERROR_CLIENT_TYPE_NOT_ALLOWED +Type de client non autorisé. + +ERROR_NOT_ENOUGH_AI_PLAYERS +Pas assez de joueurs IA. + +ERROR_TOO_MANY_AI_PLAYERS +Trop de joueurs IA. + +ERROR_NOT_ENOUGH_HUMAN_PLAYERS +Pas assez de joueurs humains. + +ERROR_TOO_MANY_HUMAN_PLAYERS +Trop de joueurs humains. + +ERROR_NOT_ENOUGH_CONNECTED_HUMAN_PLAYERS +Pas assez de joueurs humains connectés contrôlant un empire. + +ERROR_TOO_MANY_UNCONNECTED_HUMAN_PLAYERS +Trop de joueurs humains non-connectés contrôlant un empire. + +ERROR_CONNECTION_WAS_REPLACED +Votre connection a été mise à jour. + +ERROR_NONPLAYER_CANNOT_CONCEDE +Seuls les joueurs peuvent capituler. + +ERROR_CONCEDE_DISABLED +Capituler n'est pas autorisé. + +ERROR_CONCEDE_EXCEED_COLONIES +Impossible de capituler; l'empire contrôle trop de colonies. + +ERROR_CONCEDE_LAST_HUMAN_PLAYER +Impossible de capituler dans une partie avec un seul joueur humain. + +ERROR_INCOMPATIBLE_VERSION +Le serveur ne peut pas lire correctement les messages de votre client, probablement en raison de versions incompatibles du jeu. + +ERROR_CHECKSUM_MISMATCH +Le contenu de contrôle entre le serveur et le client diffère. + + ## ## Game Rules ## +RULE_PRODUCTION_QUEUE_FRONTLOAD_FACTOR +Surengagement de PP par Tour + +RULE_PRODUCTION_QUEUE_FRONTLOAD_FACTOR_DESC +Pourcentage de surengagement de PP par tour de production d'un item afin de le produire dans les temps. + +RULE_PRODUCTION_QUEUE_TOPPING_UP_FACTOR +Surengagement final de PP + +RULE_PRODUCTION_QUEUE_TOPPING_UP_FACTOR_DESC +Pourcentage de surengagement de PP pour le tour final de production d'un item afin de le produire dans les temps. + RULE_CHEAP_AND_FAST_TECH_RESEARCH Technologies gratuites et immédiates @@ -992,11 +1093,17 @@ Passes de Combat RULE_NUM_COMBAT_ROUNDS_DESC Nombre de passes de combat se déroulant à chaque tour. +RULE_AGGRESSIVE_SHIPS_COMBAT_VISIBLE +Visibilité astronefs en mode Agression + +RULE_AGGRESSIVE_SHIPS_COMBAT_VISIBLE_DESC +Les astronefs au sein d'une flotte en mode Agression débutent les combats en étant visibles pour tous les empires, indépendamment des niveaux de Furtivité et de Capacité de Détection. + RULE_RESEED_PRNG_SERVER Regénération aléatoire RULE_RESEED_PRNG_SERVER_DESC -La génération du nombre aléatoire d'une graine est effectuée plus fréquemment pour favoriser des caractéristiques plus prévisibles et reproductibles. +Avec la regénération aléatoire, rejouer la même sauvegarde ou utiliser les mêmes réglages et Graine de hasard, ne doivent pas produire les mêmes effets aléatoires. Cela est préférable si les joueurs veulent s'assurer qu'aucun d'entre eux ne puissent prédire les événements aléatoires selon les expériences antérieures ou si un joueur désire 're-jouer' l'issue d'un combat ou d'un effet aléatoire en relançant un tour, et ne pas obtenir les mêmes résultats que lors du précédent essai. RULE_SHIP_SPEED_FACTOR Ajustement Vitesse des Astronefs @@ -1040,6 +1147,18 @@ Activer Expérimentateurs RULE_ENABLE_EXPERIMENTORS_DESC Active les Expérimentateurs lors de la génération de la galaxie +RULE_ENABLE_SUPER_TESTER +Activer Invasion Super-Testeurs + +RULE_ENABLE_SUPER_TESTER_DESC +Active la structure [[BLD_SUPER_TEST]] + +RULE_STOCKPILE_IMPORT_LIMITED +Limite Import Stock + +RULE_STOCKPILE_IMPORT_LIMITED_DESC +Active la limite d'import du stock par tour + RULE_TEST_INT Test des Entiers (Integer) @@ -1052,19 +1171,116 @@ Test des Chaînes (String) RULE_TEST_STRING_DESC # translated Test +RULE_STARLANES_EVERYWHERE +Voies Spatiales Systématiques + +RULE_STARLANES_EVERYWHERE_DESC +Les Voies spatiales sont générées entre chaque paire de systèmes, indépendamment de toute géométrie. Toutefois, ces Voies peuvent toujours être supprimées durant une partie. Dans ce mode, les Voies ne sont pas affichées sur la carte galactique. + +RULE_HABITABLE_SIZE_TINY +Zone habitable Minuscules Planètes + +RULE_HABITABLE_SIZE_SMALL +Zone habitable Petites Planètes + +RULE_HABITABLE_SIZE_MEDIUM +Zone habitable Moyennes Planètes + +RULE_HABITABLE_SIZE_LARGE +Zone habitable Grandes Planètes + +RULE_HABITABLE_SIZE_HUGE +Zone habitable Immenses Planètes + +RULE_HABITABLE_SIZE_ASTEROIDS +Zone habitable Astéroïdes + +RULE_HABITABLE_SIZE_GASGIANT +Zone habitable Géantes Gazeuses + +RULE_HABITABLE_SIZE_DESC +Valeur utilisée pour ajuster les variations de [[METER_POPULATION]] sur une planète de cette taille. + +RULE_ALLOW_CONCEDE +Autoriser Capitulation + +RULE_ALLOW_CONCEDE_DESC +Autorise les empires à capituler dans une partie. Si un empire capitule, tout ce qu'il possède dans l'univers sera automatiquement détruit. La partie continue avec les joueurs restants. + +RULE_CONCEDE_COLONIES_THRESHOLD +Nombre maximum de colonies pour capituler + +RULE_CONCEDE_COLONIES_THRESHOLD_DESC +Les empires contrôlant un nombre de colonies supérieur à cette valeur ne peuvent pas capituler. + +RULE_THRESHOLD_HUMAN_PLAYER_WIN +Maximum de joueurs humains vainqueurs + +RULE_THRESHOLD_HUMAN_PLAYER_WIN_DESC +Nombre maximum de joueurs humains pouvant gagner collectivement dans une partie multijoueurs s'il n'y a pas d'autres joueurs humains survivants. + +RULE_ONLY_ALLIANCE_WIN +Seuls les joueurs alliés gagnent + +RULE_ONLY_ALLIANCE_WIN_DESC +Les joueurs alliés peuvent gagner collectivement si le nombre de joueurs humains au sein de leur alliance n'est pas supérieur au maximum de joueurs humains vainqueurs et s'il n'y a pas d'autres joueurs humains survivants. + +RULE_SHIP_PART_BASED_UPKEEP +Activer Entretien des Équipements + +RULE_SHIP_PART_BASED_UPKEEP_DESC +Calculer le coût d'entretien selon les équipements de l'astronef plutôt que selon le modèle d'astronef lui-même. + +RULE_ENABLE_ALLIED_REPAIR +Réparation en Cale Sèche et Spatiale pour astronefs alliés + +RULE_ENABLE_ALLIED_REPAIR_DESC +Autorise la structure Cale Sèche Orbitale et la technologie Réparation Spatiale à réparer les astronefs des empires alliés. + +RULE_SHOW_DETAILED_EMPIRES_DATA +Afficher les données détaillées des empires + +RULE_SHOW_DETAILED_EMPIRES_DATA_DESC +Afficher la recherche et la production, les structures acquises, coques et équipements, de tous les empires. + +RULE_DIPLOMACY +Interactions Diplomatie + +RULE_DIPLOMACY_DESC +Gère les restrictions en matière de diplomatie entre les joueurs. Par défaut, toutes les interactions sont autorisées. + +RULE_DIPLOMACY_ALLOWED_FOR_ALL +Toutes autorisées + +RULE_DIPLOMACY_FORBIDDEN_FOR_ALL +Toutes interdites + ## ## Command-line and options database entries ## +COMMAND_LINE_NOT_FOUND +Aucune option ou section correspondante trouvée + COMMAND_LINE_USAGE -'''Usage : ''' +Usage: -h | --help [nom groupe | nom option] COMMAND_LINE_DEFAULT -'''Défaut : ''' +Défaut + +COMMAND_LINE_SECTIONS +Groupes d'Options + +COMMAND_LINE_OPTIONS # translated +Options OPTIONS_DB_HELP -Copier ce message d'aide. +'''Afficher ce message d'aide. +Accepte un argument pour un nom partiel ou complet d'option. +Des arguments spéciaux sont prévus pour: +all - [[OPTIONS_DB_SECTION_ALL]] +raw - [[OPTIONS_DB_SECTION_RAW]]''' OPTIONS_DB_VERSION Copier le numéro de version et quitter. @@ -1072,6 +1288,15 @@ Copier le numéro de version et quitter. OPTIONS_DB_SINGLEPLAYER Démarre le serveur en mode hébergement d'une partie solo. Seuls les clients du serveur local peuvent se connecter. +OPTIONS_DB_HOSTLESS +Démarre le serveur en mode sans hôte. Le serveur accepte les joueurs dans le salon multijoueur, et retourne au salon lorsque la session de jeu est terminée. + +OPTIONS_DB_SKIP_CHECKSUM +Ignore le contrôle du dossier de ressources. Ceci permet un démarrage plus rapide lorsque le client et le serveur se trouve sur la même machine, utilisant le même dossier de ressources. + +OPTIONS_DB_TESTING +Linux uniquement. L'IA enverra le fichier journal vers la console au lieu de créer un fichier externe. + OPTIONS_DB_GENERATE_CONFIG_XML Génère un fichier config.xml à partir des réglages contenus dans les fichiers config.xml existants et donnés dans la ligne de commande. Ceci écrasera le fichier config.xml actuel, s'il existe. @@ -1105,10 +1330,10 @@ OPTIONS_DB_UI_SAVE_DIALOG_COLUMNS Colonnes valides: time, turn, player, empire, systems, seed, galaxy_age, galaxy_shape, planet_freq, native_freq, specials_freq, starlane_freq''' OPTIONS_DB_UI_SAVE_DIALOG_COLUMN_WIDE_AS -Si UI.save-file-dialog.[name].wide-as est activé, la colonne sera toujours assez large pour contenir tout le texte. +Si ui.dialog.save.columns.[name].width.chars est activé, la colonne sera toujours assez large pour contenir tout le texte. OPTIONS_DB_UI_SAVE_DIALOG_COLUMN_STRETCH -Si UI.save-file-dialog.[name].stretch est activé, la colonne permettra ce facteur d'agrandissement si visible. +Si ui.dialog.save.columns.[name].stretch est activé, la colonne permettra ce facteur d'agrandissement si visible. OPTIONS_DB_GALAXY_MAP_GAS Rendu de la densité gazeuse qui donne sa forme à la galaxie. Peut ralentir l'affichage sur les machines les plus anciennes. @@ -1116,6 +1341,9 @@ Rendu de la densité gazeuse qui donne sa forme à la galaxie. Peut ralentir l'a OPTIONS_DB_GALAXY_MAP_STARFIELDS Rendu du champ d'étoiles sur lequel se détachent les systèmes. Peut ralentir l'affichage sur les machines les plus anciennes. +OPTIONS_DB_GALAXY_MAP_STARFIELDS_SCALE +Fixe la taille du fond stellaire autour des systèmes. + OPTIONS_DB_GALAXY_MAP_SCALE_LINE Affiche la barre d'échelle des distances sur la carte galactique. @@ -1155,6 +1383,15 @@ Fixe l'espacement des points indiquant la trajectoire des flottes. OPTIONS_DB_FLEET_SUPPLY_LINE_DOT_RATE Fixe la vitesse de défilement des points indiquant la trajectoire des flottes. +OPTIONS_DB_FLEET_EXPLORE_IGNORE_HOSTILE +Ignorer les astronefs hostiles lorsqu'une flotte est en mode exploration + +OPTIONS_DB_FLEET_EXPLORE_SYSTEM_ROUTE_LIMIT +Limite le nombre de flottes évaluant la meilleure route vers chaque système, pour les flottes réglées en exploration automatique. Une limite basse permet un calcul plus rapide mais peut causer un routage moins optimal. Une valeur de -1 désactive la limite. + +OPTIONS_DB_FLEET_EXPLORE_SYSTEM_KNOWN_MULTIPLIER +Multiplicateur de la valeur de priorité des systèmes inexplorés ayant été déjà vus par l'empire. Une valeur inférieure à 1.0 donnera la priorité aux systèmes connus sur ceux inconnus. + OPTIONS_DB_GALAXY_MAP_DETECTION_RANGE Afficher/Cacher, sur la carte galactique, les zones circulaires autour des objets figurant leur Champ de Détection. @@ -1173,6 +1410,48 @@ Adresse à laquelle se connecter pour rejoindre une partie multijoueurs. OPTIONS_DB_MP_PLAYER_NAME Nom utilisé par le joueur lorsqu'il héberge ou rejoint une partie multijoueurs. +OPTIONS_DB_MP_AI_MIN +Définit le nombre minimum de joueurs IA requis pour démarrer une partie multijoueurs. + +OPTIONS_DB_MP_AI_MAX +Limite le nombre de joueurs IA dans une partie multijoueurs. Une valeur de -1 correspond à un nombre illimité. + +OPTIONS_DB_MP_HUMAN_MIN +Définit le nombre minimum de joueurs humains requis pour démarrer une partie multijoueurs. + +OPTIONS_DB_MP_HUMAN_MAX +Limite le nombre de joueurs humains dans une partie multijoueurs. Une valeur de -1 correspond à un nombre illimité. + +OPTIONS_DB_MP_CONN_HUMAN_MIN +Nombre minimum de joueurs humains actifs, au-dessous duquel le serveur stoppera la partie en cours. + +OPTIONS_DB_MP_UNCONN_HUMAN_MAX +Nombre maximum de joueurs, contrôlant des empires non-éliminés, pouvant être non-connectés au serveur avant que ce dernier ne stoppe la partie en cours. Désactivé pour une valeur de 0, c'est-à-dire que le serveur ne stoppera jamais la partie en raison de joueurs contrôlant un empire et étant non-connectés. + +OPTIONS_DB_COOKIES_EXPIRE +Valeur en minutes au-delà de laquelle le cookie enregistré sera considéré comme expiré. + +OPTIONS_DB_PUBLISH_STATISTICS +Autoriser l'envoi des statistiques d'empire au joueur. + +OPTIONS_DB_PUBLISH_SEED +Autoriser l'envoi de la Graine de galaxie au joueur. + +OPTIONS_DB_FIRST_TURN_TIME +Référence de temps absolu au format "2019-01-20 11:59:59" UTC. Si vide, la fin du premier tour interviendra après l'intervalle d'expiration. Nécessite l'activation de l'intervalle fixe. + +OPTIONS_DB_TIMEOUT_INTERVAL +Intervalle maximum en secondes entre la validation automatique de chaque tour. Si 0, la fin du tour par temps limite est désactivée, et le passage au tour suivant interviendra seulement une fois que tous les joueurs auront terminé leur tour. La validation automatique d'un tour est également désactivée après qu'un joueur ait gagné la partie. + +OPTIONS_DB_TIMEOUT_FIXED_INTERVAL +Le passage au tour suivant est exécuté à intervalle fixe, indépendamment du fait que les joueurs aient envoyé ou non leurs ordres pour le tour en cours. + +OPTIONS_DB_CLIENT_MESSAGE_SIZE_MAX +Limite de taille en octets des messages client. + +OPTIONS_DB_DROP_EMPIRE_READY +Annuler la lecture du statut de l'empire en rejoignant une partie en cours. + OPTIONS_DB_UI_MAIN_MENU_X Position horizontale du centre du menu principal de l'écran d'introduction, en pourcentage de la largeur totale de la fenêtre de jeu ou du plein écran. @@ -1207,7 +1486,7 @@ OPTIONS_DB_MAX_FPS Fixe la valeur limite du Ni/s (nombre d'images par seconde) lorsque sa limitation est activée avec l'option Limitation Ni/s. OPTIONS_DB_LIMIT_FPS_NO_FOCUS -Active/désactive la limitation du Ni/s lorsque la fenêtre principale de jeu n'est pas sélectionnée. La limite est définie avec l'option Ni/s Maximal (hors-fenêtre). +Active/Désactive la limitation du Ni/s lorsque la fenêtre principale de jeu n'est pas sélectionnée. La limite est définie avec l'option Ni/s Maximal (hors-fenêtre). OPTIONS_DB_MAX_FPS_NO_FOCUS Fixe la valeur limite du Ni/s (nombre d'images par seconde) lorsque la fenêtre principale de jeu n'est pas sélectionnée, si la limitation est activée avec l'option Limitation Ni/s (hors-fenêtre). @@ -1222,10 +1501,10 @@ OPTIONS_DB_UI_SOUND_BUTTON_CLICK Adresse du fichier son à jouer lorsqu'un bouton est cliqué. OPTIONS_DB_UI_SOUND_TURN_BUTTON_CLICK -Adresse du fichier son à jouer lorsque le bouton de Tour est cliqué. +Adresse du fichier son à jouer lorsque le bouton Tour est cliqué. OPTIONS_DB_UI_SOUND_NEWTURN_TOGGLE -Active/désactive la lecture d'un fichier son au début d'un nouveau tour. +Active/Désactive la lecture d'un fichier son au début d'un nouveau tour. OPTIONS_DB_UI_SOUND_NEWTURN_FILE Le fichier son joué au début d'un nouveau tour, si activé. @@ -1380,6 +1659,24 @@ Définit la couleur des rayures horizontales (brouillard de guerre) sur les icô OPTIONS_DB_UI_PLANET_FOG_CLR Définit la couleur des rayures horizontales (brouillard de guerre) sur les planètes non-visibles dans le panneau latéral. +OPTIONS_DB_UI_PLANET_STATUS_ICON_SIZE +Fixe la taille de l'icône de statut d'une planète. + +OPTIONS_DB_UI_PLANET_STATUS_ICON_TITLE +Notification statut de planète + +OPTIONS_DB_UI_PLANET_STATUS_ATTACKED +La planète %1% a été attaquée au tour précédent. + +OPTIONS_DB_UI_PLANET_STATUS_CONQUERED +La planète %1% vient d'être conquise. Le Bonheur sera probablement réduit. + +OPTIONS_DB_UI_PLANET_STATUS_UNHAPPY +La Population sur la planète %1% est très mécontente. Ni l'[[metertype METER_INDUSTRY]] ni la [[metertype METER_RESEARCH]] n'augmenteront. Cette population ne peut entreprendre aucune colonisation. + +OPTIONS_DB_UI_PLANET_STATUS_NO_SUPPLY +La planète %1% est déconnectée des lignes d'Approvisionnement. L'Industrie générée sur cette planète ne peut être utilisée que localement. + OPTIONS_DB_UI_SYSTEM_CIRCLES Affiche/Cache sur la carte galactique les cercles entourant les icônes de systèmes stellaires. @@ -1411,7 +1708,7 @@ OPTIONS_DB_UI_SMALL_FLEET_BUTTON_MIN_ZOOM Fixe le niveau minimal de zoom auquel les icônes de petite taille des flottes apparaissent sur la carte galactique. OPTIONS_DB_UI_TINY_FLEET_BUTTON_MIN_ZOOM -Fixe le niveau minimal de zoom auquel les icônes de taille minimale des flottes apparaissent sur la carte galactique. À un niveau de zoom inférieur, aucune icône de flotte n'est visible sur la carte galactique. +Fixe le niveau minimal de zoom auquel les icônes de taille minimum des flottes apparaissent sur la carte galactique. À un niveau de zoom inférieur, aucune icône de flotte n'est visible sur la carte galactique. OPTIONS_DB_UI_FLEET_SELECTION_INDICATOR_SIZE Fixe la taille sur la carte galactique de l'indicateur de sélection d'une flotte, relativement à la taille des icônes de flotte. @@ -1432,7 +1729,7 @@ OPTIONS_DB_UI_SYSTEM_SELECTION_INDICATOR_FPS Fixe la vitesse de rotation (en tours par minute) de l'indicateur de sélection d'un système stellaire. OPTIONS_DB_UI_SYSTEM_TINY_ICON_SIZE_THRESHOLD -Fixe la taille des icônes de système stellaire au-dessous de laquelle les icônes minimales affichées auront une taille fixe. +Fixe la taille des icônes de système stellaire au-dessous de laquelle les icônes minimales de taille fixe seront affichées. OPTIONS_DB_UI_TOOLTIP_DELAY Fixe le délai d'apparition dans l'interface utilisateur des bulles d'information, en millisecondes. @@ -1441,21 +1738,21 @@ OPTIONS_DB_UI_TOOLTIP_LONG_DELAY Fixe le délai d'apparition dans l'interface utilisateur des bulles d'information alternatives, en millisecondes. OPTIONS_DB_UI_ENC_SEARCH_ARTICLE -Lors d'une recherche dans l'Encyclopédie, vérifier les correspondances dans le contenu des articles. +Lors d'une recherche dans l'Encyclopédie, vérifier les correspondances dans le contenu des articles. La recherche sera plus lente, mais peut retourner davantage de résultats. # Section title for options window logging OPTIONS_DB_UI_LOGGER_THRESHOLDS Seuils de journalisation OPTIONS_DB_UI_LOGGER_THRESHOLD_TOOLTIP -'''Chaque fichier journal (freeorion.log, freeoriond.log, and AI_x.log) collecte les événements à partir de différentes sources (sections de code). -Régler le seuil pour une source rendra possible l'enregistrement des événements pour ce niveau et au-delà quant à la source en question. +'''Chaque fichier journal (freeorion.log, freeoriond.log, and AI_x.log) collecte les événements à partir de différentes sources (sections de code). +Régler le seuil pour une source rendra possible l'enregistrement des événements pour ce niveau et au-delà quant à la source en question. Chaque processus client, serveur, et IA dispose d'une source générale et peut avoir des sources détaillées additionnelles (eg. log de résolution de combat).''' # This is a label to designate the logger as the general or default logger for a process # %1% is the name of a process logger, client, server or ai -OPTIONS_DB_UI_LOGGER_PER_PROCESS_GENERAL -Général %1% +#OPTIONS_DB_UI_LOGGER_PER_PROCESS_GENERAL +#%1% general OPTIONS_DB_KEYPRESS_REPEAT_DELAY Fixe le délai entre le maintien d'une touche et l'activation de la répétition de la même touche. @@ -1469,6 +1766,9 @@ Fixe le délai entre le maintien d'un bouton de la souris et l'activation de la OPTIONS_DB_MOUSE_REPEAT_INTERVAL Fixe le délai de répétition de clics quand un bouton de la souris est maintenu. +OPTIONS_DB_DISPLAY_TIMESTAMP +Active l'horodatage des messages dans la fenêtre de messagerie. + OPTIONS_DB_UI_MULTIPLE_FLEET_WINDOWS Si activé, cliquer sur plusieurs boutons de flotte ouvrent plusieurs Fenêtres Flottes en même temps. Si désactivé, toute ouverture d'une Fenêtre Flottes ferme celle précédemment affichée. @@ -1509,6 +1809,9 @@ OPTIONS_DB_GAMESETUP_SEED '''La Graine de hasard initie la génération d'une galaxie. Les galaxies générées avec les mêmes options et la même Graine de hasard seront identiques.''' +OPTIONS_DB_GAMESETUP_UID +L'UID de la partie pour les nouvelles parties. L'option serveur-seul peut être utilisée pour obtenir un nom de partie prévisible sur les serveurs sans hôte. + OPTIONS_DB_GAMESETUP_STARS '''Le nombre approximatif de systèmes stellaires à générer dans la galaxie. @@ -1555,7 +1858,7 @@ Certaines espèces indigènes peuvent être technologiquement avancées et crée OPTIONS_DB_GAMESETUP_AI_MAX_AGGRESSION '''Le niveau maximal d'agressivité des adversaires gérés par l'ordinateur. -La plupart des IAs seront réglées sur la valeur définie, certaines pourront se situer à un niveau inférieur. Tous les adversaires se manifesteront dans la Fenêtre Messages lors du premier tour, vous indiquant ainsi leur niveau.''' +La plupart des IAs seront réglées sur la valeur définie, certaines pourront se situer à un niveau inférieur. Tous les adversaires se manifesteront dans la Fenêtre Messages lors du premier tour, indiquant ainsi leur niveau.''' OPTIONS_DB_GAMESETUP_EMPIRE_NAME Le nom de votre empire dans le jeu. @@ -1591,7 +1894,10 @@ OPTIONS_DB_UI_TECH_CTRL_ICON_SIZE Ajuste la taille des icônes de la barre d'affichage des technologies dans la Fenêtre Recherche. OPTIONS_DB_SAVE_DIR -L'adresse du répertoire où sauvegarder (et d'où charger) les parties. +L'adresse du répertoire où sauvegarder et à partir duquel charger les parties. + +OPTIONS_DB_SERVER_SAVE_DIR +Le répertoire public pour les parties sauvegardées sur le serveur. OPTIONS_DB_RESOURCE_DIR L'adresse du répertoire où sont localisées les ressources du jeu (fichiers exécutables et fichiers de données). @@ -1599,6 +1905,9 @@ L'adresse du répertoire où sont localisées les ressources du jeu (fichiers ex OPTIONS_DB_LOG_LEVEL Outrepasse tous les seuils d'enregistrement à ce niveau ou au-delà, à partir duquel les messages du journal seront créés. +OPTIONS_DB_LOG_FILE +Outrepasse l'emplacement par défaut du fichier journal. + OPTIONS_DB_LOGGER_FILE_SINK_LEVEL Fixe le seuil à partir duquel les messages du journal seront générés pour la source par défaut de ce processus. @@ -1612,7 +1921,7 @@ OPTIONS_DB_AI_FOLDER_PATH Définit le chemin du dossier contenant les fichiers de script de l'IA, pour l'exécution en cours uniquement, par rapport au dossier principal; par défaut "AI". A pour but de faciliter le test de l'IA. OPTIONS_DB_AI_CONFIG -Est disponible pour l'IA via l'interface freeorion, et utilisé pour l'éxécution en cours uniquement. Permet de nommer un fichier de configuration optionnel de l'IA à l'intérieur du dossier de scripts de l'IA; par défaut une valeur nulle. A pour but de faciliter le test de l'IA. +Est disponible pour l'IA via l'interface freeorion, et utilisé pour l'éxécution en cours uniquement. Permet de nommer un fichier de configuration optionnel de l'IA à l'intérieur du dossier de scripts de l'IA; par défaut une valeur nulle. A pour but de faciliter le test de l'IA. OPTIONS_DB_AI_CONFIG_TRAIT_AGGRESSION_FORCED Valeur boléenne pour test d'IA indiquant si toutes les IAs ont forcément le même Niveau d'Agressivité. @@ -1626,11 +1935,17 @@ Valeur boléenne pour test d'IA indiquant si toutes les IAs ont forcément le m OPTIONS_DB_AI_CONFIG_TRAIT_EMPIREID_FORCED_VALUE Valeur forcée pour le Niveau selon l'EmpireID. Une valeur de 0 à 39. -OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER -Si activé, les parties solo bénéficieront d'une sauvegarde automatique régulière. +OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER_TURN_START +Active la sauvegarde automatique au début de chaque tour pour les parties solo. + +OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER_TURN_END +Active la sauvegarde automatique à la fin de chaque tour pour les parties solo. -OPTIONS_DB_AUTOSAVE_MULTIPLAYER -Si activé, les parties multijoueurs bénéficieront d'une sauvegarde automatique régulière. +OPTIONS_DB_AUTOSAVE_MULTIPLAYER_TURN_START +Active la sauvegarde automatique au début de chaque tour pour les parties multijoueurs. + +OPTIONS_DB_AUTOSAVE_MULTIPLAYER_TURN_END +Active la sauvegarde automatique à la fin de chaque tour pour les parties multijoueurs. OPTIONS_DB_AUTOSAVE_TURNS Fixe le nombre de tours séparant les sauvegardes automatiques. @@ -1638,6 +1953,18 @@ Fixe le nombre de tours séparant les sauvegardes automatiques. OPTIONS_DB_AUTOSAVE_LIMIT Fixe le nombre maximal de sauvegardes automatiques à conserver. +OPTIONS_DB_AUTOSAVE_GALAXY_CREATION +Active une sauvegarde automatique dès que la galaxie est créée et avant toute action de jeu. + +OPTIONS_DB_AUTOSAVE_GAME_CLOSE +Active une sauvegarde automatique lors d'un abandon ou de la fermeture du jeu. En mode multijoueurs sans hôte, active une sauvegarde automatique lorsque le serveur ferme une partie suite à la déconnexion d'un joueur ou lorsque le serveur est arrêté. + +OPTIONS_DB_AUTOSAVE_HOSTLESS +Active la sauvegarde automatique en mode sans hôte. + +OPTIONS_DB_AUTOSAVE_INTERVAL +Délai en secondes après le tour le plus récent ou dernière sauvegarde automatique, jusqu'à la prochaine sauvegarde automatique. Évite la perte des ordres du joueurs lors d'un crash de serveur. 0 si désactivé. + OPTIONS_DB_UI_MOUSE_LR_SWAP Si activé, les effets des clics droit et gauche de la souris seront inversés. @@ -1645,7 +1972,10 @@ OPTIONS_DB_MUSIC_VOLUME Fixe le volume sonore de la musique (de 0 à 255). OPTIONS_DB_QUICKSTART -Lance une nouvelle partie en court-circuitant le menu principal. +Lance une nouvelle partie en court-circuitant le menu principal en mode solo ou le salon en multijoueurs. En multijoueurs, nécessite le mode sans hôte et aucune restriction sur le nombre minimum de joueurs connectés, car le serveur démarrera sans joueurs connectés. + +OPTIONS_DB_CONTINUE +Reprend une partie depuis la sauvegarde la plus récente, sans passer par le menu principal. OPTIONS_DB_AUTO_N_TURNS Clique le bouton "Tour" automatiquement pour les N premiers tours (jusqu'à 400 tours, valeur défaut à zéro); utile pour le débogage notamment avec --quickstart ou --load, et éventuellement --auto-quit. @@ -1669,7 +1999,7 @@ OPTIONS_DB_UI_SITREP_ICONSIZE Définit la largeur et la hauteur des icônes du Rapport de Situation; par défaut 16 (min 12, max 64). OPTIONS_DB_LOAD -Charge la partie solo sauvegardée spécifiée. +Charge et démarre la partie sauvegardée solo ou multijoueurs spécifiée. En multijoueurs, nécessite le mode sans hôte et aucune restriction sur le nombre minimum de joueurs connectés, car le serveur démarrera sans joueurs connectés. OPTIONS_DB_EFFECTS_THREADS_UI_DESC Spécifie le nombre de threads simultanés pour le rendu des effets dans l'interface utilisateur. Plus d'un thread peut mener à des plantages inopinés. @@ -1686,6 +2016,9 @@ Quitter automatiquement une fois au tour spécifié par --auto-advance-n-turns ( OPTIONS_DB_BINARY_SERIALIZATION Utilise la sérialisation binaire pour les fichiers de sauvegarde. La sérialisation binaire est plus rapide lors de la sauvegarde et du chargement, mais peut rencontrer des problèmes de compatibilité si le fichier de sauvegarde est utilisé sur un système d'exploitation différent. +OPTIONS_DB_SERVER_BINARY_SERIALIZATION +Le serveur utilisera la sérialisation binaire pour l'interaction client-serveur si la version client correspond à la version serveur. La sérialisation binaire est plus rapide à transmettre, mais peut s'avérer incompatible entre différents systèmes d'exploitation et architectures. + OPTIONS_DB_XML_ZLIB_SERIALIZATION Lors d'une sauvegarde utilisant la sérialisation XML, compresser une grande partie du code XML avant de créer le fichier. La compression réduit substantiellement la taille du fichier de sauvegarde, mais peut rendre impossible le chargement dudit fichier en raison de la quantité de mémoire nécessaire pour décompresser les données de sauvegarde. @@ -1717,7 +2050,7 @@ OPTIONS_DB_UI_WINDOWS_HEIGHT_WINDOWED La hauteur d'une fenêtre en mode fenêtré. OPTIONS_DB_UI_WINDOWS_VISIBLE -Active/désactive l'affiche d'une fenêtre, lorsque celle-ci est disponible. +Active/Désactive l'affichage d'une fenêtre, lorsque celle-ci est disponible. OPTIONS_DB_UI_WINDOWS_PINNED Définit si une fenêtre est ancrée. @@ -1777,6 +2110,89 @@ OPTIONS_DB_NETWORK_MESSAGE_PORT Port réseau à utiliser pour la communication client-serveur. +## +## Option sections +## + +OPTIONS_DB_SECTION_ALL +Toutes les options + +OPTIONS_DB_SECTION_AUDIO # translated +Audio + +OPTIONS_DB_SECTION_AUDIO_MUSIC +Musique de fond + +OPTIONS_DB_SECTION_AUDIO_EFFECTS +Effets sonores + +OPTIONS_DB_SECTION_AUDIO_EFFECTS_PATHS +Chemins des Effets sonores + +OPTIONS_DB_SECTION_EFFECTS +Effets + +OPTIONS_DB_SECTION_LOGGING +Connexion + +OPTIONS_DB_SECTION_MISC +Divers + +OPTIONS_DB_SECTION_NETWORK +Réseau et serveur + +OPTIONS_DB_SECTION_RAW +Toutes les options avec seulement les descriptions brutes des clés + +OPTIONS_DB_SECTION_RESOURCE +Ressources des données de jeu + +OPTIONS_DB_SECTION_SAVE +Sauvegarde de partie + +OPTIONS_DB_SECTION_SETUP +Réglages de départ pour une nouvelle partie + +OPTIONS_DB_SECTION_UI +Interface utilisateur + +OPTIONS_DB_SECTION_UI_COLORS +Couleurs + +OPTIONS_DB_SECTION_UI_CONTROL +Contrôle (générique) + +OPTIONS_DB_SECTION_UI_HOTKEYS +Raccourcis clavier + +OPTIONS_DB_SECTION_UI_MAP +Carte principale + +OPTIONS_DB_SECTION_UI_MAP_FLEET +Flottes (carte) + +OPTIONS_DB_SECTION_UI_FLEET +Fenêtre Flottes + +OPTIONS_DB_UI_WINDOW +Fenêtre (générique) + +OPTIONS_DB_SECTION_VERSION # translated +Version + +OPTIONS_DB_SECTION_VIDEO +Vidéo + +OPTIONS_DB_SECTION_VIDEO_FPS +Frames par seconde + +OPTIONS_DB_SECTION_VIDEO_FULLSCREEN +Mode plein écran + +OPTIONS_DB_SECTION_VIDEO_WINDOWED +Mode fenêtré + + ## ## File dialog ## @@ -1842,6 +2258,9 @@ A INTRO_WINDOW_TITLE Menu principal +INTRO_BTN_CONTINUE +Continuer + INTRO_BTN_SINGLE_PLAYER Partie solo @@ -1887,24 +2306,38 @@ SCONNECT_WINDOW_TITLE Connexion au Serveur LAN_GAME_LABEL -Partie locale chez : +Session locale chez : INTERNET_GAME_LABEL -Partie Internet chez : +Session Internet chez : PLAYER_NAME_LABEL Nom du joueur HOST_GAME_BN -Héberger une partie +Héberger une nouvelle partie JOIN_GAME_BN -Rejoindre une partie +Rejoindre partie en REFRESH_LIST_BN Rafraîchir +## +## Password Dialog +## + +AUTHENTICATION_WINDOW_TITLE +Authentification + +AUTHENTICATION_DESC +Ce nom de joueur requiert une authentification. Rappel! Le mot de passe sera transmis en simple format texte. + +PASSWORD_LABEL +Mot de Passe + + ## ## Multiplayer lobby ## @@ -1933,6 +2366,12 @@ Joueur précédent MULTIPLAYER_PLAYER_LIST_STARTING_SPECIES Espèce de départ +EDITABLE_GALAXY_SETTINGS +Réglages modifiables par tous + +EDITABLE_GALAXY_SETTINGS_DESC +Tous les joueurs peuvent modifier les réglages de la galaxie et de l'IA. + NEW_GAME_BN Nouvelle partie @@ -1948,6 +2387,18 @@ Accepter NOT_READY_BN Refuser +# %1% is a turn progress phase +PLAYING_GAME +Partie en cours: %1% + +# %1% is a player name +PLAYER_ENTERED_GAME +%1% a rejoint la partie + +# %1% is a player name +PLAYER_LEFT_GAME +%1% a quitté la partie + ## ## Galaxy Setup Screen @@ -1975,7 +2426,7 @@ GSETUP_SEED Graine de hasard GSETUP_RANDOM_SEED -Génère une Graine de hasard aléatoire +Active/Désactive la génération d'une Graine de hasard aléatoire GSETUP_STARS Systèmes @@ -2132,7 +2583,10 @@ GAME_MENU_LOAD Charger GAME_MENU_RESIGN -Abandonner +Quitter vers Menu + +GAME_MENU_CONCEDE +Capituler GAME_MENU_SAVE_FILES Fichiers de sauvegarde @@ -2141,7 +2595,19 @@ BUTTON_DISABLED Bouton désactivé SAVE_DISABLED_BROWSE_TEXT -Le bouton "Sauvegarder" est désactivé, soit parce qu'il s'agit d'une partie multijoueurs dont vous n'êtes ni l'Hôte ni le Modérateur, soit parce que le menu a été ouvert alors qu'au moins une des IAs n'a pas encore terminé son tour de jeu (indiqué par un triangle vert à gauche de l'icône représentant le type de joueur dans la fenêtre Empires). +'''Le bouton "Sauvegarder" est désactivé. Possibles causes: + +- Il s'agit d'une partie multijoueurs dont ce client n'est ni l'Hôte ni le Modérateur. + +- Le menu a été ouvert alors qu'au moins une des IAs n'a pas encore terminé son tour de jeu (indiqué par un triangle vert à gauche de l'icône représentant le type de joueur dans la fenêtre Empires). + +- Une autre opération de sauvegarde est déjà en cours.''' + +GAME_MENU_REALLY_CONCEDE +Confirmer la capitulation? Cet empire sera supprimé de cette partie. + +GAME_MENU_CONFIRM_NOT_READY +Confirmer quitter? Ordres non finalisés. ## @@ -2226,11 +2692,11 @@ Chemin: # %1% entered path to the save game file that should be deleted. SAVE_REALLY_DELETE -Êtes-vous sûr(e) de vouloir supprimer %1%? +Supprimer définitivement %1%? # %1% entered path to the save game file that should be overwritten. SAVE_REALLY_OVERRIDE -Êtes-vous sûr(e) de vouloir écraser %1%? +Écraser définitivement %1%? ## @@ -2240,6 +2706,9 @@ SAVE_REALLY_OVERRIDE OPTIONS_TITLE # translated Options +OPTIONS_PEDIA_SEARCH_ARTICLE_TEXT +Rechercher dans contenu article Encyclopédie + OPTIONS_MULTIPLE_FLEET_WNDS Fenêtres Flottes multiples @@ -2252,15 +2721,12 @@ Affichage des planètes dans le panneau latéral OPTIONS_AUTO_REPOSITION_WINDOWS Repositionnement automatique des fenêtres +OPTIONS_DISPLAY_TIMESTAMP +Afficher horodatage messagerie + OPTIONS_MISC_UI Options diverses de l'interface utilisateur -OPTIONS_SINGLEPLAYER -Parties solo - -OPTIONS_MULTIPLAYER -Parties multijoueurs - OPTIONS_AUTOSAVE_LIMIT Nombre maximal de sauvegardes automatiques @@ -2279,6 +2745,9 @@ Taille des polices OPTIONS_FONT_TEXT Texte +OPTIONS_FONT_BOLD_TEXT +Texte gras + OPTIONS_FONT_TITLE Titres des fenêtres @@ -2418,13 +2887,13 @@ OPTIONS_FLEET_ICONS Icônes de Flotte OPTIONS_UI_TINY_FLEET_BUTTON_MIN_ZOOM -Zoom minimal de l'icône d'une flotte minuscule +Zoom minimal icône minuscule d'une flotte OPTIONS_UI_SMALL_FLEET_BUTTON_MIN_ZOOM -Zoom minimal de l'icône d'une petite flotte +Zoom minimal petite icône d'une flotte OPTIONS_UI_MEDIUM_FLEET_BUTTON_MIN_ZOOM -Zoom minimal de l'icône d'une flotte moyenne +Zoom minimal icône moyenne d'une flotte OPTIONS_UI_FLEET_SELECTION_INDICATOR_SIZE Taille relative du sélecteur de flottes @@ -2436,7 +2905,10 @@ OPTIONS_GALAXY_MAP_GAS Rendu du fond nébuleux de la galaxie OPTIONS_GALAXY_MAP_STARFIELDS -Rendu du fond étoilé de la galaxie +Rendu du fond stellaire de la galaxie + +OPTIONS_GALAXY_MAP_STARFIELDS_SCALE +Taille des étoiles du fond stellaire de la galaxie OPTIONS_GALAXY_MAP_SCALE_LINE Barre d'échelle des distances galactiques @@ -2675,6 +3147,9 @@ Fichiers de ressources OPTIONS_FOLDER_SAVE Fichiers de sauvegarde +OPTIONS_SERVER_FOLDER_SAVE +Fichiers de sauvegarde Serveur + OPTIONS_LANGUAGE_FILE Fichiers de langue @@ -2723,6 +3198,49 @@ Utiliser des fichiers de sauvegarde binaire OPTIONS_USE_XML_ZLIB_SERIALIZATION Compresser les fichiers de sauvegarde XML +OPTIONS_CREATE_PERSISTENT_CONFIG +Écrire Fichier de Configuration Persistante + +OPTIONS_CREATE_PERSISTENT_CONFIG_TOOLTIP_TITLE +Créer Configuration Persistante (Option Avancée) + +OPTIONS_CREATE_PERSISTENT_CONFIG_TOOLTIP_DESC +'''Sauvegarde les réglages actuels de configuration comme réglages persistants par défaut. + +Ces réglages sont prioritaires sur tout réglage du fichier config.xml, mais pas sur la ligne de commande. + +N'enregistre que les réglages dont la valeur par défaut a été modifiée. + +Tout fichier de configuration persistante déjà existant est remplacé. + +Les réglages peuvent ne plus être valides d'une version à l'autre de Freeorion. +Les réglages définis pour une fenêtre du jeu peuvent ne pas être conservés si cette dernière n'a pas été ouverte lors de la session de jeu en cours. + +Se référer à l'article [[CONFIG_GUIDE_TITLE]] pour davantage d'informations.''' + +OPTIONS_CREATE_PERSISTENT_CONFIG_SUCCESS +Fichier persistent_config.xml (re)créé avec succès. + +OPTIONS_CREATE_PERSISTENT_CONFIG_FAILURE +Impossible de créer le fichier persistent_config.xml, consultez le fichier log pour plus de détails. + +OPTIONS_CREATE_ALL_CONFIG_SUCCESS +Fichier config.xml créé avec succès. + +OPTIONS_CREATE_ALL_CONFIG_FAILURE +Impossible de créer le fichier config.xml, consultez le fichier log pour plus de détails. + +OPTIONS_WRITE_ALL_CONFIG +Écrire Fichier de Configuration Complète + +OPTIONS_CREATE_ALL_CONFIG_TOOLTIP_TITLE +Créer Configuration Complète (Option Intermédiaire) + +OPTIONS_CREATE_ALL_CONFIG_TOOLTIP_DESC +'''Sauvegarde les réglages actuels dans le fichier config.xml en incluant tous les réglages, même ceux dont les valeurs par défaut n'ont pas été modifiées. + +Le fichier options.xml existant est remplacé, mais peut être recréé (avec seulement les réglages modifiés) si une nouvelle option est modifiée, y compris le repositionnement de certaines fenêtres de jeu ou via le Menu Options.''' + ## ## Main map window @@ -2732,13 +3250,68 @@ Compresser les fichiers de sauvegarde XML MAP_BTN_TURN_UPDATE Tour %1% +# %1% current turn number. +MAP_BTN_TURN_UNREADY +Réviser Ordres %1% + +MAP_BTN_TURN_TOOLTIP +Lancer Tour + +MAP_BTN_TURN_TOOLTIP_DESC_SP +'''Lance le traitement du tour selon la séquence suivante: + +[[MAP_BTN_TURN_TOOLTIP_SEQUENCE_MACRO]] +''' + +MAP_BTN_TURN_TOOLTIP_DESC_MP +'''Envoie les ordres au serveur. Le traitement du tour sera exécuté selon la séquence suivante: + +[[MAP_BTN_TURN_TOOLTIP_SEQUENCE_MACRO]] +''' + +MAP_BTN_TURN_TOOLTIP_DESC_OBS +Les spectateurs ne peuvent pas lancer le traitement d'un tour. + +MAP_BTN_TURN_TOOLTIP_DESC_MOD +'''Lance le traitement du tour même si certains joueurs n'ont pas envoyé leurs ordres au serveur. +Le traitement du tour sera exécuté selon la séquence suivante: + +[[MAP_BTN_TURN_TOOLTIP_SEQUENCE_MACRO]] +''' + +MAP_BTN_TURN_TOOLTIP_DESC_WAIT +En attente des ordres des autres joueurs. Cliquez à nouveau pour réviser vos ordres. + +MAP_BTN_TURN_TOOLTIP_SEQUENCE_MACRO +'''Exécution Ordres +Colonisation, Invasion, Bombardement, Dons, Démolition et Déplacement des Flottes + +Résolution Combats + +Production, Recherche et Croissance +Application des effets, production des astronefs et structures, déverrouillage des nouvelles technologies, croissance de la Population, mise à jour de la propagation de l'Approvisionnement''' + +# %1% number of seconds +MAP_TIMEOUT_SECONDS +%1% secondes + +# %1% number of minutes +# %2% number of seconds +MAP_TIMEOUT_MINS_SECS # translated +%1% mins %2% secs + +# %1% number of hours +# %2% number of minutes +MAP_TIMEOUT_HRS_MINS # translated +%1% hrs %2% mins + # %1% number of frames currently displayed per second. MAP_INDICATOR_FPS %1% i/s # %1% number of 'universe units' which the bar scale on the map represents. -#MAP_SCALE_INDICATOR -#%1% uu +MAP_SCALE_INDICATOR # translated +%1% uu MAP_BTN_MENU # translated Menu @@ -2821,39 +3394,43 @@ MAP_BTN_MESSAGES_DESC MAP_PRODUCTION_TITLE # translated Production -MAP_PROD_WASTED_TITLE -Production inutilisée +MAP_PRODUCTION_WASTED_TITLE +Production gaspillée -# %1% number of production points generated. -# %2% number of production points currently not used for production. -MAP_PROD_WASTED_TEXT -'''Production totale : %1% -Production gaspillée : %2% +MAP_PROD_CLICK_TO_OPEN +''' +Cliquez ici pour ouvrir le menu de Production''' -Cliquer pour ouvrir le menu de production''' +MAP_RESEARCH_TITLE +Recherche -RESOURCE_TT_USED -Utilisée +MAP_RESEARCH_WASTED_TITLE +Recherche gaspillée + +MAP_RES_CLICK_TO_OPEN +''' +Cliquez ici pour ouvrir le menu de Recherche''' + +MAP_STOCKPILE_TITLE +Stock Impérial RESOURCE_TT_OUTPUT -Capacité actuelle +Capacité RESOURCE_TT_TARGET_OUTPUT -Capacité prévue +Projection Capacité -MAP_RESEARCH_TITLE -Recherche +RESOURCE_TT_USED +Utilisée -MAP_RES_WASTED_TITLE -Recherche inutilisée +RESOURCE_TT_EXCESS +Excédent -# %1% number of research points generated. -# %2% number of research points currently not used for research. -MAP_RES_WASTED_TEXT -'''Recherche totale : %1% -Recherche gaspillée : %2% +RESOURCE_TT_TO_STOCKPILE +Vers Stock -Cliquer pour ouvrir le menu de recherche''' +RESOURCE_TT_WASTED +Gaspillée MAP_POPULATION_DISTRIBUTION Population @@ -2913,7 +3490,7 @@ Système - %1% : %2% # %1% resource type currently consumed by the system. # %2% amount of the resource currently consumed by the system. RESOURCE_ALLOCATION_TOOLTIP -Syst. - %1% : %2% +Système - %1% : %2% INDUSTRY_PRODUCTION Capacité industrielle @@ -2924,6 +3501,9 @@ Capacité scientifique TRADE_PRODUCTION Capacité commerciale +STOCKPILE_GENERATION +Génération Stock + INDUSTRY_CONSUMPTION Consommation industrielle @@ -2933,6 +3513,9 @@ Consommation scientifique TRADE_CONSUMPTION Consommation commerciale +STOCKPILE_USE +Usage Stock + IMPORT_EXPORT_TOOLTIP # translated Import / Export @@ -2984,14 +3567,15 @@ Annuler bombardement # %1% the planet size as text (small, medium, large, ...). # %2% type of the planet environment (desert, oceanic, terran, ...). -#PL_TYPE_SIZE -#%1% %2% +PL_TYPE_SIZE +%1% %2% # %1% the planet size as text (small, medium, large, ...). # %2% type of the planet environment (desert, oceanic, terran, ...). # %3% suitability of the planet for a concrete species (adequate, hostile, ...). -#PL_TYPE_SIZE_ENV -#%1% %2% (%3%) +# %4% species name for which suitability applies (Human, Trith, ...). +PL_TYPE_SIZE_ENV +%1% %2% (%4%: %3%) PL_NO_VISIBILITY La planète n'est pas visible actuellement. @@ -3028,11 +3612,17 @@ La dernière [[metertype METER_STEALTH]] connue est obsolète. La furtivité ré MENUITEM_SET_FOCUS Régler Focus -MENUITEM_ENQUEUE_BUILDING -Produire Structure +MENUITEM_ENQUEUE_SHIPDESIGN_TO_TOP_OF_QUEUE +Ajouter Astronef en tête de file d'attente MENUITEM_ENQUEUE_SHIPDESIGN -Produire Astronef +Ajouter Astronef en file d'attente + +MENUITEM_ENQUEUE_BUILDING_TO_TOP_OF_QUEUE +Ajouter Structure en tête de file d'attente + +MENUITEM_ENQUEUE_BUILDING +Ajouter Structure en file d'attente ## @@ -3397,6 +3987,9 @@ Créer une Flotte pour chaque type d'astronef FW_ORDER_DISMISS_SENSOR_GHOST Supprimer les données obsolètes de Détection +FW_ORDER_DISMISS_SENSOR_GHOST_ALL +Supprimer ici toutes les données obsolètes de Détection + ORDER_SHIP_SCRAP Envoyer l'astronef à la casse @@ -3427,6 +4020,7 @@ Offrir la planète à... ORDER_CANCEL_GIVE_PLANET Annuler l'offre de la planète + ## ## Fleet Button ## @@ -3440,6 +4034,7 @@ Cette flotte est actuellement soumise au blocus d'une flotte hostile, et ne peut FB_TOOLTIP_BLOCKADE_MONSTER Cette flotte de monstres est soumise au blocus d'une flotte de l'empire. Les monstres bloqués par des flottes impériales ne peuvent plus se déplacer. + ## ## Moderator ## @@ -3476,9 +4071,18 @@ Diplomatie PEACE Paix +AT_PEACE_WITH +En paix avec + WAR Guerre +AT_WAR_WITH +En guerre avec + +ALLIED_WITH +Allié avec + WAR_DECLARATION Déclarer la guerre @@ -3602,13 +4206,31 @@ PRODUCTION_INFO_EMPIRE %1% %2% PRODUCTION_INFO_TOTAL_PS_LABEL -Total Points disponibles +Points disponibles PRODUCTION_INFO_WASTED_PS_LABEL -Points gaspillés +Excédent + +PRODUCTION_INFO_STOCKPILE_PS_LABEL +Points stockés + +PRODUCTION_INFO_STOCKPILE_USE_MAX_LABEL +Usage maximum -PRODUCTION_INFO_LOCAL_PS_LABEL -Points disponibles ici +PRODUCTION_INFO_STOCKPILE_USE_PS_LABEL +Usage Stock + +STOCKPILE_LABEL +Stock + +STOCKPILE_USE_LABEL +Usage Stock + +STOCKPILE_USE_LIMIT +Limite Usage Stock + +STOCKPILE_CHANGE_LABEL +Stock prochain tour ## @@ -3657,6 +4279,10 @@ Disponibles PRODUCTION_WND_AVAILABILITY_UNAVAILABLE Indisponibles +PRODUCTION_WND_AVAILABILITY_OBSOLETE_AND_UNAVAILABLE +'''Obsolètes +Indisponibles''' + PRODUCTION_WND_REDUNDANT Redondants @@ -3711,10 +4337,10 @@ La Fenêtre Production permet d'interagir directement avec la carte de la [[enc (position par défaut: en haut à gauche) Le panneau "Récapitulation de Production" affiche les Points de Production disponibles. -"Total Points disponibles" fait référence aux PP disponibles à travers tout l'empire. -"Points disponibles ici" concerne le maximum de points disponibles pour le système sélectionné. Cette valeur peut être inférieure au total des points disponibles lorsque tous les systèmes de votre empire ne sont pas reliés par lignes d'[[metertype METER_SUPPLY]]. -"Points gaspillés" indique le nombre de points qui seront perdus si ceux-ci ne sont pas alloués avant la fin du tour. - +Si un système est sélectionné, deux colonnes sont visibles. La colonne de gauche correspond aux points de production à travers tout l'empire. La colonne de droite affiche les points de production pour le système sélectionné (absente si aucun système n'est sélectionné). +"Points disponibles" fait référence aux PP disponibles pour la Production. La valeur de droite correspond au maximum de points disponibles pour le système sélectionné. Cette valeur peut être inférieure au total disponible lorsque tous les systèmes de votre empire ne sont pas reliés par lignes d'[[metertype METER_SUPPLY]]. +Le Stock correspond aux PP disponibles pour la Production, et provenant du Stock Impérial. Les Projets de Production sont autorisés à puiser par défaut dans le Stock Impérial, excepté si son usage a été individuellement bloqué pour un projet. +Les PP non-alloués à un projet dans la file d'attente de Production seront automatiquement ajoutés au Stock Impérial. Panneau Projets de Production @@ -3733,7 +4359,7 @@ Les objets indisponibles, si affichés, permettront par un survol de souris d'ac Le panneau "File d'Attente de Production" contient les Commandes de Production de l'empire. Les Points de Production sont alloués aux Commandes de Production en partant du haut de la file d'attente. Toute commande ayant pour lieu le système sélectionné sur la carte galactique affichera le nom du système de la même couleur que celle de votre empire (en haut à droite de la commande). -Ces commandes peuvent être déplacées dans la file d'attente à l'aide d'un glisser/déposer vers une nouvelle position. +Ces commandes peuvent être déplacées dans la file d'attente à l'aide d'un glisser-déposer vers une nouvelle position. Un clic-droit sur une commande affichera un menu permettant de déplacer, retirer, ou mettre en pause une commande (d'autres actions sont disponibles, telle Rechercher dans l'Encyclopédie). Lorsqu'une commande est retirée de la file d'attente, tout point déjà dépensé est perdu, aucun n'est récupéré ou réaffecté sur une autre commande. Mettre en pause une commande empêche toute nouvelle dépense de points sur ce projet, jusqu'à ce que la commande soit remise en route. @@ -3760,7 +4386,7 @@ Un Astronef Furtif coûte 36 PP avec un minimum de 3 tours (max 12 PP/tour). Vous lancez une autre commande d'un Astronef Furtif avec une répétition de 2. (commande #2) Vous lancez une autre commande d'un Astronef Furtif et réglez la quantité sur 2. --Tour 2: Commande #1 progresse de 10 PP, 26 PP restants. 10 PP sont prévus pour le tour suivant. +-Tour 2: Commande #1 progresse de 10 PP, 26 PP restants. 10 PP sont prévus pour le tour suivant. (PP complétés / PP restants -> Prévus tour suivant) @@ -3830,7 +4456,11 @@ PRODUCTION_WND_PROGRESS # %1% location where the item is produced. PRODUCTION_QUEUE_ITEM_LOCATION -sur %1% +Sur %1% + +# %1% location where the item is produced. +PRODUCTION_QUEUE_ITEM_RALLIED_FROM_LOCATION +Provenant de %1% # %1% location where the item is enqueued for production. PRODUCTION_QUEUE_ENQUEUED_ITEM_LOCATION @@ -3842,6 +4472,18 @@ Peut être construit ici PRODUCTION_LOCATION_INVALID NE PEUT PAS être construit ici +IMPERIAL_STOCKPILE +Stock Impérial + +PRODUCTION_QUEUE_ITEM_STOCKPILE_ENABLED +(l'usage du [[IMPERIAL_STOCKPILE]] est Autorisé pour ce Projet) + +ALLOW_IMPERIAL_PP_STOCKPILE_USE +Autoriser l'usage du [[IMPERIAL_STOCKPILE]] pour ce projet + +DISALLOW_IMPERIAL_PP_STOCKPILE_USE +Bloquer l'usage du [[IMPERIAL_STOCKPILE]] pour ce projet + # %1% name of the system this item production should be relocated to. PRODUCTION_QUEUE_RALLIED_TO Ralliera %1% @@ -3859,6 +4501,12 @@ Déplacer en haut de la file MOVE_DOWN_QUEUE_ITEM Déplacer en bas de la file +MOVE_UP_LIST_ITEM +Déplacer en tête de liste + +MOVE_DOWN_LIST_ITEM +Déplacer en fin de liste + SPLIT_INCOMPLETE Dissocier Répétition actuelle @@ -3866,10 +4514,19 @@ DUPLICATE Dupliquer PRODUCTION_WND_TOOLTIP_PROD_COST -Coût production +Coût production: %1% + +PRODUCTION_WND_TOOLTIP_PROD_TIME_MINIMUM +Tours minimum production: %1% + +PRODUCTION_WND_TOOLTIP_LOCATION_DEPENDENT +Dépend du lieu de production PRODUCTION_WND_TOOLTIP_PROD_TIME -Temps production +Tours de production entièrement financée sur %2%: %1% + +NO_PRODUCTION_HERE_CANT_PRODUCE +Production insuffisante sur %1% PRODUCTION_WND_TOOLTIP_PARTS Équipements Astronef @@ -3910,6 +4567,18 @@ Modèle obsolète DESIGN_WND_UNOBSOLETE_DESIGN Modèle non-obsolète +DESIGN_WND_OBSOLETE_HULL +Coque obsolète + +DESIGN_WND_UNOBSOLETE_HULL +Coque non-obsolète + +DESIGN_WND_OBSOLETE_PART +Équipement obsolète + +DESIGN_WND_UNOBSOLETE_PART +Équipement non-obsolète + DESIGN_WND_DELETE_DESIGN Supprimer Modèle @@ -3934,6 +4603,9 @@ Sauvegardés DESIGN_WND_MONSTERS Monstres +DESIGN_WND_ALL +Connus + DESIGN_WND_PART_PALETTE_TITLE Équipements d'Astronef @@ -3959,7 +4631,7 @@ DESIGN_INVALID Modèle invalide DESIGN_INV_MODERATOR -Les modérateurs et observateurs ne peuvent pas créer ou modifier des modèles d'astronefs. +Les modérateurs et spectateurs ne peuvent pas créer ou modifier des modèles d'astronefs. DESIGN_INV_NO_HULL Démarrer la conception d'un astronef par le choix de la coque. @@ -3967,26 +4639,37 @@ Démarrer la conception d'un astronef par le choix de la coque. DESIGN_INV_NO_NAME La dénomination du modèle d'astronef ne peut pas rester vide. -DESIGN_KNOWN +DESIGN_WND_KNOWN Modèle déjà existant # %1% name of the equivalent ship design. -DESIGN_KNOWN_DETAIL +DESIGN_WND_KNOWN_DETAIL Ce modèle est une réplique de "%1%". -DESIGN_COMPONENT_CONFLICT +DESIGN_WND_RENAME_FINISHED +Renommer Modèle Finalisé + +# %1% name of the equivalent ship design. +# %2% new name of the ship design. +DESIGN_WND_RENAME_FINISHED_DETAIL +'''Renommer modèle finalisé +"%1%" +en +"%2%"''' + +DESIGN_WND_COMPONENT_CONFLICT Modèle incompatible # %1% first conflicting ship part name. # %2% second conflicting ship part name. -DESIGN_COMPONENT_CONFLICT_DETAIL +DESIGN_WND_COMPONENT_CONFLICT_DETAIL Un modèle d'astronef ne peut contenir à la fois les équipements %1% et %2%. DESIGN_WND_ADD_FINISHED Ajouter Modèle Finalisé # %1% name of the new ship design. -DESIGN_WND_ADD_DETAIL_FINISHED +DESIGN_WND_ADD_FINISHED_DETAIL '''Ajouter nouveau modèle nommé "%1%" aux modèles finalisés.''' @@ -3996,7 +4679,7 @@ Actualiser Modèle Finalisé # %1% previous name of the existing design. # %2% new name of the existing design. -DESIGN_WND_UPDATE_DETAIL_FINISHED +DESIGN_WND_UPDATE_FINISHED_DETAIL '''Remplacer modèle finalisé "%1%" avec nouveau modèle @@ -4007,7 +4690,7 @@ DESIGN_WND_ADD_SAVED Ajouter Modèle Sauvegardé # %1% name of the new ship design. -DESIGN_WND_ADD_DETAIL_SAVED +DESIGN_WND_ADD_SAVED_DETAIL '''Ajouter un modèle nommé "%1%" aux modèles sauvegardés.''' @@ -4017,7 +4700,7 @@ Actualiser Modèle Sauvegardé # %1% old name of the saved design. # %2% new name of the saved design. -DESIGN_WND_UPDATE_DETAIL_SAVED +DESIGN_WND_UPDATE_SAVED_DETAIL '''Remplacer modèle sauvegardé "%1%" avec nouveau modèle @@ -4036,10 +4719,19 @@ Activez/Désactivez les filtres Obsolètes, Disponibles et Indisponibles pour af ADD_FIRST_SAVED_DESIGN_QUEUE_PROMPT Sélectionnez un Modèle Finalisé existant, clic-droit puis cliquez sur "[[DESIGN_SAVE]]" pour créer un modèle sauvegardé. +ALL_AVAILABILITY_FILTERS_BLOCKING_PROMPT +Activez/Désactivez les filtres Obsolètes, Disponibles et Indisponibles pour afficher les items + +NO_SAVED_OR_DEFAULT_DESIGNS_ADDED_PROMPT +'''Choisissez une coque, ajoutez des équipements puis cliquez sur "[[DESIGN_WND_ADD_FINISHED]]" pour créer un modèle utilisable. + +Aucun Modèle Sauvegardé ou Modèle par Défaut n'ont été ajoutés à l'empire au démarrage de la partie. Les options pour ajouter ces modèles au démarrage d'une partie peuvent être modifiées dans Options->Autres. En cours de partie, il est possible d'ajouter directement les Modèles Sauvegardés à l'aide du menu contextuel de n'importe quel modèle dans l'onglet "Sauvegardés".''' + # %1% File path of unwritable file ERROR_UNABLE_TO_WRITE_FILE Impossible de créer le fichier:\n"%1%" + ## ## Statistics ## @@ -4095,6 +4787,9 @@ Planètes impériales dépeuplées PLANETS_INVADED Planètes envahies +TOTAL_POPULATION_STAT # translated +Population + #STATISTICS_TEST_1 #Statistics Test 1 @@ -4111,14 +4806,14 @@ Planètes envahies # %1% name of the species. ENC_SPECIES_PLANET_TYPE_SUITABILITY_COLUMN1 -'''%1% : ''' +%1%: # %1% content of ENC_SPECIES_PLANET_TYPE_SUITABILITY_COLUMN1 expanded to match # column width. # %2% the planet environment for a species. # %3% the planet population capacity for a species. ENC_SPECIES_PLANET_TYPE_SUITABILITY # translated -%1% %2% (%3%) +%1% %2% (%3%) # %1% name of the planet. ENC_SUITABILITY_REPORT_POSITIVE_HEADER @@ -4141,6 +4836,30 @@ L'Habitabilité est définie par la distance au type d'[[encyclopedia ENVIRONMEN ENC_GRAPH [Partie - Graphiques] +USE_LINEAR_SCALE +Échelle Linéaire + +USE_LOG_SCALE +Échelle Logarithmique + +SHOW_SCALE +Afficher Échelle + +HIDE_SCALE +Cacher Échelle + +SHOW_LINES +Tracer Lignes + +SHOW_POINTS +Tracer Points + +SCALE_TO_ZERO +Échelle sur Zéro + +SCALE_FREE +Sans Échelle + ENC_GALAXY_SETUP [Partie - Galaxie] @@ -4154,6 +4873,7 @@ ENC_GALAXY_SETUP # %8% monster frequency in the current galaxy. # %9% natives frequency in the current galaxy. # %10% upper AI aggression level in this galaxy. +# %11% game UID ENC_GALAXY_SETUP_SETTINGS '''Graine: %1% Nombre de systèmes: %2% @@ -4165,19 +4885,20 @@ Planètes: %6% Monstres: %8% Indigènes: %9% Agressivité max de l'IA: %10% +UID Partie: %11% ''' ENC_GAME_RULES [Partie - Règles] ENC_SHIP_PART --[Équipement Astronef] +Equipement Astronef ENC_SHIP_PART_DESC Un Équipement d'Astronef peut être monté sur une Coque d'Astronef (dans une [[encyclopedia SLOT_TITLE]]) lors de la conception d'un astronef, afin d'améliorer ses statistiques. Certains équipements d'entrée de gamme sont disponibles par défaut, mais [[encyclopedia RESEARCH_TECH_GUIDE_TITLE]] permet de débloquer de meilleures versions ou de nouveaux types d'équipement. Chaque équipement d'astronef appartient à l'une des catégories listées ci-dessous. ENC_SHIP_HULL --[Coque Astronef] +Coque Astronef ENC_SHIP_HULL_DESC Une Coque d'Astronef est l'élément de base de l'architecture d'un astronef, et définit les statistiques basiques d'un modèle d'astronef (si aucun Équipement d'Astronef n'est encore monté). Certaines coques d'entrée de gamme sont disponibles par défaut, mais [[encyclopedia RESEARCH_TECH_GUIDE_TITLE]] permet de débloquer de meilleures versions ou de nouvelles classes de coque. Les coques d'astronef sont classées selon les catégories listées ci-dessous. @@ -4201,13 +4922,13 @@ ENC_SPECIES Espèce ENC_SPECIES_DESC -Chaque espèce est classifiée selon son [[encyclopedia METABOLISM_TITLE]]. +Chaque espèce est classifiée selon son [[encyclopedia METABOLISM_TITLE]]. ENC_FIELD_TYPE Type de Régions ENC_SHIP_DESIGN --[Modèle Astronef] +[Partie - Modèle Astronef] SHIPS_OF_DESIGN '''Astronefs de ce modèle : ''' @@ -4233,11 +4954,41 @@ ID Empire EMPIRE_METERS Indicateurs : +RESEARCHED_TECHS +'''Technologies recherchées : ''' + +BEFORE_FIRST_TURN +Avant Tour initial + +TURN +Tour + +NO_TECHS_RESEARCHED +Aucune Technologie recherchée + +AVAILABLE_PARTS +'''Équipements disponibles : ''' + +NO_PARTS_AVAILABLE +Aucun Équipement disponible + +AVAILABLE_HULLS +'''Coques disponibles : ''' + +NO_HULLS_AVAILABLE +Aucune Coque disponible + +AVAILABLE_BUILDINGS +'''Structures disponibles : ''' + +NO_BUILDINGS_AVAILABLE +Aucune Structure disponible + OWNED_PLANETS '''Planètes impériales : ''' NO_OWNED_PLANETS_KNOWN -Aucune planète impériale connue +Aucune Planète impériale connue OWNED_FLEETS '''Flottes impériales : ''' @@ -4248,7 +4999,7 @@ OWNED_FLEET_AT_SYSTEM %1% dans %2% NO_OWNED_FLEETS_KNOWN -Aucune flotte impériale connue +Aucune Flotte impériale connue EMPIRE_SHIPS_DESTROYED Astronefs impériaux détruits @@ -4299,7 +5050,7 @@ ENC_MONSTER [Partie - Monstres] ENC_MONSTER_TYPE -Type de Monstres +[Partie - Type de Monstres] ENC_FLEET [Partie - Flottes] @@ -4322,9 +5073,21 @@ ENC_HOMEWORLDS ENC_TEXTURES # translated [Test - Textures] +ENC_POP_CENTER +Centre Population + +ENC_PROD_CENTER +Centre Production + +ENC_FIGHTER +Chasseur + #ENC_TEXTURE_INFO #%4% -- %1% x %2% px @ %3% BPP +ENC_VECTOR_TEXTURE_INFO +%4% -- %1% x %2% px avec %3% formes + ENC_INDEX Index de l'Encyclopédie @@ -4376,7 +5139,7 @@ ENC_AUTO_TIME_COST_INVARIANT_STR Temps et Coût -Le temps et le coût pour %1% cet objet ne varient pas selon le lieu.''' +Le temps et le coût pour %1% cet objet ne varient pas selon le lieu de production.''' # %1% content of ENC_VERB_PRODUCE_STR. ENC_AUTO_TIME_COST_VARIABLE_STR @@ -4384,7 +5147,7 @@ ENC_AUTO_TIME_COST_VARIABLE_STR Temps et Coût -Le temps et/ou le coût pour %1% cet objet varient selon le lieu. +Le temps et/ou le coût pour %1% cet objet varient selon le lieu de production. Dans les expressions décrivant ces variations, le terme Source correspond à la Capitale de l'empire en question, et le terme Cible correspond au lieu pris en considération. ''' @@ -4510,7 +5273,6 @@ percés ENC_COMBAT_ATTACK_SIMPLE_STR %1% attaque %2% - # %1% number of repeated events. # %2% link to the attacking unit or empire. # %3% link to the attacking unit or empire. @@ -4532,6 +5294,15 @@ ENC_COMBAT_RECOVER_STR ENC_COMBAT_UNKNOWN_OBJECT Inconnu +# %1% empire name +# %2% species name +# %3% fighter type name +ENC_COMBAT_EMPIRE_FIGHTER_NAME +%3% %2% %1% + +ENC_COMBAT_ROGUE +Rebelle + # %1% number of combat round. ENC_ROUND_BEGIN ''' @@ -4567,7 +5338,7 @@ L'astronef %2% a été détruit # %1% unused # %2% name of empire ENC_COMBAT_INITIAL_STEALTH_LIST -L'empire %2% ne peut pas cibler : +L'empire %2% ne peut pas initialement détecter : # %1 link to the attacking unit # %2 link to the defending unit. @@ -4605,7 +5376,7 @@ ENC_COMBAT_PLATFORM_TARGET_AND_DAMAGE ENC_COMBAT_PLATFORM_NO_DAMAGE_1_EVENTS %2% n'a pas pu endommagé -# %1% number of targets +# %1% number of targets # %2% name of ship unable to damage targets ENC_COMBAT_PLATFORM_NO_DAMAGE_MANY_EVENTS %2% n'a pas pu endommagé %1% cibles : @@ -4724,9 +5495,15 @@ CATEGORY_GAME_CONCEPTS GROWTH_ARTICLE_SHORT_DESC Croissance +STOCKPILE_ARTICLE_SHORT_DESC +Stock + OUTPOSTS_ARTICLE_SHORT_DESC Avant-Poste +HOMEWORLDS_ARTICLE_SHORT_DESC +Planète Natale + PRODUCTION_ARTICLE_SHORT_DESC # translated Production @@ -4750,24 +5527,25 @@ Gestion ## -## Encyclopedia subcategories +## Encyclopedia articles in Guides category ## -# In Guides category - INTERFACE_TITLE *Interface* INTERFACE_TEXT Liste non-exhaustive des fonctionnalités de l'Interface de FreeOrion : -# In Game Concepts category + +## +## Encyclopedia articles in Game Concepts category +## DIPLOMACY_TITLE Diplomatie DIPLOMACY_TEXT -Liste non-exhaustive des aspects de Diplomatie : +Liste des politiques de Diplomatie : METABOLISM_TITLE Métabolisme @@ -4803,10 +5581,14 @@ SYSTEM_BLOCKADE_TITLE Blocus Système SYSTEM_BLOCKADE_TEXT -'''Une flotte armée et en mode Agression arrivant dans un système, établira un blocus de ce système contre tous les empires ennemis (si ce système n'est pas déjà soumis à un blocus). Une fois le blocus établi, la propagation de l'[[metertype METER_SUPPLY]] est interrompue pour l'empire soumis au blocus. De plus, si cet empire possède des colonies au sein de ce système, la production générée par ces colonies peut être utilisée seulement sur la planète dont elle est originaire. Si un combat a lieu simultanément au blocus, les effets fonctionnant seulement si aucun combat n'est en cours, tels que la [[encyclopedia ORBITAL_DRYDOCK_REPAIR_TITLE]] et l'augmentation de l'indicateur [[metertype METER_TROOPS]], sont suspendus pour le tour en cours. -Lors d'un blocus, tous les accès par Voies spatiales au système bloqué seront gardés par la force appliquant le blocus. Par conséquent, toutes forces hostiles entrant dans le système ne pourra en sortir que par la Voie spatiale empruntée pour y entrer. Toutefois, si des flottes supplémentaires de l'empire bloqué entrent dans le système par d'autres Voies spatiales, ces entrées deviendront alors également disponibles comme sorties. Si toutes les flottes armées appartenant à l'empire bloqué sont détruites ou quittent le système avant l'arrivée de nouvelles flottes de l'empire, les points de sortie précédemment ouverts seront à nouveau bloqués. +'''Une flotte armée et en mode Agression arrivant dans un système, établira un blocus de ce système contre tous les empires avec lesquels la flotte est en [[encyclopedia WAR_TITLE]] (si ce système n'est pas déjà soumis à un blocus). Une fois le blocus établi, la propagation de l'[[metertype METER_SUPPLY]] est interrompue pour l'empire soumis au blocus. + +Lors d'un blocus, tous les accès par Voies spatiales au système bloqué seront gardés par la force appliquant le blocus. Par conséquent, toutes forces hostiles entrant dans le système ne pourra en sortir que par la Voie spatiale empruntée pour y entrer. Toutefois, si des flottes supplémentaires de l'empire bloqué entrent dans le système par d'autres Voies spatiales, ces entrées deviendront alors également disponibles comme sorties. Si toutes les flottes armées appartenant à l'empire bloqué sont détruites ou quittent le système avant l'arrivée de nouvelles flottes de ce même empire, les points de sortie précédemment ouverts seront à nouveau bloqués. + Les flottes alliées n'ont pas d'impact direct sur l'interruption de l'Approvisionnement et la disponibilité des Voies spatiales, si ce n'est aider à l'élimination des forces de l'empire exerçant le blocus. + Si des flottes de deux empires hostiles ou plus arrivent simultanément dans un système non soumis à un blocus, aucun des deux empires ou plus n'établira un blocus à l'encontre de l'autre, mais ces derniers bloqueront le système contre tout autre empire hostile voulant éventuellement accéder au système par la suite. + Les Monstres spatiaux peuvent également établir des blocus. Cependant, si une flotte de l'empire est bloquée par un monstre spatial, le blocus sera interrompu dès qu'un combat aura eu lieu, permettant ainsi à la flotte de l'empire de quitter le système par n'importe quelle Voie spatiale, ou bien d'établir à son tour un blocus à l'encontre du monstre. De plus, si la flotte bloquée est armée et en mode Agression, la propagation de l'Approvisionnement sera rétablie. Si un Monstre spatial et une flotte de l'empire entrent dans le système simultanément, la flotte de l'empire pourra agir en premier et établir un blocus contre le Monstre.''' @@ -4892,6 +5674,12 @@ METER_MAX_SUPPLY_VALUE_LABEL METER_MAX_SUPPLY_VALUE_DESC [[METER_SUPPLY_VALUE_DESC]] +METER_MAX_STOCKPILE_VALUE_LABEL +[[METER_STOCKPILE_VALUE_LABEL]] + +METER_MAX_STOCKPILE_VALUE_DESC +[[METER_STOCKPILE_VALUE_DESC]] + METER_MAX_TROOPS_VALUE_LABEL [[METER_TROOPS_VALUE_LABEL]] @@ -4902,7 +5690,12 @@ METER_POPULATION_VALUE_LABEL # translated Population METER_POPULATION_VALUE_DESC -Valeur abstraite représentant la taille d'une colonie, en fonction de l'espèce. +'''La Population représente la taille d'une colonie sur une planète. Sa valeur maximale dépend de l'[[encyclopedia ENC_SPECIES]] et de son adaptation à l'[[encyclopedia ENVIRONMENT_TITLE]] d'une planète. Plusieurs bonus de capacité en [[metertype METER_INDUSTRY]] et en [[metertype METER_RESEARCH]] sont liés à la taille de la Population: plus la Population est importante, plus les bonus sont élevés. + +Une Population supérieure ou égale à 3 et un [[metertype METER_HAPPINESS]] minimum de 5 sont requis pour qu'une colonie puisse lancer la colonisation d'autres planètes. Un astronef colonisateur est peuplé de Colons, leur nombre étant déterminé par la capacité de [[encyclopedia PC_COLONY]] du modèle d'astronef. Lors de la colonisation d'une planète, le nombre de Colons à bord de l'astronef constituera la Population de départ sur une planète nouvellement colonisée. + +La Projection Population est la valeur optimale de Population qu'une planète tend à approcher, compte tenu des différents facteurs influençant la Population planétaire. Sa valeur est liée aux Types de Structures bâtis sur la planète, aux [[encyclopedia ENC_TECH]] recherchées par l'empire, aux Particularités liées à la planète, ainsi que divers effets de jeu. Si ces facteurs changent, alors la valeur de Projection Population est susceptible de fluctuer à son tour. +''' METER_INDUSTRY_VALUE_LABEL Industrie @@ -4910,7 +5703,9 @@ Industrie METER_INDUSTRY_VALUE_DESC '''L'Industrie, mesurée en Points de Production (PP), représente la capacité de production et de transformation de biens matériels de l'ensemble des planètes de l'empire. Elle est nécessaire pour la construction des structures, des astronefs et tout autre projet. -L'Industrie d'une planète peut participer à tout projet lancé sur les planètes qui lui sont reliées par lignes d'[[metertype METER_SUPPLY]] de l'empire. L'Industrie qui n'est affectée à aucun projet durant un tour est perdue (production gaspillée). +L'Industrie d'une planète peut participer à tout projet lancé sur les planètes qui lui sont reliées par lignes d'[[metertype METER_SUPPLY]] de l'empire. L'Industrie n'étant affectée à aucun projet durant un tour est ajoutée au Stock Impérial. + +La Projection Industrie est le niveau optimal de capacité en Industrie qu'une planète tend à approcher, compte tenu des différents facteurs influençant les ressources en Industrie. Sa valeur est liée aux Types de Structures bâtis sur la planète, aux [[encyclopedia ENC_TECH]] recherchées par l'empire, aux Particularités liées à la planète, ainsi que divers effets de jeu. Si ces facteurs changent, alors la valeur de Projection Industrie est susceptible de fluctuer à son tour. Pour davantage d'informations sur l'interface de Production, consultez l'article [[encyclopedia PRODUCTION_WINDOW_ARTICLE_TITLE]]. ''' @@ -4921,10 +5716,12 @@ Recherche METER_RESEARCH_VALUE_DESC '''La Recherche, mesurée en Points de Recherche (PR), est utilisée par toutes les planètes de l'empire pour la découverte de nouvelles [[encyclopedia ENC_TECH]]. -La Recherche qui n'est allouée à aucune technologie durant un tour est perdue (recherche gaspillée). L'assimilation complète de chaque technologie nécessite un nombre minimal de tours. Par exemple, si une technologie consomme 20 PR et requiert 5 tours, un maximum de 4 PR seulement par tour peut lui être consacré. -Les PR sont affectés aux technologies à acquérir selon leur position dans la file d'attente : la technologie en tête de file est prioritaire. Les PR restants sont alloués au projet suivant dans la file et ainsi de suite, jusqu'à épuisement des points disponibles. L'ordre des technologies dans la file d'attente peut être modifié : les technologies que vous jugez prioritaires peuvent ainsi être déplacées en tête de file. +La Projection Recherche est le niveau optimal de capacité en Recherche qu'une planète tend à approcher, compte tenu des différents facteurs influençant les ressources en Recherche. Sa valeur est liée aux Types de Structures bâtis sur la planète, aux [[encyclopedia ENC_TECH]] recherchées par l'empire, aux Particularités liées à la planète, ainsi que divers effets de jeu. Si ces facteurs changent, alors la valeur de Projection Recherche est susceptible de fluctuer à son tour. + +L'assimilation complète de chaque technologie nécessite un nombre minimal de tours. Par exemple, si une technologie coûte 15 PR et requiert un minimum de 3 tours, seulement 5 PR par tour peuvent lui être alloués. +Les PR sont affectés aux technologies à assimiler selon leur position dans la file d'attente : la technologie en tête de file est prioritaire. Les PR restants sont alloués au projet suivant dans la file et ainsi de suite, jusqu'à épuisement des points disponibles. L'ordre des technologies dans la file d'attente peut être modifié : les technologies que vous jugez prioritaires peuvent ainsi être déplacées en tête de file. Si des PR restent non-alloués après que chaque technologie ait reçu son maximum autorisé de PR, ces PR en excédent sont automatiquement attribués à une ou plusieurs technologies déverrouillées et présentant le moins de PR nécessaires ou restants avant assimilation. -À l'inverse de l'Industrie, la Recherche n'impose pas d'échanges matériels entre planètes et n'est donc pas dépendante des lignes d'[[metertype METER_SUPPLY]] de l'empire.''' +À l'inverse de l'[[metertype METER_INDUSTRY]], la Recherche n'impose pas d'échanges matériels entre planètes et n'est donc pas dépendante des lignes d'[[metertype METER_SUPPLY]] de l'empire.''' METER_TRADE_VALUE_LABEL Commerce @@ -4936,13 +5733,17 @@ METER_CONSTRUCTION_VALUE_LABEL Infrastructure METER_CONSTRUCTION_VALUE_DESC -L'Infrastructure englobe toutes les structures et organisations qui assurent la pérennité et la production des populations de l'empire : l'énergie, les transports, les télécommunications, les services sociaux, etc. Chaque colonie présente un indicateur 'Infrastructure'. +'''L'Infrastructure englobe toutes les structures et organisations qu'abrite une planète colonisée: l'énergie, les transports, les télécommunications, les services sociaux, etc. + +La Projection Infrastructure est le niveau optimal d'Infrastructure qu'une planète tend à approcher, compte tenu des différents facteurs influençant l'Infrastructure planétaire. Sa valeur est liée aux Types de Structures bâtis sur la planète, aux [[encyclopedia ENC_TECH]] recherchées par l'empire, aux Particularités liées à la planète, ainsi que divers effets de jeu. Si ces facteurs changent, alors la valeur de Projection Infrastructure est susceptible de fluctuer à son tour.''' METER_HAPPINESS_VALUE_LABEL Bonheur METER_HAPPINESS_VALUE_DESC -Le Bonheur de la population d'une colonie est initialement faible suite au succès d'une colonisation ou d'une invasion, et peut également dépendre d'autres facteurs. Un Bonheur minimum de 5 est requis pour qu'une colonie puissent lancer la colonisation d'autres planètes. +'''Le Bonheur de la [[metertype METER_POPULATION]] d'une colonie débute entre 1 et 5 suite à la colonisation initiale, et dépend de l'adaptation de l'[[encyclopedia ENC_SPECIES]] à l'[[encyclopedia ENVIRONMENT_TITLE]] de la planète. Le Bonheur est réduit à 0 suite à une invasion, et peut également dépendre d'autres facteurs. Un Bonheur minimum de 5 est requis pour qu'une colonie puisse entreprendre la colonisation d'autres planètes. L'[[metertype METER_INDUSTRY]] et la [[metertype METER_RESEARCH]] ne commenceront à croître que si le Bonheur est au moins égal à 5. + +La Projection Bonheur est le niveau optimal de Bonheur que la Population d'une planète tend à approcher, compte tenu des différents facteurs influençant le Bonheur planétaire. Sa valeur est liée aux Types de Structures bâtis sur la planète, aux [[encyclopedia ENC_TECH]] recherchées par l'empire, aux Particularités liées à la planète, ainsi que divers effets de jeu. Si ces facteurs changent, alors la valeur de Projection Bonheur est susceptible de fluctuer à son tour.''' METER_CAPACITY_VALUE_LABEL Stat - Primaire @@ -4961,7 +5762,11 @@ METER_FUEL_VALUE_LABEL Carburant METER_FUEL_VALUE_DESC -Le carburant permet aux astronefs de parcourir les Voies spatiales non-situées dans les zones d'[[metertype METER_SUPPLY]] de l'empire. En dehors de ces dernières, un astronef brûle 1 unité de carburant pour chaque Voie spatiale parcourue. Un arrêt d'un astronef sur les lignes d'[[metertype METER_SUPPLY]] de l'empire permet de refaire le plein en carburant. S'il voyage au-delà des lignes d'approvisionnement de son empire, un astronef récupérera lentement son carburant seulement s'il est immobile (stationnaire dans un système, dans l'espace interstellaire, ou sur une Voie spatiale). La quantité initiale de carburant qu'embarque un Modèle d'Astronef dépend de son type de Coque, et peut être augmentée à l'aide de certains Équipements d'Astronef. +'''Le carburant permet aux astronefs de parcourir les Voies spatiales situées hors des zones d'[[metertype METER_SUPPLY]] de l'empire. En dehors de ces dernières, un astronef consomme du carburant pour chaque Voie spatiale parcourue (soit un voyage). Un arrêt d'un astronef sur les lignes d'[[metertype METER_SUPPLY]] de l'empire permet de refaire le plein en carburant. S'il voyage au-delà des lignes d'approvisionnement de son empire ou de celles d'un allié, un astronef récupérera lentement son carburant seulement s'il est immobile (stationnaire dans un système, dans l'espace interstellaire, ou sur une Voie spatiale). + +La quantité effective de carburant qu'embarque un Modèle d'Astronef dépend de la valeur de base en carburant de la Coque et d'effets additionnels de carburant, déterminés par le [[encyclopedia FUEL_EFFICIENCY_TITLE]] de la Coque de l'astronef. Le Rendement Carburant modifie la capacité effective en carburant des Équipements d'Astronef et multipliera tout autre effet modificateur en carburant. + +Le jeu affichera continuellement les niveaux effectifs de carburant, en fonction du Rendement Carburant. Les détails à propos de l'effet de Rendement peuvent être consultés via l'infobulle de la coque d'astronef.''' METER_SHIELD_VALUE_LABEL Boucliers @@ -4998,7 +5803,17 @@ Les lignes d'approvisionnement sont représentées par les Voies spatiales aux c Les Voies spatiales d'approvisionnement essentielles d'un empire (les lignes les plus directes reliant les systèmes producteurs de ressources de chaque Groupe Ressource) auront une épaisseur plus importante que les Voies spatiales non-essentielles. Si les PP sont gaspillés au sein d'un Groupe Ressource, les bandes extérieures des Voies Spatiales auront une couleur spécifique. -Les lignes d'approvisionnement peuvent également servir à ravitailler en [[metertype METER_FUEL]] les astronefs.''' +Les lignes d'approvisionnement peuvent également servir à ravitailler en [[metertype METER_FUEL]] les astronefs. + +Les lignes d'approvisionnement seront bloquées par les astronefs ennemis armés appartenant aux empires avec lesquels l'empire est en [[encyclopedia WAR_TITLE]]. À l'opposé du spectre diplomatique, les empires sous traité d'[[encyclopedia ALLIANCE_TITLE]] partageront leurs lignes d'approvisionnement ainsi que tous les bénéfices y étant associés.''' + +METER_STOCKPILE_VALUE_LABEL +Stock + +METER_STOCKPILE_VALUE_DESC +'''La capacité du Stock Impérial indique la compétence d'un empire à gérer les surplus de production et à utiliser les points de production stockés. Le Stock total d'un empire correspond à la somme des indicateurs Stock de toutes les planètes de l'empire, plus les éventuels bonus de technologie. Ce total détermine combien de PP peuvent être puisés dans le Stock à chaque tour afin d'être utilisés pour la production. + +La compétence de gestion du Stock varie selon les espèces.''' METER_TROOPS_VALUE_LABEL Infanterie @@ -5006,7 +5821,11 @@ Infanterie METER_TROOPS_VALUE_DESC '''Le terme 'Infanterie' désigne toutes sortes de forces militaires capables d'opérer à la surface d'une planète. -L'indicateur 'Infanterie' d'une planète représente la puissance des troupes terrestres de défense. L'empire envahisseur doit envoyer une infanterie supérieure en force (c-à-d un plus grand nombre) pour parvenir à conquérir la planète. Les astronefs doivent être dotés d'équipements spécifiques permettant le transport de l'Infanterie, et les troupes en question ne peuvent envahir une planète tant que cette dernière dispose de [[metertype METER_SHIELD]] actifs.''' +L'indicateur 'Infanterie' d'une planète représente la puissance des troupes terrestres de défense. L'empire envahisseur doit envoyer une infanterie supérieure en force (c-à-d un plus grand nombre) pour parvenir à conquérir la planète. + +Les astronefs doivent être dotés d'équipements d'[[encyclopedia PC_TROOPS]] permettant le transport de troupes en vue d'une invasion. + +Les astronefs ne peuvent pas envahir une planète tant que cette dernière dispose de [[metertype METER_SHIELD]] actifs.''' METER_REBEL_TROOPS_VALUE_LABEL Infanterie Rebelle @@ -5034,7 +5853,7 @@ Champ de Détection METER_DETECTION_VALUE_DESC '''L'indicateur 'Champ de Détection' indique, en nombre d'uus, la portée des capteurs. Un empire ne peut voir que les objets ayant une [[metertype METER_STEALTH]] inférieure ou égale à sa [[encyclopedia DETECTION_TITLE]], et qui se trouvent dans le Champ de Détection d'un astronef ou d'une planète qu'il contrôle. -Les astronefs et les planètes ont un indicateur 'Champ de Détection'. Dans le Menu d'Options, le réglage 'Cercles de détection' permet d'afficher sur la carte galactique les zones circulaires (en uu) figurant les Champs de Détection. Afin de faciliter la mesure de ce qui se trouve à l'intérieur d'un Champ de Détection, le réglage 'Barre d'échelle des distances' permet d'afficher l'échelle de la carte (en uu) tandis que le réglage 'Cercle d'échelle des distances' permet de figurer la même distance que la barre d'échelle lorsqu'un système stellaire est sélectionné. Ces deux indicateurs d'échelle s'adaptent au niveau de zoom sur la carte et peuvent être activés/désactivés via des raccourcis claviers. +Les astronefs et les planètes ont un indicateur 'Champ de Détection'. Dans le Menu d'Options, le réglage 'Cercles de détection' permet d'afficher sur la carte galactique les zones circulaires (en uu) figurant les Champs de Détection. Afin de faciliter la mesure de ce qui se trouve à l'intérieur d'un Champ de Détection, le réglage 'Barre d'échelle des distances' permet d'afficher l'échelle de la carte (en uu) tandis que le réglage 'Cercle d'échelle des distances' permet de figurer la même distance que la barre d'échelle lorsqu'un système stellaire est sélectionné. Ces deux indicateurs d'échelle s'adaptent au niveau de zoom sur la carte et peuvent être activés/désactivés via des raccourcis claviers. Certains objets ou aptitudes permettent la vision de zones situées en dehors du Champ de Détection, telles les compétences spéciales des espèces [[species SP_TRITH]] ou [[species SP_GEORGE]], ou en raison des effets induits par la structure [[buildingtype BLD_SCRYING_SPHERE]].''' @@ -5050,17 +5869,41 @@ METER_DETECTION_STRENGTH_VALUE_LABEL METER_DETECTION_STRENGTH_VALUE_DESC [[DETECTION_TEXT]] +WAR_TITLE +Guerre + +WAR_TEXT +La Guerre est le statut diplomatique par défaut entre empires. Les astronefs armés imposeront un [[encyclopedia SYSTEM_BLOCKADE_TITLE]] envers les astronefs ennemis, bloqueront les lignes d'[[metertype METER_SUPPLY]], feront obstacle aux politiques ennemies de colonisation, et engageront le combat contre tout astronef ennemi. Les planètes ennemies peuvent être soumises à des invasions ou des bombardements. + PEACE_TITLE Paix PEACE_TEXT -La paix est le rapport diplomatique entre empires ayant convenu de ne pas attaquer planètes et astronefs, de ne pas bloquer les lignes d'[[metertype METER_SUPPLY]] et de ne pas faire obstacle à l'essor des colonies de chacun d'entre eux. +La Paix est le statut diplomatique entre empires ayant convenu de ne pas attaquer mutuellement leurs planètes et astronefs, de ne pas imposer un [[encyclopedia SYSTEM_BLOCKADE_TITLE]] envers leurs astronefs respectifs, de ne pas bloquer leurs lignes d'[[metertype METER_SUPPLY]], et de ne pas faire obstacle aux actions de colonisation de chacun d'entre eux. La Paix est le niveau minimum requis de diplomatie pour que les [[encyclopedia GIFTING_TITLE]] entre empires soient permis. + +ALLIANCE_TITLE +Alliance + +ALLIANCE_TEXT +L'Alliance est le statut diplomatique le plus cordial entre empires. Les empires alliés partagent leurs zones de visibilité et leurs lignes d'[[metertype METER_SUPPLY]]. Les empires alliés peuvent partager la victoire finale si la Règle de Partie correspondante est activée. + +GIFTING_TITLE +Dons + +GIFTING_TEXT +Les empires jouissant de relations diplomatiques suffisamment cordiales ([[encyclopedia PEACE_TITLE]] ou plus), peuvent s'offrir réciproquement des planètes ou des flottes, à condition que l'empire recevant le don soit présent (via un astronef ou une planète) dans le même système où se trouve l'objet étant offert. OUTPOSTS_TITLE Avant-Poste OUTPOSTS_TEXT -Les Avant-Postes sont des stations inhabitées pouvant être implantées en des lieux trop hostiles pour la survie d'une colonie. Ils n'ont pas de population et ne produisent en principe aucune ressource, mais peuvent cependant contribuer aux lignes d'[[metertype METER_SUPPLY]] de l'empire (à l'aide de la technologie appropriée) et augmenter sa zone de détection. Des colonies peuvent être fondées sur les planètes déjà pourvues d'un Avant-Poste. Pour construire un Avant-Poste, il suffit de placer un [[shippart CO_OUTPOST_POD]] sur un astronef. Des Avant-Postes sont également créés lorsque la population d'une colonie tombe à zéro, quelle qu'en soit la raison. +Les Avant-Postes sont des stations inhabitées pouvant être implantées en des lieux trop hostiles pour la survie d'une colonie. Ils n'ont pas de [[metertype METER_POPULATION]] et ne produisent en principe aucune ressource, mais peuvent cependant contribuer aux lignes d'[[metertype METER_SUPPLY]] de l'empire (à l'aide de la technologie appropriée), augmenter sa zone de détection et fournir du carburant. Des colonies peuvent être fondées sur les planètes déjà pourvues d'un Avant-Poste. Pour construire un Avant-Poste, il suffit de placer un [[shippart CO_OUTPOST_POD]] sur un astronef. Des Avant-Postes sont également créés lorsque la Population d'une colonie tombe à zéro, quelle qu'en soit la raison. + +HOMEWORLDS_TITLE +Planète Natale + +HOMEWORLDS_TEXT +Les Planètes Natales sont les mondes d'origine où se situent les espèces au démarrage d'une partie (à noter qu'une espèce peut avoir plusieurs Planètes Natales). La technologie [[tech SPY_CUSTOM_ADVISORIES]] révèle leur existence et leur nombre, mais pas leur localisation. Une Planète Natale dispose de plusieurs avantages, tant que celle-ci est toujours habitée par l'espèce d'origine: les indicateurs [[metertype METER_TARGET_POPULATION]], [[metertype METER_MAX_TROOPS]], et [[metertype METER_TARGET_HAPPINESS]] sont accrus, et le [[encyclopedia GROWTH_FOCUS_TITLE]] est activé (à moins que l'espèce soit de type [[encyclopedia SELF_SUSTAINING_SPECIES_CLASS]]), augmentant ainsi la [[metertype METER_TARGET_POPULATION]] des autres colonies habitées par la même espèce. FOCUS_TITLE Focus @@ -5076,11 +5919,9 @@ GROWTH_FOCUS_TITLE Focus Croissance GROWTH_FOCUS_TEXT -'''Privilégier la Croissance signifie exporter des matériaux et substances rares afin de favoriser l'accroissement démographique sur une planète. Cela n'est possible que dans certains cas : sur quelques Planètes Natales et sur des planètes présentant une Particularité de Croissance pouvant être exportée. - -Une planète privilégiant la Croissance augmente la Population max des seules autres planètes lui étant reliées par lignes d'[[metertype METER_SUPPLY]]. +'''Privilégier la Croissance signifie exporter des matériaux et substances rares afin d'accroître la [[metertype METER_TARGET_POPULATION]] d'une planète. Cela n'est possible que dans certains cas : sur quelques Planètes Natales et sur des planètes présentant une Particularité de Croissance pouvant être exportée. -Une Planète Natale privilégiant la Croissance n'aide d'autres colonies à croître que si celles-ci abritent la même classification d'espèces. +Une planète privilégiant la Croissance propage ces bénéfices aux autres planètes lui étant reliées par lignes d'[[metertype METER_SUPPLY]], à condition que les espèces peuplant ces autres planètes soient du type adéquat, c'est-à-dire qu'une Planète Natale privilégiant la Croissance favorise seulement les autres planètes peuplées par la même espèce, tandis que les Particularités de Croissance (et le Focus Croissance en corrélation) ne sont bénéfiques qu'aux espèces ayant le même type de [[encyclopedia METABOLISM_TITLE]]. Les bénéfices des différents types applicables de Focus Croissance s'additionnent, mais plusieurs sources d'un même type spécifique de Focus Croissance ne s'additionnent pas. [[GROWTH_SPECIALS_ENTRY_LIST]]''' @@ -5088,7 +5929,7 @@ TRADE_FOCUS_TITLE Focus Commerce TRADE_FOCUS_TEXT -Le Commerce est encore sans effet. Il s'agit juste d'un espace réservé indiquant que d'autres ressources sont prévues. +Le Commerce est sans effet. Il s'agit juste d'un espace réservé indiquant que d'autres ressources sont prévues. PROTECTION_FOCUS_TITLE Focus Protection @@ -5096,6 +5937,16 @@ Focus Protection PROTECTION_FOCUS_TEXT Privilégier la Protection consiste pour une planète à se consacrer entièrement à sa propre défense. Ses effectifs d'[[metertype METER_TROOPS]], sa capacité de [[metertype METER_DEFENSE]], et la solidité de ses [[metertype METER_SHIELD]] sont par conséquent doublés. +STOCKPILE_FOCUS_TITLE +Focus Stock + +STOCKPILE_FOCUS_TEXT +'''Le Focus Stock augmente le [[metertype METER_STOCKPILE]] d'une planète. + +Une Planète Natale privilégiant le Stock favorise également les autres planètes peuplées par la même espèce, en y augmentant le [[metertype METER_STOCKPILE]] de 0.04 par [[metertype METER_POPULATION]]. + +[[METER_STOCKPILE_VALUE_DESC]]''' + ARMOR_TITLE Blindage @@ -5126,11 +5977,11 @@ Chaque [[encyclopedia ENC_SPECIES]] a ses propres préférences environnementale [[PE_UNINHABITABLE]] < [[PE_HOSTILE]] < [[PE_POOR]] < [[PE_ADEQUATE]] < [[PE_GOOD]] -Le rapport d'Habitabilité d'une planète peut être affiché par un clic-droit sur une planète dans le panneau latéral. À la suite de la donnée d'Habitabilité, un nombre de couleur verte indique la population maximale pouvant être atteinte si l'espèce colonise la planète, tandis qu'un nombre de couleur rouge indique que la population n'évoluera pas ou diminuera, pour finalement disparaître si l'espèce tente de coloniser la planète. +Le rapport d'Habitabilité d'une planète peut être affiché par un clic-droit sur une planète dans le panneau latéral. À la suite de la donnée d'Habitabilité, un nombre de couleur verte indique la [[metertype METER_POPULATION]] maximale que la planète peut atteindre si colonisée par cette espèce, tandis qu'un nombre de couleur rouge indique que la colonie ne sera pas viable. La Population diminuera sur plusieurs tours, jusqu'à ce que la totalité des Colons ait péri et que la colonie soit perdue. Lors de la terraformation d'une planète dans le but de mieux l'adapter aux préférences environnementales de l'espèce (via la [[metertype METER_RESEARCH]] ou si la planète dispose de la Particularité [[special GAIA_SPECIAL]]), l'Environnement d'origine de la planète est modifié par paliers, jusqu'à que ce dernier atteigne l'Habitabilité [[PE_GOOD]] pour l'espèce désirant vivre sur la planète. Les étapes de terraformation d'une planète peuvent être consultées ci-dessous sous la forme d'une représentation circulaire des différents types d'Environnement. -Depuis midi dans le sens horaire: [[PT_TERRAN]], [[PT_OCEAN]], [[PT_SWAMP]], [[PT_TOXIC]], [[PT_INFERNO]], [[PT_RADIATED]], [[PT_BARREN]], [[PT_TUNDRA]], [[PT_DESERT]] +Depuis midi dans le sens horaire: [[PT_TERRAN]], [[PT_OCEAN]], [[PT_SWAMP]], [[PT_TOXIC]], [[PT_INFERNO]], [[PT_RADIATED]], [[PT_BARREN]], [[PT_TUNDRA]], [[PT_DESERT]] ''' @@ -5138,27 +5989,27 @@ ORGANIC_SPECIES_TITLE Métabolisme Organique ORGANIC_SPECIES_TEXT -Les espèces de type [[encyclopedia ORGANIC_SPECIES_CLASS]] sont plus ou moins 'la vie telle que nous la connaissons'. La population maximale de ces espèces peut être augmentée lorsque leur Planète Natale est réglée sur le [[encyclopedia GROWTH_FOCUS_TITLE]] (et connectée par lignes d'[[metertype METER_SUPPLY]]), ou lorsqu'un Objet à Particularité de Croissance favorable à l'espèce est colonisé et focalisé sur la Croissance. Les Objets à Particularité de Croissance Organique sont marqués d'un petit 'O' vert. +Les espèces de type [[encyclopedia ORGANIC_SPECIES_CLASS]] sont plus ou moins 'la vie telle que nous la connaissons'. La [[metertype METER_TARGET_POPULATION]] de ces espèces peut être augmentée lorsque leur Planète Natale est réglée sur le [[encyclopedia GROWTH_FOCUS_TITLE]] (et connectée par lignes d'[[metertype METER_SUPPLY]]), ou lorsqu'un Objet à Particularité de Croissance favorable à l'espèce est colonisé et focalisé sur la Croissance. Les Objets à Particularité de Croissance Organique sont marqués d'un petit 'O' vert. LITHIC_SPECIES_TITLE Métabolisme Lithique LITHIC_SPECIES_TEXT -Les espèces de type [[encyclopedia LITHIC_SPECIES_CLASS]] sont des organismes à base de silicium ou de minéraux. La population maximale de ces espèces peut être augmentée lorsque leur Planète Natale est réglée sur le [[encyclopedia GROWTH_FOCUS_TITLE]] (et connectée par lignes d'[[metertype METER_SUPPLY]]), ou lorsqu'un Objet à Particularité de Croissance favorable à l'espèce est colonisé et focalisé sur la Croissance. Les Objets à Particularité de Croissance Lithique sont marqués d'un petit 'L' gris. +Les espèces de type [[encyclopedia LITHIC_SPECIES_CLASS]] sont des organismes à base de silicium ou de minéraux. La [[metertype METER_TARGET_POPULATION]] de ces espèces peut être augmentée lorsque leur Planète Natale est réglée sur le [[encyclopedia GROWTH_FOCUS_TITLE]] (et connectée par lignes d'[[metertype METER_SUPPLY]]), ou lorsqu'un Objet à Particularité de Croissance favorable à l'espèce est colonisé et focalisé sur la Croissance. Les Objets à Particularité de Croissance Lithique sont marqués d'un petit 'L' gris. ROBOTIC_SPECIES_TITLE Métabolisme Robotique ROBOTIC_SPECIES_TEXT -Les espèces de type [[encyclopedia ROBOTIC_SPECIES_CLASS]] sont des machines douées de conscience. La population maximale de ces espèces peut être augmentée lorsque leur Planète Natale est réglée sur le [[encyclopedia GROWTH_FOCUS_TITLE]] (et connectée par lignes d'[[metertype METER_SUPPLY]]), ou lorsqu'un Objet à Particularité de Croissance favorable à l'espèce est colonisé et focalisé sur la Croissance. Les Objets à Particularité de Croissance Robotique sont marqués d'un petit 'R' rouge. +Les espèces de type [[encyclopedia ROBOTIC_SPECIES_CLASS]] sont des machines douées de conscience. La [[metertype METER_TARGET_POPULATION]] de ces espèces peut être augmentée lorsque leur Planète Natale est réglée sur le [[encyclopedia GROWTH_FOCUS_TITLE]] (et connectée par lignes d'[[metertype METER_SUPPLY]]), ou lorsqu'un Objet à Particularité de Croissance favorable à l'espèce est colonisé et focalisé sur la Croissance. Les Objets à Particularité de Croissance Robotique sont marqués d'un petit 'R' rouge. PHOTOTROPHIC_SPECIES_TITLE Métabolisme Phototropique PHOTOTROPHIC_SPECIES_TEXT -'''Les espèces de type [[encyclopedia PHOTOTROPHIC_SPECIES_CLASS]] vivent principalement, si ce n'est entièrement, de la lumière stellaire. La population maximale de ces espèces est fortement influencée par la luminosité de leur étoile et par la taille de leur planète - plus elles sont respectivement lumineuses ou grandes, plus le bénéfice est important. Les Particularités de Croissance n'ont aucun effet sur ces organismes. +'''Les espèces de type [[encyclopedia PHOTOTROPHIC_SPECIES_CLASS]] vivent principalement, si ce n'est entièrement, de la lumière stellaire. La [[metertype METER_TARGET_POPULATION]] de ces espèces est fortement influencée par la luminosité de leur étoile et par la taille de leur planète - plus elles sont respectivement lumineuses ou grandes, plus le bénéfice est important. Les Particularités de Croissance n'ont aucun effet sur ces organismes. -Influence sur la population: +Influence sur la Population: * Étoile [[STAR_BLUE]]: ([[SZ_TINY]] +3) ([[SZ_SMALL]] +6) ([[SZ_MEDIUM]] +9) ([[SZ_LARGE]] +12) ([[SZ_HUGE]] +15) @@ -5174,7 +6025,13 @@ SELF_SUSTAINING_SPECIES_TITLE Métabolisme Autosuffisant SELF_SUSTAINING_SPECIES_TEXT -Les espèces de type [[encyclopedia SELF_SUSTAINING_SPECIES_CLASS]] sont des organismes cristallins, voire purement énergétiques, et peut-être même d'une physiologie encore plus étrange. Leur constitution est telle qu'ils n'ont pas besoin d'apports vitaux, leur population maximale étant de ce fait très élevée. Tandis qu'aucune Particularité n'augmente leur Croissance, leur population maximale évolue plutôt à la manière d'une espèce profitant simultanément des trois Particularités basées sur le [[encyclopedia GROWTH_FOCUS_TITLE]]. +Les espèces de type [[encyclopedia SELF_SUSTAINING_SPECIES_CLASS]] sont des organismes cristallins, voire purement énergétiques, et peut-être même d'une physiologie encore plus étrange. Leur constitution est telle qu'ils n'ont pas besoin d'apports vitaux, leur [[metertype METER_TARGET_POPULATION]] étant de ce fait très élevée. Tandis qu'aucune Particularité n'augmente leur Croissance, leur Projection Population évolue plutôt à la manière d'une espèce profitant simultanément des trois Particularités basées sur le [[encyclopedia GROWTH_FOCUS_TITLE]]. + +GASEOUS_SPECIES_TITLE +Métabolisme Gazeux + +GASEOUS_SPECIES_TEXT +Les espèces de type [[encyclopedia GASEOUS_SPECIES_CLASS]] ont un métabolisme basé sur les éléments rencontrés sous forme gazeuse dans un environnement de type terrestre. La plupart des chercheurs s'accordent pourtant sur le fait qu'une vie complexe dotée d'un métabolisme gazeux est physiquement impossible. Par conséquent, la recherche de vie sur le plus probable des habitats, les géantes gazeuses, est généralement négligée. Bien que les géantes gazeuses soient immenses, la zone habitable est similaire à celle d'une planète de taille moyenne. Par ailleurs, il n'existe pas de Particularités ayant une influence sur la croissance des espèces gazeuses. TELEPATHIC_TITLE Télépathie @@ -5186,7 +6043,7 @@ XENOPHOBIC_SPECIES_TITLE Xénophobe XENOPHOBIC_SPECIES_TEXT -'''Les espèces xénophobes utilisent une partie de leur Industrie pour harceler les autres espèces se trouvant aux alentours. En conséquence, les espèces sujettes à ce harcèlement développeront un malus industriel. Une espèce Xénophobe et de [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]] considère la présence d'espèces étrangères aux alentours comme une entrave à son équilibre naturel, ce qui entraîne une diminution de sa population maximale (il n'est toujours pas déterminé si cela est dû à des effets directs ou bien au fait que l'espèce gaspille des ressources dans le harcèlement des espèces étrangères). +'''Les espèces xénophobes utilisent une partie de leur effort industriel pour harceler les autres espèces se trouvant aux alentours. En conséquence, les espèces sujettes à ce harcèlement développeront un malus industriel. Une espèce Xénophobe et de [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]] considère la présence d'espèces étrangères aux alentours comme une entrave à son équilibre naturel, ce qui entraîne une diminution de sa [[metertype METER_TARGET_POPULATION]] (il n'est toujours pas déterminé si cela est dû à des effets directs ou bien au fait que l'espèce gaspille des ressources dans le harcèlement des espèces étrangères). La technologie [[tech CON_CONC_CAMP]] est disponible par défaut pour les espèces xénophobes.''' @@ -5200,8 +6057,8 @@ EVACUATION_TITLE Plan d'Évacuation EVACUATION_TEXT -'''La construction d'un [[buildingtype BLD_EVACUATION]] retire toute la population d'une planète. À chaque tour, une partie de la population est évacuée. S'il existe des planètes de l'empire colonisées par la même espèce, ayant de l'espace libre et étant connectées aux lignes d'[[metertype METER_SUPPLY]], l'une d'entre elles sera choisie au hasard et la population y sera déplacée. Si aucune planète appropriée n'est disponible, la population évacuée disparaîtra dans l'espace infini. -Durant une évacuation, la production tombe à zéro. Quand une colonie est vidée de toute population, elle retourne à l'état d'[[encyclopedia OUTPOSTS_TITLE]]. L'empire en est toujours propriétaire, et peut lancer une nouvelle colonisation dès que le [[buildingtype BLD_EVACUATION]] est automatiquement détruit.''' +'''La construction d'un [[buildingtype BLD_EVACUATION]] retire toute la [[metertype METER_POPULATION]] d'une planète. À chaque tour, une partie de la Population est évacuée. S'il existe des planètes de l'empire colonisées par la même espèce, ayant de l'espace libre et étant connectées aux lignes d'[[metertype METER_SUPPLY]], l'une d'entre elles sera choisie au hasard et la Population y sera déplacée. Si aucune planète appropriée n'est disponible, la Population évacuée disparaîtra dans l'espace infini. +Durant une évacuation, la production tombe à zéro. Quand une colonie est vidée de toute Population, elle retourne à l'état d'[[encyclopedia OUTPOSTS_TITLE]]. L'empire en est toujours propriétaire, et peut lancer une nouvelle colonisation dès que le [[buildingtype BLD_EVACUATION]] est automatiquement détruit.''' AI_LEVELS_TITLE Niveaux d'Agressivité de l'IA @@ -5209,7 +6066,7 @@ Niveaux d'Agressivité de l'IA AI_LEVELS_TEXT '''Les niveaux d'agressivité de l'IA correspondent plus ou moins aux niveaux de difficulté, mais pas entièrement. Ils varient sur des aspects comme la prise de risque et l'hostilité, avec des seules limitations étant appliquées au niveaux les plus bas. Alors qu'ils peuvent toujours se comporter de manière intelligente, les plus bas niveaux d'agressivité sont plus faciles à gérer, même s'il n'est pas nécessairement plus aisé de les soumettre totalement. Les niveaux les plus agressifs font bien mieux à mesure que la partie progresse. -L'IA renomme sa capitale d'empire au tour 1, en y ajoutant un préfixe. Une fois que vous avez découvert son système natal, vous pouvez déterminer sa personnalité. +L'IA renomme sa capitale d'empire au tour 1, en y ajoutant un préfixe. Une fois que son système natal est découvert, la personnalité de l'IA peut être identifiée. Liste des préfixes: Balbutiant: @@ -5242,7 +6099,7 @@ MAP_WINDOW_ARTICLE_TITLE Fenêtre Carte MAP_WINDOW_ARTICLE_TEXT -'''La Fenêtre Carte peut être considérée comme l'écran de jeu 'principal' de FreeOrion. Ce dernier affiche la carte de la galaxie et permet d'accéder à toutes les autres fenêtres ou menus du jeu. La barre supérieure réunit les éléments principaux de contrôle. +'''La Fenêtre Carte peut être considérée comme l'écran de jeu 'principal' de FreeOrion. Ce dernier affiche la carte de la galaxie et permet d'accéder à toutes les autres fenêtres ou menus du jeu. La barre supérieure réunit les éléments principaux de contrôle. Dans le coin supérieur gauche se trouve le Bouton Tour, indiquant "Tour n" (n étant le numéro du tour en cours); un clic-gauche sur le Bouton Tour permet de faire avancer la partie d'un tour. Immédiatement à droite se trouve le bouton d'Avance Automatique; lorsque ce dernier affiche une icône double flèche, la partie n'avancera pas automatiquement; un clic modifie l'icône en flèches circulaires et la partie avancera automatiquement au tour suivant une fois que tous les empires auront donné leurs ordres pour le tour en cours. @@ -5256,16 +6113,16 @@ Fenêtre Conception DESIGN_WINDOW_ARTICLE_TEXT '''La [[DESIGN_WINDOW_ARTICLE_TITLE]] permet de visualiser ou de modifier les modèles de monstres ou d'astronefs. -Les modèles et équipements d'astronef peuvent être [[PRODUCTION_WND_AVAILABILITY_AVAILABLE]], [[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]] ou [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]]. Les modèles et équipements [[PRODUCTION_WND_AVAILABILITY_AVAILABLE]] sont libres d'utilisation pour l'empire. L'accès aux modèles et équipements [[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]] est soumis à certaines conditions. Certains modèles et équipements indisponibles peuvent être débloqués via la [[METER_RESEARCH]] des [[encyclopedia ENC_TECH]] associées. Les modèles [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]] sont déclarés obsolètes/non-obsolètes selon les besoins de l'empire. Les modèles [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]] n'apparaîtront pas dans la [[encyclopedia PRODUCTION_WINDOW_ARTICLE_TITLE]] mais seulement dans la [[DESIGN_WINDOW_ARTICLE_TITLE]] lorsque le filtre "[[PRODUCTION_WND_AVAILABILITY_OBSOLETE]]" est activé. Les équipements d'astronef ne peuvent pas être déclarés obsolètes. +Les modèles et équipements d'astronef peuvent être [[PRODUCTION_WND_AVAILABILITY_AVAILABLE]], [[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]] ou [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]]. Les modèles et équipements [[PRODUCTION_WND_AVAILABILITY_AVAILABLE]] sont libres d'utilisation pour l'empire. L'accès aux modèles et équipements [[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]] est soumis à certaines conditions. Certains modèles et équipements indisponibles peuvent être débloqués via la [[METER_RESEARCH]] des [[encyclopedia ENC_TECH]] associées. Les modèles, coques et équipements [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]] sont déclarés obsolètes/non-obsolètes selon les besoins de l'empire. Les modèles [[PRODUCTION_WND_AVAILABILITY_OBSOLETE]] n'apparaîtront pas dans la [[encyclopedia PRODUCTION_WINDOW_ARTICLE_TITLE]] mais seulement dans la [[DESIGN_WINDOW_ARTICLE_TITLE]] lorsque le filtre "[[PRODUCTION_WND_AVAILABILITY_OBSOLETE]]" est activé. Les équipements d'astronef ne peuvent pas être déclarés obsolètes. La [[DESIGN_WINDOW_ARTICLE_TITLE]] est composée de quatre panneaux: [[DESIGN_WND_STARTS]], [[DESIGN_WND_PART_PALETTE_TITLE]], [[DESIGN_WND_MAIN_PANEL_TITLE]], [[MAP_BTN_PEDIA]]. -Le panneau [[DESIGN_WND_STARTS]] propose quatre onglets différents: les [[DESIGN_WND_HULLS]] de départ, les modèles [[DESIGN_WND_FINISHED_DESIGNS]], les modèles [[DESIGN_WND_SAVED_DESIGNS]] lors de précédentes parties, et enfin les modèles de [[DESIGN_WND_MONSTERS]] pour consultation. Glissez-déposez ou double-cliquez sur une coque, un modèle ou un monstre pour les examiner plus en détails ou les modifier sur le plan de travail. L'ordre des modèles dans la liste peut être également modifié par un glisser/déposer. +Le panneau [[DESIGN_WND_STARTS]] propose quatre onglets différents: les [[DESIGN_WND_HULLS]] de départ, les modèles [[DESIGN_WND_FINISHED_DESIGNS]], les modèles [[DESIGN_WND_SAVED_DESIGNS]] lors de précédentes parties, et enfin les modèles de [[DESIGN_WND_MONSTERS]] pour consultation. Glissez-déposez ou double-cliquez sur une coque, un modèle ou un monstre pour les examiner plus en détails ou les modifier sur le plan de travail. L'ordre des modèles et des coques dans la liste peut être également modifié par un glisser-déposer. -Les modèles [[DESIGN_WND_FINISHED_DESIGNS]] sont affichés dans un ordre identique à celui de la Fenêtre Production. Le menu contextuel suite à un clic-droit propose plusieurs options. Déclarer un modèle obsolète/non-obsolète avec l'entrée "[[DESIGN_WND_OBSOLETE_DESIGN]]" (ctrl+clic sur un modèle permettra de basculer directement entre les deux états). Supprimer un modèle avec l'entrée "[[DESIGN_WND_DELETE_DESIGN]]" ; les modèles supprimés ne peuvent pas être récupérés (les astronefs déjà placés en file d'attente de production seront cependant toujours construits sur la base du modèle obsolète ou supprimé). Renommer un modèle avec l'entrée "[[DESIGN_RENAME]]". Sauvegarder un modèle sur le disque dur avec l'entrée "[[DESIGN_SAVE]]", afin de pouvoir le réutiliser sans passer à nouveau par l'étape de conception lors de futures parties. Activez/désactivez l'option "[[DESIGN_WND_ADD_ALL_DEFAULT_START]]" pour utiliser/ne pas utiliser les modèles par défaut lors d'une nouvelle partie. +Les modèles [[DESIGN_WND_FINISHED_DESIGNS]] sont affichés dans un ordre identique à celui de la Fenêtre Production. Le menu contextuel suite à un clic-droit propose plusieurs options. Déclarer un modèle obsolète/non-obsolète avec l'entrée "[[DESIGN_WND_OBSOLETE_DESIGN]]" (ctrl + clic sur un modèle ou une coque permettra de basculer directement entre les deux états). Supprimer un modèle avec l'entrée "[[DESIGN_WND_DELETE_DESIGN]]" ; les modèles supprimés ne peuvent pas être récupérés (les astronefs déjà placés en file d'attente de production seront cependant toujours construits sur la base du modèle obsolète ou supprimé). Renommer un modèle avec l'entrée "[[DESIGN_RENAME]]". Sauvegarder un modèle sur le disque dur avec l'entrée "[[DESIGN_SAVE]]", afin de pouvoir le réutiliser sans passer à nouveau par l'étape de conception lors de futures parties. Activez/désactivez l'option "[[DESIGN_WND_ADD_ALL_DEFAULT_START]]" pour utiliser/ne pas utiliser les modèles par défaut lors d'une nouvelle partie. -Un clic-droit sur les modèles [[DESIGN_WND_SAVED_DESIGNS]] ouvre un menu contextuel permettant de les ajouter à l'unité ou en totalité aux modèles [[DESIGN_WND_FINISHED_DESIGNS]] de l'empire (ctrl+clic sur un modèle permettra de l'ajouter directement aux modèles [[DESIGN_WND_FINISHED_DESIGNS]]). Un modèle sauvegardé peut être supprimé définitivement avec l'entrée "[[DESIGN_WND_DELETE_SAVED]]". Si l'option "[[OPTIONS_ADD_SAVED_DESIGNS]]" est activée dans les options de jeu, tous les modèles [[DESIGN_WND_SAVED_DESIGNS]] seront automatiquement importés à chaque lancement d'une toute nouvelle partie. +Un clic-droit sur les modèles [[DESIGN_WND_SAVED_DESIGNS]] ouvre un menu contextuel permettant de les ajouter à l'unité ou en totalité aux modèles [[DESIGN_WND_FINISHED_DESIGNS]] de l'empire (ctrl + clic sur un modèle permettra de l'ajouter directement aux modèles [[DESIGN_WND_FINISHED_DESIGNS]]). Un modèle sauvegardé peut être supprimé définitivement avec l'entrée "[[DESIGN_WND_DELETE_SAVED]]". Si l'option "[[OPTIONS_ADD_SAVED_DESIGNS]]" est activée dans les options de jeu, tous les modèles [[DESIGN_WND_SAVED_DESIGNS]] seront automatiquement importés à chaque lancement d'une toute nouvelle partie. Le panneau [[DESIGN_WND_PART_PALETTE_TITLE]] affiche comme son nom l'indique les équipements. Ceux dont la technologie associée a été recherchée et assimilée sont disponibles et peuvent être intégrés dans un modèle d'astronef. D'autres équipements sont indisponibles si aucune [[metertype METER_RESEARCH]] n'a encore été effectuée concernant la technologie associée ou si cet équipement est seulement utilisable par les monstres. @@ -5277,7 +6134,7 @@ Le panneau [[DESIGN_WND_PART_PALETTE_TITLE]] propose également des filtres pour Le panneau [[DESIGN_WND_MAIN_PANEL_TITLE]] fait office de plan de travail pour le modèle en cours. -Un modèle peut accueillir plusieurs types de [[encyclopedia SLOT_TITLE]]: interne, externe, et centrale. Un glisser-déposer ou un double-clic sur un équipement dans le panneau [[DESIGN_WND_PART_PALETTE_TITLE]] ajoute celui-ci au modèle en cours, tandis que la même action le retire si elle est effectuée depuis le panneau [[DESIGN_WND_MAIN_PANEL_TITLE]]. +Un modèle peut accueillir plusieurs types de [[encyclopedia SLOT_TITLE]]: interne, externe, et centrale. Un glisser-déposer ou un double-clic sur un équipement dans le panneau [[DESIGN_WND_PART_PALETTE_TITLE]] ajoute celui-ci au modèle en cours, tandis que la même action le retire si elle est effectuée depuis le panneau [[DESIGN_WND_MAIN_PANEL_TITLE]]. Ctrl + glisser-déposer ajoute/retire/remplace tous les équipements de même type. Un nom pour le modèle conçu est requis tandis que sa description reste optionnelle. @@ -5308,7 +6165,7 @@ HIDDEN_SETTINGS__ARTICLE_TEXT Parmi les nombreux réglages disponibles permettant de définir l'emplacement/taille des divers éléments de l'UI, ces 'Réglages Cachés' intègrent: -UI.design-pedia-dynamic : Active/Désactive la mise à jour dynamique de l'article de l'Encyclopédie concernant un modèle d'astronef si le nom de ce modèle a été modifié dans la [[encyclopedia DESIGN_WINDOW_ARTICLE_TITLE]] (peut engendrer des ralentissements) +ui.window.design.pedia.title.dynamic.enabled : Active/Désactive la mise à jour dynamique de l'article de l'Encyclopédie concernant un modèle d'astronef si le nom de ce modèle a été modifié dans la [[encyclopedia DESIGN_WINDOW_ARTICLE_TITLE]] (peut engendrer des ralentissements) show-fleet-eta : Active/Désactive l'Estimation du Temps d'Arrivée d'une flotte (Estimated Time of Arrival soit ETA), en nombre de tours à partir du tour actuel, dans le Panneau de la flotte se trouvant dans la Fenêtre Flottes. @@ -5340,7 +6197,6 @@ Salutations Noble Voyageur Cosmique, et Bienvenue dans l'Univers Freeorion! GREETINGS_GUIDE_REFERENCE Les nouveaux venus dans l'Univers Freeorion peuvent consulter le [[encyclopedia QUICK_START_GUIDE_TITLE]]. - GREETINGS_GUIDE_TEXT '''[[GREETINGS_INTRO]] @@ -5356,7 +6212,7 @@ QUICK_START_GUIDE_TITLE **Guide de Démarrage Rapide** QUICK_START_GUIDE_TEXT -'''Voici une brève introduction au gameplay de Freeorion. Ce guide in-game, ajouté récemment, est pour le moment assez sommaire mais sera complété au fil des versions. Un guide plus conséquent est disponible sur le site freeorion.org (URL actuelle: freeorion.org/index.php/V0.4_Quick_Play_Guide). +'''Voici une brève introduction au gameplay de Freeorion. Un guide plus conséquent est disponible sur le site freeorion.org (URL actuelle: freeorion.org/index.php/V0.4_Quick_Play_Guide). [[DETAILED_PEDIA_REFERENCE]] Il est conseillé de consulter en priorité les sections [[encyclopedia CATEGORY_GAME_CONCEPTS]], [[encyclopedia CATEGORY_GUIDES]], et [[encyclopedia ENC_METER_TYPE]]. @@ -5390,7 +6246,7 @@ Les types de SitRep apparaissent dans la liste Filtres seulement une fois que l' Ignorer est utile pour masquer un événement récurrent, tels les rappels pour construire un [[buildingtype BLD_GAS_GIANT_GEN]] sur une planète. -Bloquer est utile si vous considérez certains types de SitReps superflus, telles les [[encyclopedia BEGINNER_HINTS]] une fois toutes celles-ci connues. +Bloquer est utile si certains types de SitReps deviennent superflus, telles les [[encyclopedia BEGINNER_HINTS]] une fois toutes celles-ci connues. ''' RESEARCH_TECH_GUIDE_TITLE @@ -5415,7 +6271,7 @@ ORBITAL_DRYDOCK_REPAIR_TITLE Réparation en Cale Sèche ORBITAL_DRYDOCK_REPAIR_TEXT -'''La [[building BLD_SHIPYARD_ORBITAL_DRYDOCK]] est considérée comme la meilleure structure de réparation, capable de remettre à neuf tout astronef endommagé. +'''La [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] est considérée comme la meilleure structure de réparation, capable de remettre à neuf tout astronef endommagé. Lorsque que l'indicateur [[metertype METER_HAPPINESS]] d'une planète est inférieur à 15, les réparations effectuées via une Cale Sèche Orbitale seront moins efficaces. Si l'indicateur Bonheur chute au-dessous de 5, aucune réparation ne pourra y être entreprise pour le tour en cours. @@ -5423,6 +6279,50 @@ Les réparations sont différées d'un tour pour les astronefs pénétrant tout Tout combat au sein d'un système interrompt les réparations pour le tour en cours.''' +CONFIG_GUIDE_TITLE +Guide de Configuration + +CONFIG_GUIDE_TEXT +'''La plupart des options de configuration du jeu sont conservées et lues à partir d'un unique fichier XML nommé config.xml. +Si ce fichier n'existe pas (ou provient d'un build ou d'une version différente), il sera (re)créé lors du démarrage du serveur client. +Le fichier de configuration est mis à jour lors de la modification d'une option, mais cette dernière peut être différée dans l'attente d'une action ultérieure (comme cliquer sur Valider ou Appliquer). +Certaines de ces options peuvent être modifiées depuis la page '[[OPTIONS_TITLE]]' du Menu Principal. + +Une option définie depuis le fichier de configuration persistante (persistent_config.xml, s'il existe) aura la priorité sur la valeur correspondante du fichier config.xml. +Une option définie par un argument de ligne de commande aura la priorité sur la valeur correspondante des deux fichiers. +Le fichier config.xml est automatiquement modifié pour prendre en compte toute nouvelle valeur de configuration. + + +Fichier de Configuration Persistante: + +Un fichier de configuration persistante est un fichier XML optionnel nommé persistent_config.xml. +Si ce fichier existe, tout réglage y étant conservé sera prioritaire sur ceux du fichier principal de configuration. +Cela s'avère utile lors du lancement d'un build différent du jeu ou suite à une mise à jour vers une nouvelle version. Afin de faciliter la compatibilité des options et de leurs valeurs, il est conseillé de conserver dans ce fichier les réglages des options essentielles (résolution d'affichage, taille des fenêtres, etc). + +Un fichier de configuration persistante peut être créé ou mis à jour depuis l'onglet '[[OPTIONS_PAGE_MISC]]' de la page '[[OPTIONS_TITLE]]', en cliquant sur le bouton '[[OPTIONS_CREATE_PERSISTENT_CONFIG]]'. +Lors de l'usage de ce bouton, certains problèmes demeurent: +Certaines options peuvent être incluses mêmes si ces dernières n'ont pas été modifiées par l'utilisateur. Après la mise à jour vers une nouvelle version, il peut être nécessaire d'effacer manuellement ces options du fichier afin d'actualiser leur valeur par défaut. +Les fenêtres n'ayant pas été ouvertes durant la session de jeu en cours peuvent causer la non-conservation des réglages modifiés. Il est conseillé lors d'une session de jeu d'ouvrir au moins une fois toutes les fenêtres en question afin d'initialiser les réglages avant l'utilisation du bouton. + +Le fichier de configuration persistante peut être créé manuellement si désiré, mais ne doit jamais contenir l'option "version.string". + + +Emplacement des Fichiers de Configuration: + +L'emplacement des fichiers est dépendant de l'OS et de la façon de déclarer une variable d'environnement. +Pour Windows, la variable est entourée par le signe % - ( %var% ). +Pour MacOSX et Linux, la variable débute par ${ et se termine par } - ( ${var} ). + +Windows: %APPDATA%\Roaming\FreeOrion + (e.g. C:\Users\username\AppData\Roaming\FreeOrion) + +MacOSX: ${HOME}/Library/Application Support/FreeOrion + (e.g. /Users/username/Library/Application Support/FreeOrion) + +Linux: ${XDG_CONFIG_HOME}/freeorion ou si $XDG_CONFIG_HOME n'est pas défini: ${HOME}/.config/freeorion + (e.g. /home/username/.config/freeorion) +''' + ## ## Turn progress @@ -5476,6 +6376,30 @@ Réception d'un état de tour inattendu de la part du serveur. MESSAGES_PANEL_TITLE # translated Messages +# %1% and %2% are color coded empire names +MESSAGES_WAR_DECLARATION +%1% et %2% sont à présent en guerre. + +# %1% and %2% are color coded empire names +MESSAGES_PEACE_TREATY +%1% et %2% sont à présent en paix. + +# %1% and %2% are color coded empire names +MESSAGES_ALLIANCE +%1% et %2% ont forgé une alliance. + +MESSAGES_HELP_COMMAND +'''/pedia [article]: ouvre l'article spécifié dans l'Encyclopédie +/pm [player] [message]: envoie un message privé au joueur spécifié +/zoom [object]: zoome sur l'objet spécifié (système, planète, astronef, flotte ou structure) +''' + +MESSAGES_WHISPER +(Chuchotements) + +MESSAGES_INVALID +Joueur invalide ou aucun message saisi. + ## ## Players list @@ -5521,6 +6445,9 @@ Planètes FLEETS_SUBMENU Flotte / Astronefs +PLANET_ENVIRONMENTS_SUBMENU +Environnement pour l'espèce + #ID #ID @@ -5551,6 +6478,12 @@ PARTS HULL Coque +ATTACK +Dommages Totaux Armement + +PRODUCTION_COST +Coût Production + OBJECT_TYPE # translated Type @@ -5602,17 +6535,35 @@ Système Suiv. PREV_SYSTEM Système Préc. +ARRIVAL_STARLANE +En Provenance Système + LAST_TURN_BATTLE_HERE Dern. Tour Bataille ici LAST_TURN_ACTIVE_IN_BATTLE Dern. Tour Bataille +LAST_TURN_RESUPPLIED +Dern. Tour Approvisionnement + +LAST_TURN_COLONIZED +Dern. Tour Colonisation + +LAST_TURN_CONQUERED +Dern. Tour Conquête + +LAST_TURN_ATTACKED_BY_SHIP +Dern. Tour Attaque par Astronef + ARRIVED_ON_TURN Arrivé au Tour SIZE_AS_DOUBLE -Taille numér. +Taille numérique + +HABITABLE_SIZE +Taille habitable DISTANCE_FROM_ORIGINAL_TYPE Distance Type d'origine de la Planète @@ -5732,6 +6683,7 @@ Tout ## Situation report ## ## Format: Ideally always start with Location: with ship/fleet names near the end. +## SITREP_PANEL_TITLE Rapport de Situation - Tour initial @@ -5777,6 +6729,12 @@ SITREP_WELCOME # translated SITREP_WELCOME_LABEL Bienvenue +SITREP_SYSTEM_GOT_INCOMING_WARNING +Dans %system%: des astronefs ennemis arrivent au prochain tour! + +SITREP_SYSTEM_GOT_INCOMING_WARNING_LABEL +Ennemis en approche + SITREP_GAS_GIANT_GENERATION_REMINDER La planète %planet% est un emplacement favorable pour la structure %buildingtype%, mais aucune n'y est construite à ce jour. @@ -5804,6 +6762,12 @@ La technologie %tech% a été assimilée. SITREP_TECH_RESEARCHED_LABEL Technologie assimilée +SITREP_EMPIRE_TECH_RESEARCHED_DETECTION +La [[encyclopedia DETECTION_TITLE]] de %empire% a été améliorée à %rawtext:dstrength% + +SITREP_EMPIRE_TECH_RESEARCHED_DETECTION_LABEL +Amélioration technologique Détection impériale + SITREP_TECH_UNLOCKED La technologie %tech% a été débloquée et peut désormais être assimilée grâce à la Recherche. @@ -5828,6 +6792,12 @@ La structure %buildingtype% a été débloquée et peut désormais être constru SITREP_BUILDING_TYPE_UNLOCKED_LABEL Structure débloquée +SITREP_WEAPONS_UPGRADED +Tous les armements %shippart% des astronefs à portée d'approvisionnement ont été améliorés par la technologie %tech%. Les armements améliorés infligent des dommages supplémentaires de %rawtext:dam% par tir. + +SITREP_WEAPONS_UPGRADED_LABEL +Amélioration technologique Armement + SITREP_COMBAT_SYSTEM Dans %system% : un %combat% a eu lieu. @@ -5921,26 +6891,26 @@ Dans %system% : un %shipdesign% a été endommagé. SITREP_UNOWNED_SHIP_DAMAGED_AT_SYSTEM_LABEL Astronef libre endommagé -SITREP_PLANET_BOMBARDED_AT_SYSTEM -Dans %system% : la planète %planet% de %empire% a été bombardée. +SITREP_PLANET_ATTACKED_AT_SYSTEM +Dans %system% : la planète %planet% de %empire% a été attaquée. -SITREP_PLANET_BOMBARDED_AT_SYSTEM_LABEL -Planète impériale bombardée +SITREP_PLANET_ATTACKED_AT_SYSTEM_LABEL +Planète impériale attaquée -SITREP_UNOWNED_PLANET_BOMBARDED_AT_SYSTEM -Dans %system% : la planète %planet% a été bombardée. +SITREP_UNOWNED_PLANET_ATTACKED_AT_SYSTEM +Dans %system% : la planète %planet% a été attaquée. -SITREP_UNOWNED_PLANET_BOMBARDED_AT_SYSTEM_LABEL -Planète libre bombardée +SITREP_UNOWNED_PLANET_ATTACKED_AT_SYSTEM_LABEL +Planète libre attaquée SITREP_GROUND_BATTLE -Sur %planet% : une bataille terrestre s'est déroulée. +Sur %planet% : une bataille terrestre a eu lieu. SITREP_GROUND_BATTLE_LABEL Bataille au sol SITREP_GROUND_BATTLE_ENEMY -Sur %planet% : une bataille terrestre s'est déroulée contre %empire%. +Sur %planet% : une bataille terrestre a eu lieu contre %empire%. SITREP_GROUND_BATTLE_ENEMY_LABEL Bataille au sol - un seul Empire @@ -5975,6 +6945,18 @@ Sur %planet% : une nouvelle colonie %species% s'est établie. SITREP_NEW_COLONY_ESTABLISHED_LABEL Nouvelle colonie établie +SITREP_FLEET_GIFTED +La flotte %fleet% a été offerte par %empire%. + +SITREP_FLEET_GIFTED_LABEL +Flotte offerte + +SITREP_PLANET_GIFTED +La planète %planet% a été offerte par %empire%. + +SITREP_PLANET_GIFTED_LABEL +Planète offerte + SITREP_FLEET_ARRIVED_AT_DESTINATION La flotte %fleet% (%rawtext% astronefs) a atteint le système %system%. @@ -6018,7 +7000,7 @@ SITREP_FOREIGN_FLEET_ARRIVED_AT_DESTINATION_LABEL Flotte étrangère arrivée à destination SITREP_POP_THRESHOLD -Sur %planet% : la colonie a atteint la taille et le [[metertype METER_HAPPINESS]] prérequis pour entreprendre la colonisation d'autres planètes. +Sur %planet% : la colonie a atteint la taille et le [[metertype METER_HAPPINESS]] prérequis pour entreprendre la colonisation d'autres planètes. L'[[metertype METER_INDUSTRY]] et la [[metertype METER_RESEARCH]] peuvent commencer à croître. SITREP_POP_THRESHOLD_LABEL Seuil de Colonisation @@ -6149,6 +7131,12 @@ La planète %planet% a été terraformée. EFFECT_TERRAFORM_LABEL Terraformation +EFFECT_NEST_REMOVAL +Sur %planet% : un nid de monstre a été éradiqué. + +EFFECT_NEST_REMOVAL_LABEL +Éradication de Nid + EFFECT_STARLANE_BORE Dans %system% : une nouvelle Voie spatiale a été ouverte vers un système proche. @@ -6167,6 +7155,24 @@ La planète %planet% a été transformée en planète artificielle. EFFECT_ART_PLANET_LABEL Trou Noir +INTERSPECIES_ACADEMY_LABEL +Académie InterDesign d'Espèce + +CONDITION_INTERSPECIES_ACADEMY_SPECIES_ALREADY_EXISTS +Des académies pour l'espèce de cette planète existent déjà dans l'empire + +EFFECT_INTERSPECIES_ACADEMY +Effet additionnel pour l'espèce %species% sur la planète %planet% rejoignant l'Académie InterDesign. Chaque académie de design ajoute 0.05 à %rawtext%. + +EFFECT_INTERSPECIES_ACADEMY_LABEL +Nouvelle Académie InterDesign + +EFFECT_INTERSPECIES_ACADEMY_DESTROY +Destruction du doublon d'académie de design sur la planète %planet%. + +EFFECT_INTERSPECIES_ACADEMY_DESTROY_LABEL +Doublon d'académie détruit + EFFECT_TAME_MONSTER_HATCHED Un %predefinedshipdesign% apprivoisé est disponible sur la planète %planet%. @@ -6299,6 +7305,12 @@ Sur %planet% : la [[tech LRN_SPATIAL_DISTORT_GEN]] a renvoyé la flotte %fleet% EFFECT_FLEET_MOVED_TO_LABEL Flotte renvoyée dans +EFFECT_SHIP_REFUELED +Dans %system% : l'astronef %ship% a suffisamment de carburant pour reprendre son voyage spatial. + +EFFECT_SHIP_REFUELED_LABEL +Astronef ravitaillé + HEAD_ON_A_SPIKE_MESSAGE La capitale de l'empire %empire% est tombée. @@ -6335,6 +7347,9 @@ VICTOIRE! L'empire %empire% a transcendé notre réalité et remporte une victoi VICTORY_ALL_ENEMIES_ELIMINATED VICTOIRE! L'empire %empire% est l'ultime survivant. +VICTORY_FEW_HUMANS_ALIVE +VICTOIRE! L'empire %empire% est l'un des derniers empires survivants contrôlé par un joueur humain. + VICTORY_EXPERIMENTOR_CAPTURE VICTOIRE! L'empire %empire% a capturé les puissants Expérimentateurs, mettant ainsi un terme à l'effroyable fléau menaçant la galaxie. @@ -6343,6 +7358,7 @@ Victoire SITREP_EMPIRE_ELIMINATED L'empire %empire% a été vaincu! + SITREP_EMPIRE_ELIMINATED_LABEL Empire vaincu @@ -6435,10 +7451,10 @@ BEGINNER_HINT_09 9: Un astronef [[predefinedshipdesign SD_COLONY_SHIP]] nécessite un délai de construction plus important qu'un astronef [[predefinedshipdesign SD_OUTPOST_SHIP]], mais ils rendent possible l'établissement de colonies sur des planètes au-delà de votre portée d'[[metertype METER_SUPPLY]]. BEGINNER_HINT_10 -10: Lorsque qu'un système est sélectionné dans la [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]], un clic-droit sur l'image d'une planète dans le panneau latéral donnera accès au rapport d'Habitabilité de la planète. Celui-ci vous indiquera quelle espèce au sein de votre empire sera en mesure de s'établir avec succès sur cette planète. Les nombres verts indiquent le maximum de Population atteignable si la planète est colonisée par cette espèce. Les nombres rouges signalent que l'espèce finirait par périr si elle tentait de coloniser cette planète. +10: Lorsque qu'un système est sélectionné dans la [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]], un clic-droit sur l'image d'une planète dans le panneau latéral donnera accès au rapport d'Habitabilité de la planète. Celui-ci vous indiquera quelle espèce au sein de votre empire sera en mesure de s'établir avec succès sur cette planète. Les nombres verts indiquent la [[metertype METER_POPULATION]] maximale atteignable si la planète est colonisée par cette espèce. Les nombres rouges signalent que l'espèce finirait par périr si elle tentait de coloniser cette planète. BEGINNER_HINT_11 -11: Les maximums de Population atteignables sur une planète peuvent être augmentés via la recherche de diverses technologies de Croissance. Certaines Particularités peuvent également accroître le maximum de Population pour les espèces d'un métabolisme donné. Consultez l'article [[encyclopedia GROWTH_FOCUS_TITLE]] pour plus d'informations sur ces Particularités. +11: Les maximums de [[metertype METER_POPULATION]] atteignables sur une planète peuvent être augmentés via la recherche de diverses technologies de Croissance. Certaines Particularités peuvent également accroître la [[metertype METER_TARGET_POPULATION]] pour les espèces d'un métabolisme donné. Consultez l'article [[encyclopedia GROWTH_FOCUS_TITLE]] pour plus d'informations sur ces Particularités. BEGINNER_HINT_12 12: Prêtez attention aux planètes dotées d'une [[encyclopedia ENC_SPECIAL]], ces dernières renferment parfois des ressources pouvant benéficier à l'ensemble de votre empire si celles-ci sont utilisées avec efficacité. Un clic-droit sur l'icône de la Particularité ouvrira l'article correspondant dans l'Encyclopédie. @@ -6450,7 +7466,7 @@ BEGINNER_HINT_14 14: Recherchez les technologies améliorant l'[[metertype METER_SUPPLY]], telle la [[tech CON_ORBITAL_CON]], afin d'accroître la distance que vos lignes d'approvisonnement peuvent atteindre à partir de vos systèmes. BEGINNER_HINT_15 -15: La plupart des espèces se retrouvent avec un indicateur [[metertype METER_HAPPINESS]] de 1 lorsque vous capturez leur planète, puis ce dernier augmente normalement de 1 à chaque tour. Faites cependant attention aux espèces [[encyclopedia XENOPHOBIC_SPECIES_TITLE]]s! Elles n'apprécient guère la proximité d'autres espèces et cela aura un effet négatif sur le Bonheur ainsi que d'autres indicateurs, à la fois pour l'espèce xénophobe et les autres espèces à proximité. +15: La plupart des espèces se retrouvent avec un indicateur [[metertype METER_HAPPINESS]] de 0 lorsque vous capturez leur planète, puis ce dernier augmente normalement de 1 à chaque tour. Faites cependant attention aux espèces [[encyclopedia XENOPHOBIC_SPECIES_TITLE]]s! Elles n'apprécient guère la proximité d'autres espèces et cela aura un effet négatif sur le Bonheur ainsi que d'autres indicateurs, à la fois pour l'espèce xénophobe et les autres espèces à proximité. BEGINNER_HINT_16 16: Il est plus judicieux de ne pas développer la technologie [[tech LRN_PSIONICS]] tant que votre empire n'abrite pas une espèce maîtrisant la [[encyclopedia TELEPATHIC_TITLE]]. @@ -6459,10 +7475,10 @@ BEGINNER_HINT_17 17: Les [[metertype METER_SHIELD]] d'astronef constituent une défense contre chaque tir ennemi, réduisant les [[encyclopedia DAMAGE_TITLE]] subis de la valeur de solidité du Bouclier de l'astronef. La solidité d'un Bouclier peut être affecté par divers facteurs comme les tempêtes spatiales, le type d'étoile au sein du système, ou la proximité d'une Planète Capitale d'un Empire ayant été capturée. BEGINNER_HINT_18 -18: [[encyclopedia FOCUS_TITLE]] - Faites attention au réglage du Focus de chaque nouvelle planète intégrant votre empire, que ce soit par l'établissement d'une nouvelle colonie ou bien par conquête militaire. Ce réglage détermine si la planète privilégie l'Industrie, la Recherche ou la Défense. À mesure que la partie avance, de nouvelles possibilités de Focus peuvent être débloquées si les [[encyclopedia ENC_TECH]] requises sont maîtrisées, tandis que différentes espèces peuvent avoir différents types de Focus disponibles. +18: [[encyclopedia FOCUS_TITLE]] - Faites attention au réglage du Focus de chaque nouvelle planète intégrant votre empire, que ce soit par l'établissement d'une nouvelle colonie ou bien par conquête militaire. Ce réglage détermine si la planète privilégie l'[[metertype METER_INDUSTRY]], la [[metertype METER_RESEARCH]] ou la [[metertype METER_DEFENSE]]. À mesure que la partie avance, de nouvelles possibilités de Focus peuvent être débloquées si les [[encyclopedia ENC_TECH]] requises sont maîtrisées, tandis que différentes espèces peuvent avoir différents types de Focus disponibles. BEGINNER_HINT_19 -19: Une nouvelle colonie peut être établie sur un [[encyclopedia OUTPOSTS_TITLE]] en y construisant une structure 'Colonie' ou bien à l'aide d'un astronef [[predefinedshipdesign SD_COLONY_SHIP]]. La plupart des structures 'Colonie' requièrent une connection d'[[metertype METER_SUPPLY]] à une planète présentant un indicateur [[metertype METER_HAPPINESS]] d'au moins 5, et une Population supérieure ou égale à 3. Lorsqu'une nouvelle espèce est débloquée, par exemple via la recherche de la technologie [[tech PRO_EXOBOTS]], ce pré-requis ne s'applique pas pour leur structure 'Colonie'. +19: Une nouvelle colonie peut être établie sur un [[encyclopedia OUTPOSTS_TITLE]] en y construisant une structure 'Colonie' ou bien à l'aide d'un astronef [[predefinedshipdesign SD_COLONY_SHIP]]. La plupart des structures 'Colonie' requièrent une connection d'[[metertype METER_SUPPLY]] à une planète présentant un indicateur [[metertype METER_HAPPINESS]] d'au moins 5, et une [[metertype METER_POPULATION]] supérieure ou égale à 3. Lorsqu'une nouvelle espèce est débloquée, par exemple via la recherche de la technologie [[tech PRO_EXOBOTS]], ce pré-requis ne s'applique pas pour leur structure 'Colonie'. BEGINNER_HINT_20 20: Les Rapports de Situation spécifiques (tel celui-ci) peuvent être temporairement ignorés via un clic-droit sur leur icône, puis en sélectionnant une des options proposées. Les Rapports d'un certain type peuvent également être filtrés en utilisant le menu 'Filtres' de la Fenêtre 'Rapport de Situation'. Les astuces de jeu destinées aux débutants sont regroupées sous la dénomination [[encyclopedia BEGINNER_HINTS]] dans la liste de filtres. @@ -6514,7 +7530,10 @@ Opinions des Empires : OPINIONS_OF_OTHER_SPECIES Opinions des autres Espèces : -# Species Pedia Categories + +## +## Encyclopedia articles in Species Types category +## LITHIC_SPECIES_CLASS Lithique @@ -6546,7 +7565,16 @@ Autosuffisant SELF_SUSTAINING_SPECIES_CLASS_DESC Liste des espèces à [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]]: -# Species Pedia Articles +GASEOUS_SPECIES_CLASS +Gazeux + +GASEOUS_SPECIES_CLASS_DESC +Liste des espèces à [[encyclopedia GASEOUS_SPECIES_TITLE]]: + + +## +## Encyclopedia articles in Species category +## SP_HUMAN Humain @@ -6569,7 +7597,8 @@ SP_SCYLIOR # translated Scylior SP_SCYLIOR_GAMEPLAY_DESC -'''Nautiloïdes aquatiques dotés de trois tentacules. +''' +Nautiloïdes aquatiques dotés de trois tentacules. Préfèrent les Planètes [[PT_OCEAN]]s. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' @@ -6587,13 +7616,15 @@ Les Scyliori considèrent les restes d'un cerveau-ruche télépathe, qu'ils surn Histoire : Voici quelques millénaires, une expédition des Précurseurs découvrit une planète que L'Esprit contrôlait en totalité grâce à ses pouvoirs télépathiques. L'Esprit surgit sans tarder et commença à aspirer les connaissances des savants et de l'équipage présents à bord de l'astronef. Mais les savants parvinrent à prélever la majeure partie du cerveau de L'Esprit, et l'emportèrent comme spécimen. Ce qui demeura de L'Esprit survécut, cherchant inlassablement à récupérer les souvenirs qui lui avaient été enlevés. L'Esprit restait cependant une entité immobile, et n'avait rencontré jusqu'alors aucun organisme intelligent susceptible de lui prêter 'main forte'. Alors survinrent les Scyliori. + ''' SP_GYSACHE # translated Gysache SP_GYSACHE_GAMEPLAY_DESC -'''Herbivores craintifs insolites, organisation en troupeau. +''' +Herbivores craintifs insolites, organisation en troupeau. Préfèrent les Planètes [[PT_SWAMP]]s. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' @@ -6610,13 +7641,15 @@ Les Gysache sont constamment sur leur garde du fait des prédateurs et sont faci Monde d'origine: Leur Monde d'origine possède une épaisse atmosphère d'azote, d'ammoniaque et de dioxyde de carbone. Bien que très humide, leur environnement d'origine ne dispose pas d'étendues océaniques, seulement 5% de la surface planétaire étant recouverte d'eau. Ses principales caractéristiques topographiques sont des hauts plateaux balafrés de canyons abyssaux, dont les fonds contiennent le peu d'eau stagnante de la planète. + ''' SP_CHATO # translated Chato SP_CHATO_GAMEPLAY_DESC -'''Entités cristallines sessiles chevauchant des animaux nommés Gormoshk. +''' +Entités cristallines sessiles chevauchant des animaux nommés Gormoshk. Préfèrent les Planètes [[PT_TOXIC]]s. [[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]] ''' @@ -6627,14 +7660,16 @@ Les Chato'matou'Gormoshk sont en réalité deux espèces reliées, bien qu'on ne Structure sociale: Dans la société Chato'matou, l'âge est le paramètre primordial, étant donné leur quasi-immortalité. Chaque commandement des anciens est respecté à la lettre et désobéir à un ancien est le plus grand des crimes au sein de la société Chato'matou. Ces organismes ont développé un système rendant possible la transmission instantanée de signaux à travers une colonie entière, leur permettant ainsi de communiquer entre eux à une vitesse proche de celle de la lumière. Cela améliore grandement les capacités mentales de l'espèce, puisque toutes nouvelles idées et théories peuvent être propagées instantanément à toute la colonie. Les Gormoshk sont considérés comme des montures de choix et sont la plupart du temps bien traités, mais restent toutefois de simples animaux pour leurs maîtres. + ''' SP_TRITH # translated Trith SP_TRITH_GAMEPLAY_DESC -'''Télépathes xénophobes tourmentés. -Préfèrent les Planètes [[PT_DESERT]]s. +''' +Télépathes xénophobes tourmentés. +Préfèrent les Planètes [[PT_RADIATED]]s. [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]], [[encyclopedia XENOPHOBIC_SPECIES_TITLE]] ''' @@ -6644,13 +7679,15 @@ Les Trith sont un mélange de matière et d'énergie psychique. Leur physionomie Structure sociale: La société Trith est une théocratie dominée par les prêtres de Quaran-Dor (le Dieu de l'Esprit). Ils ont prêté le serment solennel de réduire au silence toutes les autres espèces pensantes, afin d'atténuer le fracas spirituel incessant qui parcourt l'Univers. + ''' SP_HAPPY # translated Happybirthday SP_HAPPY_GAMEPLAY_DESC -'''Robots submersibles abandonnés et solitaires, programmés pour chanter "Happy Birthday" à intervalles réguliers. +''' +Robots submersibles abandonnés et solitaires, programmés pour chanter "Happy Birthday" à intervalles réguliers. Préfèrent les Planètes [[PT_OCEAN]]s. [[encyclopedia ROBOTIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] ''' @@ -6671,13 +7708,16 @@ Le processus de colonisation est lent et l'industrie peu efficace, en raison du Histoire: Triste. Que ce soit leur exploration solitaire de leur planète natale, leur abandon par leurs créateurs, ou leur quête incessante d'amitié, l'existence des Happybirthdays est triste et solitaire. + + ''' SP_HHHOH # translated Hhhoh SP_HHHOH_GAMEPLAY_DESC -'''Mammouths gigantesques et lents, dotés d'une multi-trompe. +''' +Mammouths gigantesques et lents, dotés d'une multi-trompe. Préfèrent les Planètes [[PT_TUNDRA]]. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' @@ -6689,13 +7729,16 @@ Les Hhhoh se déplacent à l'aide de leurs deux puissantes pattes avant, tandis Comportement: Les Hhhoh se considèrent comme des pragmatiques sociables. Les anciens sont vénérés et respectés, et peuvent atteindre un âge de 230 ans. Leur particularité physique et leur physiologie font que cette espèce a une aversion pour les comportements hâtifs et les décisions précipitées. + + ''' SP_EAXAW # translated Eaxaw SP_EAXAW_GAMEPLAY_DESC -'''Vers amazones agressifs et xénophobes. +''' +Vers amazones agressifs et xénophobes. Préfèrent les Planètes [[PT_TERRAN]]s. [[encyclopedia ORGANIC_SPECIES_TITLE]], [[encyclopedia XENOPHOBIC_SPECIES_TITLE]] ''' @@ -6709,6 +7752,7 @@ Les Eaxaw sont organisés en groupes matriarcaux de reproduction. Une femelle do Histoire: Il y a bien longtemps, un vaisseau abandonné des Précurseurs s'écrasa sur la planète d'origine des Eaxaw. Alors qu'ils découvraient certains de ses secrets, ils devinrent conscients que d'autres espèces intelligentes peuplaient l'Univers. La Grande Chambre Matriarcal édicta alors sa plus fameuse proclamation : "Tous doivent périr!". + ''' SP_DERTHREAN # translated @@ -6732,7 +7776,8 @@ SP_LAENFA # translated Laenfa SP_LAENFA_GAMEPLAY_DESC -'''Vignes sensibles, télépathes, et insidieuses. +''' +Vignes sensibles, télépathes, et insidieuses. Préfèrent les Planètes [[PT_OCEAN]]s. [[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] ''' @@ -6743,20 +7788,41 @@ Les Laenfa sont de longs organismes végétaux semblables à des vignes, capable Structure sociale: Les Laenfa jouissent d'une culture unifiée due à la télépathie, mais comme leur capacité télépathique s'affaiblit avec la distance, le tronc central de la vigne est en charge des décisions importantes. Cette vigne centrale gère seule les arbitrages, mais prend en compte les voix des Laenfa qu'elle est en mesure de détecter. Les Laenfa évoluent donc à la manière d'un super-organisme, chaque individu œuvrant pour le collectif, dont le but est d'acquérir le savoir des autres espèces et de protéger le leur. + +''' + +SP_SLY # translated +Sly + +SP_SLY_GAMEPLAY_DESC +'''Sacs de gaz affables mais peu connus. +Préfèrent grandement les planètes de type [[PT_GASGIANT]]. +[[encyclopedia GASEOUS_SPECIES_TITLE]] +''' + +SP_SLY_DESC +'''Description: +Les Sly vivent sur les géantes gazeuses. Ces dernières étant si étrangères à la plupart des espèces dans l'univers, l'existence et l'histoire des Sly sont par conséquent peu connues. +Dans l'incapacité de produire tout dispositif technologique, les Sly ne peuvent quitter leur monde natal. Mais récemment, ils ont pu obtenir de marchands galactiques l'équipement et les technologies nécessaires au voyage spatial en échange de données sur leur espèce et son histoire. +Sous un régime alimentaire adapté, les Sly excrètent du carburant raffiné. Un astronef à équipage Sly régénère donc son carburant lorsqu'il se trouve dans un système abritant une géante gazeuse. + +Structure sociale: +Les Sly ont une espérance de vie très importante et transmettent de génération en génération des chants dans lesquels l'ensemble du savoir culturel de l'espèce est encodé. Les Sly sont très ouverts et extrêmement curieux envers la galaxie et les autres espèces, avec lesquelles ils peuvent enfin entrer en contact. ''' SP_LEMBALALAM # translated Lembala'Lam + SP_LEMBALALAM_GAMEPLAY_DESC '''Reptiles ancestraux égocentriques et dégénérescents, ne pouvant plus se reproduire. Préfèrent les Planètes [[PT_DESERT]]s. [[encyclopedia ORGANIC_SPECIES_TITLE]] -Contrôler les Lembala octroie les technologies suivantes : [[tech SPY_STEALTH_1]], [[tech SPY_STEALTH_2]] et [[tech GRO_LIFECYCLE_MAN]]. +Contrôler les Lembala octroie les technologies suivantes : [[tech SPY_STEALTH_1]], [[tech SPY_STEALTH_2]] et [[tech GRO_LIFECYCLE_MAN]]. ''' SP_LEMBALALAM_DESC '''Description: -Les Lembala'Lam sont une ancienne espèce au crépuscule de leur existence. Semblables par le passé à des reptiles tels les serpents ou les geckos, l'évolution de leur physionomie d'origine a été corrompue par leur mode de vie fortement influencé par l'amélioration mécanique. Sans machines de toutes sortes, ils seraient aujourd'hui incapables de se mouvoir ou de se nourrir. Leur système de reproduction s'en est retrouvé totalement inhibé, mais encore plus important, plus aucune relation de confiance n'unit l'espèce. Car le dernier clou planté dans le cercueil de la société Lembala fut l'invention d'un système automatisé transférant l'esprit d'un Lembala mourant dans un clone de remplacement, chaque Lembala s'assurant de disposer de centaines de corps de rechange précieusement conservés et dissimulés. La quasi-totalité des ressources mentales et matérielles des Lembala est utilisée pour entretenir cette immortalité artificielle, au détriment du développement réel de leur civilisation. +Les Lembala'Lam sont une ancienne espèce au crépuscule de leur existence. Semblables par le passé à des reptiles tels les serpents ou les geckos, l'évolution de leur physionomie d'origine a été corrompue par leur mode de vie fortement influencé par l'amélioration mécanique. Sans machines de toutes sortes, ils seraient aujourd'hui incapables de se mouvoir ou de se nourrir. Leur système de reproduction s'en est retrouvé totalement inhibé, mais encore plus important, plus aucune relation de confiance n'unit l'espèce. Car le dernier clou planté dans le cercueil de la société Lembala fut l'invention d'un système automatisé transférant l'esprit d'un Lembala mourant dans un clone de remplacement, chaque Lembala s'assurant de disposer de centaines de corps de rechange précieusement conservés et dissimulés. La quasi-totalité des ressources mentales et matérielles des Lembala est utilisée pour entretenir cette immortalité artificielle, au détriment du développement réel de leur civilisation. Structure sociale: Les Lembala connaissent chacun de leurs semblables depuis les âges les plus reculés, mais la méfiance envers tout un chacun est le seul sentiment partagé. Ces relations délétères engendrent de nombreuses impasses sociétales et paralysent leur gouvernement, pourtant démocratique, depuis une éternité. En raison de cette paranoïa constante et du manque d'altruisme des Lembala, même la coopération la plus basique est vouée à l'échec, les rares fois où une tentative de collaboration semble émerger. @@ -6872,7 +7938,8 @@ SP_PHINNERT # translated Phinnert SP_PHINNERT_GAMEPLAY_DESC -'''Singes volants industrialisés. +''' +Singes volants industrialisés. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' @@ -6885,6 +7952,27 @@ La vie est bien plus courte pour les Phinnerts que pour la majorité des espèce Planète Natale: Leur planète possède une faible gravité et une atmosphère très épaisse. Sa composition géologique est unique du fait de la faible proportion de métaux lourds, mais de la bien plus forte présence d'autres éléments lourds, particulièrement les gaz nobles. De son activité tectonique très stable, résulte une croûte terrestre très plate, qui par conséquent est principalement recouverte d'eaux stagnantes et peu profondes. La totalité de leur monde s'apparente à une zone marécageuse géante. + +''' + +SP_REPLICON # translated +Replicon + +SP_REPLICON_GAMEPLAY_DESC +'''Robots auto-réplicants à peine sensibles. +Préfèrent les Planètes [[PT_RADIATED]]s. +[[encyclopedia ROBOTIC_SPECIES_TITLE]] +''' + +SP_REPLICON_DESC +'''Description: +Un Replicon a l'apparence d'une araignée robotique d'une envergure de 3m et munie d'ailes. Ces ailes sont en réalité des panneaux solaires hautement efficients car couvrant une grande superficie. "L'abdomen" de la créature s'avère être un "centre industriel de production" où les membres de remplacement et les nouveaux Replicons sont créés. + +Structure sociale: +Les Replicons ont une structure sociale relativement primitive. Ils sont dirigés par un gouvernement planétaire s'appuyant sur quelques seigneurs féodaux, ces derniers ayant découvert le voyage spatial, devenu rapidement une solution très pratique pour lancer des attaques contre des peuplades très lointaines. Leur gouvernement est donc féodal en raison d'un fait assez inhabituel: la découverte du vol spatial par quelques individus avant l'avènement d'une révolution industrielle à l'échelle d'une nation. De par la grande variété des machines auto-réplicantes sur Cisarruj, la plupart des besoins normalement satisfaits par l'industrie sur les autres planètes, le sont par "l'agriculture" ou "reproduction" chez les Replicons. En effet, les Replicons ne disposent d'aucune base industrielle ni d'aucune connaissance technologique à proprement parler, autres que l'usage d'équipements et produits créés "naturellement" par leur soin. Par conséquent, leur société est très peu érudite, la plupart des individus transmettant la connaissance d'une manière discontinue et partielle: comment forger un revêtement de titane ou quelle quantité de minerai d'uranium est nécessaire pour alimenter son réacteur de production. La plupart des connaissances que les sociétés développées regroupent, partagent et mettent en relation, ne sont détenues que par les seigneurs, le reste de la société se développant aléatoirement grâce aux quelques savoirs appris et conservés, à la manière d'un monde post-apocalyptique. + +Planète natale: +Cisarruj est une planète fortement irradiée, dominée par une grande variété de machines autonomes auto-réplicantes, et entourée d'un grand mystère. Il existe bien quelques rares archives historiques sur les protagonistes de la guerre ayant mené à la naissance de l'Empire Cisarruj. Cependant, la plupart de ces écrits ne relatent que les faits classiques d'une ère pré-industrielle, parsemée de périodes d'essor ou de déclin. Le Grand Mystère des Replicons quant à lui, reste entier. Une race supérieure a-t-elle conçu les Replicons au terme de nombreuses expériences, ou sont-ils le résultat d'un accident industriel poussant leurs infortunés créateurs à abandonner la planète? Ou bien ont-ils subi un destin encore plus funeste de la main-même des créatures qu'ils ont engendrées... ''' SP_EGASSEM # translated @@ -6912,7 +8000,7 @@ Sslith SP_SSLITH_GAMEPLAY_DESC '''Créatures aquatiques plates et souples. -Préfèrent les Planètes [[PT_TUNDRA]]. +Préfèrent les Planètes [[PT_OCEAN]]s. [[encyclopedia ORGANIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] ''' @@ -6982,13 +8070,14 @@ SP_TRENCHERS # translated Trenchers SP_TRENCHERS_GAMEPLAY_DESC -'''Robots énigmatiques munis de roues, se consacrant exclusivement à sculpter la surface de leur planète en courbes et spirales les plus élaborées. +''' +Robots énigmatiques munis de roues, se consacrant exclusivement à sculpter la surface de leur planète en courbes et spirales les plus élaborées. Préfèrent les Planètes [[PT_BARREN]]s. [[encyclopedia ROBOTIC_SPECIES_TITLE]] ''' -#SP_TRENCHERS_DESC -#'''''' +SP_TRENCHERS_DESC # translated + SP_RAAAGH # translated Raaagh @@ -7069,7 +8158,8 @@ SP_UGMORS # translated Ugmors SP_UGMORS_GAMEPLAY_DESC -'''Amas d'écume magmatique, pouvant s'agglomérer temporairement en golems. +''' +Amas d'écume magmatique, pouvant s'agglomérer temporairement en golems. Préfèrent les Planètes [[PT_INFERNO]]s. [[encyclopedia LITHIC_SPECIES_TITLE]] ''' @@ -7087,6 +8177,7 @@ Un Ugmor peut revêtir plusieurs formes, mais ils sont tous constitués de roche Planète Natale: Le monde d'origine des Ugmors est une planète rocheuse à la période de rotation extrêmement rapide (une rotation complète toutes les 3 heures et demie). 30% de sa surface est recouverte de mers de lave et de métaux liquides. + ''' SP_GISGUFGTHRIM # translated @@ -7124,18 +8215,21 @@ SP_ABADDONI # translated Abaddoni SP_ABADDONI_GAMEPLAY_DESC -'''Organismes troglodytes généralement inoffensifs, et totalement soumis à un ordinateur central surprotecteur créé par ces derniers. +''' +Organismes troglodytes généralement inoffensifs, et totalement soumis à un ordinateur central surprotecteur créé par ces derniers. Préfèrent les Planètes [[PT_INFERNO]]s. [[encyclopedia LITHIC_SPECIES_TITLE]] ''' SP_ABADDONI_DESC '''Description: + Les Abaddoni ont une forme ovoïde d'environ 0.8 mètres de diamètre, arborent une profonde couleur rouge, tachetée de points noirs sur le dessus du corps. Leur caractéristique morphologique principale est une grande bouche s'étendant sur l'avant du corps à partir de ces points noirs, soit près d'un tiers de leur surface corporelle. À l'extrémité de chacune de leurs 6 pattes ressemblant à celles de crustacés, se trouve une sorte de sac gonflable qui leur permet de s'accrocher par effet de ventouse à n'importe quelle surface, quelle que soit son inclinaison. Comme les Abaddoni se développent en milieu souterrain, ils ne possèdent pas d'organe de vision, se référant ainsi principalement à leur odorat, et dans une moindre proportion, à l'ouïe et au toucher pour se déplacer. Structure sociale: L'espèce entière des Abaddonni vit sous le joug d'une intelligence artificielle sensible appelée Mère. Cette IA fut créée par les Abaddoni comme une assistante gouvernementale, mais elle prit lentement le dessus sur ses créateurs. À présent, chaque nouveau-né Abaddoni se voit implanter un transmetteur dans le cerveau, pour que Mère puisse connaître les pensées de chacun de ses sujets et pouvoir contrôler leurs fonctions vitales au besoin. Il n'y aucun semblant de structure sociale chez les Abaddoni: ils sont tous semblables, c'est-à-dire esclaves. + ''' SP_ACIREMA # translated @@ -7209,6 +8303,32 @@ Raison de la sédentarité: Les Volp-Uglush ayant à présent une société hautement complexe et intriquée, il leur est impossible de quitter Hive pour fonder ailleurs une nouvelle colonie, cela nécessitant un esprit d'indépendance et d'innovation leur faisant désormais défaut. ''' +SP_FULVER +Fulver + +SP_FULVER_GAMEPLAY_DESC +'''Fourmis précognitives insatisfaites. +Préfèrent les planètes [[PT_TUNDRA]]. +[[encyclopedia LITHIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] +''' + +SP_FULVER_DESC +'''Biologie: +Les Fulvers ont l'apparence de grandes fourmis nerveuses. Ces dernières sont dotées d'un cerveau quantique qui leur permet de prédire avec une grande efficacité tous les futurs proches possibles, rendant très difficile de les surprendre en situation de combat. + +Nom: +La capacité de précognition est unique à l'espèce des Fulvers au sein du monde natal des Fulvers. Par conséquent, un "Fulver" est "Celui qui peut voir" toutes vos réactions possibles. + +Personnalité: +Un Fulver préfère se concentrer sur sa force individuelle et vivre au jour le jour. Il favorise le plus souvent les outils et les décisions qui lui permettent d'appréhender le plus grand nombre de choix futurs. Ayant une mauvaise mémoire et étant dans l'impossibilité de prévoir le futur à long terme, les Fulvers ne peuvent pas entreprendre sur le long terme, dans le domaine scientifique ou la construction de structures défensives complexes. Se sentant entravés par ce nombre limité de choix, les Fulvers, constamment agités et insatisfaits, tentent cependant de surmonter leur condition. + +Raison de l'exode: +Les possibilités infinies qu'offrent l'espace motivent grandement les Fulvers, leur permettant au final de développer une technologie efficace de voyage spatial. Atteignant constamment de nouvelles frontières, les Fulvers ne se soucient guère de ce qu'ils laissent derrière eux. + +Structure sociale: +Leur obsession du futur proche empêche les Fulvers de bâtir des structures de long terme. Étant conscients en permanence de tous les choix possibles de leurs semblables et agissant en conséquence, la société des Fulvers peut être considérée au mieux comme une anarchie informelle. +''' + SP_FURTHEST # translated Furthest @@ -7236,7 +8356,8 @@ SP_BANFORO # translated Banforo SP_BANFORO_GAMEPLAY_DESC -'''Êtres adorables et contemplateurs d'étoiles. +''' +Êtres adorables et contemplateurs d'étoiles. Préfèrent les Planètes [[PT_BARREN]]s. [[encyclopedia LITHIC_SPECIES_TITLE]] ''' @@ -7250,6 +8371,7 @@ Les Banforo sont très sociables et tissent des liens très forts entre eux. Les Raison de l'extinction: La raison de leur disparition n'a pas été clairement établie, mais la thèse scientifique opte pour un événement cosmique ayant rendu les conditions environnementales invivables pour les Banforo, sur une période de temps assez longue pour que leur nombre décline et que leur espèce finisse par s'éteindre. + ''' SP_KILANDOW # translated @@ -7307,6 +8429,7 @@ SP_EXOBOT_DESC SP_ANCIENT_GUARDIANS Gardiens Ancestraux + SP_ANCIENT_GUARDIANS_GAMEPLAY_DESC '''Robots gardiens conçus en des temps immémoriaux par une civilisation de Précurseurs. [[encyclopedia ROBOTIC_SPECIES_TITLE]] @@ -7361,21 +8484,21 @@ GAIA_SPECIAL # translated Gaia GAIA_SPECIAL_DESC -'''Terraforme la planète puis accroît sa Population max selon la taille de la planète : +'''Terraforme la planète puis accroît sa [[metertype METER_TARGET_POPULATION]] selon la taille de la planète : • [[SZ_TINY]] (+3) • [[SZ_SMALL]] (+6) • [[SZ_MEDIUM]] (+9) • [[SZ_LARGE]] (+12) • [[SZ_HUGE]] (+15) -et augmente son [[metertype METER_HAPPINESS]] max de 5. +et augmente la [[metertype METER_TARGET_HAPPINESS]] de 5. -Cette planète s'est muée en organisme vivant, ce qui la rend capable de se modifier et de s'adapter afin de satisfaire les besoins de sa population. Si l'[[encyclopedia ENVIRONMENT_TITLE]] devient parfait pour l'espèce, un bonus susbstantiel de Population sera acquis. Dans le cas contraire, la planète se terraformera lentement par paliers pour finalement atteindre une Habitabilité [[PE_GOOD]] pour l'espèce (NB: Les [[species SP_EXOBOT]]s ne s'adaptant parfaitement à aucun Environnement, une planète terraformée pour cette espèce ne pourra dépasser le stade [[PE_ADEQUATE]]).''' +Cette planète s'est muée en organisme vivant, ce qui la rend capable de se modifier et de s'adapter afin de satisfaire les besoins de sa Population. Si l'[[encyclopedia ENVIRONMENT_TITLE]] devient parfait pour l'espèce, un bonus susbstantiel de Population sera acquis. Dans le cas contraire, la planète se terraformera lentement par paliers pour finalement atteindre une Habitabilité [[PE_GOOD]] pour l'espèce (NB: Les [[species SP_EXOBOT]]s ne s'adaptant parfaitement à aucun Environnement, une planète terraformée pour cette espèce ne pourra dépasser le stade [[PE_ADEQUATE]]).''' WORLDTREE_SPECIAL Arbre Planétaire WORLDTREE_SPECIAL_DESC -'''Augmente l'[[metertype METER_SUPPLY]] de +1, le [[metertype METER_DETECTION]] de +10, la Population max de +1, et le [[metertype METER_HAPPINESS]] de +5 sur la planète présentant cette Particularité. Augmente le [[metertype METER_HAPPINESS]] de +1 sur toutes les autres planètes appartenant au même empire. +'''Augmente l'[[metertype METER_SUPPLY]] de +1, le [[metertype METER_DETECTION]] de +10, la [[metertype METER_TARGET_POPULATION]] de +1, et la [[metertype METER_TARGET_HAPPINESS]] de +5 sur la planète présentant cette Particularité. Augmente la [[metertype METER_TARGET_HAPPINESS]] de +1 sur toutes les autres planètes appartenant au même empire. Un arbre gigantesque s'est développé à tel point que sa cime surplombe l'atmosphère de la planète. Les scientifiques pensent que cet arbre fut planté par les Soigneurs dans les temps ancestraux.''' @@ -7383,7 +8506,7 @@ ANCIENT_RUINS_DEPLETED_SPECIAL Ruines Ancestrales (Exhumées) ANCIENT_RUINS_DEPLETED_SPECIAL_DESC -'''Augmente la [[metertype METER_RESEARCH]] sur cette planète de 1 par Population, si le Focus Recherche est sélectionné. +'''Augmente la [[metertype METER_TARGET_RESEARCH]] sur cette planète de 1 par [[metertype METER_POPULATION]], si le Focus Recherche est sélectionné. Cette planète abrite les ruines d'une puissante civilisation ancestrale et oubliée... Mais une découverte de grande valeur y a déjà eu lieu.''' @@ -7391,7 +8514,7 @@ ANCIENT_RUINS_SPECIAL Ruines Ancestrales ANCIENT_RUINS_SPECIAL_DESCRIPTION -'''Augmente la [[metertype METER_RESEARCH]] sur cette planète de 1 par Population, si le Focus Recherche est sélectionné. +'''Augmente la [[metertype METER_TARGET_RESEARCH]] sur cette planète de 1 par [[metertype METER_POPULATION]], si le Focus Recherche est sélectionné. Cette planète abrite les ruines d'une puissante civilisation ancestrale et oubliée... Le premier empire prenant pied sur la planète et maîtrisant la [[tech LRN_XENOARCH]], acquiert gratuitement la maîtrise d'une technologie avancée, ou bien découvre une structure ou un astronef aux puissantes caractéristiques. La découverte de corps parfaitement conservés d'une espèce éteinte est également envisageable.''' @@ -7454,7 +8577,7 @@ ECCENTRIC_ORBIT_SPECIAL Orbite Excentrissime ECCENTRIC_ORBIT_SPECIAL_DESC -'''Augmente de 3 la [[metertype METER_RESEARCH]], mais réduit de 2 l'[[metertype METER_SUPPLY]]. +'''Augmente de 3 la [[metertype METER_TARGET_RESEARCH]], mais réduit de 2 l'[[metertype METER_SUPPLY]]. L'orbite de la planète autour de son étoile est très excentrique : la distance de son aphélie est un très grand multiple de la distance de son périhélie. L'intensité de l'ensoleillement varie ainsi énormément au cours de l'année. Ces conditions variables ralentissent la gestion de l'approvisionnement sur la planète, mais s'avèrent un terrain exaltant pour la recherche scientifique.''' @@ -7462,20 +8585,20 @@ TIDAL_LOCK_SPECIAL Rotation Synchrone TIDAL_LOCK_SPECIAL_DESC -'''Augmente l'[[metertype METER_INDUSTRY]] de la planète de 0.2 par Population si le Focus [[metertype METER_INDUSTRY]] est activé. Diminue la Population selon la taille de la planète (quel que soit le Focus) : +'''Augmente la [[metertype METER_TARGET_INDUSTRY]] de la planète de 0.2 par [[metertype METER_POPULATION]] si le Focus [[metertype METER_INDUSTRY]] est activé. Diminue la [[metertype METER_TARGET_POPULATION]] selon la taille de la planète (quel que soit le Focus) : • [[SZ_TINY]] (-1) • [[SZ_SMALL]] (-2) • [[SZ_MEDIUM]] (-3) • [[SZ_LARGE]] (-4) • [[SZ_HUGE]] (-5) -Cette planète est en rotation synchrone lors de son orbite autour de son étoile. Les durées du jour et de l'année sont rigoureusement égales, ce qui signifie qu'une face de la planète est perpétuellement diurne tandis que l'autre est perpétuellement nocturne. La croissance de la population s'en trouve impactée, mais l'industrie tire profit des conditions stables à la surface de la planète.''' +Cette planète est en rotation synchrone lors de son orbite autour de son étoile. Les durées du jour et de l'année sont rigoureusement égales, ce qui signifie qu'une face de la planète est perpétuellement diurne tandis que l'autre est perpétuellement nocturne. La croissance de la Population s'en trouve impactée, mais l'Industrie tire profit des conditions stables à la surface de la planète.''' TEMPORAL_ANOMALY_SPECIAL Anomalie Temporelle TEMPORAL_ANOMALY_SPECIAL_DESC -'''Augmente la [[metertype METER_RESEARCH]] sur cette planète de 5 par Population si le Focus [[metertype METER_RESEARCH]] est activé. Diminue la Population selon la taille de la planète (quel que soit le Focus) : +'''Augmente la [[metertype METER_TARGET_RESEARCH]] sur cette planète de 5 par [[metertype METER_POPULATION]] si le Focus [[metertype METER_RESEARCH]] est activé. Diminue la [[metertype METER_TARGET_POPULATION]] selon la taille de la planète (quel que soit le Focus) : • [[SZ_TINY]] (-5) • [[SZ_SMALL]] (-10) • [[SZ_MEDIUM]] (-15) @@ -7497,7 +8620,7 @@ COMPUTRONIUM_SPECIAL Lune Ordinatrice COMPUTRONIUM_SPECIAL_DESC -'''Lorsqu'une Lune Ordinatrice orbite autour d'une planète privilégiant la [[metertype METER_RESEARCH]], toutes les planètes de l'empire réglées sur le même Focus améliorent leur [[metertype METER_RESEARCH]] de 0.2 par Population. +'''Lorsqu'une Lune Ordinatrice orbite autour d'une planète privilégiant la [[metertype METER_RESEARCH]], toutes les planètes de l'empire réglées sur le même Focus améliorent leur [[metertype METER_TARGET_RESEARCH]] de 0.2 par [[metertype METER_POPULATION]]. Abandonnée par une civilisation dont le souvenir s'est effacé avec le temps, cette lune est en réalité un ordinateur d'une puissance de calcul inouïe. Elle peut effectuer des simulations et résoudre les équations les plus complexes en moins de temps qu'il ne faut pour les lui proposer.''' @@ -7505,7 +8628,7 @@ HONEYCOMB_SPECIAL Réserve Alvéolaire HONEYCOMB_SPECIAL_DESC -'''Lorsqu'une planète dotée d'une Réserve Alvéolaire privilégie l'[[metertype METER_INDUSTRY]], toutes les planètes de l'empire réglées sur le même Focus et lui étant reliées par ligne d'[[metertype METER_SUPPLY]], améliorent leur [[metertype METER_INDUSTRY]] de 0.5 par Population. +'''Lorsqu'une planète dotée d'une Réserve Alvéolaire privilégie l'[[metertype METER_INDUSTRY]], toutes les planètes de l'empire réglées sur le même Focus et lui étant reliées par ligne d'[[metertype METER_SUPPLY]], améliorent leur [[metertype METER_TARGET_INDUSTRY]] de 0.5 par [[metertype METER_POPULATION]]. La Réserve Alvéolaire est une planète qui fut utilisée comme telle par les Constructeurs dans les temps ancestraux. Quasiment la totalité des planètes aux alentours furent dépouillées de l'ensemble de leurs ressources puis détruites. Les matériaux récupérés furent triés et stockés séparément dans de gigantesques tubes creusés dans la croûte terrestre. Ces réserves abritent des éléments rares et de grande valeur, des molécules difficiles à synthétiser, et une vaste gamme de sources d'énergie. Ce que les Constructeurs désiraient créer à partir de ce trésor et la raison pour laquelle ils ne l'ont finalement pas utilisé, restent à ce jour un mystère.''' @@ -7513,7 +8636,7 @@ PHILOSOPHER_SPECIAL Planète Métaphysique PHILOSOPHER_SPECIAL_DESC -'''Aussi longtemps que la Planète Métaphysique demeure inhabitée, toutes les planètes habitées du même système stellaire voient leur [[metertype METER_RESEARCH]] accrue de 5 points. L'indicateur [[metertype METER_CONSTRUCTION]] d'une colonie installée sur une Planète Métaphysique est réduit de 20 points. +'''Aussi longtemps que la Planète Métaphysique demeure inhabitée, toutes les planètes habitées du même système stellaire voient leur [[metertype METER_TARGET_RESEARCH]] augmenter de 5. La [[metertype METER_TARGET_CONSTRUCTION]] d'une colonie installée sur une Planète Métaphysique diminue de 20. L'unique et dernier représentant d'une espèce vagabonde de taille planétaire a trouvé sa dernière demeure près de cette étoile, autour de laquelle il orbite comme une planète normale. Âgé de milliers d'années, il a perdu l'envie et la capacité de voyager et se consacre désormais à la méditation sur tous les mystères de l'univers qu'il a rencontré au cours de sa longue existence. Déroutant et légèrement perturbé, il refuse de communiquer avec quiconque n'étant ni de sa taille ni de sa forme, ne laissant pas aux savants d'autres choix que de l'interroger via la voix de la planète sur laquelle ils résident. Sans surprise, coloniser la Planète Métaphysique la perturbe gravement et provoque un mutisme profond. La construction à sa surface s'avère difficile, comme il pouvait l'être présumé sur une entité vivante.''' @@ -7565,13 +8688,13 @@ CONC_CAMP_MASTER_SPECIAL Voile Assassin CONC_CAMP_MASTER_SPECIAL_DESC -Tous les [[buildingtype BLD_CONC_CAMP]] implantés sur la planète, fonctionnant sans relâche et exerçant une terreur répressive sur la population, peuvent être dissimulés à tout hôte par le Voile Assassin. Mais tout être sensible reste capable de percer à jour un Voile Assassin. +Tous les [[buildingtype BLD_CONC_CAMP]] implantés sur la planète, fonctionnant sans relâche et exerçant une terreur répressive sur la [[metertype METER_POPULATION]], peuvent être dissimulés à tout hôte par le Voile Assassin. Mais tout être sensible reste capable de percer à jour un Voile Assassin. CONC_CAMP_SLAVE_SPECIAL Morosité Psychique CONC_CAMP_SLAVE_SPECIAL_DESC -Les [[buildingtype BLD_CONC_CAMP]] n'opèrent peut-être plus aujourd'hui à la surface de la planète, mais leur souvenir est encore vif, et les restes de fosses communes sont encore parfois découverts. Une blessure psychique handicape la population, qui ne peut prospérer. +Les [[buildingtype BLD_CONC_CAMP]] n'opèrent peut-être plus aujourd'hui à la surface de la planète, mais leur souvenir est encore vif, et les restes de fosses communes sont encore parfois découverts. Une blessure psychique handicape la [[metertype METER_POPULATION]], qui ne peut prospérer. CLOUD_COVER_MASTER_SPECIAL Émetteurs de Nuées @@ -7728,6 +8851,10 @@ Structure OBJ_SHIP Astronef +# Note: the OBJ_SHIP and OBJ_BUILDING entries are used for the build item type of ships and buildings. +BUILD_ITEM_TYPE_PROJECT +Projet + # Universe object types OBJ_FLEET Flotte @@ -7738,11 +8865,11 @@ Planète # Universe object types OBJ_POP_CENTER -Centre de Population +Centre Population # Universe object types OBJ_PROD_CENTER -Centre de Production +Centre Production # Universe object types OBJ_SYSTEM @@ -7912,6 +9039,13 @@ Croissance FOCUS_INDUSTRY Industrie +# Focus types +FOCUS_STOCKPILE +Stock + +FOCUS_STOCKPILE_DESC +Permet aux technologies de stockage d'augmenter la capacité d'usage du stock + # Focus types FOCUS_RESEARCH Recherche @@ -7970,27 +9104,27 @@ Type d'indicateur invalide # Meter types METER_TARGET_POPULATION -Population Optimale Locale +Projection Population # Meter types METER_TARGET_INDUSTRY -Industrie Optimale Locale +Projection Industrie # Meter types METER_TARGET_RESEARCH -Recherche Optimale Locale +Projection Recherche # Meter types METER_TARGET_TRADE -Commerce Optimal Local +Projection Commerce # Meter types METER_TARGET_CONSTRUCTION -Infrastructure Optimale Locale +Projection Infrastructure # Meter types METER_TARGET_HAPPINESS -Bonheur Optimal Local +Projection Bonheur # Meter types METER_MAX_CAPACITY @@ -8020,6 +9154,10 @@ Max Défense METER_MAX_SUPPLY Max Approvisionnement +# Meter types +METER_MAX_STOCKPILE +Max Stock + # Meter types METER_MAX_TROOPS Max Infanterie @@ -8076,6 +9214,10 @@ Défense METER_SUPPLY Approvisionnement +# Meter types +METER_STOCKPILE +Stock + # Meter types METER_TROOPS Infanterie @@ -8191,6 +9333,10 @@ structure BT_SHIP astronef +# Build types +BT_STOCKPILE +stock + # Resource types INVALID_RESOURCE_TYPE type de ressource invalide @@ -8279,7 +9425,10 @@ PC_TROOPS_DESC '''Les équipements d'Infanterie permettent d'augmenter les effectifs d'[[metertype METER_TROOPS]] à bord d'un astronef, ces derniers pouvant être utilisés pour envahir les planètes ennemies. Pour envahir une planète avec succès, l'effectif total d'[[metertype METER_TROOPS]] de tous les astronefs prenant part à l'invasion doit être supérieur à l'effectif total de défense (les égalités sont en faveur du défenseur). -Un astronef d'invasion devient définitivement inutilisable suite à l'opération d'atterrissage (tout équipement additionnel sur l'astronef est également perdu).''' + +Lors de l'invasion d'une planète, les astronefs d'Infanterie sélectionnés dans la Fenêtre Flottes seront utilisés pour l'opération d'invasion. Si aucun astronef d'Infanterie n'est sélectionné, les astronefs d'Infanterie seront automatiquement sélectionnés parmi les astronefs disponibles et en fonction du nombre de troupes défensives. + +Un astronef d'invasion devient définitivement inutilisable suite à l'opération d'atterrissage. Tous les équipements de l'astronef sont également perdus.''' # Ship part classes PC_DETECTION @@ -8317,10 +9466,10 @@ Colonisation PC_COLONY_DESC '''Les équipements de Colonisation d'un astronef fournissent les outils nécessaires afin de s'établir sur une nouvelle planète et en prendre possession. -Ces équipements peuvent également augmenter la [[metertype METER_POPULATION]] sur un astronef. -Lors de la colonisation d'une planète à l'aide d'un astronef colonisateur peuplé, une nouvelle colonie pour l'[[encyclopedia ENC_SPECIES]] est créée, débutant avec la même population que celle qui se trouvait à bord de l'astronef. +Ces équipements peuvent également augmenter la [[metertype METER_POPULATION]] sur un astronef (Colons). +Lors de la colonisation d'une planète à l'aide d'un astronef colonisateur peuplé, une nouvelle colonie pour l'[[encyclopedia ENC_SPECIES]] est créée, dont la Population de départ est égale à celle qui se trouvait à bord de l'astronef. -Un astronef sans population disposera tout de même d'un personnel suffisant pour établir un [[encyclopedia OUTPOSTS_TITLE]]. +Un astronef sans Population disposera tout de même d'un personnel suffisant pour établir un [[encyclopedia OUTPOSTS_TITLE]]. Un Avant-Poste peut fonder une colonie pour toute espèce de l'empire s'il se trouve à portée d'[[metertype METER_SUPPLY]], sous condition que la source d'approvisionnement satisfasse certains critères. La combinaison d'un astronef bâtisseur d'Avant-Poste et la fondation d'une colonie à cet emplacement est moins onéreuse que la production d'un astronef colonisateur peuplé. @@ -8378,23 +9527,58 @@ Les équipements de Production sont intégrés à un astronef pour former un [[O INVALID_VISIBILITY Visibilité invalide -# Visibility levels -VIS_NO_VISIBILITY # translated -Invisible +# Visibility levels +VIS_NO_VISIBILITY # translated +Invisible + +# Visibility levels +VIS_BASIC_VISIBILITY +Basique + +# Visibility levels +VIS_PARTIAL_VISIBILITY +Partielle + +# Visibility levels +VIS_FULL_VISIBILITY +Complète + +# Path types +PATH_BINARY +'''''' + +# Path types +PATH_RESOURCE +'''''' + +# Path types +PATH_PYTHON +'''''' + +# Path types +PATH_DATA_ROOT +'''''' + +# Path types +PATH_DATA_USER +'''''' + +# Path types +PATH_CONFIG +'''''' -# Visibility levels -VIS_BASIC_VISIBILITY -Basique +# Path types +PATH_SAVE +'''''' -# Visibility levels -VIS_PARTIAL_VISIBILITY -Partielle +# Path types +PATH_TEMP +'''''' -# Visibility levels -VIS_FULL_VISIBILITY -Complète -# Ship hull categories +## +## Ship hull categories +## HULL_LINE_GENERIC Classe Coque: Générique @@ -8494,9 +9678,6 @@ DESC_VAR_LOCAL_CANDIDATE DESC_VAR_ROOT_CANDIDATE éligible aux conditions d'origine -DESC_STATISTIC -statistique - DESC_STAT_TYPE type de statistique @@ -8743,8 +9924,17 @@ dernier tour où une bataille s'est déroulée ici DESC_COMPLEX Variable Complexe -DESC_VARIABLE_NAME -Nom de la Variable +DESC_VAR_JUMPSBETWEEN +voies spatiales entre les systèmes contenant les objets avec les ids %1% et %2% + +DESC_STATISTIC +Statistique + +DESC_VAR_COUNT +le nombre d'objets%2% + +DESC_VAR_IF +0, ou 1 s'il existe tout objet%2% # FOCS variable reference description text. # %1% reference type of the FOCS variable. @@ -9085,7 +10275,7 @@ DESC_DESIGN_HAS_PART_NOT # matching a part class. # %3% textual description of the ship part class. DESC_DESIGN_HAS_PART_CLASS -''' intégrant un équipement de classe %1%''' +''' intégrant entre un %1% et %2% équipements de classe %3%''' # %1% textual description of the lower limit for the number of ship parts # matching a part class. @@ -9093,7 +10283,7 @@ DESC_DESIGN_HAS_PART_CLASS # matching a part class. # %3% textual description of the ship part class. DESC_DESIGN_HAS_PART_CLASS_NOT -''' n'intégrant pas un équipement de classe %1%''' +''' n'intégrant pas entre un %1% et %2% équipements de classe %3%''' # %1% name of the ship design. DESC_PREDEFINED_SHIP_DESIGN @@ -9305,11 +10495,130 @@ DESC_ORDERED_BOMBARDED DESC_ORDERED_BOMBARDED_NOT ''' n'étant pas bombardé par un objet %1%''' +# %1% content type description, eg. species, building, focus, tech +# %2% name of specific scripted content, eg. Human, Imperial Palace, Industry Focus, The Physicsal Brain +DESC_LOCATION +''' qui correspond à la condition d'emplacement de %1% %2%''' + +# %1% content type description, eg. species, building, focus, tech +# %2% name of specific scripted content, eg. Human, Imperial Palace, Industry Focus, The Physicsal Brain +DESC_LOCATION_NOT +''' qui ne correspond pas à la condition d'emplacement de %1% %2%''' + +# %1% content type description, eg. species, building, focus, tech +# %2% name of specific scripted content, eg. Human, Imperial Palace, Industry Focus, The Physicsal Brain +DESC_COMBAT_TARGET +''' qui correspond à la condition de cibles de combat de %1% %2%''' + +# %1% content type description, eg. species, building, focus, tech +# %2% name of specific scripted content, eg. Human, Imperial Palace, Industry Focus, The Physicsal Brain +DESC_COMBAT_TARGET_NOT +''' qui ne correspond pas à la condition de cibles de combat de %1% %2%''' + +# AND Descriptions for expressions 'AND [ A B C ]' +DESC_AND_BEFORE_OPERANDS +''' [''' + DESC_AND_BETWEEN_OPERANDS -''' et''' +, et + +DESC_AND_AFTER_OPERANDS +''' ]''' + +DESC_AND_BEFORE_SINGLE_OPERAND +'''''' + +DESC_AND_AFTER_SINGLE_OPERAND +'''''' + +# NOT AND Descriptions for expressions 'NOT AND [ A B C ]' +# We actually describe the the logically equivalent expression ( (not A) or (not B) or (not C) ) +# Note: the operands (not A), (not B).. are handled in the descriptions of A, B.. respectively +DESC_NOT_AND_BEFORE_OPERANDS +[[DESC_NOT_OR_BEFORE_OPERANDS]] + +DESC_NOT_AND_BETWEEN_OPERANDS +[[DESC_NOT_OR_BETWEEN_OPERANDS]] + +DESC_NOT_AND_AFTER_OPERANDS +[[DESC_NOT_OR_AFTER_OPERANDS]] + +# Description for 'not ( AND [ A ] )' expressions +# Actually instead we describe 'OR [ not A ]' and leave the description of negation to A +DESC_NOT_AND_BEFORE_SINGLE_OPERAND +[[DESC_OR_BEFORE_SINGLE_OPERAND]] + +DESC_NOT_AND_AFTER_SINGLE_OPERAND +[[DESC_OR_AFTER_SINGLE_OPERAND]] + +# OR Descriptions: for expressions of the form ( A or B or C ) +DESC_OR_BEFORE_OPERANDS +''' (soit''' DESC_OR_BETWEEN_OPERANDS -''' ou''' +, ou + +DESC_OR_AFTER_OPERANDS +''' )''' + +DESC_OR_BEFORE_SINGLE_OPERAND +'''''' + +DESC_OR_AFTER_SINGLE_OPERAND +'''''' + +# NOT OR Descriptions; for expressions of the form 'NOT OR [ A B C ]' +# We actually describe the logically equivalent expression 'AND [ (NOT A) (NOT B) (NOT C) ]' +# Note: (NOT A), (NOT B).. are handled in the descriptions of A, B.. respectively +DESC_NOT_OR_BEFORE_OPERANDS +[[DESC_AND_BEFORE_OPERANDS]] + +DESC_NOT_OR_BETWEEN_OPERANDS +[[DESC_AND_BETWEEN_OPERANDS]] + +DESC_NOT_OR_AFTER_OPERANDS +[[DESC_AND_AFTER_OPERANDS]] + +# for expressions of the form 'NOT OR [ A ]' we describe instead 'AND [ NOT A ]' +# Note: (NOT A) is handled in the description of A +DESC_NOT_OR_BEFORE_SINGLE_OPERAND +[[DESC_AND_BEFORE_SINGLE_OPERAND]] + +DESC_NOT_OR_AFTER_SINGLE_OPERAND +[[DESC_AND_AFTER_SINGLE_OPERAND]] + +# OrderedAlternativesOf Descriptions for expressions 'OrderedAlternativesOf [ A B C ]' +DESC_ORDERED_ALTERNATIVES_BEFORE_OPERANDS +qui correspond à la première condition d'éligibilité { + +DESC_ORDERED_ALTERNATIVES_BETWEEN_OPERANDS +, soit + +DESC_ORDERED_ALTERNATIVES_AFTER_OPERANDS +''' }''' + +DESC_ORDERED_ALTERNATIVES_BEFORE_SINGLE_OPERAND +{ + +DESC_ORDERED_ALTERNATIVES_AFTER_SINGLE_OPERAND +(si également des non-correspondances)} + +# Expressions like 'NOT OrderedAlternativesOf [ A B C ]' +# Note: returned matches rely on (NOT A), (NOT B).. and are handled in the descriptions of A, B.. respectively +DESC_NOT_ORDERED_ALTERNATIVES_BEFORE_OPERANDS +qui correspond à la première condition d'éligibilité pour les candidats non-éligibles: { + +DESC_NOT_ORDERED_ALTERNATIVES_BETWEEN_OPERANDS +, soit + +DESC_NOT_ORDERED_ALTERNATIVES_AFTER_OPERANDS +''' }''' + +DESC_NOT_ORDERED_ALTERNATIVES_BEFORE_SINGLE_OPERAND +{ + +DESC_NOT_ORDERED_ALTERNATIVES_AFTER_SINGLE_OPERAND +(si également des non-correspondances)} # %1% textual description of the lower turn limit. # %2% textual description of the upper turn limit. @@ -9637,7 +10946,7 @@ LRN_ALGO_ELEGANCE Élégance Algorithmique LRN_ALGO_ELEGANCE_DESC -'''Augmente la [[metertype METER_RESEARCH]] Optimale de 0.1 par Population sur toutes les planètes réglées sur le Focus Recherche. +'''Augmente la [[metertype METER_TARGET_RESEARCH]] de 0.1 par [[metertype METER_POPULATION]] sur toutes les planètes en Focus Recherche. Avec une difficulté grandissante des problèmes d'analyse de données, les démarches traditionnelles des performances algorithmiques deviennent moins efficaces, en raison des limitations de la complexité irréductible. À ce stade, de nouvelles formes et fonctions algorithmiques deviennent significatives: esthétiquement et métaphoriquement, l'élégance de la solution doit être optimisée.''' @@ -9661,7 +10970,7 @@ LRN_ARTIF_MINDS Intelligences Artificielles LRN_ARTIF_MINDS_DESC -'''Augmente la [[metertype METER_RESEARCH]] Optimale de 2 sur toutes les planètes. +'''Augmente la [[metertype METER_TARGET_RESEARCH]] de 2 sur toutes les planètes. Alors que les ordinateurs traditionnels ont des capacités de calcul presque infinies, ils leur manquent cependant une qualité essentielle: la conscience. Bien que la définition complète du concept de conscience reste insaisissable, cette qualité peut être toutefois synthétisée et modifiée de manière convaincante. Ces recherches ouvrent de nouvelles voies en matière de sciences cognitives et de métaparadigmes. @@ -9730,7 +11039,7 @@ LRN_QUANT_NET Réseau Quantique LRN_QUANT_NET_DESC -'''Augmente la [[metertype METER_RESEARCH]] Optimale de 0.5 par Population sur toutes les planètes réglées sur le Focus Recherche. +'''Augmente la [[metertype METER_TARGET_RESEARCH]] de 0.5 par [[metertype METER_POPULATION]] sur toutes les planètes en Focus Recherche. Un réseau massif de donnés, à l'échelle de l'empire, capable de transférer instantanément des informations entre deux lieux, plutôt que par le système lent et indirect des voies spatiales. Ce progrès facilite grandement la Recherche dans tout l'empire. @@ -9752,7 +11061,7 @@ GRO_PLANET_ECOL Écologie Planétaire GRO_PLANET_ECOL_DESC -'''Accroît de 1 la Population max des Planètes [[PE_GOOD]]s et [[PE_ADEQUATE]]s. Ce bonus ne s'additionne pas avec la technologie [[tech GRO_SYMBIOTIC_BIO]]. +'''Accroît de 1 la [[metertype METER_TARGET_POPULATION]] des Planètes [[PE_GOOD]]s et [[PE_ADEQUATE]]s. Ce bonus ne s'additionne pas avec la technologie [[tech GRO_SYMBIOTIC_BIO]]. L'agriculture et la science médicale peuvent considérablement augmenter les chances de survie et entraîner d'importants accroissements de population ainsi que de production de nourriture. Toutefois, la croissance démographique se trouve limitée par la capacité en ressources naturelles de la planète et des biosphères qui la constituent. Comprendre les écosystèmes naturels et les contraintes qu'il subissent, permet la conservation et l'amélioration de l'environnement planétaire, au bénéfice des générations futures.''' @@ -9772,7 +11081,7 @@ GRO_SYMBIOTIC_BIO Biologie Symbiotique GRO_SYMBIOTIC_BIO_DESC -Accroît la Population max des Planètes [[PE_GOOD]]s, [[PE_ADEQUATE]]s et [[PE_POOR]]s selon leur taille : ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). +Accroît la [[metertype METER_TARGET_POPULATION]] des Planètes [[PE_GOOD]]s, [[PE_ADEQUATE]]s et [[PE_POOR]]s selon leur taille : ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). GRO_GENETIC_MED Médecine Génétique @@ -9784,7 +11093,7 @@ GRO_LIFECYCLE_MAN Manipulation du Cycle de Vie GRO_LIFECYCLE_MAN_DESC -'''En plus du nouvel équipement d'astronef disponible, cette technologie augmente à 3 la Population des nouvelles colonies fondées à l'aide des structures de colonisation. +'''En plus du nouvel équipement d'astronef disponible, cette technologie augmente à 3 la [[metertype METER_POPULATION]] des nouvelles colonies fondées à l'aide des structures de colonisation. Grâce aux manipulations chimiques et cryogéniques, les processus vitaux peuvent être interrompus de façon provisoire. Dans cet état, les organismes sont conservés indéfiniment, ne consomment aucune ressource et ne nécessitent qu'un très petit (non-vital) espace de stockage. À l'aide de cette technique, les colons peuvent voyager d'une manière bien plus efficace, tandis que sont grandement accrus le nombre initial de colons au sein d'une nouvelle colonie ainsi que la capacité d'accueil des astronefs de colonisation. @@ -9800,7 +11109,7 @@ GRO_XENO_GENETICS Génétique Xénobiologique GRO_XENO_GENETICS_DESC -'''Accroît la Population max des planètes, selon leur habitabilité et leur taille : +'''Accroît la [[metertype METER_TARGET_POPULATION]] des planètes, selon leur habitabilité et leur taille : • Planète [[PE_ADEQUATE]] ou [[PE_POOR]]: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10) • Planète [[PE_HOSTILE]]: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5) @@ -9821,7 +11130,7 @@ GRO_XENO_HYBRIDS Hybridation Xénobiologique GRO_XENO_HYBRIDS_DESC -'''Accroît la Population max des planètes, selon leur habitabilité et leur taille : +'''Accroît la [[metertype METER_TARGET_POPULATION]] des planètes, selon leur habitabilité et leur taille : • Planète [[PE_POOR]]: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5) • Planète [[PE_HOSTILE]]: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10) @@ -9846,17 +11155,78 @@ GRO_ENERGY_META Métabolisme d'Énergie Pure GRO_ENERGY_META_DESC -'''Augmente le [[metertype METER_FUEL]] max de 2 sur tous les astronefs, la [[metertype METER_DEFENSE]] max de 5 sur toutes les planètes, les [[metertype METER_SHIELD]] max de 5 sur toutes les planètes, l'[[metertype METER_INDUSTRY]] Optimale de 0.2 par Population sur toutes les planètes réglées sur le Focus Industrie, et la [[metertype METER_RESEARCH]] Optimale de 0.5 par Population sur toutes les planètes réglées sur le Focus Recherche. +'''Augmente le [[metertype METER_FUEL]] max de 1 sur tous les astronefs (avant mise à l'échelle selon le [[encyclopedia FUEL_EFFICIENCY_TITLE]]), la [[metertype METER_DEFENSE]] max de 5 sur toutes les planètes, les [[metertype METER_SHIELD]] max de 5 sur toutes les planètes, la [[metertype METER_TARGET_INDUSTRY]] de 0.2 par [[metertype METER_POPULATION]] sur toutes les planètes en Focus Industrie, et la [[metertype METER_TARGET_RESEARCH]] de 0.5 par [[metertype METER_POPULATION]] sur toutes les planètes en Focus Recherche. Tandis que la transformation d'une population entière en êtres de pure énergie s'avérerait peu pertinente, certaines tâches conviennent mieux à des individus dénués de corporalité. L'équipage d'un astronef par exemple, n'aurait plus besoin d'optimiser l'espace disponible pour les quartiers d'équipage ou le stockage des rations. Des êtres dotés d'une capacité de canalisation de l'énergie seraient en mesure d'améliorer les défenses et boucliers planétaires, ainsi que la production de ressources. Le stade ultime du développement physique est justement de transcender le caractère physique des choses. Sans corps, les individus et les sociétés pourraient migrer, croître et exister librement, sans être entravés par des limitations physiques inutiles et des processus métaboliques improductifs, gaspillant l'énergie afin d'assurer l'homéostasie.''' +PRO_TRANS_DESIGN +Design Transcendant + +PRO_TRANS_DESIGN_DESC +'''Transcendant les limitations de créativité des espèces, les représentants les plus inventifs et innovants des différentes espèces peuplant l'empire, entreprennent d'œuvrer collectivement à la conception de biens universellement utilisables. +Bâtir la structure [[buildingtype BLD_INTERSPECIES_ACADEMY]] sur différentes planètes garantit que ce procédé soit diffusé à travers tout l'empire. + +Le service de Stock Impérial utilise le résultat de cette expérience pour produire des biens pouvant être utilisés par tout citoyen.''' + +IMPERIAL_STOCKPILE_SHORT_DESC +Autorise le Stockage Impérial de Production + +PRO_PREDICTIVE_STOCKPILING +Stockage Anticipatif + +PRO_PREDICTIVE_STOCKPILING_DESC +'''Connaître où la demande future de certains biens augmentera, permet de stocker ceux-ci en prévision. +Le service de Stock Impérial dispose d'une flotte acheminant les matériaux bruts vers des lieux où l'on prévoit que d'importants projets puissent être entrepris. +Cette tâche s'avère difficile étant donné que le service doit présager du moment où la demande sera stipulée, de l'endroit, des matériaux et biens qui seront requis, et de leurs utilisateurs. + +Les PP inutilisés sont transférés automatiquement au Stock Impérial de Production. +Un projet de la file d'attente de Production puise automatiquement dans le Stock Impérial si son usage n'a pas été explicitement bloqué pour le projet en question. + +Il s'agit donc de la technologie de base du Stock Impérial, augmentant de 1 le [[metertype METER_STOCKPILE]] des planètes réglées sur le [[encyclopedia STOCKPILE_FOCUS_TITLE]].''' + +PRO_GENERIC_SUPPLIES +Stocks Génériques + +PRO_GENERIC_SUPPLIES_DESC +'''Par une gestion coordonnée des chaînes d'approvisionnement, des différents matériaux et des nombreux modèles de conception, des Stocks Génériques sont constitués, pouvant être facilement dissociés et réutilisés selon les besoins dans la plupart des productions industrielles. + +Les Stocks Génériques peuvent être constitués bien avant que la demande ne se fasse sentir. Les packages d'approvisionnement intègrent habituellement des dispositifs de reconstruction d'infrastructures permettant de redéfinir la production de nouveaux biens à partir des Stocks Génériques. +En atteignant ce degré de sophistication, les Stocks Génériques peuvent être utilisés pour concevoir de nouveaux produits technologiques qui n'étaient même pas envisagés au moment de la constitution des stocks. + +Cela exempte le service du Stock Impérial de connaître à l'avance quels biens seront exactement demandés. Prévoir le lieu de la demande et les quantités s'avère désormais suffisant. + +Il s'agit donc d'une technologie d'amélioration du Stock Impérial. Le [[metertype METER_STOCKPILE]] augmente de 2, plus un supplément de 0.01 par [[metertype METER_POPULATION]], plus un supplément de 3 pour chaque planète réglée sur le [[encyclopedia STOCKPILE_FOCUS_TITLE]]. +Ces améliorations sont cumulables avec le bonus de la technologie [[tech PRO_PREDICTIVE_STOCKPILING]].''' + +PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY +Complexe d'Intrication Interstellaire + +PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY_DESC +'''L'intrication quantique des salles de contrôle permet de manœuvrer des machines industrielles à distance à partir d'autres systèmes stellaires. +Dissocier le personnel et les moyens de production favorise un procédé optimal de conception et augmente l'efficience du service de Stock Impérial. + +Il s'agit donc d'une technologie d'amélioration du Stock Impérial. Le [[metertype METER_STOCKPILE]] augmente de 6, plus un supplément de 0.02 par [[metertype METER_POPULATION]]. +Ces améliorations sont cumulables avec les bonus des technologies [[tech PRO_PREDICTIVE_STOCKPILING]], [[tech PRO_GENERIC_SUPPLIES]] et [[tech PRO_VOID_PREDICTION]]. + +Débloque également la structure [[buildingtype BLD_STOCKPILING_CENTER]], augmentant additionnellement le [[metertype METER_STOCKPILE]] de 0.1 par [[metertype METER_INDUSTRY]].''' + +PRO_VOID_PREDICTION +Prédiction du Vide + +PRO_VOID_PREDICTION_DESC +'''Tandis que les prédictions classiques se basent invariablement sur la connaissance statistique des faits passés, l'étude de la mémoire chaotique du Vide mène à de solides prédictions de faits pourtant initiaux ou étranges. +Pour le service du Stock Impérial, la Prédiction du Vide est l'instrument par excellence permettant de fournir les bons matériaux avant même que le besoin ne se manifeste. + +Il s'agit donc d'une technologie d'amélioration du Stock Impérial. Le [[metertype METER_STOCKPILE]] augmente de 0.2 par [[metertype METER_POPULATION]]. +Ces améliorations sont cumulables avec les bonus des technologies [[tech PRO_PREDICTIVE_STOCKPILING]], [[tech PRO_GENERIC_SUPPLIES]] et [[tech PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY]].''' + PRO_MICROGRAV_MAN Production en Microgravité PRO_MICROGRAV_MAN_DESC -'''Augmente l'[[metertype METER_INDUSTRY]] de 5 sur toutes les colonies en Focus Recherche situées dans un système abritant des colonies ou avant-postes sur une Ceinture d'Astéroïdes. Plusieurs Ceintures d'Astéroïdes n'engendrent pas de bénéfices additionnels. +'''Augmente la [[metertype METER_TARGET_INDUSTRY]] de 5 sur toutes les colonies en Focus Recherche situées dans des systèmes abritant des colonies ou avant-postes sur une Ceinture d'Astéroïdes. Plusieurs Ceintures d'Astéroïdes n'engendrent pas de bénéfices additionnels. L'extraction de ressources minières sur des planétoïdes implique de nombreux challenges mais aussi de réelles opportunités en comparaison d'une exploitation planétaire classique. Ne disposant pas de la masse nécessaire pour la liquéfaction et la différenciation, les astéroïdes permettent un accès bien plus aisé à certains éléments lourds, tandis que l'absence de gravité rend l'extraction en vue d'une utilisation directe dans l'espace bien plus efficace. Par contre, la taille généralement limitée des astéroïdes requiert des infrastructures d'extraction auto-suffisantes et d'une grande mobilité. Par conséquent, les défis relatifs à un environnement en microgravité doivent être surmontés, nécessitant une refonte totale des méthodes traditionnelles. @@ -9866,7 +11236,7 @@ PRO_ROBOTIC_PROD Production Robotisée PRO_ROBOTIC_PROD_DESC -'''Augmente l'[[metertype METER_INDUSTRY]] Optimale de 0.1 par Population sur toutes les planètes réglées sur le Focus Industrie. +'''Augmente la [[metertype METER_TARGET_INDUSTRY]] de 0.1 par [[metertype METER_POPULATION]] sur toutes les planètes en Focus Industrie. Les Exobots, désignation sous laquelle ils sont communément connus, constituent l'aboutissement du design allié à la fonctionnalité. Robots conçus pour effectuer n'importe quelle tâche de production, de l'électronique microscopique à la construction à grande échelle, leur production de masse est aisée, tandis que leur utilisation reste la plus avantageuse dans les colonies assignant une haute priorité à l'industrie. Chaque monde privilégiant l'industrie se voit gratifiée d'une équipe d'Exobots, améliorant ainsi considérablement sa production industrielle. @@ -9874,6 +11244,7 @@ Bien qu'un haut niveau en conception et en configuration d'usine requiert toujou PRO_EXOBOTS # translated Exobots + PRO_EXOBOTS_DESC Permet la construction de colonies d'[[species SP_EXOBOT]]s optimisés pour l'industrie sur les Planètes [[PE_HOSTILE]]s. @@ -9881,7 +11252,7 @@ PRO_FUSION_GEN Génération par Fusion PRO_FUSION_GEN_DESC -'''Augmente l'[[metertype METER_INDUSTRY]] Optimale de 0.2 par Population sur toutes les planètes réglées sur le Focus Industrie. +'''Augmente la [[metertype METER_TARGET_INDUSTRY]] de 0.2 par [[metertype METER_POPULATION]] sur toutes les planètes en Focus Industrie. Les réacteurs de fusion nucléaire, bien que coûteux à produire et à entretenir, s'avèrent rentables pour les mondes industrialisés à forte consommation énergétique. La dépendance aux réacteurs basés sur la fission nucléaire deviendra peu à peu une relique du passé. @@ -9905,7 +11276,7 @@ PRO_SENTIENT_AUTOMATION Automates Adaptatifs PRO_SENTIENT_AUTOMATION_DESC -'''Augmente l'[[metertype METER_INDUSTRY]] Optimale de 5 sur toutes les planètes. +'''Augmente la [[metertype METER_TARGET_INDUSTRY]] de 5 sur toutes les planètes. En concevant des usines automatisées ne nécessitant aucune supervision directe, il devient alors possible d'accroître la production industrielle via un processus totalement autonome. Ces usines peuvent être construites et entretenues sur n'importe quelle planète, quel que soit son Focus, engendrant un bonus industriel dans tout l'empire. @@ -9943,6 +11314,12 @@ Extraction Neutronium PRO_NEUTRONIUM_EXTRACTION_DESC Le cœur d'une Étoile [[STAR_NEUTRON]] est constitué de matière dégénérée, comprimée par la gravité et d'une densité infiniment plus importante que la matière normale la plus lourde. Ce matériau exotique présente de nombreuses applications une fois extrait de l'étoile, permettant ainsi la production de structures et d'équipements d'astronef inédits et performants. +CON_OUTPOST +Avant-Poste + +CON_OUTPOST_DESC +La technologie nécessaire pour établir un [[encyclopedia OUTPOSTS_TITLE]] sur toute planète ou ceinture d'astéroïdes. + CON_ARCH_PSYCH Psychologie Architecturale @@ -9973,7 +11350,7 @@ CON_FRC_ENRG_STRC Structures Synergiques CON_FRC_ENRG_STRC_DESC -'''Accroît la progression des indicateurs de ressources inférieurs à ceux de la planète référence de 3 par tour, et la diminution des indicateurs de ressources supérieurs à ceux de la planète référence de 5 par tour. +'''Accroît la progression des indicateurs de ressources et d'infrastructure inférieurs à la valeur projetée de 3 par tour, et la diminution des indicateurs de ressources supérieurs à la valeur projetée de 5 par tour. L'[[metertype METER_INDUSTRY]] et la [[metertype METER_RESEARCH]] n'augmentent que si le [[metertype METER_HAPPINESS]] est au moins égal à 5. La flexibilité et la versatilité des structures synergiques permettent de redéfinir plus facilement le rôle des infrastructures des colonies en vue de nouvelles applications, augmentant ainsi la rapidité et le taux d'une modification de Focus. @@ -10017,15 +11394,15 @@ CON_ORBITAL_HAB Habitation Orbitale CON_ORBITAL_HAB_DESC -Accroît la Population max de toutes les planètes habitables selon leur taille : ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). +Accroît la [[metertype METER_TARGET_POPULATION]] de toutes les planètes habitables selon leur taille : ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). CON_NDIM_STRC Structures Multidimensionnelles CON_NDIM_STRC_DESC -'''Accroît la Population max de toutes les planètes habitables selon leur taille : ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10). +'''Accroît la [[metertype METER_TARGET_POPULATION]] de toutes les planètes habitables selon leur taille : ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10). -Augmente également de 10 l'[[metertype METER_CONSTRUCTION]] Optimale de la planète hôte si celle-ci est habitée. +Augmente également de 10 la [[metertype METER_TARGET_CONSTRUCTION]] des planètes habitées. Les facteurs principaux limitant la capacité de population d'une planète ne sont autres que les milieux environnementaux et l'espace disponible. En répartissant le développement des infrastructures de base sur de multiples dimensions, les limitations spatiales disparaissent, et dans des conditions environnementales idéales, le maximum de population d'une planète peut être considérablement accru. @@ -10061,7 +11438,13 @@ SHP_SPACE_FLUX_DRIVE Propulsion Flux Spatial SHP_SPACE_FLUX_DRIVE_DESC -D'ordinaire, la propulsion dans l'espace est limitée par le propulseur ou le milieu de propulsion lui-même. Cependant, à des échelles de grandeur relativement petites, il est possible de propulser un objet dans l'espace de la même manière qu'il le serait dans l'eau, c'est-à-dire en agissant directement sur le milieu dans lequel l'objet est immergé. Cela nécessite la manipulation de l'espace lui-même à une dimension quantique, chaque quantum d'espace étant réorganisé en un système d'unités fixes et adjacentes d'espace. Cette méthode de propulsion est très efficace, mais s'avère dangereuse à grande échelle et facilement détectable en raison des effets dimensionnels résultants. Des recherches avancées en matière de technologies de furtivité peuvent minimiser les désagréments causés par ces perturbations dimensionnelles. +D'ordinaire, la propulsion dans l'espace est limitée par le propulseur ou le milieu de propulsion lui-même. Cependant, à des échelles de grandeur relativement petites, il est possible de propulser un objet dans l'espace de la même manière qu'il le serait dans l'eau, c'est-à-dire en agissant directement sur le milieu dans lequel l'objet est immergé. Cela nécessite la manipulation de l'espace lui-même à une dimension quantique, chaque quantum d'espace étant réorganisé en un système d'unités fixes et adjacentes d'espace. Cette méthode de propulsion est très efficace, mais s'avère dangereuse à grande échelle et facilement détectable en raison des effets dimensionnels résultants. Des recherches avancées en matière de technologies de furtivité peuvent minimiser les désagréments causés par ces perturbations dimensionnelles. La coque d'astronef résultant de ces recherches est d'un design légèrement plus sophistiqué que celle issue de la technologie [[tech SHP_SPACE_FLUX_BUBBLE]]. + +SHP_SPACE_FLUX_BUBBLE +Bulle Flux Spatial + +SHP_SPACE_FLUX_BUBBLE_DESC +D'ordinaire, la propulsion dans l'espace est limitée par le propulseur ou le milieu de propulsion lui-même. Cependant, à des échelles de grandeur relativement petites, il est possible de propulser un objet dans l'espace de la même manière qu'il le serait dans l'eau, c'est-à-dire en agissant directement sur le milieu dans lequel l'objet est immergé. Cela nécessite la manipulation de l'espace lui-même à une dimension quantique, chaque quantum d'espace étant réorganisé en un système d'unités fixes et adjacentes d'espace. Cette méthode de propulsion est très efficace, mais s'avère dangereuse à grande échelle et facilement détectable en raison des effets dimensionnels résultants. Des recherches avancées en matière de technologies de furtivité peuvent minimiser les désagréments causés par ces perturbations dimensionnelles. La plus simple solution pour concevoir un astronef utilisant la propulsion par flux s'avère être la modulation en bulle, laquelle recouvre une coque de forme également sphérique. Des solutions plus complexes sont néanmoins possibles via une meilleure technologie de [[tech SHP_SPACE_FLUX_DRIVE]]. SHP_CONTGRAV_MAINT Maintenance Anti-Gravitationnelle @@ -10126,13 +11509,13 @@ SHP_MULTICELL_CAST_DESC Même lorsqu'il est inerte, un matériau organique est bien plus polyvalent que n'importe quel matériau standard. Un astronef multicellulaire non-vivant pourrait offrir certains avantages des matériaux organiques mais sans les inconvénients inhérents à la vie organique. SHP_DOMESTIC_MONSTER -Domestication Méga-Faune +Méga-Faune: Domestication SHP_DOMESTIC_MONSTER_DESC La pratique ancestrale de la domestication des autres espèces peut également s'appliquer aux monstres spatiaux peuplant les voies spatiales. Construire des Colonies et des Avant-Postes à proximité de leurs lieux de nidification rendrait possible la domestication de tels monstres. SHP_ENDOCRINE_SYSTEMS -Endocrinologie Méga-Faune +Méga-Faune: Endocrinologie SHP_ENDOCRINE_SYSTEMS_DESC L'étude des systèmes endocriniens d'organismes évoluant dans l'espace permet le développement des structures internes des astronefs à caractère organique. @@ -10196,11 +11579,13 @@ Une coque constituée d'énergie pure, bien que plus efficiente que n'importe qu SHP_KRILL_SPAWN Génération de Krill + SHP_KRILL_SPAWN_DESC Débloque l'équipement [[shippart SP_KRILL_SPAWNER]] pour les astronefs, qui provoque l'apparition de krill sauvage dans les systèmes abritant des Ceintures d'Astéroïdes. SPY_ROOT_DECEPTION Tromperie + SPY_ROOT_DECEPTION_DESC L'art de la tromperie n’est pas naturel à toutes les espèces, mais la possibilité de dissimuler la vérité peut s'avérer bien utile. @@ -10215,6 +11600,9 @@ Donne la victoire! SHIP_PART_UNLOCK_SHORT_DESC Débloque un équipement d'astronef +SHIP_FUEL_IMPROVE_SHORT_DESC +Améliore un réservoir d'astronef + SHIP_WEAPON_UNLOCK_SHORT_DESC Débloque un armement d'astronef @@ -10336,7 +11724,7 @@ GRO_SUBTER_HAB Habitat Souterrain GRO_SUBTER_HAB_DESC -Accroît la Population max de toutes les planètes selon leur taille : ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). +Accroît la [[metertype METER_TARGET_POPULATION]] de toutes les planètes selon leur taille : ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). GRO_GENOME_BANK Banque du Génome @@ -10354,7 +11742,7 @@ GRO_TERRAFORM Terraformation GRO_TERRAFORM_DESC -Modifier le type d'environnement d'une planète est une entreprise titanesque, le challenge de ce processus résidant principalement dans l'immense logistique nécessaire. La méthode en question dépend quant à elle de l'environnement d'origine de la planète et du résultat désiré. Cependant, dans tous les cas, la quasi-totalité de la surface planétaire et de l'atmosphère nécessite d'être convertie en d'autres types de molécules, diffusées dans l'espace à modifier ou injectées sous la croûte terrestre. Par conséquent, le processus requiert que la majeure partie de la planète soit accessible aux terraformeurs, et ne peut être enclencher si la Population max est inférieure à un. +Modifier le type d'environnement d'une planète est une entreprise titanesque, le challenge de ce processus résidant principalement dans l'immense logistique nécessaire. La méthode en question dépend quant à elle de l'environnement d'origine de la planète et du résultat désiré. Cependant, dans tous les cas, la quasi-totalité de la surface planétaire et de l'atmosphère nécessite d'être convertie en d'autres types de molécules, diffusées dans l'espace à modifier ou injectées sous la croûte terrestre. Par conséquent, le processus requiert que la majeure partie de la planète soit accessible aux terraformeurs, et ne peut être enclencher si la [[metertype METER_TARGET_POPULATION]] est inférieure à un. GRO_TERRAFORM_SHORT_DESC Permet la terraformation @@ -10378,8 +11766,8 @@ GRO_CYBORG # translated Cyborgs GRO_CYBORG_DESC -'''Accroît la Population max des Planètes [[PE_HOSTILE]]s selon leur taille : ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10). -Accroît l'[[metertype METER_TROOPS]] max de 0.2 par Population sur toutes les planètes. +'''Accroît la [[metertype METER_TARGET_POPULATION]] des Planètes [[PE_HOSTILE]]s selon leur taille : ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10). +Accroît l'[[metertype METER_TROOPS]] max de 0.2 par [[metertype METER_POPULATION]] sur toutes les planètes. Une fusion hautement polyvalente d'un organisme vivant et d'une machine, capable de s'adapter à un nombre considérable de situations. La génération spontanée d'organes spécialisés ainsi qu'une force bio-mécanique fortement accrue permettent aux cyborgs d'évoluer avec aisance dans quasiment tous les types d'environnement.''' @@ -10392,6 +11780,15 @@ En théorie, il est possible de prédire en totalité l'évolution écologique d GRO_GAIA_TRANS_SHORT_DESC Permet la Transformation Gaia +GRO_MEGA_ECO +Méga-Faune: Écologie + +GRO_MEGA_ECO_DESC +L'étude des cycles de vie des organismes spatiaux permet de faire émerger des caractères communs pour une grande variété d'espèces. Il est donc possible d'exploiter ces connaissances afin de contrôler plus efficacement ces organismes, en ayant recours à la manipulation de leurs processus métaboliques et reproductifs. + +GRO_MEGA_ECO_SHORT_DESC +Contrôle Nid de Monstre + LRN_OBSERVATORY_I Observatoire @@ -10408,7 +11805,7 @@ LRN_DISTRIB_THOUGHT Centralisation Cérébrale LRN_DISTRIB_THOUGHT_DESC -'''Augmente la [[metertype METER_RESEARCH]] Optimale de 0.1 par Population sur toutes les planètes, quel que soit le Focus choisi. +'''Augmente la [[metertype METER_TARGET_RESEARCH]] de 0.1 par [[metertype METER_POPULATION]] sur toutes les planètes, quel que soit le Focus choisi. Le citoyen moyen gaspille littéralement ses capacités cérébrales, pourtant issues d'un des outils de calcul les plus fins de l'univers. En intégrant les cycles cérébraux inactifs d'une population suffisament importante au sein d'un réseau de simples émetteurs-récepteurs cybernétiques, la totalité des centres de recherche d'un empire peuvent accéder à une puissance de calcul considérable et peu coûteuse.''' @@ -10416,7 +11813,7 @@ LRN_STELLAR_TOMOGRAPHY Tomographie Stellaire LRN_STELLAR_TOMOGRAPHY_DESC -'''Augmente la [[metertype METER_RESEARCH]] Optimale des planètes réglées sur le Focus Recherche, par Population et selon le type d'étoile : +'''Augmente la [[metertype METER_TARGET_RESEARCH]] des planètes réglées sur le Focus Recherche, par [[metertype METER_POPULATION]] et selon le type d'étoile : * [[STAR_BLACK]] (x1) * [[STAR_NEUTRON]] (x0.75) * [[STAR_BLUE]] ou [[STAR_WHITE]] (x0.5) @@ -10452,8 +11849,8 @@ LRN_PSY_DOM Domination Psychique LRN_PSY_DOM_DESC -'''A 10% de chance de prendre le contrôle des astronefs ennemis avec équipages non-télépathiques, et se trouvant dans le même système qu'une planète en Focus Domination, à moins que l'empire ennemi ne maîtrise également la [[tech LRN_PSY_DOM]]. -Immunise également contre la prise de contrôle d'un astronef par un [[predefinedshipdesign SM_PSIONIC_SNOWFLAKE]]. +'''A 10% de chance de prendre le contrôle des astronefs ennemis avec équipages non-télépathiques, et se trouvant dans le même système qu'une planète en Focus Domination, à moins que l'empire ennemi ne maîtrise également la [[tech LRN_PSY_DOM]]. Le Focus Domination ne peut être activé que sur une planète colonisée par une espèce télépathique. +Réduit la probabilité de prise de contrôle d'un astronef par un [[predefinedshipdesign SM_PSIONIC_SNOWFLAKE]]. Il est difficile de soumettre un individu conscient à un contrôle mental total par une entité étrangère, et ce contre son gré. En manipulant cependant les durées de soumission, la force mentale accumulée par une population sur plusieurs heures peut être canalisée sur un unique individu durant quelques secondes, engendrant ainsi une compulsion quasi-irrésistible de se joindre à l'esprit unifié.''' @@ -10576,7 +11973,7 @@ DEF_ROOT_DEFENSE Auto-défense DEF_ROOT_DEFENSE_DESC -'''Augmente les [[metertype METER_SHIELD]] max de 1 sur chaque planète appartenant à l'empire. Pour les espèces disposant de troupes de défense, augmente l'[[metertype METER_TROOPS]] max de 0.2 par Population sur la planète. +'''Augmente les [[metertype METER_SHIELD]] max de 1 sur chaque planète appartenant à l'empire. Régénère 1 point de [[metertype METER_SHIELD]] par tour, seulement durant les tours où la planète n'est pas attaquée. Pour les espèces disposant de troupes de défense, augmente l'[[metertype METER_TROOPS]] max de 0.2 par [[metertype METER_POPULATION]] sur la planète. Le concept d'Auto-défense est à la racine de maints développements technologiques.''' @@ -10590,7 +11987,7 @@ DEF_GARRISON_2 Milice Planétaire Défensive DEF_GARRISON_2_DESC -Augmente l'[[metertype METER_TROOPS]] max de 0.4 par Population sur toutes les planètes de l'empire (variable selon les caractéristiques défensives de l'espèce), et régénère l'Infanterie d'un bonus de 1 par tour. +Augmente l'[[metertype METER_TROOPS]] max de 0.4 par [[metertype METER_POPULATION]] sur toutes les planètes de l'empire (variable selon les caractéristiques défensives de l'espèce), et régénère l'Infanterie d'un bonus de 1 par tour. DEF_GARRISON_3 Réseau Fortication Planétaire @@ -10602,7 +11999,7 @@ DEF_GARRISON_4 Armée Planétaire Défensive DEF_GARRISON_4_DESC -Augmente l'[[metertype METER_TROOPS]] max de 0.4 par Population sur toutes les planètes de l'empire (variable selon les caractéristiques défensives de l'espèce), et régénère l'Infanterie d'un bonus de 3 par tour cumulable avec les bonus du [[DEF_GARRISON_3]] et de la [[DEF_GARRISON_2]]. +Augmente l'[[metertype METER_TROOPS]] max de 0.4 par [[metertype METER_POPULATION]] sur toutes les planètes de l'empire (variable selon les caractéristiques défensives de l'espèce), et régénère l'Infanterie d'un bonus de 3 par tour cumulable avec les bonus du [[DEF_GARRISON_3]] et de la [[DEF_GARRISON_2]]. DEF_PLANET_CLOAK Camouflage Planétaire @@ -10648,34 +12045,41 @@ Bombardement SHP_BOMBARD_DESC Permet le développement d'armes de bombardement planétaire. -##Fighter explanation, needs improving and a Pedia article writing + +## +## Fighter explanation, needs improving and a Pedia article writing +## + SHP_FIGHTERS_1 Chasseurs et Rampes de Lancement SHP_FIGHTERS_1_DESC -'''Permet à l'empire de produire des astronefs intégrant des Hangars à Chasseurs et des Rampes de Lancement. Un astronef ne peut abriter qu'un seul type de [[encyclopedia PC_FIGHTER_HANGAR]], mais cependant autant que le permet le nombre de [[encyclopedia SLOT_TITLE]]s disponibles. Chaque Hangar dispose d'une capacité maximale de chasseurs et ces derniers auront tous la même Puissance (pouvant être modifiée selon l'espèce les contrôlant et en procédant à des recherches plus avancées dans le domaine). +'''Permet à l'empire de produire des astronefs intégrant des Hangars à Chasseurs et des Rampes de Lancement. Un astronef ne peut abriter qu'un seul type de [[encyclopedia PC_FIGHTER_HANGAR]], mais cependant autant que le permet le nombre de [[encyclopedia SLOT_TITLE]]s disponibles. Chaque Hangar dispose d'une capacité maximale de chasseurs et ces derniers auront tous la même Puissance (pouvant être modifiée selon l'espèce les contrôlant et en procédant à des recherches plus avancées dans le domaine). Une Rampe de Lancement peut faire décoller un nombre défini de chasseurs lors de chaque passe de combat. Ces chasseurs deviennent des cibles valides et peuvent attaquer les astronefs et chasseurs ennemis lors de chaque passe suivante de combat. Toutefois, un unique tir de n'importe quelle nature subi par un chasseur détruira ce dernier. Contrairement aux [[encyclopedia DAMAGE_TITLE]] qu'infligent les armements traditionnels, les dommages résultant d'un tir de chasseur ne sont pas minorés par les [[metertype METER_SHIELD]] d'astronef (les chasseurs faisant feu une fois à l'intérieur de la zone de protection d'un bouclier). +L'attribut "Pilotes" affecte les chasseurs issus d'un [[shippart FT_HANGAR_2]], d'un [[shippart FT_HANGAR_3]], ou d'un [[shippart FT_HANGAR_4]]: "Mauvais Pilotes" réduit les dommages de 1 par chasseur; "Bons Pilotes" augmente les dommages de 1, "Excellents Pilotes" de 2, et "Parfaits Pilotes" de 3 par tir de chasseur. +L'attribut "Pilotes" n'affecte pas les chasseurs provenant d'un [[shippart FT_HANGAR_1]]. + Au terme d'un combat, les astronefs transporteurs récupèreront automatiquement les chasseurs non-détruits. Si un transporteur est stationnaire dans une zone d'[[metertype METER_SUPPLY]], ses hangars seront rééquipés à leur capacité maximale.''' SHP_FIGHTERS_2 Chasseurs Laser SHP_FIGHTERS_2_DESC -Améliore les chasseurs de tous les transporteurs à portée d'[[metertype METER_SUPPLY]] en les dotant d'un armement Laser. Les [[encyclopedia DAMAGE_TITLE]] infligés par les chasseurs d'un [[shippart FT_HANGAR_1]] augmentent de 1, augmentent de 2 pour un [[shippart FT_HANGAR_2]] et de 3 pour un [[shippart FT_HANGAR_3]]. +Améliore les chasseurs de tous les transporteurs à portée d'[[metertype METER_SUPPLY]] en les dotant d'un armement Laser. Les [[encyclopedia DAMAGE_TITLE]] infligés par les chasseurs d'un [[shippart FT_HANGAR_2]] augmentent de 2, augmentent de 3 pour un [[shippart FT_HANGAR_3]] et de 6 pour un [[shippart FT_HANGAR_4]]. Pour les chasseurs d'un [[shippart FT_HANGAR_1]], la capacité ainsi que la cadence de lancement de la [[shippart FT_BAY_1]] augmentent de 1. SHP_FIGHTERS_3 Chasseurs Plasma SHP_FIGHTERS_3_DESC -Améliore les chasseurs de tous les transporteurs à portée d'[[metertype METER_SUPPLY]] en les dotant d'un armement Plasma. Les [[encyclopedia DAMAGE_TITLE]] infligés par les chasseurs d'un [[shippart FT_HANGAR_1]] augmentent de 1, augmentent de 3 pour un [[shippart FT_HANGAR_2]] et de 4 pour un [[shippart FT_HANGAR_3]]. +Améliore les chasseurs de tous les transporteurs à portée d'[[metertype METER_SUPPLY]] en les dotant d'un armement Plasma. Les [[encyclopedia DAMAGE_TITLE]] infligés par les chasseurs d'un [[shippart FT_HANGAR_2]] augmentent de 2, augmentent de 3 pour un [[shippart FT_HANGAR_3]] et de 6 pour un [[shippart FT_HANGAR_4]]. Pour les chasseurs d'un [[shippart FT_HANGAR_1]], la capacité ainsi que la cadence de lancement de la [[shippart FT_BAY_1]] augmentent de 1. SHP_FIGHTERS_4 Chasseurs Gamma SHP_FIGHTERS_4_DESC -Améliore les chasseurs de tous les transporteurs à portée d'[[metertype METER_SUPPLY]] en les dotant d'un armement Gamma. Les [[encyclopedia DAMAGE_TITLE]] infligés par les chasseurs d'un [[shippart FT_HANGAR_1]] augmentent de 1, augmentent de 5 pour un [[shippart FT_HANGAR_2]] et de 7 pour un [[shippart FT_HANGAR_3]]. +Améliore les chasseurs de tous les transporteurs à portée d'[[metertype METER_SUPPLY]] en les dotant d'un armement Gamma. Les [[encyclopedia DAMAGE_TITLE]] infligés par les chasseurs d'un [[shippart FT_HANGAR_2]] augmentent de 2, augmentent de 3 pour un [[shippart FT_HANGAR_3]] et de 6 pour un [[shippart FT_HANGAR_4]]. Pour les chasseurs d'un [[shippart FT_HANGAR_1]], la capacité ainsi que la cadence de lancement de la [[shippart FT_BAY_1]] augmentent de 1. SHP_WEAPON_1_2 Canon Mitrailleur 2 @@ -10767,6 +12171,23 @@ Rayon Gamma 4 SHP_WEAPON_4_4_DESC Améliore l'équipement [[shippart SR_WEAPON_4_1]] de tous les astronefs situés en zone d'[[metertype METER_SUPPLY]] (ou s'ils s'y rendent ultérieurement). Les [[encyclopedia DAMAGE_TITLE]] par tir sont de 30 une fois l'équipement amélioré. +SHP_WEAPON_ARC_DISRUPTOR_1 +Perturbateurs Électro-Conducteurs + +SHP_WEAPON_ARC_DISRUPTOR_1_DESC +Débloque l'équipement [[shippart SR_ARC_DISRUPTOR]], un système automatique d'armement infligeant de faibles dommages à de multiples cibles simultanément. Les [[encyclopedia DAMAGE_TITLE]] de l'équipement de base sont de 2 par tir. + +SHP_WEAPON_ARC_DISRUPTOR_2 +Perturbateurs Électro-Conducteurs 2 + +SHP_WEAPON_ARC_DISRUPTOR_2_DESC +Améliore l'équipement [[shippart SR_ARC_DISRUPTOR]] de tous les astronefs situés en zone d'[[metertype METER_SUPPLY]] (ou s'ils s'y rendent ultérieurement). Les [[encyclopedia DAMAGE_TITLE]] par tir sont de 4 une fois l'équipement amélioré. + +SHP_WEAPON_ARC_DISRUPTOR_3 +Perturbateurs Électro-Conducteurs 3 + +SHP_WEAPON_ARC_DISRUPTOR_3_DESC +Améliore l'équipement [[shippart SR_ARC_DISRUPTOR]] de tous les astronefs situés en zone d'[[metertype METER_SUPPLY]] (ou s'ils s'y rendent ultérieurement). Les [[encyclopedia DAMAGE_TITLE]] par tir sont de 7 une fois l'équipement amélioré. SHP_ION_CANNON Canon Ionique @@ -10886,7 +12307,7 @@ Couverture Nuageuse Planétaire SPY_STEALTH_1_DESC '''Confère la Particularité [[special CLOUD_COVER_SLAVE_SPECIAL]] à toutes les planètes et avant-postes de l'empire, augmentant ainsi de 20 la [[metertype METER_STEALTH]] des planètes et des structures qui s'y trouvent. -Le coût de recherche de cette technologie est diminué de la valeur du coût de recherche de l'[[tech SPY_STEALTH_PART_1]], si cette dernière a été déjà effectuée.''' +Le coût de [[metertype METER_RESEARCH]] de cette technologie est diminué de la valeur du coût de recherche de l'[[tech SPY_STEALTH_PART_1]], si cette dernière a été déjà effectuée.''' SPY_STEALTH_PART_1 Affaiblissement Électromagnétique @@ -10900,7 +12321,7 @@ Cendres Volcaniques Planétaires SPY_STEALTH_2_DESC '''Confère la Particularité [[special VOLCANIC_ASH_SLAVE_SPECIAL]] à toutes les planètes et avant-postes de l'empire, augmentant ainsi de 40 la [[metertype METER_STEALTH]] des planètes et des structures qui s'y trouvent. -Le coût de recherche de cette technologie est diminué de la valeur du coût de recherche de l'[[tech SPY_STEALTH_PART_2]], si cette dernière a été déjà effectuée.''' +Le coût de [[metertype METER_RESEARCH]] de cette technologie est diminué de la valeur du coût de recherche de l'[[tech SPY_STEALTH_PART_2]], si cette dernière a été déjà effectuée.''' SPY_STEALTH_PART_2 Absorption Radiative @@ -10914,7 +12335,7 @@ Camouflage Dimensionnel Planétaire SPY_STEALTH_3_DESC '''Confère la Particularité [[special DIM_RIFT_SLAVE_SPECIAL]] à toutes les planètes et avant-postes de l'empire, augmentant ainsi de 60 la [[metertype METER_STEALTH]] des planètes et des structures qui s'y trouvent. -Le coût de recherche de cette technologie est diminué de la valeur du coût de recherche du [[tech SPY_STEALTH_PART_3]], si cette dernière a été déjà effectuée.''' +Le coût de [[metertype METER_RESEARCH]] de cette technologie est diminué de la valeur du coût de recherche du [[tech SPY_STEALTH_PART_3]], si cette dernière a été déjà effectuée.''' SPY_STEALTH_PART_3 Camouflage Dimensionnel @@ -10975,13 +12396,19 @@ SHP_DEUTERIUM_TANK Réservoir de Deutérium SHP_DEUTERIUM_TANK_DESC -Débloque l'équipement [[shippart FU_DEUTERIUM_TANK]] pour les astronefs. +Légère amélioration de chaque [[shippart FU_BASIC_TANK]] d'un astronef. Augmente la capacité en [[metertype METER_FUEL]] d'un astronef et par conséquent le nombre maximum de Voies spatiales pouvant être parcourues avant de devoir ravitailler en carburant. + +SHP_DEUTERIUM_TANK_EFFECT +Technologie Réservoir de Deutérium SHP_ANTIMATTER_TANK Réservoir d'Anti-Matière SHP_ANTIMATTER_TANK_DESC -Débloque l'équipement [[shippart FU_ANTIMATTER_TANK]] pour les astronefs. +Notable amélioration de chaque [[shippart FU_BASIC_TANK]] d'un astronef. Augmente la capacité en [[metertype METER_FUEL]] d'un astronef et par conséquent le nombre maximum de Voies spatiales pouvant être parcourues avant de devoir ravitailler en carburant. + +SHP_ANTIMATTER_TANK_EFFECT +Technologie Réservoir d'Anti-Matière SHP_ZERO_POINT Générateur Zero-Point de Carburant @@ -11212,7 +12639,7 @@ BLD_EVACUATION Système d'Évacuation BLD_EVACUATION_DESC -Extrait la population de cette planète sur plusieurs tours selon un [[encyclopedia EVACUATION_TITLE]] prédéfini. Si une planète de remplacement avec un [[encyclopedia ENVIRONMENT_TITLE]] approprié est disponible et déjà peuplée par la même espèce, tout en disposant de suffisamment de place libre, la population y sera transférée. +Extrait la [[metertype METER_POPULATION]] de cette planète sur plusieurs tours selon un [[encyclopedia EVACUATION_TITLE]] prédéfini. Si une planète de remplacement avec un [[encyclopedia ENVIRONMENT_TITLE]] approprié est disponible et déjà peuplée par la même espèce, tout en disposant de suffisamment de place libre, la Population y sera transférée. BLD_OBSERVATORY Observatoire @@ -11226,7 +12653,7 @@ BLD_CULTURE_ARCHIVES Archives Culturelles BLD_CULTURE_ARCHIVES_DESC -'''Augmente la [[metertype METER_RESEARCH]] Optimale de 5, ainsi que l'[[metertype METER_INDUSTRY]] Optimale de la moitié de la Population planétaire. +'''Augmente la [[metertype METER_TARGET_RESEARCH]] de 5, ainsi que la [[metertype METER_TARGET_INDUSTRY]] de la moitié de la [[metertype METER_POPULATION]] planétaire. Abrite le savoir accumulé durant des milliers d'années d'existence sur cette planète, améliorant différents aspects de la productivité planétaire.''' @@ -11234,15 +12661,15 @@ BLD_CULTURE_LIBRARY Bibliothèque Culturelle BLD_CULTURE_LIBRARY_DESC -'''Augmente la [[metertype METER_RESEARCH]] Optimale de 5. +'''Augmente la [[metertype METER_TARGET_RESEARCH]] de 5. -Abrite le savoir accumulé durant des milliers d'années d'existence sur cette planète, octroyant ainsi un bonus à la Recherche. Cette structure sera détruite si la population planétaire est réduite à zéro.''' +Abrite le savoir accumulé durant des milliers d'années d'existence sur cette planète, octroyant ainsi un bonus à la Recherche. Cette structure sera détruite si la [[metertype METER_POPULATION]] planétaire est réduite à zéro.''' BLD_AUTO_HISTORY_ANALYSER Analyseur Historique Automatisé BLD_AUTO_HISTORY_ANALYSER_DESC -'''L' Analyseur Historique Automatisé peut être construit sur une planète abritant des [[buildingtype BLD_CULTURE_ARCHIVES]]. Augmente la [[metertype METER_RESEARCH]] Optimale de 5. +'''L' Analyseur Historique Automatisé peut être construit sur une planète abritant des [[buildingtype BLD_CULTURE_ARCHIVES]]. Augmente la [[metertype METER_TARGET_RESEARCH]] de 5. Un centre de recherche automatisé doté d'une énorme puissance de calcul, parcourant continuellement la multitude de données au sein des archives culturelles, afin de faire émerger de possibles analogies, d'établir des parallèles interdisciplinaires, ou bien de redécouvrir des concepts tombés dans l'oubli ou non finalisés.''' @@ -11250,7 +12677,7 @@ BLD_IMPERIAL_PALACE Palais Impérial BLD_IMPERIAL_PALACE_DESC -'''Augmente de 2 la portée de l'[[metertype METER_SUPPLY]], l'[[metertype METER_CONSTRUCTION]] de 20, la [[metertype METER_DEFENSE]] de 5, et l'[[metertype METER_TROOPS]] de 6. Son emplacement définit également la Capitale de l'empire. +'''Augmente de 2 la portée de l'[[metertype METER_SUPPLY]], la [[metertype METER_TARGET_CONSTRUCTION]] de 20, la [[metertype METER_DEFENSE]] de 5, et l'[[metertype METER_TROOPS]] de 6. Son emplacement définit également la Capitale de l'empire. Représente le pouvoir et le prestige impérial et remplit la fonction de centre de contrôle pour toutes les possessions de l'empire.''' @@ -11364,9 +12791,9 @@ BLD_BIOTERROR_PROJECTOR Base Bioterroriste BLD_BIOTERROR_PROJECTOR_DESC -'''Donne accès au Focus Bioterrorisme à la planète sur laquelle est construite la Base Bioterroriste. Cela a pour effet de réduire de 2 par tour la Population des planètes ennemies, seulement si ces dernières sont distantes de quatre Voies spatiales ou moins et si l'empire ennemi ne dispose pas d'une [[buildingtype BLD_GENOME_BANK]]. +'''Donne accès au Focus Bioterrorisme à la planète sur laquelle est construite la Base Bioterroriste. Cela a pour effet de réduire de 2 par tour la [[metertype METER_POPULATION]] des planètes ennemies, seulement si ces dernières sont distantes de quatre Voies spatiales ou moins et si l'empire ennemi ne dispose pas d'une [[buildingtype BLD_GENOME_BANK]]. -Cette base clandestine d'armements biologiques provoque des ravages sur les planètes ennemies à proximité, en réduisant progressivement la Population de ces dernières jusqu'à extermination totale. Cependant, de telles activités n'étant pas approuvées par les lois galactiques, cette structure ne peut être construite que sur une planète autour de laquelle orbite une [[special RESONANT_MOON_SPECIAL]], et s'avère sans effets si l'empire ennemi dispose d'une Banque du Génome. Cette structure peut être construite sur un [[encyclopedia OUTPOSTS_TITLE]], si une Lune Synchrone orbite également autour de ce dernier.''' +Cette base clandestine d'armements biologiques provoque des ravages sur les planètes ennemies à proximité, en réduisant progressivement la Population de ces dernières jusqu'à extermination totale. Cependant, de telles activités n'étant pas approuvées par les lois galactiques, cette structure ne peut être construite que sur une planète autour de laquelle orbite une [[special RESONANT_MOON_SPECIAL]], et s'avère sans effets si l'empire ennemi dispose d'une Banque du Génome. [[BUILDING_AVAILABLE_ON_OUTPOSTS]], si une Lune Synchrone orbite également autour de ce dernier.''' BLD_LIGHTHOUSE Phare Interstellaire @@ -11386,18 +12813,37 @@ BLD_INDUSTRY_CENTER Centre Industriel BLD_INDUSTRY_CENTER_DESC -'''Augmente l'indicateur [[metertype METER_INDUSTRY]] de 0.2 par Population sur toutes les planètes lui étant reliées par ligne d'[[metertype METER_SUPPLY]] et étant réglées sur le Focus Industrie. +'''Augmente la [[metertype METER_TARGET_INDUSTRY]] de 0.2 par [[metertype METER_POPULATION]] sur toutes les planètes lui étant reliées par ligne d'[[metertype METER_SUPPLY]] et étant réglées sur le Focus Industrie. [[NO_STACK_SUPPLY_CONNECTION_TEXT]] L'amélioration [[tech PRO_INDUSTRY_CENTER_II]] double le bonus de base. L'amélioration [[tech PRO_INDUSTRY_CENTER_III]] triple le bonus de base.''' +BLD_INTERSPECIES_ACADEMY +Académie InterDesign d'Espèce + +BLD_INTERSPECIES_ACADEMY_DESC +'''Augmente le Stock de 10 sur la planète, si cette dernière est réglée sur le Focus Stock. +Augmente la [[metertype METER_RESEARCH]] de 5 sur la planète, si cette dernière est réglée sur le Focus Recherche. + +Apprendre à concevoir des dispositifs utilisables par différentes espèces est crucial afin de développer des outils universellement exploitables. + +Chaque empire peut abriter une académie sur une à six planètes peuplées par différentes espèces. Pour accueillir une académie, le [[metertype METER_HAPPINESS]] de la [[metertype METER_POPULATION]] sur la planète doit être élevé et cette dernière doit se trouver à au moins trois Voies spatiales de l'académie existante la plus proche.''' + +BLD_STOCKPILING_CENTER +Centre d'Intrication Impériale + +BLD_STOCKPILING_CENTER_DESC +'''Augmente le [[metertype METER_STOCKPILE]] de 0.1 par [[metertype METER_INDUSTRY]]. + +Au lieu de recourir à l'implantation classique d'infastructures associant le lieu de production et la main d'œuvre planétaire, un noyau central peut permettre la connexion entre n'importe quelle infrastructure et main d'œuvre disponible. Cela engendre une très grande flexibilité des lieux de production.''' + BLD_MEGALITH Mégalithe BLD_MEGALITH_DESC -'''Cette structure ne peut être construite que dans la Capitale de l'empire, amplifiant sa splendeur conjointement au [[buildingtype BLD_IMPERIAL_PALACE]] déjà existant. Tous les indicateurs de ressources de la planète sur laquelle le Mégalithe est construit peuvent atteindre leur optimum en un seul tour, tandis que l'indicateur [[metertype METER_CONSTRUCTION]] augmente de 30. De plus, la portée de l'[[metertype METER_SUPPLY]] augmente de 1 pour toutes les planètes habitées appartenant à l'empire. Les planètes habitées distantes de deux Voies spatiales ou moins verront leur [[metertype METER_TROOPS]] augmenter de 10. +'''Cette structure ne peut être construite que dans la Capitale de l'empire, amplifiant sa splendeur conjointement au [[buildingtype BLD_IMPERIAL_PALACE]] déjà existant. Tous les indicateurs de ressources de la planète sur laquelle le Mégalithe est construit peuvent atteindre leur optimum en un seul tour, tandis que la [[metertype METER_TARGET_CONSTRUCTION]] augmente de 30. De plus, la portée de l'[[metertype METER_SUPPLY]] augmente de 1 pour toutes les planètes habitées appartenant à l'empire. Les planètes habitées distantes de deux Voies spatiales ou moins verront leur [[metertype METER_TROOPS]] augmenter de 10. Le Mégalithe est une structure si resplendissante et massive qu'elle tutoie littéralement les étoiles, procurant une inspiration illimitée à tous les architectes de l'empire.''' @@ -11421,13 +12867,13 @@ BLD_GAIA_TRANS Transformateur Gaia BLD_GAIA_TRANS_DESC -'''Dote une planète de la Particularité [[special GAIA_SPECIAL]] et accroît la Population max pour les [[encyclopedia ENVIRONMENT_TITLE]]s à Habitabilité [[PE_GOOD]], selon la taille de la planète : +'''Dote une planète de la Particularité [[special GAIA_SPECIAL]] et accroît la [[metertype METER_TARGET_POPULATION]] pour les [[encyclopedia ENVIRONMENT_TITLE]]s à Habitabilité [[PE_GOOD]], selon la taille de la planète : • [[SZ_TINY]] (+3) • [[SZ_SMALL]] (+6) • [[SZ_MEDIUM]] (+9) • [[SZ_LARGE]] (+12) • [[SZ_HUGE]] (+15) -et augmente le [[metertype METER_HAPPINESS]] max de 5. +et augmente la [[metertype METER_TARGET_HAPPINESS]] de 5. Transforme une planète en Monde Gaia à l'aide d'un programme informatique cellulaire et conscient, quasiment d'essence divine. Cette planète, dont la contemplation procure un ravissement de tous les instants, est connue à travers toute la galaxie comme une célébration de la vie et de l'harmonie. Les habitants de ce monde pensent et agissent comme une partie d'un organisme planétaire unique, dans l'intérêt de ce dernier, celui-ci agissant en retour dans l'intérêt de ses habitants.''' @@ -11435,7 +12881,7 @@ BLD_COLLECTIVE_NET Réseau de Pensée Collective BLD_COLLECTIVE_NET_DESC -'''Augmente l'[[metertype METER_INDUSTRY]] de 0.5 par Population sur toutes les planètes réglées sur le Focus Industrie, et la [[metertype METER_RESEARCH]] de 0.5 par Population sur toutes les planètes réglées sur le Focus Recherche. Tout trajet sur Voies spatiales dans un périmètre de 200 uu affaiblira l'efficacité de cette structure et supprimera tout bonus. +'''Augmente la [[metertype METER_TARGET_INDUSTRY]] de 0.5 par [[metertype METER_POPULATION]] sur toutes les planètes en Focus Industrie, et la [[metertype METER_TARGET_RESEARCH]] de 0.5 par [[metertype METER_POPULATION]] sur toutes les planètes en Focus Recherche. Tout voyage sur Voies spatiales dans un périmètre de 200 uu affaiblira l'efficacité de cette structure et supprimera tout bonus. [[NO_STACK_SUPPLY_CONNECTION_TEXT]] En transférant l'esprit dans le cyberespace, des milliers d'esprits peuvent agir comme un seul, résolvant des problèmes et faisant des découvertes qui s'avéreraient hors de portée pour un unique esprit, aussi brillant soit-il.''' @@ -11452,7 +12898,7 @@ BLD_ENCLAVE_VOID Enclave Du Vide BLD_ENCLAVE_VOID_DESC -'''Augmente la [[metertype METER_RESEARCH]] Optimale de 0.75 par Population sur toutes les planètes réglées sur le Focus Recherche. +'''Augmente la [[metertype METER_TARGET_RESEARCH]] de 0.75 par [[metertype METER_POPULATION]] sur toutes les planètes en Focus Recherche. Les bonus de plusieurs exemplaires ne s'additionnent pas. La population entière d'une planète est modifiée génétiquement afin de l'harmoniser avec l'Esprit du Vide, et ainsi diffuser sa sagesse dans tout l'empire. Les habitants de l'Enclave du Vide sont considérés comme des prêtres, prophètes d'une pensée supérieure et d'un savoir suprême. Dans tous les domaines de grande importance pour la bonne marche de l'empire, leurs conseils et avis s'avèrent d'une grande valeur. Des émissaires de l'Enclave peuvent donc prendre part à tout projet impérial de Recherche et apporter ainsi une aide précieuse.''' @@ -11473,7 +12919,7 @@ BLD_HYPER_DAM Barrage Hyperspatial BLD_HYPER_DAM_DESC -'''Pour les planètes non-reliées à un [[buildingtype BLD_BLACK_HOLE_POW_GEN]], augmente l'[[metertype METER_INDUSTRY]] Optimale de 1 par Population sur toutes les planètes reliées par lignes d'[[metertype METER_SUPPLY]] et réglées sur le Focus Industrie, mais diminue la Population de ces planètes selon leur taille : ([[SZ_TINY]] -1) ([[SZ_SMALL]] -2) ([[SZ_MEDIUM]] -3) ([[SZ_LARGE]] -4) ([[SZ_HUGE]] -5). +'''Pour les planètes non-reliées à un [[buildingtype BLD_BLACK_HOLE_POW_GEN]], augmente la [[metertype METER_TARGET_INDUSTRY]] de 1 par [[metertype METER_POPULATION]] sur toutes les planètes reliées par lignes d'[[metertype METER_SUPPLY]] et réglées sur le Focus Industrie, mais diminue la [[metertype METER_TARGET_POPULATION]] de ces planètes selon leur taille : ([[SZ_TINY]] -1) ([[SZ_SMALL]] -2) ([[SZ_MEDIUM]] -3) ([[SZ_LARGE]] -4) ([[SZ_HUGE]] -5). [[NO_STACK_SUPPLY_CONNECTION_TEXT]] Une déchirure dans le tissu de l'univers, contrôlée et manipulée en toute sécurité. Chaque planète peut construire son propre barrage hyperspatial, mais un seul centre de contrôle est requis pour s'assurer que le continuum de l'espace-temps n'est pas endommagé. Par contre, toutes les planètes ayant recours à un barrage hyperspatial voient leur population souffrir de différents problèmes de santé, à moins que les planètes en question n'orbitent autour d'un [[STAR_BLACK]]. @@ -11490,9 +12936,9 @@ BLD_SOL_ORB_GEN Générateur Orbital Solaire BLD_SOL_ORB_GEN_DESC -'''Augmente l'[[metertype METER_INDUSTRY]] Optimale sur toutes les planètes lui étant reliées par ligne d'[[metertype METER_SUPPLY]], par Population et selon le type d'étoile : +'''Augmente la [[metertype METER_TARGET_INDUSTRY]] sur toutes les planètes lui étant reliées par ligne d'[[metertype METER_SUPPLY]], par [[metertype METER_POPULATION]] et selon le type d'étoile : * [[STAR_BLUE]] ou [[STAR_WHITE]] (x0.4) -* [[STAR_YELLOW]] ou [[STAR_ORANGE]] (x0.2) +* [[STAR_YELLOW]] ou [[STAR_ORANGE]] (x0.2) * [[STAR_RED]] (x0.1) [[NO_STACK_SUPPLY_CONNECTION_TEXT]] Le meilleur bonus s'applique. @@ -11502,7 +12948,7 @@ BLD_CLONING_CENTER Centre de Clonage BLD_CLONING_CENTER_DESC -'''Accroît la Population max de toutes les planètes selon leur taille : ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). +'''Accroît la [[metertype METER_TARGET_POPULATION]] de toutes les planètes selon leur taille : ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5). Le [[tech GRO_INDUSTRY_CLONE]] permet de produire industriellement des populations sur-mesure, améliorant considérablement leur croissance.''' @@ -11524,6 +12970,12 @@ Terraformeur à Distance BLD_REMOTE_TERRAFORM_DESC Terraforme une planète en un type d'[[encyclopedia ENVIRONMENT_TITLE]] un palier plus proche de l'Environnement favori de l'espèce. Ce processus peut être enclenché depuis un [[encyclopedia OUTPOSTS_TITLE]]. +BLD_NEST_ERADICATOR +Éradicateur de Nid + +BLD_NEST_ERADICATOR_DESC +Éradique un [[encyclopedia KRAKEN_NEST_SPECIAL]], un [[encyclopedia SNOWFLAKE_NEST_SPECIAL]], ou un [[encyclopedia JUGGERNAUT_NEST_SPECIAL]] de la planète sur laquelle le dispositif est mis en place. [[BUILDING_AVAILABLE_ON_OUTPOSTS]]. + BLD_NEUTRONIUM_EXTRACTOR Extracteur Neutronium @@ -11552,9 +13004,11 @@ BLD_CONC_CAMP Camps de Concentration BLD_CONC_CAMP_DESC -'''Réduit la Population d'une planète de 3 par tour, et augmente l'[[metertype METER_INDUSTRY]] Optimale de 5 fois la population planétaire. Le [[metertype METER_HAPPINESS]] Optimal est réduit à 0. +'''Réduit la [[metertype METER_POPULATION]] d'une planète de 3 par tour, et augmente la [[metertype METER_TARGET_INDUSTRY]] de 2 fois la [[metertype METER_POPULATION]] planétaire. La [[metertype METER_TARGET_HAPPINESS]] est réduite à 0. -Purger une planète d'une espèce indésirable permet l'introduction d'une espèce plus adaptée. En créant un réseau planétaire de camps de concentration conçus pour abattre les internés à la tâche, il est possible d'atteindre rapidement et efficacement le but fixé. Cette structure peut être construite sur un [[encyclopedia OUTPOSTS_TITLE]].''' +Si supprimé, des [[buildingtype BLD_CONC_CAMP_REMNANT]] subsistent durant plusieurs tours. + +Purger une planète d'une espèce indésirable permet l'introduction d'une espèce plus adaptée. En créant un réseau planétaire de camps de concentration conçus pour abattre les internés à la tâche, il est possible d'atteindre rapidement et efficacement le but fixé.''' BLD_CONC_CAMP_REMNANT Vestiges de Camps de Concentration @@ -11566,7 +13020,7 @@ BLD_BLACK_HOLE_POW_GEN Générateur de Singularité BLD_BLACK_HOLE_POW_GEN_DESC -'''Augmente l'[[metertype METER_INDUSTRY]] de 1 par Population sur toutes les planètes lui étant reliées par ligne d'[[metertype METER_SUPPLY]] et étant réglées sur le Focus Industrie. +'''Augmente la [[metertype METER_TARGET_INDUSTRY]] de 1 par [[metertype METER_POPULATION]] sur toutes les planètes lui étant reliées par ligne d'[[metertype METER_SUPPLY]] et étant réglées sur le Focus Industrie. [[NO_STACK_SUPPLY_CONNECTION_TEXT]] [[BUILDING_AVAILABLE_ON_OUTPOSTS]], mais doit se trouver dans un système abritant un [[STAR_BLACK]].''' @@ -11575,7 +13029,7 @@ BLD_PLANET_DRIVE Navigateur Planétaire BLD_PLANET_DRIVE_DESC -'''Permet à une planète de se déplacer entre les systèmes stellaires. Un [[buildingtype BLD_LIGHTHOUSE]] doit se trouver à une distance de 200 uu ou moins du système visé, afin que la planète y arrive en toute sécurité. Dans le cas contraire, la planète aura 50% de chance de se perdre ou d'être détruite durant son voyage. S'agissant dans tous les cas d'un périple dangereux, même si la planète arrive à bon port, seule la moitié de sa population survivra. [[BUILDING_AVAILABLE_ON_OUTPOSTS]], mais ne peut pas être utilisée à moins que la planète ne soit colonisée. +'''Permet à une planète de se déplacer entre les systèmes stellaires. Un [[buildingtype BLD_LIGHTHOUSE]] doit se trouver à une distance de 200 uu ou moins du système visé, afin que la planète y arrive en toute sécurité. Dans le cas contraire, la planète aura 50% de chance de se perdre ou d'être détruite durant son voyage. S'agissant dans tous les cas d'un périple dangereux, même si la planète arrive à bon port, seule la moitié de sa [[metertype METER_POPULATION]] survivra, qu'un Phare Interstellaire ait été utilisé ou non. [[BUILDING_AVAILABLE_ON_OUTPOSTS]], mais ne peut pas être exploitée à moins que la planète ne soit colonisée. En raison d'un manque de précision du dispositif de navigation, une [[buildingtype BLD_PLANET_BEACON]], ou bien un astronef équipé d'une [[shippart SP_PLANET_BEACON]], est nécessaire et doit se trouver à l'arrivée de la Voie spatiale empruntée. L'un comme l'autre s'auto-détruiront une fois utilisés, afin que la planète puisse événtuellement se déplacer vers un autre système si nécessaire. @@ -11655,7 +13109,7 @@ BLD_GAS_GIANT_GEN Convertisseur de Géante Gazeuse BLD_GAS_GIANT_GEN_DESC -'''Cette structure ne peut être construite que sur une [[PT_GASGIANT]] et augmente de 10 l'indicateur [[metertype METER_INDUSTRY]] sur les planètes du même système réglées sur le Focus Industrie. +'''Cette structure ne peut être construite que sur une [[PT_GASGIANT]] et augmente de 10 la [[metertype METER_TARGET_INDUSTRY]] sur les planètes du même système réglées sur le Focus Industrie. Si le [[BLD_GAS_GIANT_GEN]] est construit sur une [[PT_GASGIANT]] inhabitée, le bonus de [[metertype METER_TARGET_INDUSTRY]] n'est que de 5. Les bonus de plusieurs exemplaires dans le même système ne s'additionnent pas. Un puissant générateur conçu pour puiser l'énergie des Géantes Gazeuses.''' @@ -11756,6 +13210,12 @@ Colonie Furthest BLD_COL_FURTHEST_DESC [[BLD_COL_PART_1]] colonie [[species SP_FURTHEST]]. [[BLD_COL_PART_2]] colonie Furthest [[BLD_COL_PART_3]]. +BLD_COL_FULVER +Colonie Fulver + +BLD_COL_FULVER_DESC +[[BLD_COL_PART_1]] colonie [[species SP_FULVER]]. [[BLD_COL_PART_2]] colonie Fulver [[BLD_COL_PART_3]]. + BLD_COL_GEORGE Colonie George @@ -11822,6 +13282,12 @@ Colonie Phinnert BLD_COL_PHINNERT_DESC [[BLD_COL_PART_1]] colonie [[species SP_PHINNERT]]. [[BLD_COL_PART_2]] colonie Phinnert [[BLD_COL_PART_3]]. +BLD_COL_REPLICON +Colonie Replicon + +BLD_COL_REPLICON_DESC +[[BLD_COL_PART_1]] colonie [[species SP_REPLICON]]. [[BLD_COL_PART_2]] colonie Replicon [[BLD_COL_PART_3]]. + BLD_COL_SCYLIOR Colonie Scylior @@ -11840,6 +13306,12 @@ Colonie Silexian BLD_COL_SILEXIAN_DESC [[BLD_COL_PART_1]] colonie [[species SP_SILEXIAN]]. [[BLD_COL_PART_2]] colonie Silexian [[BLD_COL_PART_3]]. +BLD_COL_SLY +Colonie Sly + +BLD_COL_SLY_DESC +[[BLD_COL_PART_1]] colonie [[species SP_SLY]]. [[BLD_COL_PART_2]] colonie Sly [[BLD_COL_PART_3]]. + BLD_COL_SSLITH Colonie Sslith @@ -11883,12 +13355,16 @@ BLD_COL_SUPER_TEST_DESC # %1% hull base starlane speed. # %2% hull base fuel capacity. -# %3% hull base starlane speed. +# %3% hull base stealth. # %4% hull base structure value. +# %5% hull slots listing HULL_DESC '''[[metertype METER_SPEED]] : %1% [[metertype METER_FUEL]] : %2% -[[metertype METER_STRUCTURE]] : %4%''' +[[metertype METER_STEALTH]]: %3% +[[metertype METER_STRUCTURE]] : %4% + +%5%''' # %1% ship part capacity (fuel, troops, colonists, fighters). PART_DESC_CAPACITY @@ -11912,11 +13388,11 @@ PART_DESC_DIRECT_FIRE_STATS '''[[encyclopedia DAMAGE_TITLE]] par tir : %1% Tirs par Attaque : %2%''' -# %1% ship part damage done (fighters). -# %2% number of deployable fighters. +# %1% number of deployable fighters. +# %2% ship part damage done (fighters). PART_DESC_HANGAR_STATS -'''[[encyclopedia DAMAGE_TITLE]] par tir : %2% -Capacité [[encyclopedia FIGHTER_TECHS]] : %1%''' +'''Capacité [[encyclopedia FIGHTER_TECHS]] : %1% +[[encyclopedia DAMAGE_TITLE]] par tir : %2%''' ## @@ -11933,10 +13409,13 @@ FT_BAY_1 Rampe de Lancement FT_BAY_1_DESC -Système de lancement pour chasseurs. +Système de lancement pour chasseurs. Lancement plus rapide des [[FT_HANGAR_1_FIGHTER]]s: peut lancer simultanément tous les chasseurs d'un [[shippart FT_HANGAR_1]]. FT_HANGAR_0 -Hangar 0 +Hangar Leurre + +FT_HANGAR_0_FIGHTER +Leurre FT_HANGAR_0_DESC Système de stockage pour vaisseau leurre non-armé. @@ -11944,44 +13423,56 @@ Système de stockage pour vaisseau leurre non-armé. FT_HANGAR_1 Hangar Intercepteur +FT_HANGAR_1_FIGHTER +Intercepteur + FT_HANGAR_1_DESC -Système de stockage pour chasseurs faiblement armés. +Système de stockage pour chasseurs faiblement armés. Peut seulement attaquer les chasseurs ennemis. Lancement plus rapide: une [[shippart FT_BAY_1]] peut lancer simultanément tous les chasseurs d'un [[shippart FT_HANGAR_1]]. FT_HANGAR_2 -Hangar Chasseur +Hangar Chasseur d'Attaque + +FT_HANGAR_2_FIGHTER +Chasseur d'Attaque FT_HANGAR_2_DESC -Système de stockage pour chasseurs moyennement armés. +Système de stockage pour chasseurs moyennement armés. Peut attaquer les astronefs et chasseurs ennemis. FT_HANGAR_3 Hangar Bombardier +FT_HANGAR_3_FIGHTER +Bombardier + FT_HANGAR_3_DESC -Système de stockage pour chasseurs correctement armés. +Système de stockage pour chasseurs correctement armés. Peut seulement attaquer les astronefs ennemis. FT_HANGAR_4 -Hangar 4 +Hangar Bombardier Lourd + +FT_HANGAR_4_FIGHTER +Bombardier Lourd FT_HANGAR_4_DESC -Système de stockage pour chasseurs lourdement armés. +Système de stockage pour chasseurs lourdement armés. Peut attaquer les astonefs ennemis ainsi que les planètes. -FT_KRILL +FT_HANGAR_KRILL Essaim Krill -FT_KRILL_DESC +FT_HANGAR_KRILL_DESC Essaim de Krill. FT_BAY_KRILL Essaim Krill FT_BAY_KRILL_DESC -Lorsque les essaims de Krill ont atteint une taille suffisante, certains Krills quittent l'essaim pour attaquer les astronefs rencontrés. +Lorsque les essaims de Krill ont atteint une taille suffisante, un Krill quitte l'essaim pour attaquer les astronefs rencontrés. SR_WEAPON_0_1 Canon Antiaérien SR_WEAPON_0_1_DESC -'''Le Canon Antiaérien inflige des dommages négligeables à la plupart des astronefs, mais reste bon marché et efficace pour contrer les chasseurs. +'''Le Canon Antiaérien est peu coûteux et efficace pour contrer les chasseurs, mais ne permet pas de cibler les astronefs et planètes. Chaque Canon Antiaérien monté sur un astronef construit par une espèce présentant l'attribut "Pilotes", aura un nombre additionnel de tirs égal à 1 par rang de Pilotes (-1 tir pour "Mauvais Pilotes").''' @@ -11989,48 +13480,59 @@ SR_WEAPON_1_1 Canon Mitrailleur SR_WEAPON_1_1_DESC -'''Le Canon Mitrailleur, une arme de base. +'''Le Canon Mitrailleur, une arme de base. Attaque astronefs et planètes. Améliorer la technologie de cet armement augmentera la valeur des dommages par tir. -Les [[encyclopedia DAMAGE_TITLE]] d'un armement combinent la valeur des dommages par tir et celle de tout modificateur actif. Chaque Canon Mitrailleur monté sur un astronef construit par une espèce présentant l'attribut "Pilotes", infligera des dommages additionnels de 1 par rang de Pilotes (-1 pour "Mauvais Pilotes").''' +Les [[encyclopedia DAMAGE_TITLE]] de cet armement combinent la valeur des dommages par tir et celle de tout modificateur actif. Chaque Canon Mitrailleur monté sur un astronef construit par une espèce présentant l'attribut "Pilotes", infligera des dommages additionnels de 1 par rang de Pilotes (-1 pour "Mauvais Pilotes").''' SR_WEAPON_2_1 Canon Laser SR_WEAPON_2_1_DESC -'''Le Canon Laser, un armement d'astronef plus puissant que le Canon Mitrailleur. +'''Le Canon Laser, un armement d'astronef plus puissant que le Canon Mitrailleur. Attaque astronefs et planètes. Améliorer la technologie de cet armement augmentera la valeur des dommages par tir. -Les [[encyclopedia DAMAGE_TITLE]] d'un armement combinent la valeur des dommages par tir et celle de tout modificateur actif. Chaque Canon Laser monté sur un astronef construit par une espèce présentant l'attribut "Pilotes", infligera des dommages additionnels de 2 par rang de Pilotes (-2 pour "Mauvais Pilotes"). Les dommages du Canon Laser peuvent également être améliorés sur les astronefs dotés d'une [[shiphull SH_ORGANIC]] couplée à un [[shippart SP_SOLAR_CONCENTRATOR]]. -''' +Les [[encyclopedia DAMAGE_TITLE]] de cet armement combinent la valeur des dommages par tir et celle de tout modificateur actif. Chaque Canon Laser monté sur un astronef construit par une espèce présentant l'attribut "Pilotes", infligera des dommages additionnels de 2 par rang de Pilotes (-2 pour "Mauvais Pilotes"). Les dommages du Canon Laser peuvent également être améliorés sur les astronefs dotés d'une [[shiphull SH_ORGANIC]] couplée à un [[shippart SP_SOLAR_CONCENTRATOR]].''' SR_WEAPON_3_1 Canon Plasma SR_WEAPON_3_1_DESC -'''Le Canon Plasma, un armement d'astronef plus puissant que le Canon Laser. +'''Le Canon Plasma, un armement d'astronef plus puissant que le Canon Laser. Attaque astronefs et planètes. Améliorer la technologie de cet armement augmentera la valeur des dommages par tir. -Les [[encyclopedia DAMAGE_TITLE]] d'un armement combinent la valeur des dommages par tir et celle de tout modificateur actif. Chaque Canon Plasma monté sur un astronef construit par une espèce présentant l'attribut "Pilotes", infligera des dommages additionnels de 3 par rang de Pilotes (-3 pour "Mauvais Pilotes").''' +Les [[encyclopedia DAMAGE_TITLE]] de cet armement combinent la valeur des dommages par tir et celle de tout modificateur actif. Chaque Canon Plasma monté sur un astronef construit par une espèce présentant l'attribut "Pilotes", infligera des dommages additionnels de 3 par rang de Pilotes (-3 pour "Mauvais Pilotes").''' SR_WEAPON_4_1 Rayon Gamma SR_WEAPON_4_1_DESC -'''Le Rayon Gamma, un armement d'astronef plus puissant que le Canon Plasma. +'''Le Rayon Gamma, un armement d'astronef plus puissant que le Canon Plasma. Attaque astronefs et planètes. Améliorer la technologie de cet armement augmentera la valeur des dommages par tir. -Les [[encyclopedia DAMAGE_TITLE]] d'un armement combinent la valeur des dommages par tir et celle de tout modificateur actif. Chaque Rayon Gamma monté sur un astronef construit par une espèce présentant l'attribut "Pilotes", infligera des dommages additionnels de 5 par rang de Pilotes (-5 pour "Mauvais Pilotes").''' +Les [[encyclopedia DAMAGE_TITLE]] de cet armement combinent la valeur des dommages par tir et celle de tout modificateur actif. Chaque Rayon Gamma monté sur un astronef construit par une espèce présentant l'attribut "Pilotes", infligera des dommages additionnels de 5 par rang de Pilotes (-5 pour "Mauvais Pilotes").''' + +SR_ARC_DISRUPTOR +Perturbateur Électro-Conducteur + +SR_ARC_DISRUPTOR_DESC +'''Le Perturbateur Électro-Conducteur, un armement d'astronef multi-tir proposant des options avantageuses de recherche à long terme. + +Des condensateurs de polaron chargés génèrent une perturbation moléculaire sous forme d'ondes se propageant à travers l'espace et frappant plusieurs astronefs avant de se disperser. Le flux perturbateur peut être fortement dévié par les boucliers d'astronef. + +Améliorer la technologie de cet armement augmentera la valeur des dommages par tir. + +Les [[encyclopedia DAMAGE_TITLE]] de cet armement combinent la valeur des dommages par tir et celle de tout modificateur actif. Les attributs "Pilotes" n'affectent pas les dommages de cet armement.''' SR_SPINAL_ANTIMATTER Canon Anti-Matière SR_SPINAL_ANTIMATTER_DESC -Un gigantesque canon automatique propulsant de lourdes charges explosives faites d'anti-matière. +Un gigantesque canon automatique propulsant de lourdes charges explosives faites d'anti-matière. Attaque astronefs et planètes. SR_JAWS Mâchoires @@ -12221,19 +13723,7 @@ FU_BASIC_TANK Réservoir Additionnel de Carburant FU_BASIC_TANK_DESC -Augmente la capacité en [[metertype METER_FUEL]] d'un astronef, permettant un voyage supplémentaire sur une Voie spatiale. - -FU_DEUTERIUM_TANK -Réservoir de Deutérium - -FU_DEUTERIUM_TANK_DESC -Augmente la capacité en [[metertype METER_FUEL]] d'un astronef. Volumineux et vulnérable à toute puissance de feu. - -FU_ANTIMATTER_TANK -Réservoir d'Anti-Matière - -FU_ANTIMATTER_TANK_DESC -Augmente la capacité en [[metertype METER_FUEL]] d'un astronef. Compact et solide. +Augmente la capacité en [[metertype METER_FUEL]] d'un astronef, permettant des voyages supplémentaires sur les Voies spatiales en fonction du [[encyclopedia FUEL_EFFICIENCY_TITLE]]. Des technologies plus avancées augmenteront le nombre de voyages supplémentaires pour chaque réservoir de carburant équipant un astronef. FU_ZERO_FUEL Générateur Zero-Point de Carburant @@ -12396,7 +13886,7 @@ CO_OUTPOST_POD Module d'Avant-Poste CO_OUTPOST_POD_DESC -'''Les Modules d'[[encyclopedia OUTPOSTS_TITLE]] permettent d'établir des stations non-habitées et peuvent être installés sur des planètes inhabitables. Ces modules n'ont aucune population et ne produisent normalement aucune ressource, mais offrent une extension des lignes d'[[metertype METER_SUPPLY]] lorsque la technologie nécessaire est assimilée. Les Modules d'Avant-Poste peuvent évoluer en colonies. +'''Les Modules d'[[encyclopedia OUTPOSTS_TITLE]] permettent d'établir des stations non-habitées et peuvent être installés sur des planètes inhabitables. Ces modules n'ont aucune [[metertype METER_POPULATION]] et ne produisent normalement aucune ressource, mais permettent de découvrir une zone donnée et offrent une extension des lignes d'[[metertype METER_SUPPLY]] lorsque la technologie nécessaire est assimilée. Les Modules d'Avant-Poste peuvent évoluer en colonies. [[COLONY_SHIP_PARTS_UPKEEP_COST]]''' @@ -12581,6 +14071,18 @@ Son [[metertype METER_DETECTION]] est standard, tandis que sa [[metertype METER_ [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]]''' +SH_SPACE_FLUX_BUBBLE +Coque Bulle Flux Spatial + +SH_SPACE_FLUX_BUBBLE_DESC +'''Cette minuscule coque est capable d'atteindre une vitesse optimale sur de longues distances en raison de la puissance fournie par la technologie de [[tech SHP_SPACE_FLUX_BUBBLE]]. Elle ne dispose cependant que d'une niche interne et sa [[metertype METER_STRUCTURE]] max est faible. + +Sa [[metertype METER_STEALTH]] débute à 15, augmente de 10 si l'astronef est réglé sur '[[FW_PASSIVE]]', mais diminue de 30 pour tout déplacement durant un tour. Les avancées permises par les technologies telles que l'[[tech SPY_STEALTH_PART_1]] permettent à l'équipage d'accroître de 10 la Furtivité de l'astronef pour chaque recherche effectuée dans cette branche. + +Son [[metertype METER_DETECTION]] est standard, tandis que sa [[metertype METER_SPEED]] est moyenne. + +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]]''' + SH_SELF_GRAVITATING Coque Auto-Gravitationnelle @@ -12599,7 +14101,7 @@ SH_NANOROBOTIC_DESC Sa [[metertype METER_STEALTH]] est standard de même que son [[metertype METER_DETECTION]], tandis que sa [[metertype METER_SPEED]] est moyenne. -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'une [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], et d'une [[buildingtype BLD_SHIPYARD_CON_NANOROBO]].''' +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'une [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], et d'une [[buildingtype BLD_SHIPYARD_CON_NANOROBO]].''' SH_TITANIC Coque Titanesque @@ -12619,7 +14121,7 @@ SH_TRANSSPATIAL_DESC Sa [[metertype METER_STEALTH]] est élevée, son [[metertype METER_DETECTION]] standard, tandis que sa [[metertype METER_SPEED]] est moyenne. -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'une [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], et d'un [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]].''' +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'une [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], et d'un [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]].''' SH_LOGISTICS_FACILITATOR Coque Logistique @@ -12629,13 +14131,13 @@ SH_LOGISTICS_FACILITATOR_DESC Sa [[metertype METER_STEALTH]] est standard de même que son [[metertype METER_DETECTION]], tandis que sa [[metertype METER_SPEED]] est moyenne. -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'une [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], d'une [[buildingtype BLD_SHIPYARD_CON_NANOROBO]], d'une [[buildingtype BLD_SHIPYARD_CON_GEOINT]], et d'un [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]].''' +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'une [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], d'une [[buildingtype BLD_SHIPYARD_CON_NANOROBO]], d'une [[buildingtype BLD_SHIPYARD_CON_GEOINT]], et d'un [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]].''' SH_ASTEROID Coque Astéroïdale SH_ASTEROID_DESC -'''Cette coque est conçue à partir d'un astéroïde de taille moyenne et s'avère assez économique à produire. Légèrement plus spacieuse que la [[shiphull SH_STANDARD]], elle dispose de quatre niches externes et de deux niches internes. +'''Cette coque est conçue à partir d'un astéroïde de taille moyenne et s'avère assez économique à produire. Légèrement plus spacieuse que la [[shiphull SH_BASIC_LARGE]], elle dispose de quatre niches externes et de deux niches internes. Sa [[metertype METER_STEALTH]] est standard mais cette coque bénéficie d'un bonus conséquent de Furtivité lorsqu'elle se trouve dans un système abritant une Ceinture d'Astéroïdes, ou en combat lorsqu'elle se dissimule dans une Ceinture d'Astéroïdes. Son [[metertype METER_DETECTION]] est standard et sa [[metertype METER_SPEED]] est faible. @@ -12903,13 +14405,13 @@ SH_SOLAR_DESC Sa [[metertype METER_STRUCTURE]] est élevée, sa [[metertype METER_STEALTH]] est très faible, son [[metertype METER_DETECTION]] est standard et sa [[metertype METER_SPEED]] très élevée. Toutefois, cet astronef a la capacité de pénétrer une étoile et de s'y cacher, lui conférant ainsi une [[metertype METER_STEALTH]] parfaite sur la carte galactique, lorsqu'il se trouve dans un système abritant une étoile autre qu'une Étoile [[STAR_NEUTRON]] ou qu'un [[STAR_BLACK]], ainsi qu'en combat, lorsqu'il se dissimule dans une étoile adéquate. -Cet astronef nécessite un gigantesque apport d'énergie lors de sa construction, cette dernière devant être collectée à partir des collisions entre particules et antiparticules aux abords de l'horizon des événements d'un [[STAR_BLACK]]. Il ne peut donc être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'un [[buildingtype BLD_SHIPYARD_ENRG_COMP]], et d'une [[buildingtype BLD_SHIPYARD_ENRG_SOLAR]].''' +Cet astronef nécessite un gigantesque apport d'énergie lors de sa construction, cette dernière devant être collectée à partir des collisions entre particules et antiparticules aux abords de l'horizon des événements d'un [[STAR_BLACK]]. Il ne peut donc être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'un [[buildingtype BLD_SHIPYARD_ENRG_COMP]], et d'une [[buildingtype BLD_SHIPYARD_ENRG_SOLAR]].''' SH_BASIC_SMALL Coque Basique Légère SH_BASIC_SMALL_DESC -'''Une petite coque interstellaire de base pour astronef. Ne dispose que d'une niche externe, mais comparée aux autres coques de base, elle est capable d'effectuer un voyage supplémentaire sur une Voie spatiale. +'''Une petite coque interstellaire de base pour astronef. Ne dispose que d'une niche externe, mais est très efficace en matière de voyage sur Voies spatiales. Sa [[metertype METER_STEALTH]] est standard de même que son [[metertype METER_DETECTION]], tandis que sa [[metertype METER_SPEED]] est moyenne. @@ -12925,10 +14427,10 @@ Sa [[metertype METER_STEALTH]] est standard de même que son [[metertype METER_D [[BLD_SHIPYARD_BASE_REQUIRED]]''' -SH_STANDARD +SH_BASIC_LARGE Coque Basique Massive -SH_STANDARD_DESC +SH_BASIC_LARGE_DESC '''Une coque interstellaire de base pour astronef de grande taille. Elle dispose de trois niches externes et d'une niche interne. Sa [[metertype METER_STEALTH]] est standard de même que son [[metertype METER_DETECTION]], tandis que sa [[metertype METER_SPEED]] est faible. @@ -12950,7 +14452,7 @@ SH_COLONY_BASE Coque Colon Stellaire SH_COLONY_BASE_DESC -'''Une coque conçue pour le transport de colons et la création d'une colonie sur une autre planète du système où elle a été produite. Elle ne peut pas voyager sur les Voies spatiales entre les systèmes stellaires, pas plus qu'elle n'est rapide dans son propre système. Étant donné son rôle, elle dispose seulement d'une niche interne. +'''Une coque conçue pour le transport de colons et la création d'une colonie sur une autre planète du système où elle a été produite. Elle ne peut pas voyager sur les Voies spatiales entre les systèmes stellaires, pas plus qu'elle n'est rapide dans son propre système. Étant donné son rôle, elle dispose seulement de trois niches internes. Sa [[metertype METER_STEALTH]] est standard de même que son [[metertype METER_DETECTION]].''' @@ -13181,23 +14683,22 @@ BLD_COL_PART_3 appartenant à l'empire, et augmentera proportionnellement à son éloignement; ce délai sera réduit si l'empire recherche des technologies permettant la production d'astronefs colonisateurs plus rapides, grâce à des équipements de motorisation plus performants et des coques d'astronefs de qualité supérieure -# Macro keys formatting for ship designs, hulls or parts: -# BLD_SHIPYARD_TYPE1_TYPE2_REQUIRED (required building type(s) owned by -# empire only in the same system) -# ANY_SYSTEM (building owned in any system by empire or ally) -# Any other key formatting is self-explanatory (e.g. LIVING_HULL_AUTO_REGEN) - ## ## Ship design/hull macros ## - -# Keys used in Predefined Ship Designs and Ship Hulls sections +## Macro keys formatting for ship designs, hulls or parts: +## BLD_SHIPYARD_TYPE1_TYPE2_REQUIRED (required building type(s) owned by +## empire only in the same system) +## ANY_SYSTEM (building owned in any system by empire or ally) +## Any other key formatting is self-explanatory (e.g. LIVING_HULL_AUTO_REGEN) +## Keys used in Predefined Ship Designs and Ship Hulls sections +## BLD_SHIPYARD_BASE_REQUIRED -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]]. +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]]. BLD_SHIPYARD_BASE_AST_REQUIRED -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], au sein d'un système abritant une Ceinture d'Astéroïdes et un [[buildingtype BLD_SHIPYARD_AST]]. +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], au sein d'un système abritant une Ceinture d'Astéroïdes et un [[buildingtype BLD_SHIPYARD_AST]]. BLD_SHIPYARD_AST_REF_REQUIRED Un [[buildingtype BLD_SHIPYARD_AST_REF]] appartenant à l'empire est également requis au sein du même système. @@ -13212,30 +14713,33 @@ BLD_SHIPYARD_BASE_ENRG_COMP_TWO_STARS_REQUIRED En raison de l'énorme apport d'énergie nécessaire, ne peut être construit qu'au sein d'un système abritant une Étoile [[STAR_BLUE]] ou un [[STAR_BLACK]], et dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]] et d'un [[buildingtype BLD_SHIPYARD_ENRG_COMP]]. BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]] et d'une [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]]. +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]] et d'une [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]]. BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'une [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], et d'une [[buildingtype BLD_SHIPYARD_CON_GEOINT]]. +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'une [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], et d'une [[buildingtype BLD_SHIPYARD_CON_GEOINT]]. LIVING_HULL_AUTO_REGEN Les coques vivantes régénèrent leur [[metertype METER_STRUCTURE]] et leur [[metertype METER_FUEL]] entre chaque combat. BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]] et d'un [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]]. +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]] et d'un [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]]. BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_REQUIRED -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'un [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], et d'une [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]]. +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'un [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], et d'une [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]]. MIN_POPULATION_THREE_REQUIRED -Ne peut être construit que dans un lieu où la Population est supérieure ou égale à 3 +Ne peut être construit qu'en un lieu où la [[metertype METER_POPULATION]] est supérieure ou égale à 3 BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_CELL_GRO_CHAMB_REQUIRED -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'un [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], et d'une [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]]. +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'un [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], et d'une [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]]. BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED -Ne peut être construit que dans un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'un [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], d'une [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]], et d'une [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]]. +Ne peut être construit qu'en un lieu disposant d'un [[buildingtype BLD_SHIPYARD_BASE]], d'un [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], d'une [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]], et d'une [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]]. + -# Other than Requirements +## +## Other than Requirements +## SHIPDESIGN_DETECTION_RESEARCH_TIPS La [[metertype METER_RESEARCH]] visant à améliorer le [[metertype METER_DETECTION]] permettra la conception de meilleurs modèles. @@ -13262,8 +14766,8 @@ SHIPDESIGN_PLANET_INVASION ## ## Ship part/tech application macros ## - -# Keys used in Ship Parts and Technology Application sections +## Keys used in Ship Parts and Technology Application sections +## BLD_SHIPYARD_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED_ANY_SYSTEM Cet équipement ne peut être produit que si l'empire ou un empire allié dispose d'une [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] et d'une [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]]. @@ -13284,7 +14788,7 @@ NO_STACK_SHIELDS_SHIP_PARTS Les [[metertype METER_SHIELD]] réduisent les [[encyclopedia DAMAGE_TITLE]] infligés par chaque tir de la valeur de solidité du Bouclier. Il ne peut y avoir qu'un seul bouclier actif par astronef, rendant impossible l'addition défensive de plusieurs boucliers. COLONY_SHIP_PARTS_MIN_POP -Toutes les classes d'astronefs colonisateurs requièrent de leur planète d'origine une Population supérieure ou égale à 3. +Toutes les classes d'astronefs colonisateurs requièrent de leur planète d'origine une [[metertype METER_POPULATION]] supérieure ou égale à 3. COLONY_SHIP_PARTS_UPKEEP_COST Le coût de cet équipement augmente à mesure que l'empire s'étend en raison des coûts de gestion plus élevés d'un vaste empire. @@ -13300,19 +14804,19 @@ SHIP_WEAPON_QUICKLY_REDUCE permettant aux astronefs de réduire rapidement ENEMY_PLANET_ORGANIC_POP -la Population Organique des planètes ennemies +la [[metertype METER_POPULATION]] Organique des planètes ennemies ENEMY_PLANET_ROBOTIC_POP -la Population Robotique des planètes ennemies +la [[metertype METER_POPULATION]] Robotique des planètes ennemies ENEMY_PLANET_LITHIC_POP -la Population Lithique des planètes ennemies +la [[metertype METER_POPULATION]] Lithique des planètes ennemies ENEMY_PLANET_PHOTOTROPHIC_POP -la Population Phototropique des planètes ennemies +la [[metertype METER_POPULATION]] Phototropique des planètes ennemies ENEMY_PLANET_ANY_POP -toute Population des planètes ennemies +toute [[metertype METER_POPULATION]] des planètes ennemies ## @@ -13337,7 +14841,7 @@ GROWTH_SPECIAL_POPULATION_LITHIC_INCREASE Les espèces à [[encyclopedia LITHIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] [[LITHIC_SPECIES_TITLE]]. GROWTH_SPECIAL_POPULATION_INCREASE -'''habitant cette planète verront leur Population max augmenter selon la taille de la planète : +'''habitant cette planète verront leur [[metertype METER_TARGET_POPULATION]] augmenter selon la taille de la planète : • [[SZ_TINY]] (+1) • [[SZ_SMALL]] (+2) • [[SZ_MEDIUM]] (+3) @@ -13348,10 +14852,10 @@ quel que soit l'[[encyclopedia ENVIRONMENT_TITLE]] planétaire. Si cette planète est réglée sur le [[encyclopedia GROWTH_FOCUS_TITLE]], le bonus s'applique également à tous les autres mondes connectés par lignes d'[[metertype METER_SUPPLY]] et peuplés d'espèces à''' GROWTH_SPECIAL_INDUSTRY_BOOST -Si cette planète est réglée sur le Focus Industrie, son [[metertype METER_INDUSTRY]] Optimale augmente de 0.2 par Population. +Si cette planète est réglée sur le Focus Industrie, sa [[metertype METER_TARGET_INDUSTRY]] augmente de 0.2 par [[metertype METER_POPULATION]]. GROWTH_SPECIALS_ENTRY_LIST -'''Les planètes présentant une Particularité de Croissance et réglées sur le Focus Croissance favorisent seulement une classification d'espèces. Certaines Particularités ne fonctionnent que pour les espèces organiques, d'autres pour les espèces lithiques, et enfin d'autres pour les espèces robotiques. +'''Certaines Particularités ne fonctionnent que pour les espèces organiques, d'autres pour les espèces lithiques, et enfin d'autres pour les espèces robotiques. Aucun autre type de [[encyclopedia METABOLISM_TITLE]] ne dispose actuellement de Particularités de Croissance lui étant exclusivement applicables. Particularités pour [[encyclopedia ORGANIC_SPECIES_TITLE]] : [[special FRUIT_SPECIAL]] | [[special SPICE_SPECIAL]] | [[special PROBIOTIC_SPECIAL]] @@ -13425,7 +14929,7 @@ ULTIMATE_DEFENSE_TROOPS_DESC +++ Parfait en [[metertype METER_TROOPS]] défensive : 300% ANCIENT_DEFENSE_TROOPS_DESC -+++ [[metertype METER_TROOPS]] ancestrale défensive : 10 par Population ++++ [[metertype METER_TROOPS]] ancestrale défensive : 10 par [[metertype METER_POPULATION]] NO_OFFENSE_TROOPS_DESC −−− Pas d'[[metertype METER_TROOPS]] offensive @@ -13484,18 +14988,25 @@ GREAT_WEAPONS_DESC ULTIMATE_WEAPONS_DESC +++ Parfaits Pilotes : dommages de base par armement d'astronef augmentés de trois niveaux. +GASEOUS_DESC +'''+ Vie sur Géantes Gazeuses ++ Ravitaillement Carburant sur Géantes Gazeuses 0.1''' + BAD_POPULATION_DESC -− Faible Population : 75% +− Mauvais en [[metertype METER_POPULATION]] : 75% AVERAGE_POPULATION_DESC -''' Population normale''' +''' Normal en [[metertype METER_POPULATION]] : 100%''' GOOD_POPULATION_DESC -+ Grande Population : 125% ++ Bon en [[metertype METER_POPULATION]] : 125% FIXED_LOW_POPULATION_DESC −− Population Maximale limitée à 5 +VERY_BAD_SUPPLY_DESC +−− Très Mauvais en [[metertype METER_SUPPLY]]: -1 + BAD_SUPPLY_DESC − Mauvais en [[metertype METER_SUPPLY]] : aucun bonus @@ -13508,6 +15019,24 @@ GREAT_SUPPLY_DESC ULTIMATE_SUPPLY_DESC ++ Parfait en [[metertype METER_SUPPLY]] : +3 +NO_STOCKPILE_DESC +−− Aucun [[metertype METER_STOCKPILE]] + +BAD_STOCKPILE_DESC +− Mauvais en [[metertype METER_STOCKPILE]] : +0.01 par [[metertype METER_POPULATION]] + +AVERAGE_STOCKPILE_DESC +''' Normal en [[metertype METER_STOCKPILE]] : +0.02 par [[metertype METER_POPULATION]]''' + +GOOD_STOCKPILE_DESC ++ Bon en [[metertype METER_STOCKPILE]] : +0.06 par [[metertype METER_POPULATION]] + +GREAT_STOCKPILE_DESC +++ Excellent en [[metertype METER_STOCKPILE]] : +0.2 par [[metertype METER_POPULATION]] + +ULTIMATE_STOCKPILE_DESC ++++ Parfait en [[metertype METER_STOCKPILE]] : +0.3 par [[metertype METER_POPULATION]] + GOOD_SHIP_SHIELD_DESC + Bons [[metertype METER_SHIELD]] d'astronefs : +1 @@ -13532,20 +15061,35 @@ FAST_COLONIZATION_DESC SLOW_COLONIZATION_DESC − Colonisation lente : +20% de délai pour bâtir une colonie. +NO_FUEL_DESC +−−− Astronefs sans [[metertype METER_FUEL]] + BAD_FUEL_DESC -− Mauvais en [[metertype METER_FUEL]] max : - 1 +− Mauvais en [[metertype METER_FUEL]] max : -0.5 + +AVERAGE_FUEL_DESC +''' Normal en [[metertype METER_FUEL]] max : +0''' + +GOOD_FUEL_DESC ++ Bon en [[metertype METER_FUEL]] max : +0.5 + +GREAT_FUEL_DESC +++ Excellent en [[metertype METER_FUEL]] max : +1 + +ULTIMATE_FUEL_DESC ++++ Parfait en [[metertype METER_FUEL]] max : +1.5 GREAT_ASTEROID_INDUSTRY_DESC -+ Bons Mineurs sur Astéroïdes: + 0.2 en [[metertype METER_INDUSTRY]] par Population lorsque que le Focus Industrie est activé dans les systèmes abritant une Ceinture d'Astéroïdes colonisée par l'empire. ++ Bons Mineurs sur Astéroïdes: + 0.2 en [[metertype METER_INDUSTRY]] par [[metertype METER_POPULATION]] lorsque que le Focus Industrie est activé dans les systèmes abritant une Ceinture d'Astéroïdes colonisée par l'empire. LIGHT_SENSITIVE_DESC -− Sensibilité à la lumière: la Population est réduite dans les systèmes abritant une Étoile [[STAR_BLUE]], et dans une moindre mesure, une Étoile [[STAR_WHITE]]. +− Sensibilité à la lumière: la [[metertype METER_POPULATION]] est réduite dans les systèmes abritant une Étoile [[STAR_BLUE]], et dans une moindre mesure, une Étoile [[STAR_WHITE]]. TELEPATHIC_DETECTION_DESC + Détection Télépathique: peut percevoir les planètes habitées proches. COMMUNAL_VISION_DESC - Vision Commune: partage ce qui est visible avec tout autre représentant de l'espèce dans l'univers. +''' Vision Commune: partage ce qui est visible avec tout autre représentant de l'espèce dans l'univers.''' ## @@ -13615,12 +15159,12 @@ Planète Natale # %1% FIXME # %2% FIXME BAD_POPULATION_LABEL -%2% Faible population 75%% +%2% Mauvais en Population # %1% FIXME # %2% FIXME GOOD_POPULATION_LABEL -%2% Grande population 125%% +%2% Bon en Population # %1% FIXME # %2% FIXME @@ -13672,6 +15216,11 @@ GREAT_RESEARCH_LABEL ULTIMATE_RESEARCH_LABEL %2% Parfait en Recherche +# %1% FIXME +# %2% FIXME +VERY_BAD_SUPPLY_LABEL +%2% Très Mauvais en Approvisionnement + # %1% FIXME # %2% FIXME BAD_SUPPLY_LABEL @@ -13692,6 +15241,61 @@ GREAT_SUPPLY_LABEL ULTIMATE_SUPPLY_LABEL %2% Parfait en Approvisionnement +# %1% FIXME +# %2% FIXME +BAD_STOCKPILE_LABEL +%2% Mauvais en Stock + +# %1% FIXME +# %2% FIXME +AVERAGE_STOCKPILE_LABEL +%2% Normal en Stock + +# %1% FIXME +# %2% FIXME +GOOD_STOCKPILE_LABEL +%2% Bon en Stock + +# %1% FIXME +# %2% FIXME +GREAT_STOCKPILE_LABEL +%2% Excellent en Stock + +# %1% FIXME +# %2% FIXME +ULTIMATE_STOCKPILE_LABEL +%2% Parfait en Stock + +# %1% FIXME +# %2% FIXME +NO_FUEL_LABEL +%2% Sans Carburant + +# %1% FIXME +# %2% FIXME +BAD_FUEL_LABEL +%2% Mauvais en Carburant max + +# %1% FIXME +# %2% FIXME +AVERAGE_FUEL_LABEL +%2% Normal en Carburant max + +# %1% FIXME +# %2% FIXME +GOOD_FUEL_LABEL +%2% Bon en Carburant max + +# %1% FIXME +# %2% FIXME +GREAT_FUEL_LABEL +%2% Excellent en Carburant max + +# %1% FIXME +# %2% FIXME +ULTIMATE_FUEL_LABEL +%2% Parfait en Carburant max + # %1% FIXME # %2% FIXME BAD_TROOPS_LABEL @@ -13726,15 +15330,25 @@ Mégalithe OUTPOST_TROOP_LABEL Avant-Poste +# %1% FIXME +# %2% FIXME +NATIVE_PLANETARY_DETECTION_LABEL +%2% Détection planétaire native + # %1% FIXME # %2% FIXME NATIVE_PLANETARY_DEFENSE_LABEL -%2% Défense planétaire +%2% Défense planétaire native # %1% FIXME # %2% FIXME NATIVE_PLANETARY_SHIELDS_LABEL -%2% Bouclier planétaire +%2% Boucliers planétaires natifs + +# %1% FIXME +# %2% FIXME +NATIVE_PLANETARY_TROOPS_LABEL +%2% Infanterie planétaire native VERY_BRIGHT_STAR Étoile très lumineuse @@ -13772,9 +15386,27 @@ Planète Natale CAPITAL_LABEL Capitale +BLD_STOCKPILING_CENTER_LABEL +Centre d'Intrication Impériale + CONCENTRATION_CAMPS_LABEL Camps de Concentration +GENERIC_SUPPLIES_FIXED_BONUS_LABEL +[[PRO_GENERIC_SUPPLIES]] bonus fixe + +GENERIC_SUPPLIES_FOCUS_BONUS_LABEL +[[PRO_GENERIC_SUPPLIES]] bonus selon Focus + +GENERIC_SUPPLIES_POPULATION_BONUS_LABEL +[[PRO_GENERIC_SUPPLIES]] bonus selon Population + +INTERSTELLAR_ENTANGLEMENT_FACTORY_POPULATION_BONUS_LABEL +Complexe d'Intrication Interstellaire bonus selon Population + +INTERSTELLAR_ENTANGLEMENT_FACTORY_FIXED_BONUS_LABEL +Complexe d'Intrication Interstellaire bonus fixe + ORBITAL_HAB_LABEL Habitation orbitale @@ -13799,6 +15431,9 @@ Excellente Vision FOCUS_PROTECTION_LABEL Focus Protection +GASEOUS_LABEL +Environnement Géante Gazeuse + XENOPHOBIC_LABEL_SELF Frénésie xénophobe (autre espèce proche) @@ -13838,15 +15473,64 @@ Brouillage Camouflage TRANSPATIAL_CLOAK_INTERACTION Interaction Propulsion Transpatiale - Camouflage +FUEL_TITLE +Carburant + +FUEL_EFFICIENCY_TITLE +Rendement Carburant + +FUEL_REFUEL_EFFICIENCY_TEXT +'''Le [[metertype METER_FUEL]] permet aux astronefs de parcourir les Voies spatiales situées hors des zones d'[[metertype METER_SUPPLY]] de l'empire. En dehors de ces dernières, un astronef consomme du carburant pour chaque Voie spatiale parcourue (soit un voyage). Un arrêt d'un astronef sur les lignes d'[[metertype METER_SUPPLY]] de l'empire permet de refaire le plein en carburant. S'il voyage au-delà des lignes d'approvisionnement de son empire ou de celles d'un allié, un astronef récupérera lentement son carburant seulement s'il est immobile (stationnaire dans un système, dans l'espace interstellaire, ou sur une Voie spatiale). + +La capacité de base en carburant d'un Modèle d'Astronef est déterminée par la [[encyclopedia ENC_SHIP_HULL]] utilisée, mais cette capacité peut toutefois être augmentée à l'aide de certains Équipements d'Astronef. + +Les coques d'astronef offrent un rendement excellent (400%), bon (200%), normal (100%), ou mauvais (60%), ce qui détermine l'efficacité de tout équipement additionnel en carburant ou autre effet modificateur en carburant. Le Rendement Carburant décroît généralement avec la masse de la coque. Par conséquent, seuls les très petits astronefs disposent d'un rendement carburant élevé tandis que les astronefs massifs ont une mobilité réduite hors des zones d'[[metertype METER_SUPPLY]]. Le jeu affichera continuellement les niveaux effectifs de carburant, en fonction du Rendement Carburant.''' + BASE_FUEL_REGEN_LABEL Récupération Stationnaire Carburant +FUEL_EFFICIENCY_DESC +[[FUEL_EFFICIENCY_TITLE]] + +BAD_FUEL_EFFICIENCY_LABEL +Coque %2% -40%% [[encyclopedia FUEL_EFFICIENCY_TITLE]] + +AVERAGE_FUEL_EFFICIENCY_LABEL +Coque %2% +0%% [[encyclopedia FUEL_EFFICIENCY_TITLE]] + +GOOD_FUEL_EFFICIENCY_LABEL +Coque %2% +100%% [[encyclopedia FUEL_EFFICIENCY_TITLE]] + +GREAT_FUEL_EFFICIENCY_LABEL +Coque %2% +300%% [[encyclopedia FUEL_EFFICIENCY_TITLE]] + +HULL_DESC_BAD_FUEL_EFFICIENCY +[[encyclopedia FUEL_EFFICIENCY_TITLE]]: 60% + +HULL_DESC_AVERAGE_FUEL_EFFICIENCY +[[encyclopedia FUEL_EFFICIENCY_TITLE]]: 100% + +HULL_DESC_GOOD_FUEL_EFFICIENCY +[[encyclopedia FUEL_EFFICIENCY_TITLE]]: 200% + +HULL_DESC_GREAT_FUEL_EFFICIENCY +[[encyclopedia FUEL_EFFICIENCY_TITLE]]: 400% + +MAX_FUEL_LESS_THAN_ONE_DESC +Ravitaillement en Carburant impossible + +MAX_FUEL_LESS_THAN_ONE_LABEL +Réservoir trop petit pour Ravitaillement (Carburant Max < 1.0) + SPATIAL_FLUX_MALUS Interférence Flux SPATIAL_FLUX_BONUS Bonus Propulsion Flux +DYING_POPULATION_LABEL +Population agonisante + ## ## Tags @@ -13876,6 +15560,9 @@ Autosuffisant TELEPATHIC Télépathique +GASEOUS +Gazeux + ORBITAL # translated Orbital @@ -13998,22 +15685,58 @@ Modèle Invalide ## AI diplomacy strings and lists ## +# Newline separated list of polite alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_ACKNOWLEDGEMENTS_MILD_LIST +'''Heureux d'avoir de vos nouvelles. +Réception de transmission confirmée. +''' + +# Newline separated list of harsh alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_ACKNOWLEDGEMENTS_HARSH_LIST +'''Si vous ne pouvez soutenir vos propres ambitions, vous n'êtes pas digne de notre aide. +Veuillez solliciter un autre bienfaiteur à qui soutirer des ressources. +''' + +# Newline separated list of polite positive alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_RESPONSES_YES_MILD_LIST +'''Nos empires peuvent tirer bénéfice d'une entraide et collaboration avancée. +Nous serions honorés de coopérer plus étroitement en vue d'une réussite mutuelle. +''' + +# Newline separated list of polite negative alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_RESPONSES_NO_MILD_LIST +'''Nous ne pensons pas qu'il résulterait un bénéfice mutuel d'un tel accord en l'état. +Nous ne pouvons octroyer les ressources qu'un accord de la sorte exigerait de notre part. +''' + +# Newline separated list of harsh positive alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_RESPONSES_YES_HARSH_LIST +'''Nous acceptons à contrecœur de soutenir votre futile entreprise. +Peut-être nous amuserons-nous de vos gesticulations inutiles et combats désespérés. +''' + +# Newline separated list of harsh negative alliance proposal acknowlegements. +AI_ALLIANCE_PROPOSAL_RESPONSES_NO_HARSH_LIST +'''Nous n'accepterons jamais de partager nos ressources avec vous! +Vous êtes totalement indigne d'une telle confiance et d'un tel soutien. +''' + # Newline separated list of polite peace proposal acknowlegements. AI_PEACE_PROPOSAL_ACKNOWLEDGEMENTS_MILD_LIST '''Heureux de recevoir de vos nouvelles. -Réception de transmission acceptée. +Réception de transmission confirmée. ''' # Newline separated list of harsh peace proposal acknowlegements. AI_PEACE_PROPOSAL_ACKNOWLEDGEMENTS_HARSH_LIST '''Comment? N'êtes-vous pas déjà mort? -Oh, pas une nouvelle pleurnicherie pathétique, de grâce! +Oh, pas encore une pleurnicherie pathétique, de grâce! ''' # Newline separated list of polite positive peace proposal acknowlegements. AI_PEACE_PROPOSAL_RESPONSES_YES_MILD_LIST '''Nous serons ravis de croire en vos bonnes intentions. -Tout le plaisir est pour nous, Sire. Chacun s'accorde à dire que vous êtes digne de confiance. +Tout le plaisir est pour nous, Sire. Chacun s'accorde à dire que vous êtes digne de confiance. ''' # Newline separated list of polite negative peace proposal acknowlegements. @@ -14024,7 +15747,7 @@ Nous sommes dans le regret de vous informer que nous ne pouvons accéder à votr # Newline separated list of harsh positive peace proposal acknowlegements. AI_PEACE_PROPOSAL_RESPONSES_YES_HARSH_LIST -'''Par grande miséricorde, nous acceptons de ne pas vous attaquer. Pour le moment. +'''Par grande miséricorde, nous acceptons de ne pas vous attaquer. Pour le moment. Insignifiantes créatures! Il n'y aurait que bien peu d'honneur à vous terrasser. ''' @@ -14048,7 +15771,7 @@ Sombres idiots, vous ne pensiez pas que cette paix allait durer, n'est-ce pas? # Newline separated pre-game acknowledgements. AI_PREGAME_ACKNOWLEDGEMENTS__LIST -'''Je médite, en attente des directives suprêmes de Dieu. Veuillez rappeler plus tard. +'''Je médite, en attente des directives suprêmes de Dieu. Veuillez rappeler plus tard. Des murmures émanant du Vide? Cela se peut-il? ''' @@ -14189,15 +15912,24 @@ Annihilez votre espèce est pour nous un devoir sacré! OPTIONS_PAGE_HOTKEYS Raccourcis clavier -HOTKEYS_Z_COMBAT -Fenêtre Combat +HOTKEYS_UI +Interface Utilisateur -HOTKEYS_MAP +HOTKEYS_UI_MAP Carte galactique +HOTKEYS_UI_MAP_FLEET +Flottes + +HOTKEYS_UI_MAP_SYSTEM +Systèmes + HOTKEYS_GENERAL Raccourcis généraux +HOTKEYS_VIDEO +Vidéo + HOTKEY_MAP_OPEN_CHAT Ouvrir Messages @@ -14309,6 +16041,9 @@ Tout sélectionner HOTKEY_DESELECT Désélectionner +HOTKEY_AUTOCOMPLETE +Autocompléter mot saisi + HOTKEY_FOCUS_PREV_WND Champ précédent @@ -14389,6 +16124,5 @@ Plein écran [on/off] -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/it.txt b/default/stringtables/it.txt index ecc2ec16b05..2e30552bc0e 100644 --- a/default/stringtables/it.txt +++ b/default/stringtables/it.txt @@ -160,24 +160,6 @@ Mark I SD_MARK1_DESC Fregata da pattugliamento base armata di cannone a rotaia. -SD_MARK_2 -Mark II - -SD_MARK2_DESC -Fregata da pattugliamento migliorata armata di cannone a rotaia. - -SD_MARK_3 -Mark III - -SD_MARK3_DESC -Fregata da pattugliamento base armata di laser. - -SD_MARK_4 -Mark IV - -SD_MARK4_DESC -Fregata da pattugliamento migliorata armata di laser. - SD_GRAVITATING1_DESC Massiccia nave da guerra armata e protetta con gli ultimi ritrovati tecnologici. Fissato il prezzo di conseguenza. @@ -314,9 +296,6 @@ COMMAND_LINE_USAGE COMMAND_LINE_DEFAULT '''Predefiniti: ''' -OPTIONS_DB_HELP -Mostra questo messaggio di aiuto. - OPTIONS_DB_GENERATE_CONFIG_XML Utilizza tutte le impostazioni da qualsiasi file config.xml esistente e quelli indicati sulla riga di comando per generare un file config.xml. Questo sovrascriverà il file config.xml corrente, se esiste. @@ -584,26 +563,6 @@ Imposta la dimensione del pianeta rotante più grande nel pannello laterale del OPTIONS_DB_UI_SIDEPANEL_PLANET_MIN_DIAMETER Imposta la dimensione del pianeta rotante più piccolo nel pannello laterale del sistema. -OPTIONS_DB_GAMESETUP_STARS -'''Il numero approssimativo di sistemi per la galassia che sarà generata. -Per una partita bilanciata il numero consigliato è 15-30 sistemi per impero. -Numeri molto elevati di sistemi potrebbero causare dei rallentamenti di FreeOrion, specialmente nelle fasi avanzate.''' - -OPTIONS_DB_GAMESETUP_GALAXY_SHAPE -La forma della galassia da generare. - -OPTIONS_DB_GAMESETUP_GALAXY_AGE -L'età della galassia da generare. - -OPTIONS_DB_GAMESETUP_PLANET_DENSITY -Il numero di pianeti per sistema da generare. - -OPTIONS_DB_GAMESETUP_STARLANE_FREQUENCY -Il numero di rotte da generare. - -OPTIONS_DB_GAMESETUP_SPECIALS_FREQUENCY -Il numero di apparizioni speciali da generare. - OPTIONS_DB_GAMESETUP_EMPIRE_NAME Il nome del tuo impero. @@ -613,9 +572,6 @@ Il nome usato nel gioco dal giocatore. OPTIONS_DB_GAMESETUP_EMPIRE_COLOR Il colore usato per il tuo impero. -OPTIONS_DB_GAMESETUP_NUM_AI_PLAYERS -Il numero di avversari IA nel gioco. - OPTIONS_DB_UI_TECH_LAYOUT_HORZ_SPACING La spaziatura orizzontale fra tecnologie nell'albero tecnologico, in multipli della larghezza di ogni singola tecnologia. @@ -634,12 +590,6 @@ Imposta il livello sopra al quale i messaggi di log verranno mostrati (livelli i OPTIONS_DB_STRINGTABLE_FILENAME Imposta il file della lingua. -OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER -Salvataggi automatici per la modalità giocatore singolo. - -OPTIONS_DB_AUTOSAVE_MULTIPLAYER -Salvataggi automatici per la modalità multigiocatore. - OPTIONS_DB_AUTOSAVE_TURNS Imposta il numero di turni da attendere fra due salvataggi automatici. @@ -974,12 +924,6 @@ Mostra pannello laterale pianeti OPTIONS_MISC_UI Impostazioni varie interfaccia utente -OPTIONS_SINGLEPLAYER -Giocatore Singolo - -OPTIONS_MULTIPLAYER -Multi giocatore - OPTIONS_AUTOSAVE_LIMIT Salvataggi automatici da conservare @@ -1343,31 +1287,9 @@ Guida MAP_PRODUCTION_TITLE Produzione -MAP_PROD_WASTED_TITLE -Produzione Inutilizzata - -# %1% number of production points generated. -# %2% number of production points currently not used for production. -MAP_PROD_WASTED_TEXT -'''Produzione Totale : %1% -Produzione Sprecata : %2% - -Click qui per aprire il menu di produzione''' - MAP_RESEARCH_TITLE Ricerca -MAP_RES_WASTED_TITLE -Ricerca Inutilizzata - -# %1% number of research points generated. -# %2% number of research points currently not used for research. -MAP_RES_WASTED_TEXT -'''Ricerca Totale : %1% -Ricerca Sprecata : %2% - -Click qui per aprire il menu di ricerca''' - MAP_POPULATION_DISTRIBUTION Censimento @@ -1383,12 +1305,6 @@ Commercio MAP_TRADE_TEXT Commercio totale impero. (Commercio non fa nulla) -MAP_FLEET_TITLE -Conteggio Flotte - -MAP_FLEET_TEXT -Numero totale di navi - ## ## Side panel/System information @@ -1729,7 +1645,7 @@ Dichiara Guerra PEACE_PROPOSAL Proponi Pace -PEACE_PROPOSAL_CANEL +PEACE_PROPOSAL_CANCEL Annulla Proposta di Pace PEACE_ACCEPT @@ -1810,9 +1726,6 @@ Ricerca RESEARCH_INFO_RP PR -RESEARCH_QUEUE_PROMPT -Doppio click oppure Shift-click su un elemento disponibile per aggiungerlo alla coda di ricerca. - ## ## Build selector window @@ -2090,54 +2003,24 @@ Popolazione METER_INDUSTRY_VALUE_LABEL Industria -METER_INDUSTRY_VALUE_DESC -'''L'industria rappresenta la produzione e la modifica di beni fisici. È necessaria per la produzione di edifici, navi spaziali e altri progetti. - -L'industria di un pianeta può essere utilizzata per qualsiasi progetto di produzione di [[enciclopedia SUPPLY_TITLE]] linea pianeti collegati. La produzione di qualunque industria non in funzione viene sprecata ad ogni turno. - -Ogni progetto ha un numero minimo di turni necessari per essere costruito. Per esempio, se un progetto che è costato 15 PP, e ha avuto un tempo minimo di 3 giri, 5 PPS è il massimo che potrebbe essere messo verso il suo completamento per turno. -Il progetto nella parte superiore della coda è dato PP prima. Qualunque PP rimanenti vengono poi dati al progetto successivo in coda, e se vi è ancora più attuale dalla prossima - e così via. È possibile modificare la priorità degli elementi della coda trascinando gli elementi più importanti più vicini alla cima. -''' - METER_RESEARCH_VALUE_LABEL Ricerca -METER_RESEARCH_VALUE_DESC -'''La ricerca (o PR "Punti Ricerca") di tutti i pianeti dell'impero è combinata per la scoperta di nuove tecnologie. Qualsiasi punto ricerca che non possa essere speso per ricercare una tecnologia è perso ad ogni turno. Ogni tecnologia ha un numero minimo di turni richiesto per essere ricercato. Per esempio, se una tecnologia che costa 15 PR e riechiede un tempo minimo di 3 turni, 5 PR è il massimo per turno che può essere assegnato al suo completamento. -Alla tecnologia in cima alla coda è assegnata la priorità di ricerca con il massimo numero di PR consentito. Ogni PR restante è poi fornito alla prossima tecnologia in coda, stessa cosa dicasi per la tecnologia successiva - e così via. È possibile modificare la priorità degli elementi della coda trascinando gli elementi più importanti più vicini alla cima. - -La ricerca non richiede la connessione di linee[[metertype METER_SUPPLY]] per essere utilizzata.''' - METER_TRADE_VALUE_LABEL Commercio METER_CONSTRUCTION_VALUE_LABEL Infrastrutture -METER_CONSTRUCTION_VALUE_DESC -Il termine infrastruttura si riferisce a strutture tecniche che supportano un pianeta colonizzato: servizi di energia, trasporti, telecomunicazioni e sociali per i cittadini. - METER_HAPPINESS_VALUE_LABEL Felicità -METER_HAPPINESS_VALUE_DESC -La felicità non svolge ancora nessuna funzione. - METER_FUEL_VALUE_LABEL Carburante METER_SHIELD_VALUE_LABEL Scudi -METER_SHIELD_VALUE_DESC -'''Il termine scudi combina tutti i tipi di campi di forza protettivi. Vengono rigenerati ad ogni turno e solitamente sono le prime protezioni che gli attacchi in arrivo devono superare. Ci sono due tipi distinti di scudi che funzionano in modo molto diverso: scudi nave e scudi barriera planetari. - - Scudi nave: Riducono il danno sostenuto da ciascun colpo tramite la resistenza agli attacchi della parte di scudo. Un'arma può penetrare uno scudo solo se il valore del suo danno è superiore alla resistenza dello scudo stesso. - -Scudi barriera planetari: Funzionano come una sorta di armatura planetaria. La loro forza è ridotta ad ogni colpo fino a quando non si esauriscono del tutto, ulteriori colpi potranno finalmente riuscire a superare le difese e danneggiare le strutture planetarie. Gli scudi barriera planetari rigenerano la loro forza ogni turno di un certo valore. - -Sia le navi spaziali che pianeti hanno un misuratore di Scudi.''' - METER_DEFENSE_VALUE_LABEL Difesa @@ -2158,11 +2041,6 @@ Il misuratore delle truppe su un pianeta rappresenta il potere delle forze di te METER_DETECTION_VALUE_LABEL Raggio di Rilevamento -METER_DETECTION_VALUE_DESC -'''Il Raggio di Rilevamento rappresenta il numero di uu che i sensori possono raggiungere. Un impero può vedere solo gli oggetti che hanno un valore di [[metertype METER_STEALTH]] inferiore o uguale alla sua [[encyclopedia DETECTION_TITLE]] e si trova nel Raggio di Rilevamento controllato da una nave spaziale o pianeta. - -Navi spaziali e pianeti hanno un misuratore di Raggio di Rilevamento. C'è l'opzione per visualizzare i cerchi del Raggio di Rilevamento nella sezione Mappa Galassia del menu Opzioni.''' - METER_STEALTH_VALUE_LABEL Occultamento @@ -2171,15 +2049,6 @@ METER_STEALTH_VALUE_DESC Navi spaziali, strutture, speciali, sistemi e pianeti hanno un misuratore di Occultamento.''' -METER_DETECTION_STRENGTH_VALUE_LABEL -Forza di Rilevamento - -METER_DETECTION_STRENGTH_VALUE_DESC -'''Il termine Forza di Rilevamento rappresenta il livello tecnologico dei sensori di un impero. Un impero può vedere solo gli oggetti che hanno un valore di [[metertype METER_STEALTH]] inferiore o uguale alla sua Forza di Rlevamento e sono all'interno di un [[metertype METER_DETECTION]] controllato da una nave spaziale o un pianeta. - -Gli imperi hanno un misuratore di Forza di Rilevamento.''' - - PEACE_TITLE Pace @@ -2195,9 +2064,6 @@ Gli avamposti sono stazioni non presidiate che possono essere fondate in luoghi GROWTH_FOCUS_TITLE Focus sulla Crescita -GROWTH_FOCUS_TEXT -Il focus sulla crescita rappresenta l'esportazione di materiali rari e sostanze che aiutano la crescita della popolazione di una determinata specie. È disponibile solo in determinate situazioni: su alcuni mondi e su pianeti con speciali caratteristiche di crescita. Un pianeta incentrato sulla crescita aumenta la popolazione massima solo su altri pianeti ad esso collegati da linee di [[enciclopedia SUPPLY_TITLE]]. Un pianeta natale focalizzato sulla crescita aiuta solo altri pianeti della stessa specie. Pianeti con caratteristiche speciali di crescita, fanno progredire lo sviluppo di una singola specie, per esempio, alcuni aiutano lo sviluppo delle specie organiche. - TRADE_FOCUS_TITLE Focus sul Commercio @@ -2490,9 +2356,6 @@ SITREP_SHIP_DAMAGED_AT_SYSTEM SITREP_UNOWNED_SHIP_DAMAGED_AT_SYSTEM La %shipdesign% è stata danneggiata da %system%. -SITREP_PLANET_BOMBARDED_AT_SYSTEM -%empire% pianeta %planet% è stato bombardato da %system%. - SITREP_GROUND_BATTLE Una battaglia si è verificata sul pianeta %planet%. @@ -2610,30 +2473,6 @@ Preferenze Ambientali: SP_HUMAN Umano -SP_HUMAN_DESC -'''Per lo più innocui. Tutte le statistiche medie per scopi di prova. -Preferiscono pianeti terrestri. -[[encyclopedia ORGANIC_SPECIES_TITLE]] -''' - -SP_SCYLIOR_GAMEPLAY_DESC -'''Tri-Tentacoli, nautiloidi acquatici. -Preferiscono pianeti oceanici. -[[encyclopedia ORGANIC_SPECIES_TITLE]] -''' - -SP_GYSACHE_GAMEPLAY_DESC -'''Codardi, bizzarri erbivori simil-pecore. -Preferiscono pianeti paludosi. -[[encyclopedia ORGANIC_SPECIES_TITLE]] -''' - -SP_CHATO_GAMEPLAY_DESC -'''Entità cristalline sessili che cavalcano gli animali Goshk. -Preferiscono pianeti tossici. -[[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]] -''' - ## ## Specials @@ -3171,9 +3010,6 @@ Singolarità Trascendentale GRO_PLANET_ECOL Ecologia Planetaria -GRO_PLANET_ECOL_DESC -L'agricoltura e la scienza medica aumentano drasticamente la capacità di sopravvivenza, inducendo, per un certo tempo, una crescita demografica esponenziale. Ma, prima o poi, nuovi limiti di crescita insorgono nella capacità di trasporto di un pianeta e nella interconnessione vitale che lo supporta. La comprensione dell'Ecologia e la sua interazione agli stress ambientali consente il sostenimento del sistema ed il godimento dei relativi vantaggi per le generazioni future. - GRO_GENETIC_ENG Ingegneria Genetica @@ -3192,11 +3028,6 @@ Medicina Genetica GRO_LIFECYCLE_MAN Controllo del Ciclo Vitale -GRO_LIFECYCLE_MAN_DESC -'''Attraverso manipolazioni chimiche e criogeniche, i processi di vita possono essere interrotti senza terminarli definitivamente. Gli esseri in questo stato sono conservati a tempo indeterminato, non consumano risorse e richiedono un po di stoccaggio - non vivono - nello spazio. Con questa tecnica, la capacità delle navi colonie può essere notevolmente aumentata. - -Gli organismi più complessi si sviluppano attraverso varie fasi fisiologiche durante una vita, se distintamente separati da metamorfosi oppure sfocate dal lento invecchiamento. In molti casi, una o più di queste fasi sono, almeno in un dato momento, più utili e desiderabili di altre. Con delicato controllo dei meccanismi ormonali o mentali, diventa possibile velocizzare o rallentare drasticamente ogni fase, permettendo agli individui completamente sviluppati e funzionali di essere prodotti in una frazione del tempo naturale e per quegli individui rimanere funzionale a tempo indeterminato diventando effettivamente immortali.''' - GRO_ADV_ECOMAN Ecomanipolazione Avanzata @@ -3566,15 +3397,21 @@ Scanner Omnidirezionale SPY_STEALTH_1 Smorzatore Elettromagnetico -SPY_STEALTH_1_DESC -Sblocca la parte della nave elettromagnetica e aumenta la velocità [[metertype METER_STEALTH]] Di tutti i pianeti e gli edifici di 20. Questo bonus non è cumulativo con quello di altre tecniche stealth. - SPY_STEALTH_2 Campo di Assorbimento SHP_DEUTERIUM_TANK Serbatoio di deuterio +SHP_DEUTERIUM_TANK_DESC +Piccolo aumento del raggio d'azione incrementando la capacità del serbatoio. + +SHP_ANTIMATTER_TANK +Serbatoio di Antimateria + +SHP_ANTIMATTER_TANK_DESC +Medio aumento del raggio d'azione incrementando la capacità del serbatoio. + SHP_ORG_HULL Scafo Organico @@ -3678,16 +3515,6 @@ Portale Spaziale ## Hull and ship part description templates ## -# %1% hull base starlane speed. -# %2% hull base fuel capacity. -# %3% hull base starlane speed. -# %4% hull base structure value. -HULL_DESC -'''Velocità Rotta Spaziale: %1% -Capacità Carburante: %2% -Velocità di Combattimento: %3% -Salute: %4%''' - # %1% ship part capacity (fuel, troops, colonists, fighters). PART_DESC_CAPACITY Capacità: %1% @@ -3696,16 +3523,6 @@ Capacità: %1% PART_DESC_STRENGTH Resistenza: %1% -# %1% ship part damage done (direct fire weapons). -# %2% number of shots done per attack. -PART_DESC_DIRECT_FIRE_STATS -'''Danno Attacco: %1% - - - -SR_WEAPON_2_DESC -Bassa potenza e massa - SR_ION_CANNON Cannone a Ioni @@ -3742,31 +3559,11 @@ Scanner Neutronico DT_DETECTOR_4 Sensori -FU_N_DIMENSIONAL_ENGINE_MATRIX_DESC -'''Aumenta la velocità di Starlane entro 15. - -La ricerca su N-Dimensionale nel subspazio ha determinato una matrice di motore più efficiente.''' - ## ## Ship parts ## -FU_SINGULARITY_ENGINE_CORE_DESC -'''Un cugino più piccolo della diga Hyperspace attrezzata per le navi, il motore singolo aumenta drasticamente la generazione di energia. Aumenta la velocità di Starlane entro 20. ''' - -FU_DEUTERIUM_TANK -Serbatoio Deuterio - -FU_DEUTERIUM_TANK_DESC -Piccolo aumento del raggio d'azione incrementando la capacità del serbatoio. Ingombrante e vulnerabile al fuoco nemico. - -FU_ANTIMATTER_TANK -Serbatoio Antimateria - -FU_ANTIMATTER_TANK_DESC -Medio aumento del raggio d'azione incrementando la capacità del serbatoio. Compatto e leggero. - ST_CLOAK_1 Smorzatore Elettromagnetico @@ -3885,7 +3682,6 @@ Orbitale ## Name lists ## -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/nl.txt b/default/stringtables/nl.txt index 4f08e708d77..e55322b5bd3 100644 --- a/default/stringtables/nl.txt +++ b/default/stringtables/nl.txt @@ -105,9 +105,6 @@ COMMAND_LINE_USAGE COMMAND_LINE_DEFAULT '''Standaard: ''' -OPTIONS_DB_HELP -Geef dit help bericht weer. - OPTIONS_DB_GENERATE_CONFIG_XML Gebruikt alle instellingen van ieder bestaand config.xml bestand en de instellingen die op de commandoregel gegeven worden om een config.xml bestand te genereren. Dit overschrijft het huidige config.xml bestand, als het bestaat. @@ -282,24 +279,6 @@ Indien waar, klikken op meerdere vloot knoppen zal meerdere vloot vensters gelij OPTIONS_DB_UI_WINDOW_QUICKCLOSE Sluit open vensters zoals de vloot venster en het systeem zijpaneel als u op de hoofdkaart rechts-klikt. -OPTIONS_DB_GAMESETUP_STARS -Het aantal sterren om in de melkweg te genereren. - -OPTIONS_DB_GAMESETUP_GALAXY_SHAPE -De vorm van de melkweg om te genereren. - -OPTIONS_DB_GAMESETUP_GALAXY_AGE -De leeftijd van de melkweg om te genereren. - -OPTIONS_DB_GAMESETUP_PLANET_DENSITY -Het aantal planeten per systeem in de melkweg om te genereren. - -OPTIONS_DB_GAMESETUP_STARLANE_FREQUENCY -Het aantal sterrenbanen in de melkweg om te genereren. - -OPTIONS_DB_GAMESETUP_SPECIALS_FREQUENCY -'''De frequentie van verschijning van specials in de melkweg om te genereren. ''' - OPTIONS_DB_UI_TECH_LAYOUT_HORZ_SPACING De horizontale afstand tussen technologiën in het onderzoeksvenster, in meerdere van de breedte voor iedere enkele theorie technologie. @@ -315,12 +294,6 @@ Zet het niveau op of boven welke log berichten uitgevoerd moeten worden (niveau OPTIONS_DB_STRINGTABLE_FILENAME Zet de taalspecifieke stringtabel bestandsnaam. -OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER -Indien waar, zal automatisch opgeslagen worden tijdens enkele speler spellen. - -OPTIONS_DB_AUTOSAVE_MULTIPLAYER -Indien waar, zal automatisch opgeslagen worden tijdens netwerkspellen. - OPTIONS_DB_AUTOSAVE_TURNS Zet het aantal beurten die moeten verstrijken tussen een auto-opslaan. @@ -335,15 +308,6 @@ Het volume (0 tot 255) waarop de muziek afgespeeld moet worden. FILE_DLG_FILES Bestand(en): -FILE_DLG_OVERWRITE_PROMPT -%1% bestaat.\nMag het overschreven worden? - -FILE_DLG_FILENAME_IS_A_DIRECTORY -"%1%"\nis een directory. - -FILE_DLG_FILE_DOES_NOT_EXIST -Bestand "%1%"\nbestaat niet. - FILE_DLG_DEVICE_IS_NOT_READY Apparaat is niet gereed. @@ -574,12 +538,6 @@ Snel-sluit vensters OPTIONS_MISC_UI Overige Gebruikersinterface Instellingen -OPTIONS_SINGLEPLAYER -Enkele speler - -OPTIONS_MULTIPLAYER -Netwerkspel - OPTIONS_AUTOSAVE_TURNS_BETWEEN Beurtem tussen een auto-opslaan @@ -1174,15 +1132,9 @@ SITREP_FLEET_ARRIVED_AT_DESTINATION ANCIENT_RUINS_SPECIAL Oude Ruïnes -ANCIENT_RUINS_SPECIAL_DESCRIPTION -Deze planeet bevat de ruïnes van een geavanceerde oude soort, die vergeten en niet in de geschiedenisboeken terug te vinden zijn, dit geeft een bonus in onderzoek. - ECCENTRIC_ORBIT_SPECIAL Eccentrische Omloopbaan -ECCENTRIC_ORBIT_SPECIAL_DESC -De omloopbaan van deze planeet is zeer eccentrisch. Er is een groot verschil tussen de afstand met zijn ster gedurende het jaar. De hoeveelheid energie die de planeet van de ster ontvangt verschilt sterk gedurende het jaar. Deze fluctuerende condities beperken infrastructuurontwikkeling, maar geven een voordelig platform voor onderzoek. - MINERALS_SPECIAL Minerale Rijkdom @@ -1351,10 +1303,6 @@ handel METER_POPULATION populatie -# Meter types -METER_HEALTH -gezondheid - # Meter types METER_INDUSTRY industrie @@ -1477,15 +1425,6 @@ planet omgeving DESC_VAR_STARTYPE ster type -DESC_VAR_MAXFUEL -max brandstof - -DESC_VAR_MAXSHIELD -max schild - -DESC_VAR_MAXDEFENSE -max defensie - DESC_VAR_TRADESTOCKPILE handel voorraad @@ -1540,30 +1479,15 @@ De structuren en hun functies in het brein worden bepaald. De electroschemische LRN_ALGO_ELEGANCE Algoritmische Elegantie -LRN_ALGO_ELEGANCE_DESC -Met toenemende complexiteit van gegevens analyse problemen, traditionele metingen van de algoritme efficientie worden steeds minder bruikbaar door de beperkingen van onomkeerbare complexiteit. Op dit punt worden andere meetmethoden van algoritmische vorm en functie significant; esthetisch en metaforisch, de elegantie van de oplossing moet geoptimaliseerd worden. - LRN_TRANSLING_THT Translinguïstiek -LRN_TRANSLING_THT_DESC -Een minder intellect worstelt met, of accepteert de beperkingen van de taalt die ze geleerd hebben. Toerijkende intellectuelen bereiken en voelen een beperking door de concepten om dingen te verwoorden. Echt grote intellectuelen kunnen zich ontdoen van de ketens van de taal, vormen en analyseren gedachten over de rand van een verhevenheid. Maar mindere grote intellectuelen zullen geisoleerd en onbeduidend achterblijven, want zonder taal om gedachten over te brengen, hoe moeten zij hun inzichten delen? - -LRN_PSIONICS_DESC -Door diepe introspectie of kunstmatige verbeteringen kan het brein eigenschappen ontwikkelen om directe interactie met het universum eromheen uit te voeren, daarmee beperkingen van het fysieke lichaam te omzijlen. Krachten als telepathie, empathie, helderziendheid, voorkennis, telekinese en psycho-energetica vervangen hun normaal biologische of technologische alternatieven. Toepassing van deze krachten, inclusief hersenspoelen, personaliteitsverandering en bezetenheid hebben diepe implicaties voor relaties tussen getallenteerde en niet-getallenteerde wezens. - LRN_ARTIF_MINDS Kunstmatig Verstand -LRN_ARTIF_MINDS_DESC -Terwijl traditionele computers een bijna onmeetbare intelligentie en calculerend vermogen hebben, missen ze de essentiele kwaliteiten van het begrip dat ze bestaan, bewustzijn en waarneming. Met de ontwikkeling van echte kunstmatig verstand kunnen deze kwaliteiten gesynthetiseerd, aangepast en veranderd worden. Deze onderzoeken openen nieuwe onderzoekswegen in congnitieve wetenschappen en nieuwe metaparadigma's. - LRN_XENOARCH Xenoarchaeologie -LRN_XENOARCH_DESC -De hedendaagse rijken, rassen en enkele-ster beschavingen zijn niet de eerste of enige wezens die in dit universum hebben geleefd. Overblijfselen, ruines en geruchten van oude beschavingen kunnen op verlaten planeten, levenloze astroiden of zwervend door de open ruimte gevonden worden. Het vinden en ontcijferen van dergelijke aanwijzingen heeft grote potentie om geheimen te onthullen, lessen te leren of waarschuwingen over vergeten kennis te geven. - LRN_GRAVITONICS Gravitoniek @@ -1579,9 +1503,6 @@ Vroege, naïve theoriën beschreven onderdelen van de vier fundamentele krachten LRN_FORCE_FIELD Krachtveld Harmoniciteit -LRN_FORCE_FIELD_DESC -Net zoals met Fourier analyse op geluid, kunnen elektromagnetisme, sterke en zwakke krachten uitgedrukt worden in harmonische superposities van quantumgolven van krachtdragende deeltjes. Door deze harmonische golven selectief te versterken, kunnen de krachten gemanilpuleerd worden om als schild, ondersteuning, opslag of als wapen gebruikt te worden. - LRN_MIND_VOID Gedachte van het Lege @@ -1603,9 +1524,6 @@ Vroege superstring theoretici spraken over 10, 11 of 26 dimensionele universums, GRO_PLANET_ECOL Planetaire Ecologie -GRO_PLANET_ECOL_DESC -Agrocultuur en medische wetenschappen kunnen overleving drastisch vergroten, waardoor de populatiegroei gedurende een tijd hyperexponentieel kan groeien. Uiteindelijk zullen nieuwe limieten voor de groeicapaciteit van een planeet en het interverbonden web van leven dat het ondersteunt verschijnen. Begrip van natuurlijke ecologie en zijn interactie met stressfactoren laten het systeem ondersteunen en de nakomende generaties er voordeel uit halen. - GRO_GENETIC_ENG Genetische Modificatie @@ -1627,39 +1545,21 @@ Traditionele Genetische Modificatie past de genetische code aan, implementeert d GRO_LIFECYCLE_MAN Levenscyclus Manipulatie -GRO_LIFECYCLE_MAN_DESC -Meeste complexe organismen gaan door een proces door verschillende fysiologische stadia gedurende een leven, zowel via duidelijk gescheiden metamorphoses als vage, continue verouderingsprocessen. In veel gevallen is een of meerdere van deze stadiums meer bruikbaar en gewenst dan andere. Door nauwkeurige controle van hormonale en mentale mechanismen wordt het mogelijk om de snelheid van elk stadium te versnellen of vertragen, waardoor volledig ontwikkelde en functionele individuen geproduceerd kunnen worden in een fractie van de natuurlijke tijdsspan. Die individuen blijven daarnaast ook oneindig functioneel, waardoor ze zo goed als onsterfelijk worden. - GRO_XENO_GENETICS Xenologische Genetica -GRO_XENO_GENETICS_DESC -Vroege genetica is beperkt door de beschikbaarheid van monsters van natuurlijk voorkomende organismen om modificaties op te baseren. Daarnaast zijn deze modificaties beperkt binnen de limieten van de fysische mechanismen voor de opslag, aanpassing en expressie van de genetische code in ontwikkelde individuen. Door equivalente systemen in compleet verschillende ecosystemen en organismen te onderzoeken kan meer inzicht en openbaringen verkregen verkregen worden, waardoor nieuwe ontwikkelingen in meer bekende genetische systemen versneld worden. - GRO_NANOTECH_MED Nanotech Medicijn -GRO_NANOTECH_MED_DESC -Terwijl genetische aanpassingen de effecten van ongewenste of schadelijke mutaties of onjuiste biochemische balansen kan repareren of ongedaan maken, kunnen grote fysische trauma's en niet-genetische aangeboren afwijkingen alleen met fysische aanpassingen behandeld worden. Nanotechnologie kan gebruikt worden om dit te bereiken, met twee voordelen: Aanpassingen kunnen zonder invasieve operaties en bijbehordende secundaire schade en risico's gedaan worden, en de tijd voor de behandeling is sterk gereduceerd aangezien de gereedschappen om de taak te doen altijd op de juiste plaats en klaar voor gebruik zijn. - PRO_MICROGRAV_MAN Microzwaartekracht Productie -PRO_MICROGRAV_MAN_DESC -De microzwaartekracht van een lage omloopbaan rond een planeet biedt veel potentie voor nieuwe productiehal ontwerpen, apparatuur ontwerpen en technieken die niet mogelijk zijn op een planeetoppervlakte. Vaste stoffen kunnen met minimale ondersteuning op hun plaats gehouden worden. Vloeistoffen en gassen verplaatsen zich vrij, gevormd door hun oppervlaktespanning, tenzij ze anders gemanipuleerd worden. Fabrieksapparatuur kan optimaal in 3 dimensies geplaatst worden. Indien op de juiste manier toegepast kunnen deze condities de efficientie en het productieproces sterk vergroten. - PRO_ROBOTIC_PROD Robotische Productie -PRO_ROBOTIC_PROD_DESC -Hoewel de het initieel ontwerp hoge eisen stelt en de initiele implementatie van de fabrieken nog steeds supervisie vereisen, zal de vervanging van het bedieningspersoneel van het feitelijke productieproces vele bottlenecks elimineren. Robots werken continue, zonder doorlopende eisen voor meer economische compensatie. Robots hebben veel meer tolerantie voor gevaarlijke omgevingscondities in de fabriek en vereisen geen additionele leefruimte buiten de werkplek. Robots vervullen hun taak foutloos, of ten minste zo goed als ze worden opgedragen. - PRO_FUSION_GEN Fusie Generatie -PRO_FUSION_GEN_DESC -Explosieve of ongecontrolleerde thermonucleaire reacties zijn relatief eenvoudig te maken op de schaal van kleine tactische kernkoppen tot supergigantische stellaire ovens. Controlleerbare, stabiele en praktische energieproductie van dergelijke reacties is iets wat moeilijker. Het proces blijft aantrekkelijk doordat er een bijna-onbeperkte aanvoer van energie ontstaat die emissie-vrij verloopt. - SHP_GAL_EXPLO Galactische Ontdekkingsreizen @@ -1689,12 +1589,6 @@ Vergroot Planeet Populatie GRO_BIOTERROR Bioterreur Faciliteiten -SPY_DETECT_2_DESC -Ontsluit Actieve Radar scheepsonderdeel. - -SHP_DEUTERIUM_TANK_DESC -Ontsluit Deuterium Tank scheepsonderdeel. - ## ## Technology refinement @@ -1770,6 +1664,5 @@ Ontsluit Deuterium Tank scheepsonderdeel. ## Name lists ## -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/pl.txt b/default/stringtables/pl.txt index 0808bb3c733..7e3aab39991 100644 --- a/default/stringtables/pl.txt +++ b/default/stringtables/pl.txt @@ -131,9 +131,6 @@ COMMAND_LINE_USAGE COMMAND_LINE_DEFAULT '''Domyślne: ''' -OPTIONS_DB_HELP -Wyświetl ten tekst pomocniczy. - OPTIONS_DB_GENERATE_CONFIG_XML Używa wszystkich ustawień z obecnego pliku config.xml o ile taki istnieje oraz tych podanych z linii komend do wygenerowania nowego pliku config.xml . Ta operacja nadpisze obecny plik config.xml , jeśli istnieje. @@ -374,33 +371,12 @@ Ustala rozmiar największych renderowanych, obracających się planet w panelu b OPTIONS_DB_UI_SIDEPANEL_PLANET_MIN_DIAMETER Ustala rozmiar najmniejszych renderowanych, obracających się planet w panelu bocznym. -OPTIONS_DB_GAMESETUP_STARS -Ilość gwiazd w galaktyce do wygenerowania. - -OPTIONS_DB_GAMESETUP_GALAXY_SHAPE -Kształt galaktyki do wygenerowania. - -OPTIONS_DB_GAMESETUP_GALAXY_AGE -Wiek galaktyki do wygenerowania. - -OPTIONS_DB_GAMESETUP_PLANET_DENSITY -Ilość planet w każdym systemie w galaktyce do wygenerowania. - -OPTIONS_DB_GAMESETUP_STARLANE_FREQUENCY -Ilość szlaków gwiezdnych w galaktyce do wygenerowania. - -OPTIONS_DB_GAMESETUP_SPECIALS_FREQUENCY -Częstotliwość pojawiania się elementów specjalnych w galaktyce do wygenerowania. - OPTIONS_DB_GAMESETUP_EMPIRE_NAME Nazwa dla Twojego imperium w grze. OPTIONS_DB_GAMESETUP_EMPIRE_COLOR Kolor Twojego imperium w grze. -OPTIONS_DB_GAMESETUP_NUM_AI_PLAYERS -Ilość przeciwników SI w grze. - OPTIONS_DB_UI_TECH_LAYOUT_HORZ_SPACING Poziomy odstęp pomiędzy technologiami na ekranie technologii, wyrażony w wielokrotności szerokości jednej teorii technologicznej. @@ -416,12 +392,6 @@ Ustala, którego poziomu lub wyższego wiadomości będą logowane (poziomy w ko OPTIONS_DB_STRINGTABLE_FILENAME Ustala nazwę pliku językowego. -OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER -Jeśli włączone, gra będzie automatycznie zapisywana podczas gry jednoosobowej. - -OPTIONS_DB_AUTOSAVE_MULTIPLAYER -Jeśli włączone, gra będzie automatycznie zapisywana podczas gry wieloosobowej. - OPTIONS_DB_AUTOSAVE_TURNS Ustala ilość tur, które powinny minąć pomiędzy autozapisami gry. @@ -439,21 +409,6 @@ Plik(i): FILE_DLG_FILE_TYPES Typ(y): -FILE_DLG_OVERWRITE_PROMPT -'''%1% już istnieje. - -Nadpisać?''' - -FILE_DLG_FILENAME_IS_A_DIRECTORY -'''"%1%" - -jest katalogiem.''' - -FILE_DLG_FILE_DOES_NOT_EXIST -'''Plik "%1%" - -nie istnieje.''' - FILE_DLG_DEVICE_IS_NOT_READY Urządzenie nie jest gotowe. @@ -683,10 +638,6 @@ Pliki zapisanych gier OPTIONS_TITLE Opcje -OPTIONS_MULTIPLAYER -Mulitplayer - - ## ## Main map window ## @@ -934,9 +885,6 @@ Wcześniejsze naiwne teorie opisywały tą teorię jako synteza czterech podstaw LRN_FORCE_FIELD Harmoniczność pól siłowych -LRN_FORCE_FIELD_DESC -Podobnie jak w przypadku analizy Fouriera fal dźwiękowych, elektromagnetyzm, a także silne i słabe siły mogą być wyrażona jako harmoniczne fale stojące superpozycji kwantowej pola sił. Poprzez selektywne rozszerzenie harmoniczności tych fal, siły te mogą być dowolnie kontrolowane zapewniając w określonych warunkach ekranowanie, wzmocnienie, osłonę lub podparcie. - LRN_MIND_VOID_DESC Nie jesteśmy sami w kosmosie... ale dlaczego nasze poszukiwania są tak ograniczone? Wiele kultur w mitach, legendach lub poprzez gorącą wiarę wskazuje na swego rodzaju wyższą świadomość. Można kochać lub walczyć z ideą Boga czy innego obserwatora, jest jednak oczywiste, że coś - być może wszechświat na pewnym poziomie - jest żywe, świadome, obserwujące. @@ -951,6 +899,17 @@ Czym jest "teraz", "przyszłość", "przeszłość"? Paradoks bliźniąt w szcze ## Technology application ## +SHP_DEUTERIUM_TANK +Zbiornik deuterium + +SHP_DEUTERIUM_TANK_DESC +Nieznacznie zwiększa zasięg okrętu powiększając jego zasoby paliwa. + +SHP_ANTIMATTER_TANK +Zbiornik antymaterii + +SHP_ANTIMATTER_TANK_DESC +Zwiększa zasięg okrętu powiększając jego zasoby paliwa. ## ## Technology refinement @@ -964,12 +923,6 @@ Czym jest "teraz", "przyszłość", "przeszłość"? Paradoks bliźniąt w szcze BLD_OBSERVATORY Obserwatorium -BLD_OBSERVATORY_DESC -'''Naturalne rozszerzenie teleskopów przemysłowych i informacyjnych, wyposażające je w wojskowe algorytmy umożliwiające wykrywanie obiektów na dużych odległościach. Planeta, na której wybudowano obserwatorium wysyła w kosmos potężne impulsy wykrywające. Z tego powodu ukrycie takiej planety staje się bardzo trudne. Z drugiej strony, impulsy te mogą zostać odbite przez wszystkie obiekty znajdujące się we względnej bliskości planety, co bardzo ułatwia wykrywanie takich obiektów z zaprzyjaźnionych planet znajdujących się w zasięgu obserwatorium. - -Dodaje +10 do współczynnika Wykrywanie na wszystkich zaprzyjaźnionych planetach znajdujących się w odległości nie większej niż 200. -Na planecie, na której wybudowano obserwatorium współczynnik Ukrywanie zmniejsza się o 10.''' - ## ## Hull and ship part description templates @@ -998,48 +951,21 @@ Radar aktywny DT_DETECTOR_2_DESC Średnia skuteczność wykrywania, niestety bardzo ułatwia też wykrycie okręty, na którym jest zamontowany. -FU_DEUTERIUM_TANK -Zbiornik deuterium - -FU_DEUTERIUM_TANK_DESC -Nieznacznie zwiększa zasięg okrętu powiększając jego zasoby paliwa. Masywny i wrażliwy na ogień przeciwnika. - -FU_ANTIMATTER_TANK -Zbiornik antymaterii - -FU_ANTIMATTER_TANK_DESC -Zwiększa zasięg okrętu powiększając jego zasoby paliwa. Zwarty, o niewielkiem masie. - ST_CLOAK_1 Tłumik elektromagnetyczny -ST_CLOAK_1_DESC -Zmniejsza wykrywalność okrętu, na którym został zamontowany, poprzez tłumienie emisji elektromagnetycznych generowanych przez systemy okrętu. Może skompensować tylko niewielką ilość takich emisji, dlatego elementy generujące wiele szumów nie będą przez niego skutecznie maskowane. - SH_DEFENSE_GRID Siatka ochronna -SH_DEFENSE_GRID_DESC -Słaba ochrona, ale nie dodaje masy. - SH_DEFLECTOR Osłony -SH_DEFLECTOR_DESC -Mocna ochrona przy zachowaniu niewielkiej masy. - CO_COLONY_POD Kapsuła kolonizacyjna -CO_COLONY_POD_DESC -Umożliwia osadnikom przetrwanie podróży do nowej planety. Umożliwia okrętom kolonizowanie nowych światów. - CO_SUSPEND_ANIM_POD Krioniczna kapsuła kolonizacyjna. -CO_SUSPEND_ANIM_POD_DESC -W trakcie podróży kolonizacyjnej osadnicy utrzymywani są w stanie obniżonego metabolizmu, dzięki czemu nie ma konieczności ich żywienia, a to z kolei zwiększa liczbę osadników przenoszonych przez jeden okręt. - ## ## Ship hulls @@ -1095,6 +1021,5 @@ W trakcie podróży kolonizacyjnej osadnicy utrzymywani są w stanie obniżonego ## Name lists ## -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/ru.txt b/default/stringtables/ru.txt index 4b55a327330..f094a845f25 100644 --- a/default/stringtables/ru.txt +++ b/default/stringtables/ru.txt @@ -6,6 +6,8 @@ Russian # * andreiwsa # * Max Koloshnitsyn # * cjckem at gmail.com +# * nikqz nikqz@mail.ru +# * Bestary (lynx.bestary@gmail.com) # # Notes: to avoid potential conflict with functional keys in other files, do not # make any stringtable keys beginning with "FUNCTIONAL_". @@ -24,6 +26,9 @@ Russian ## Common phrases ## +OK +ОК + APPLY Применить @@ -51,6 +56,8 @@ YES NO Нет +# Used as a placeholder for unexpanded content in the combat log window. + EMPIRE Империя @@ -66,11 +73,20 @@ AI_PLAYER ADD_AI_PLAYER Добавить компьютер +OBSERVER +Наблюдатель + MODERATOR Модератор NO_PLAYER -Никто +Нет роли + +NO_PLAYERS +Нет игроков + +NO_EMPIRE +Нет империи DROP_PLAYER Сбросить @@ -78,6 +94,9 @@ DROP_PLAYER ALL Все +NONE +Нет + RENAME Переименовать @@ -99,6 +118,27 @@ BACK LAST В конец +PAUSE +Пауза + +PAUSED +На паузе + +RESUME +Продолжить + +DIFFICULTY +Сложность + +X +Х координата + +Y +Y координата + +INVALID_POSITION +Недопустимое положение + # Name for a newly created general purpose fleet. # %1% represents a unique number. NEW_FLEET_NAME @@ -109,11 +149,55 @@ NEW_FLEET_NAME NEW_FLEET_NAME_NO_NUMBER Флот +# Name for a newly created monster-only fleet. +# %1% represents a unique number. +NEW_MONSTER_FLEET_NAME +Стая %1% + +# Name for a newly created colony-only fleet. +# %1% represents a unique number. +NEW_COLONY_FLEET_NAME +Поселенческий флот %1% + +# Name for a newly created reconnaissance-only fleet. +# %1% represents a unique number. +NEW_RECON_FLEET_NAME +Разведывательный флот %1% + +# Name for a newly created troop carrier only fleet. +# %1% represents a unique number. +NEW_TROOP_FLEET_NAME +Десантный флот %1% + +# Name for a newly created bomber only fleet. +# %1% represents a unique number. +NEW_BOMBARD_FLEET_NAME +Флот бомбардировщиков %1% + +# Name for a newly created combat group group fleet. This includes battleships, +# bombers and troop carriers. +# %1% represents a unique number. +NEW_BATTLE_FLEET_NAME +Военный флот %1% + +# Name for a newly created planet. +# Suffix bears some explanation: +# - Planets are grouped for asteroids and non-asteroids. +# - Suffix is a roman numeral, with additional rules for asteroids. +# - The roman numeral is a rank for proximity to the center of the system, +# in relation to other planets in the same group. +# - For asteroids, the suffix starts with a localized NEW_ASTEROIDS_SUFFIX. +# If any other asteroids are in the system, the roman numeral is appended. +# %1% name of the system this planet is created in. +# %2% suffix for this planet + +# The label pre-pended to a new asteroids naming suffix + EMPTY_SPACE -Открытый космос +Глубокий космос UNEXPLORED_REGION -Неизвестно +Неисследованная область UNEXPLORED_SYSTEM Неисследованная система @@ -125,16 +209,19 @@ INTERCEPTOR Перехватчик DEFAULT_EMPIRE_NAME -Империя Земли +Империя землян DEFAULT_PLAYER_NAME Игрок +MONSTER +Монстр + PASSED -(ЕСТЬ) +(ПРОШЕЛ) FAILED -(НЕТ) +(НЕУДАЧА) ALL_OF Условия: @@ -146,281 +233,593 @@ DUMP Дамп данных для отладки UNOWNED -'''Бесхозный ''' +Ничей + +NOWHERE +Не может быть произведено + +# Prefix to use for menu items that will open a pedia entry +POPUP_MENU_PEDIA_PREFIX +'''Помощь: ''' + +GENERAL +Основное +TEST +Тест + +BALANCE +Балансировка + +CONTENT +Галактика + +MULTIPLAYER +Мультиплеер ## ## Major errors ## +ERROR_SOUND_INITIALIZATION_FAILED +'''Не удалось инициализировать аудиосистему OpenAL. +Проверьте лог-файл для получения более подробной информации. +''' + +# Used as a prefix for the FORMAT_LIST_[1-MANY]_ITEMS translation entries. +FORMAT_LIST_DEFAULT_PLURAL_HEADER +Имеются: + +# Used as a prefix for the FORMAT_LIST_0_ITEMS translation entry. +FORMAT_LIST_DEFAULT_SINGLE_HEADER +Есть один: + +FORMAT_LIST_DEFAULT_EMPTY_HEADER +Отсутствует. + +# Used as a prefix for the FORMAT_LIST_[0-MANY]_ITEMS translation entries. +FORMAT_LIST_DEFAULT_DUAL_HEADER +Имеются: + +FORMAT_LIST_1_ITEMS +%1% %2% + +FORMAT_LIST_2_ITEMS +%1% %2% и %3% + +FORMAT_LIST_3_ITEMS +%1% %2%, %3% и %4% + +FORMAT_LIST_4_ITEMS +%1% %2%, %3%, %4% и %5% + +FORMAT_LIST_5_ITEMS +%1% %2%, %3%, %4%, %5% и %6% + +FORMAT_LIST_6_ITEMS +%1% %2%, %3%, %4%, %5%, %6% и %7% + +FORMAT_LIST_7_ITEMS +%1% %2%, %3%, %4%, %5%, %6%, %7% и %8% + +FORMAT_LIST_8_ITEMS +%1% %2%, %3%, %4%, %5%, %6%, %7%, %8% и %9% + +FORMAT_LIST_9_ITEMS +%1% %2%, %3%, %4%, %5%, %6%, %7%, %8%, %9% и %10% + +FORMAT_LIST_10_ITEMS +%1% %2%, %3%, %4%, %5%, %6%, %7%, %8%, %9%, %10% и %11% + +## +## Build Projects +## + +PROJECT_BT_STOCKPILE +Поставка ресурсов + +PROJECT_BT_STOCKPILE_SHORT_DESC +Переносит очки промышленности в резервы империи. + +PROJECT_BT_STOCKPILE_DESC +'''Очки производства, переданные с помощью [[PROJECT_BT_STOCKPILE]], добавляются к имперским резервам. Можно указать сумму перевода и как много раз должна повторяться передача. + +Только очки промышленности, поставляемые из групп снабжения (и не включенные в имперские резервы) могут быть направлены в [[PROJECT_BT_STOCKPILE]]. + +Все не потраченные очки ресурсов переходят в имперские резервы (но не более верхнего предела), не важно, есть ли [[PROJECT_BT_STOCKPILE]] в очереди на производство, или нет - она лишь гарантирует отправку необходимого количества ресурсов в резервы, когда все очки производства полностью расходуются очередью производства, без излишков. + +Пример: империя хочет перенести очки промышленности из главной группы снабжения в одну из второстепенных, где эти очки не производятся. Для этого сперва очки нужно запасти в основной группе снабжения, где они образуются. В основной группе производится 100 очков промышленности за ход, а верхний предел резервов - 10. Производственная очередь требует 120 очков за ход, следовательно все имеющиеся у империи мощности будут уходить на неё. +Чтобы иметь возможность передать что-либо нуждающейся группе, нужно внести в очередь на производство 10х [[PROJECT_BT_STOCKPILE]], поместив эти элементы в самый верх. Они должны производиться в основной группе снабжения, где наличествуют очки производства. +Так как [[PROJECT_BT_STOCKPILE]] расположена выше остальных элементов очереди, её финансирование гарантируется, следовательно резервы империи будут точно пополняться на 10 очков производства за ход. Для стабильной, постоянной передачи ресурсов, число повторений для [[PROJECT_BT_STOCKPILE]] устанавливается 99.''' ## ## Predefined Ship Designs (located in default/scripting/ship_designs/) ## +SD_CARRIER +Лёгкий авианосец + +SD_CARRIER_DESC +Авианосец, предназначенный для защиты других вооруженных судов. Способен запускать эскадрильи перехватчиков. + +SD_CARRIER_2 +Авианосец флота + +SD_CARRIER_2_DESC +Авианосец, предназначенный для наступательных действий флота. Оснащён эскадрильями перехватчиков и электромагнитным орудием. + SD_SCOUT Разведчик SD_SCOUT_DESC -Маленький дешевый безоружный челнок, спроектированный для разведки. +Маленький, дешевый, безоружный корабль, спроектированный для разведки. [[SHIPDESIGN_DETECTION_RESEARCH_TIPS]] [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_SCOUT_2 +Радиолокационный разведчик + +SD_SCOUT_2_DESC +Маленький, дешевый, безоружный корабль, оснащенный улучшенным [[metertype METER_DETECTION]], предназначенный для разведки и исследований. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_SCOUT_3 +Сканирующий разведчик + +SD_SCOUT_3_DESC +Маленький, дешевый, безоружный корабль, оснащенный улучшенным [[metertype METER_DETECTION]], предназначенный для разведки и исследований. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_SCOUT_4 +Сенсорный разведчик + +SD_SCOUT_4_DESC +Маленький, дешевый, безоружный корабль, оснащенный улучшенным [[metertype METER_DETECTION]], предназначенный для разведки и исследований. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_ENG_SCOUT +Энергетический разведчик + +SD_ENG_SCOUT_DESC +Маленький и быстрый безоружный корабль, предназначенный для разведки и исследований. [[SHIPDESIGN_DETECTION_RESEARCH_TIPS]] [[BLD_SHIPYARD_BASE_ENRG_COMP_THREE_STARS_REQUIRED]] + +SD_SMALL_MARK_1 +Корвет M + +SD_SMALL_MARK1_DESC +Маленький, дешёвый корабль с электромагнитным орудием. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_MARK_1 +Фрегат Ms SD_MARK1_DESC -Простой патрульный фрегат с электро-магнитным орудием. +Фрегат с электромагнитным орудием. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_LARGE_MARK_1 +Крейсер Ms + +SD_LARGE_MARK1_DESC +Крейсер, оборудованный для длительной независимой работы, с улучшенными орудиями и бронёй. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_LARGE_MARK_2 +Крейсер Lz + +SD_LARGE_MARK2_DESC +Крейсер, оборудованный для длительной независимой работы. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_LARGE_MARK_3 +Эсминец Ms + +SD_LARGE_MARK3_DESC +Эсминец, спроектированный для нужд флота. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_LARGE_MARK_4 +Эсминец Lz + +SD_LARGE_MARK4_DESC +Эсминец, спроектированный для нужд флота, с улучшенными лазерами и бронёй. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_ROBOTIC_OUTPOST +Роботизированный аванпост + +SD_ROBOTIC_OUTPOST_DESC +Предназначен для создания аванпостов в отдалённых системах. + +SD_ROBO_FLUX_SCOUT +Поточный разведчик + +SD_ROBO_FLUX_SCOUT_DESC +Быстрый разведывательный корабль предназначенный для скрытного передвижения. + +SD_ROBO_FLUX_TROOPS +Поточный десантный корабль + +SD_ROBO_FLUX_TROOPS_DESC +Быстрый поточный десантный корабль + +SD_ROBO_FLUX_TROOPS_HVY +Тяжелый поточный десантный корабль + +SD_ROBO_FLUX_TROOPS_HVY_DESC +Очень быстрый поточный десантный корабль + +SD_ROBOTIC1 +Роботизированный крейсер Mfs + +SD_ROBOTIC1_DESC +Крейсер, спроектированный для нужд флота и самостоятельных действий. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] + +SD_ROBOTIC2 +Эсминец Mfs DG + +SD_ROBOTIC2_DESC +Роботизированный корабль, спроектированный для взаимодействия с флотом. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] + +SD_ROBOTIC3 +Роботизированный крейсер Lzi + +SD_ROBOTIC3_DESC +Роботизированный крейсер, предназначенный для длительных самостоятельных действий. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] -SD_MARK2_DESC -Улучшенный патрульный фрегат с электро-магнитным орудием. +SD_ROBOTIC_CARRIER1 +Авианосец флота MBs -SD_MARK3_DESC -Простой патрульный фрегат с лазерным орудием. +SD_ROBOTIC_CARRIER1_DESC +Ударный авианосец, спроектированный для нужд флота. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] -SD_MARK4_DESC -Улучшенный патрульный фрегат с лазерным орудием. +SD_ROBOTIC_CARRIER2 +Авианосец флота LBz + +SD_ROBOTIC_CARRIER2_DESC +Ударный авианосец, спроектированный для нужд флота. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] + +SD_ROBOTIC_CARRIER3 +Лёгкий авианосец MIs + +SD_ROBOTIC_CARRIER3_DESC +Лёгкий авианосец, предназначенный для охраны крупных кораблей, транспортов и десантных судов во время их перемещения или стоянок. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]] + +SD_GRAVITATING1 +Линкор Pfd DS SD_GRAVITATING1_DESC -Огромный корабль, вооруженный и бронированный по последнему слову техники. Стоит соответственно. +Гравитационный корабль, предназначенный для масштабных действий флота. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED]] + +SD_GRAVITATING2 +Линкор Dfx BS + +SD_GRAVITATING2_DESC +Большой броненосный боевой корабль, вооруженный и защищенный последними технологиями. Стоит соответственно. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED]] + +SD_ROBO_TITAN1 +Дредноут + +SD_ROBO_TITAN1_DESC +Большой современный корабль, вооружённый и защищённый по последнему слову техники. Стоит соответственно. [[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED]] + +SD_AST_1 +Каменный эсминец (L) + +SD_AST_1_DESC +Астероидный корпус, спроектированный для нужд флота. [[BLD_SHIPYARD_BASE_AST_REQUIRED]] SD_COLONY_SHIP -Корабль с поселенцами +Колониальный корабль SD_COLONY_SHIP_DESC -Безоружный корабль, везущий миллионы граждан для создания нового поселения. +Безоружный корабль, [[SHIPDESIGN_MILLIONS_COLONIZATION_CAPACITY]]. [[MIN_POPULATION_THREE_REQUIRED]], в системе с [[buildingtype BLD_SHIPYARD_BASE]]. + +SD_CRYONIC_COLONY_SHIP +Колониальный корабль с модулем криозаморозки + +SD_CRYONIC_COLONY_SHIP_DESC +Безоружный корабль, [[SHIPDESIGN_MANY_MILLIONS_COLONIZATION_CAPACITY]]. [[MIN_POPULATION_THREE_REQUIRED]], в системе с [[buildingtype BLD_SHIPYARD_BASE]]. SD_OUTPOST_SHIP Аванпост SD_OUTPOST_SHIP_DESC -Безоружный корабль, создающий аванпост в непригодных для жилья местах. +Безоружный корабль, [[SHIPDESIGN_OUTPOSTS_CAPACITY]]. [[BLD_SHIPYARD_BASE_REQUIRED]] + +SD_ORG_OUTPOST_SHIP +Органический аванпост + +SD_ORG_OUTPOST_SHIP_DESC +Невооруженный органический корабль, [[SHIPDESIGN_OUTPOSTS_CAPACITY]]. [[BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED]] SD_COLONY_BASE -База поселенцев +База для колонии SD_COLONY_BASE_DESC -Безоружный корабль, может создать поселение только в системе, где он был построен. +Безоружный корабль, способный создавать [[SHIPDESIGN_COLONIZATION_CAPACITY_SAME_SYSTEM]]. [[MIN_POPULATION_THREE_REQUIRED]]. [[SHIPDESIGN_NO_TRAVEL]] + +SD_CRYONIC_COLONY_BASE +Крионическая база для колонии + +SD_CRYONIC_COLONY_BASE_DESC +Безоружный корабль, способный создавать большие [[SHIPDESIGN_COLONIZATION_CAPACITY_SAME_SYSTEM]]. [[MIN_POPULATION_THREE_REQUIRED]]. [[SHIPDESIGN_NO_TRAVEL]] SD_OUTPOST_BASE Аванпост (база) SD_OUTPOST_BASE_DESC -Безоружный корабль, создающий аванпост только в системе, где он был построен. +Безоружный корабль, который [[SHIPDESIGN_OUTPOSTS_CAPACITY]], только в той системе, где был построен. [[SHIPDESIGN_NO_TRAVEL]] + +SD_BASE_DECOY +Спутник связи + +SD_BASE_DECOY_DESC +Невооруженный спутник, предназначенный для активации планетарной обороны против вражеских судов, находящихся в пассивном режиме. Планетарная оборона не активируется самостоятельно при наличии пассивных врагов, например разведчиков. Тактическая задача спутника - активировать битву. Но, не обладая собственным вооружением, спутник полностью зависит от планетарной системы обороны и легко может быть уничтожен. Может быть построен на любой колонизированной планете, для чего не требуется [[buildingtype BLD_SHIPYARD_BASE]]. + +SD_TROOP_DROP +Десантный отряд + +SD_TROOP_DROP_DESC +Переносит отряд [[SHIPDESIGN_PLANET_INVASION]]. [[SHIPDESIGN_NO_TRAVEL]] + +SD_TROOP_DROP_HVY +Десантные войска + +SD_TROOP_DROP_HVY_DESC +Переносит продвинутый отряд [[SHIPDESIGN_PLANET_INVASION]]. [[SHIPDESIGN_NO_TRAVEL]] + +SD_SMALL_TROOP_SHIP +Малый десантный корабль + +SD_SMALL_TROOP_SHIP_DESC +Несет на борту отряд [[SHIPDESIGN_PLANET_INVASION]]. [[BLD_SHIPYARD_BASE_REQUIRED]] SD_TROOP_SHIP Десантный корабль SD_TROOP_SHIP_DESC -Несет на борту бригаду [[metertype METER_TROOPS]] и соответствующего оборудования для вторжения на чужие планеты. +Несет на борту 3 отряда [[SHIPDESIGN_PLANET_INVASION]]. [[BLD_SHIPYARD_BASE_REQUIRED]] SD_DRAGON_TOOTH Зуб дракона SD_DRAGON_TOOTH_DESC -Древний корабль с мощным вооружением и защитной системой. - +Древний корабль с мощным вооружением и защитной системой. [[BLD_SHIPYARD_BASE_REQUIRED]] ## ## Monsters ## SM_MONSTER -Злобный монстр +Монстр SM_KRILL_1 Маленький рой космического планктона SM_KRILL_1_DESC -Космический планктон - это маленькие непонятные насекомые, обитающие в пылевых облаках и астероидах. Они общаются с помощью световых импульсов, координируя действия всего роя. Не агрессивны. Являются препятствием для навигации кораблей. Стремительно размножаются в поясах астероидов. +[[SM_KRILL_MACRO_1]]. Хотя обычно они не агрессивны, даже в небольшом количестве космический планктон может создавать проблемы в навигации и пополнении запасов топлива. [[SM_KRILL_MACRO_2]] SM_KRILL_2 Рой космического планктона SM_KRILL_2_DESC -Космический планктон - это маленькие, непонятные насекомые, обитающие в пылевых облаках и астероидах. Они общаются с помощью световых импульсов, координируя действия всего роя. Не агрессивны. Являются препятствием для навигации кораблей. Стремительно размножаются в поясах астероидов. +[[SM_KRILL_MACRO_1]]. Хотя обычно они не агрессивны, их роение может создавать проблемы в навигации и пополнении запасов. [[SM_KRILL_MACRO_2]] SM_KRILL_3 Большой рой космического планктона SM_KRILL_3_DESC -Космический планктон - это маленькие, непонятные насекомые, обитающие в пылевых облаках и астероидах. Они общаются с помощью световых импульсов, координируя действия всего роя. В больших количествах пытаются съесть корабли. Являются препятствием для навигации. Стремительно размножаются в поясах астероидов. +[[SM_KRILL_MACRO_1]]. В достаточно больших количествах они становятся агрессивными и нападают на корабли. [[SM_KRILL_MACRO_2]] SM_KRILL_4 Тьма космического планктона SM_KRILL_4_DESC -Космический планктон - это маленькие, непонятные насекомые, обитающие в пылевых облаках и астероидах. Они общаются с помощью световых импульсов, координируя действия всего роя. При достижении максимума популяции (десятки миллионов особей) беспощадно нападают на корабли и сжирают космические постройки. Являются препятствием для навигации кораблей. Стремительно размножаются в местах со слабой гравитацией, например в поясах астероидов. +[[SM_KRILL_MACRO_1]]. Достигая критических отметок в численности популяции, космический планктон радикально меняет своё поведение. Обычно не доставляющий много проблем, здесь он становится очень агрессивным и начинает атаковать корабли, уничтожая орбитальные структуры. [[SM_KRILL_MACRO_2]] SM_TREE -Космический лес +Лес Дайсона SM_TREE_DESC -Космический лес состоит из нескольких "деревьев" с волокнистыми кронами, которые, разрастаясь, образуют кокон вокруг звезды системы. Космический лес является препятствием для навигации и разрастаясь может перекинуться на соседние звезды. +Космический "лес", состоящий из многочисленных "деревьев", с волокнистыми ветвями. Деревья размножились и сформировали нечто наподобие сферы, на соответствующем расстоянии вокруг звезды. Леса Дайсона препятствуют навигации, а со временем они разрастаются, и их становится сложнее искоренять. Если этого не сделать, то периодически они будут рассылать свои семена через звёздные пути и занимать остальные звёзды. Семена [[SM_TREE]] известны как "[[predefinedshipdesign SM_FLOATER]]", и их может быть трудно обнаружить. SM_FLOATER Скиталец SM_FLOATER_DESC -Луковице образное образование внутри наполненное газом, свободно летящее в космосе. +Существо в форме луковицы, наполненное газом, блуждающее в космосе. Их распростроняет [[predefinedshipdesign SM_TREE]] для создания новых лесов вокруг новых звёзд. Рекомендуются как можно скорей уничтожить [[SM_FLOATER]], однако, они очень малы и труднообнаружимы. Для этого обычно требуется технология [[tech SPY_DETECT_2]]. SM_DRAGON Вакуумный дракон SM_DRAGON_DESC -Опасный большой зверь, рыскающий в космосе в поисках добычи. +Ужасный гигантский монстр, рыскающий по космосу в поисках добычи и причиняющий ущерб населённым планетам. SM_DRONE Боевой робот SM_DRONE_DESC -Инструмент давно забытой войны, боевой робот все еще служит своей программе: атаковать все, что не может от него улететь. +Инструмент давно забытой войны, всё ещё исполняющий своё первоначальное предназначение: бездумно атаковать любые корабли в радиусе своего действия. SM_DRONE_FACTORY -Фабрика Боевых роботов +Фабрика боевых роботов SM_DRONE_FACTORY_DESC -Созданная в давно ушедшие времена для защиты давно погибших цивилизаций, фабрика до сих пор каким-то чудом функционирует, производя боевых роботов. +Производственная фабрика, построенная в давным-давно забытую войну для защиты уже давно исчезнувших её строителей. Удивительно, но она по-прежнему функционирует, иногда создавая новых Боевых роботов. + +SM_GUARD_0 +Технический корабль + +SM_GUARD_0_DESC +Беспилотный корабль, запрограммированный Предтечами для обслуживания звёздных путей системы. Он легко вооружён, но атакует каждого незваного гостя. SM_GUARD_1 Часовой SM_GUARD_1_DESC -Часовые обычно охраняют не очень ценные объекты. +Небольшой беспилотный сторожевой корабль. [[SM_GUARD_MACRO]] SM_GUARD_2 Страж SM_GUARD_2_DESC -Стражи обычно охраняют ценные объекты. +Беспилотный сторожевой корабль. [[SM_GUARD_MACRO]] SM_GUARD_3 Надзиратель SM_GUARD_3_DESC -Надзиратель обычно охраняют весьма интересные объекты. +Мощный, беспилотный сторожевой корабль. [[SM_GUARD_MACRO]] SM_KRAKEN_1 -Маленький спрут +Маленький кракен SM_KRAKEN_1_DESC -В зародышевом состоянии это осторожный и безобидный вид космической фауны. Обычно питается планктоном, вырастая и становясь опаснее для более крупных объектов. +'''В своей личиночной форме - это осторожный и относительно безвредный вид космической фауны. Его естественной добычей является космический планктон. Хорошо откормившийся маленький кракен может вырасти в более крупную и опасную взрослую форму. + +[[SM_KRAKEN_ENVIRONMENT]]''' SM_KRAKEN_2 -Спрут +Кракен SM_KRAKEN_2_DESC -Внушительный спрут средних размеров. +'''Грозный космический монстр средних размеров. Его естественной добычей является космический планктон. Хорошо откормившийся кракен может вырасти в ещё более крупную и опасную форму. + +[[SM_KRAKEN_ENVIRONMENT]]''' SM_KRAKEN_3 -Королевский спрут +Королевский Кракен SM_KRAKEN_3_DESC -Опасный зверь больших размеров. +'''Грозный и очень сильный космический монстр. + +[[SM_KRAKEN_ENVIRONMENT]]''' + +SM_WHITE_KRAKEN +Белый Кракен + +SM_WHITE_KRAKEN_DESC +Белый Кракен - это доисторический предок нынешнего монстра [[SM_KRAKEN_2]], имеющий белый цвет. SM_BLACK_KRAKEN -Чёрный спрут +Чёрный Кракен SM_BLACK_KRAKEN_DESC -Неестественно-большой, опасный космический монстр. +'''Мощный космический монстр, выведенный искуственным путём. + +Чёрный кракен - биоинженерный вид монстров, невероятно мощный, с опасным оружием, имеющий высокий параметр [[metertype METER_STEALTH]]. Потребуется сильный флот и хорошая [[encyclopedia DETECTION_TITLE]], чтобы уничтожить его. Они блуждают в поисках планет с видимыми зданиями и атакуют их население.''' SM_SNOWFLAKE_1 Малая снежинка SM_SNOWFLAKE_1_DESC -Внушительный но маленький зверь с малым весом. +'''Маленький и безвредный космический монстр с необычайным обзором. + +[[SM_SNOWFLAKE_ENVIRONMENT]]''' SM_SNOWFLAKE_2 Снежинка SM_SNOWFLAKE_2_DESC -Внушительный космический зверь с малым весом. +'''Легковесный космический монстр с необычайным обзором. + +[[SM_SNOWFLAKE_ENVIRONMENT]]''' SM_SNOWFLAKE_3 Большая снежинка SM_SNOWFLAKE_3_DESC -Создание внушительных размеров, но с малым весом. +'''Огромный и легкий космический монстр. + +[[SM_SNOWFLAKE_ENVIRONMENT]]''' SM_PSIONIC_SNOWFLAKE Псионная снежинка SM_PSIONIC_SNOWFLAKE_DESC -Этот монстр может брать контроль над сознанием органических существ. +'''Монстр, обладающий силой уничтожать разум органических существ. + +Псионная снежинка - биоинженерный космический монстр с опасным оружием, умеющий брать под контроль судна с экипажем [[encyclopedia ORGANIC_SPECIES_TITLE]] на борту. Для борьбы с ней понадобится мощный флот. Псионные снежинки блуждают в космосе и атакуют космические корабли противников, заставляя экипажи, уязвимые для их психических атак, покидать свою империю. Они также могут атаковать население планеты.''' SM_JUGGERNAUT_1 -Маленький вышибала +Маленький джаггернаут SM_JUGGERNAUT_1_DESC -Тяжелый опасный монстр. +'''Огромный, тяжёлый космический монстр с некоторым количеством брони. + +[[SM_JUGGERNAUT_ENVIRONMENT]]''' SM_JUGGERNAUT_2 -Вышибала +Джаггернаут SM_JUGGERNAUT_2_DESC -Тяжелый опасный монстр. +'''Огромный, тяжёлый космический монстр, оснащённый бронёй. + +[[SM_JUGGERNAUT_ENVIRONMENT]]''' SM_JUGGERNAUT_3 -Большой вышибала +Большой джаггернаут SM_JUGGERNAUT_3_DESC -Тяжелый опасный монстр. +'''Огромный, тяжёлый космический монстр с мощной бронёй. + +[[SM_JUGGERNAUT_ENVIRONMENT]]''' SM_BLOATED_JUGGERNAUT -Обожравшийся вышибала +Распухший джаггернаут SM_BLOATED_JUGGERNAUT_DESC -Массивный, очень плохо выглядящий космический монстр. +'''Массивный, пугающе выглядящий космический монстр. + +Распухший джаггернаут - это биоинженерный космический монстр, невероятно мощный, с опасным оружием, имеющий высокий параметр [[metertype METER_STEALTH]]. Потребуется сильный флот и хорошая [[encyclopedia DETECTION_TITLE]], чтобы уничтожить его. Он блуждает в поисках планет с видимыми зданиями и атакуют их население.''' SM_CLOUD Космическое облако SM_CLOUD_DESC -Туманное существо, дающее [[metertype METER_STEALTH]] планетам. +Блуждающая туманность, дающая [[metertype METER_STEALTH]] случайным планетам. SM_ASH Космический вулкан SM_ASH_DESC -Туманное существо, дающее [[metertype METER_STEALTH]] планетам. +Блуждающее облако пепла, дающее [[metertype METER_STEALTH]] случайным планетам. SM_DIM -Измеритель +Бродяга по измерениям SM_DIM_DESC -Туманное существо, дающее [[metertype METER_STEALTH]] планетам. +Блуждающая туманность, дающая [[metertype METER_STEALTH]] случайным планетам. SM_VOID Бегемот пустоты SM_VOID_DESC -Туманное существо, дающее [[metertype METER_STEALTH]] планетам. +Блуждающая туманность, дающая [[metertype METER_STEALTH]] случайным планетам. SM_SNAIL Астероидная улитка SM_SNAIL_DESC -Питающаяся минералами, колония таких улиток очень похожа на обычный астероид, из-за чего их сложно заметить. +Робкий монстр, питающийся минералами. Его оболочка очень сильно похожа на обычный астероид, что повышает [[metertype METER_STEALTH]], если улитка попробует скрыться внутри пояса астероидов. SM_DAMPENING_CLOUD -Грязное облако +Разряжающее облако SM_DAMPENING_CLOUD_DESC -Космическое облако высокозаряженных частиц, обладающее некой чувствительностью. Быстро притягивается и разряжает любые накопители энергии, что влечет паралич целых звездных флотилий. +Космическое облако высокозаряженных частиц, получившее некоторую силу чувствовать. Оно образуется из энергии, излучаемой колониями. Так или иначе, его очень привлекают резервы потенциальной энергии, поэтому оно крадёт [[metertype METER_FUEL]] из кораблей в системе. SM_ACIREMA_GUARD -Защитник Ахиремы +Защитник Ациремы SM_ACIREMA_GUARD_DESC -Беспилотный вооруженный корабль, построенный Ахиремами для защиты системы. +Беспилотный вооруженный корабль, построенный Ациремами для защиты их системы. SM_EXP_OUTPOST -Геомиссия Экспериментаторов +Эксперимент Ноль SM_EXP_OUTPOST_DESC -Геологическая экспедиция, построенная Экспериментаторами для перевозки экспериментального оборудования между галактиками. +'''Древнее творение Экспериментаторов, которое уменьшает [[metertype METER_SHIELD]] и [[encyclopedia DAMAGE_TITLE]]. + +Эксперимент Ноль был отправлен в эту галактику, чтобы охранять аванпост Экспериментаторов своими уникальными способностями. Хотя он и не атакует корабли напрямую, но он наполняет всю систему квадриллионами крошечных частиц, создающими помехи во вражеских щитах и оружиях такой степени, что всё, кроме самого лучшего оборудования, становится неэффективным.''' SM_COSMIC_DRAGON Космический дракон SM_COSMIC_DRAGON_DESC -Устрашающий космический монстр, способный разрушить звездную систему. +'''Устрашающий космический монстр, способный разрушать звёздные системы. +Космические драконы - это биоинженерные космические монстры, невероятно мощные, опасно вооружённые, имеющие высокий параметр [[metertype METER_STEALTH]]. Потребуется сильный флот и хорошая [[encyclopedia DETECTION_TITLE]], чтобы уничтожить такого. Драконы блуждают в поисках обитаемых планет и атакуют, и способны даже испарять целые системы, если с ними никак не бороться.''' ## ## Fields @@ -430,20 +829,31 @@ FLD_ION_STORM Ионный шторм FLD_ION_STORM_DESC -Магнитный вихрь электрически-заряженных частиц, который блокирует работу сенсоров и может нанести урон кораблям и планетам. +Магнитный вихрь релятивистских заряженных частиц, который может мешать сенсорам и скрывать все объекты внутри себя. Все объекты внутри шторма имеют повышенную на 40 [[metertype METER_STEALTH]] и уменьшенное на 40 [[metertype METER_DETECTION]]. FLD_MOLECULAR_CLOUD -Молекулярное Облако +Молекулярное облако FLD_MOLECULAR_CLOUD_DESC -Диффузное облако сложных молекул, которое разрушает щиты корабля. +Диффузное облако сложных молекул, которое нарушает работу щитов корабля. У всех кораблей уменьшен [[metertype METER_SHIELD]] на 15. FLD_NEBULA_1 Туманность FLD_NEBULA_1_DESC -'''Облако водорода, которое может сжаться, сформировав новую звезду. ''' +Облако водорода, которое может сжаться, сформировав новую звезду. + +FLD_SUBSPACE_RIFT +Пространственный разрыв +FLD_SUBSPACE_RIFT_DESC +Жестокий, разрушительный разрыв пространства и времени, который быстро коллапсирует. Он втягивает и поглощает корабли, системы и планеты, оказавшиеся в зоне его досягаемости, однако быстрые корабли могут успеть улететь. + +FLD_ACCRETION_DISC +Аккреционный диск + +FLD_ACCRETION_DISC_DESC +Аккреционный диск представляет из себя структуру (часто - околозвёздный диск), образованную веществом, рассеянном в направлении орбитального вращения вокруг массивного центрального небесного тела. Все планеты в затронутой им системе получают штраф -1 к [[metertype METER_SUPPLY]]. ## ## Predefined starting fleets @@ -461,13 +871,12 @@ FN_COLONY_FLEET MONSTERS Монстры - ## ## Status update messages ## RETURN_TO_INTRO -Возврат в главное меню +Возврат в главное меню. SERVER_WONT_START Сервер не может быть запущен. @@ -478,61 +887,301 @@ SERVER_TIMEOUT SERVER_LOST Связь с сервером потеряна. +LOCAL_SERVER_ALREADY_RUNNING_ERROR +Невозможно создать сервер. Локальный сервер уже запущен. + PLAYER_DISCONNECTED -Игрок %1% покинул сервер. +Игрок %1% потерял связь с сервером. + +SERVER_SAVE_INITIATE_ACK +Сохранение... + +# %1% save game file path as requested by the client. +# %2% save game file size in bytes. +SERVER_SAVE_COMPLETE +Сохранено %2% байт в файле: %1% INVALID_CLIENT_SAVE_DATA_RECEIVED -Сервер получил некорректные сохраненные данные. Некоторые из них будут проигнорированы. +Сервер получил некорректные данные сохранения. Некоторые из них могут быть проигнорированы. NON_HOST_SAVE_REQUEST_IGNORED -Сервер получил некорректный запрос о сохранении игры. Вы не являетесь сервером, по-этому не можете сохранить игру. +Сервер получил некорректный запрос на сохранение игры от вашего клиента. Вы не являетесь сервером, поэтому не можете сохранять игру. UNABLE_TO_WRITE_CONFIG_XML -Ошибка при записи файла config.xml: невозможно записать! +Ошибка при записи файла config.xml. Невозможно сохранить настройки. UNABLE_TO_READ_CONFIG_XML -Ошибка при чтении config.xml. Будут использованы настройки по умолчанию! +Ошибка при чтении config.xml. Будут использованы настройки по умолчанию. + +UNABLE_TO_READ_PERSISTENT_CONFIG_XML +Ошибка при чтении файла дополнения persistent_config.xml (вероятно, что его не существует). + +UNABLE_TO_WRITE_PERSISTENT_CONFIG_XML +Ошибка при записи файла persistent_config.xml. UNABLE_TO_WRITE_SAVE_FILE -Ошибка при сохранении игры: невозможно записать файл. +Ошибка записи файла сохранения игры. UNABLE_TO_READ_SAVE_FILE -Ошибка при чтении файла сохраненной игры. +Ошибка чтения файла сохранения игры. + +UNABLE_TO_SAVE_NOW_TRY_AGAIN +Не удалось сохранить игру во время обработки действий Компьютера. Повторите попытку, когда все игроки-Компьютеры закончат свои ходы. + +SAVE_GAME_IN_PROGRESS +Выполняется сохранение игры. + +ABORT_SAVE_AND_RESET +Вернуться в главное меню без сохранения. + +ABORT_SAVE_AND_EXIT +Выйти из игры без сохранения. EMPIRE_NOT_FOUND_CANT_HANDLE_ORDERS Вы не управляете этой империей и не можете отдавать приказы! ORDERS_FOR_WRONG_EMPIRE -Приказы были отданы империи, которая вам неподвластна. +Приказы были отданы империи, которой вы не управляете. SERVER_ALREADY_HOSTING_GAME -На сервере уже запущена игра. +На этом сервере уже запущена игра. SERVER_UNABLE_TO_SELECT_HOST -Сервер не может выбрать новый host. +Сервер не может выбрать новый хост. SERVER_FOUND_NO_ACTIVE_PLAYERS -Нельзя создать игру без игрока. +Нельзя создать игру без активных игроков. + +SERVER_UNIVERSE_GENERATION_ERRORS +Генерация Вселенной выполнена с ошибками. Подробнее об этих ошибках смотрите в файлах логов. Игра может стартовать, но в её процессе, возможно, возникнут проблемы. + +SERVER_TURN_EVENTS_ERRORS +Скрипты создания событий выполнились с ошибками. Подробнее об этих ошибках смотрите в файлах логов. Игра может быть продолжена, но в её процессе, возможно, возникнут проблемы. + +SERVER_ALREADY_PLAYING_GAME +На данный момент сервер не принимает игроков, так как игра уже была сыграна. + +ERROR_PYTHON_AI_CRASHED +Во время хода игрока %1% произошла ошибка. + +ERROR_PLAYER_NAME_ALREADY_USED +Имя игрока %1% уже используется. + +ERROR_WRONG_PASSWORD +Неверный пароль для %1%. + +ERROR_CLIENT_TYPE_NOT_ALLOWED +Данный клиент запрещён. + +ERROR_NOT_ENOUGH_AI_PLAYERS +Недостаточно игроков-компьютеров. + +ERROR_TOO_MANY_AI_PLAYERS +Слишком много игроков-компьютеров. + +ERROR_NOT_ENOUGH_HUMAN_PLAYERS +Недостаточно игроков-людей. + +ERROR_TOO_MANY_HUMAN_PLAYERS +Слишком много игроков-людей. + +ERROR_CONNECTION_WAS_REPLACED +Ваше подключение было изменено. + +ERROR_NONPLAYER_CANNOT_CONCEDE +Только игроки могут признавать поражение. + +ERROR_CONCEDE_DISABLED +Возможность признания поражения отключена. + +ERROR_CONCEDE_EXCEED_COLONIES +Невозможно сдаться: у вашей империи ещё достаточно много колоний. + +ERROR_CONCEDE_LAST_HUMAN_PLAYER +Нельзя признавать поражение при наличии в игре только одного игрока-человека. + +## +## Game Rules +## + +RULE_CHEAP_AND_FAST_TECH_RESEARCH +Быстрые и дешёвые науки + +RULE_CHEAP_AND_FAST_TECH_RESEARCH_DESC +Все науки стоят 1 очко исследований и изучаются за 1 ход. + +RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION +Быстрые и дешёвые здания + +RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION_DESC +Все строения стоят 1 очко промышленности и создаются за 1 ход. + +RULE_CHEAP_AND_FAST_SHIP_PRODUCTION +Быстрые и дешёвые корабли + +RULE_CHEAP_AND_FAST_SHIP_PRODUCTION_DESC +Все корабли стоят 1 очко промышленности и собираются за 1 ход. + +RULE_NUM_COMBAT_ROUNDS +Этапов битвы за ход + +RULE_NUM_COMBAT_ROUNDS_DESC +Какое количество этапов боя может произойти за каждый ход. + +RULE_SHIP_SPEED_FACTOR +Скорость кораблей + +RULE_SHIP_SPEED_FACTOR_DESC +Коэффициент умножения для параметров скорости корпусов кораблей и их деталей. + +RULE_SHIP_STRUCTURE_FACTOR +Броня кораблей + +RULE_SHIP_STRUCTURE_FACTOR_DESC +Коэффициент умножения для параметров защиты корпусов кораблей и деталей их брони. + +RULE_SHIP_HULL_COST_FACTOR +Топливо кораблей + +RULE_SHIP_HULL_COST_FACTOR_DESC +Коэффициент умножения для параметра расхода топлива у кораблей разных типов. + +RULE_SHIP_PART_COST_FACTOR +Стоимость деталей + +RULE_SHIP_PART_COST_FACTOR_DESC +Коэффициент умножения для стоимости отдельных деталей кораблей. + +RULE_BUILDING_COST_FACTOR +Стоимость строений + +RULE_BUILDING_COST_FACTOR_DESC +Коэффициент умножения стоимости различных строений. + +RULE_TECH_COST_FACTOR +Стоимость исследований + +RULE_TECH_COST_FACTOR_DESC +Коэффициент умножения затрат на изучение различных наук. + +RULE_ENABLE_EXPERIMENTORS +Генерация Экспериментаторов +RULE_ENABLE_EXPERIMENTORS_DESC +Создает планету с Экспериментаторами во время генерации галактики. + +RULE_ENABLE_SUPER_TESTER +Превращение в Супер-тестеров + +RULE_ENABLE_SUPER_TESTER_DESC +Включает возможность построить [[BLD_SUPER_TEST]], которое превращает текущую расу в расу Супер-тестеров. + +RULE_STOCKPILE_IMPORT_LIMITED +Предел импорта резервов + +RULE_STOCKPILE_IMPORT_LIMITED_DESC +Активирует предел для импорта резервов за ход. + +RULE_STARLANES_EVERYWHERE +Звёздные пути повсюду + +RULE_STARLANES_EVERYWHERE_DESC +Звёздные пути образуются автоматически между любыми двумя ближайшими системами, вне зависимости от первоначальной формы галактики. Они также не отображаются на галактической карте, но всё же могут быть удалены в ходе игрового процесса. + +RULE_HABITABLE_SIZE_TINY +Население крошечных планет + +RULE_HABITABLE_SIZE_SMALL +Население маленьких планет + +RULE_HABITABLE_SIZE_MEDIUM +Население средних планет + +RULE_HABITABLE_SIZE_LARGE +Население больших планет + +RULE_HABITABLE_SIZE_HUGE +Население огромных планет + +RULE_HABITABLE_SIZE_ASTEROIDS +Население астероидов + +RULE_HABITABLE_SIZE_GASGIANT +Население газовых гигантов + +RULE_HABITABLE_SIZE_DESC +Значение, задающее максимальное число параметра [[encyclopedia POPULATION_TITLE_SHORT_DESC]] для планет данного размера. + +RULE_ALLOW_CONCEDE +Разрешить признавать поражение + +RULE_ALLOW_CONCEDE_DESC +Разрешает империям признавать поражение. При этом уничтожаются любые данные об этих империях. Далее игра продолжается среди оставшихся игроков. + +RULE_CONCEDE_COLONIES_THRESHOLD +Колоний для поражения + +RULE_CONCEDE_COLONIES_THRESHOLD_DESC +Империи, имеющие большее число колоний, чем это, не могут признавать поражение. + +RULE_THRESHOLD_HUMAN_PLAYER_WIN +Максимум победителей + +RULE_THRESHOLD_HUMAN_PLAYER_WIN_DESC +Максимальное число игроков-людей, которые могут победить в мультиплеере, когда в галактике больше не осталось других выживших игроков-людей. + +RULE_ONLY_ALLIANCE_WIN +Побеждают только альянсы + +RULE_ONLY_ALLIANCE_WIN_DESC +Игроки в альянсе могут побеждать вместе, если число человеческих игроков в их альянсе не превышает возможное количество людей-победителей для данной игры. При этом в галактике не должно оставаться других выживших игроков-людей. ## ## Command-line and options database entries ## +COMMAND_LINE_NOT_FOUND +Совпадений не найдено + COMMAND_LINE_USAGE -Используется: +'''Используется: ''' COMMAND_LINE_DEFAULT -По умолчанию: +'''По умолчанию: ''' + +COMMAND_LINE_SECTIONS +Настройки группы + +COMMAND_LINE_OPTIONS +Настройки OPTIONS_DB_HELP -Выводит это справочное сообщение. +'''Выводит это справочное сообщение. +Принимает аргументы для частей или полных названий. +Специальные аргументы для: +всех - [[OPTIONS_DB_SECTION_ALL]] +необработанных - [[OPTIONS_DB_SECTION_RAW]]''' + +OPTIONS_DB_VERSION +Вывести версию и выйти. + +OPTIONS_DB_SINGLEPLAYER +Запустите сервер в режиме одиночной игры. Это позволит подключаться к нему только локальным клиентам. + +OPTIONS_DB_HOSTLESS +Запустите сервер в режиме без доступа. Сервер будет принимать игроков в многопользовательском лобби и возвращать их туда после окончания игрового сеанса. + +OPTIONS_DB_SKIP_CHECKSUM +Пропустить сравнивание контрольных сумм директории ресурсов. Это позволит игре быстрее стартовать, когда клиент и сервер расположены на одном компьютере и пользуются одной директорией ресурсов. OPTIONS_DB_GENERATE_CONFIG_XML -Использовать все параметры настройки из любого существующего файла config.xml чтобы создать новый config.xml (текущий файл, если он существует будет переписан). +Использовать настройки по-умолчанию, настройки из существующего файла config.xml или настройки, переданные командной строкой для создания файла config.xml. Это перезапишет текущий файл config.xml, если тот уже существует. OPTIONS_DB_VERSION_STRING -Отслеживать версию FreeOrion, для которой был создан файл настроек config.xml. Config.xml, созданный для других версий FreeOrion будет проигнорирован. +Отслеживать версию FreeOrion, для которой был создан файл настроек config.xml. Config.xml созданный для других версий FreeOrion будет проигнорирован. + +OPTIONS_DB_RENDER_SIMPLE +Устанавливает несколько вариантов рендеринга карт и графического интерфейса для повышения частоты кадров и снижения нагрузки на процессор. Полезно для игры на адаптерах с низким энергопотреблением без настройки каждой опции отдельно. OPTIONS_DB_SOUND_ON Включить звуки. @@ -541,52 +1190,71 @@ OPTIONS_DB_MUSIC_ON Включить музыку. OPTIONS_DB_BG_MUSIC -Выбрать музыку на задний фон. +Выбрать фоновую музыку. OPTIONS_DB_FULLSCREEN -Запускать игру в полноэкранном режиме. +Запускать игру в полноэкранном режиме. При нажатии клавиши "Применить" настройки могут применится сразу, или может потребоваться рестарт игры. + +OPTIONS_DB_FAKE_MODE_CHANGE +Не изменять разрешение экрана в реальности. Вместо этого, игра открывается в окне, с выбранным разрешением и масштабом. Это позволяет избежать некоторых проблем, связанных с реальным изменением разрешения экрана, наблюдаемых в Линуксе. OPTIONS_DB_FULLSCREEN_MONITOR_ID -'''Выберите, монитор для использования в полноэкранном режиме. Основной монитор имеет индексом 0. Необходим перезапуск, чтобы изменения вступили в силу. ''' +Выберите монитор для использования полноэкранного режима. Основной монитор должен иметь индекс 0. Может потребоваться рестарт, чтобы изменения применились. + +OPTIONS_DB_UI_SAVE_DIALOG_COLUMNS +'''Список столбцов, отображаемых в диалоговом окне при сохранении файлов, разделённые запятыми. +Доступные столбцы: time, turn, player, empire, systems, seed, galaxy_age, galaxy_shape, planet_freq, native_freq, specials_freq, starlane_freq''' + +OPTIONS_DB_UI_SAVE_DIALOG_COLUMN_WIDE_AS +Если ui.dialog.save.columns.[name].width.chars установлен, столбец всегда будет достаточно широким, чтобы вместить текст. + +OPTIONS_DB_UI_SAVE_DIALOG_COLUMN_STRETCH +Если ui.dialog.save.columns.[name].stretch установлен, у столбца появляется функция растягивания. OPTIONS_DB_GALAXY_MAP_GAS -Отображение газовых облаков в качестве фона на карте галактики. Может замедлить рендеринг на старых компьютерах. +Отображать газовые облака вокруг систем для придания формы галактике. Это может замедлить рендеринг на старых компьютерах. OPTIONS_DB_GALAXY_MAP_STARFIELDS -Отображение звездного неба фоном на карте галактики. Может замедлить рендеринг на старых компьютерах. +Отображать звездные поля вокруг систем. Это может замедлить рендеринг на старых компьютерах. OPTIONS_DB_GALAXY_MAP_SCALE_LINE -Показывать полосы прокрутки для карты галактики. +Показывать масштабную линейку космических расстояний на галактической карте. + +OPTIONS_DB_GALAXY_MAP_SCALE_CIRCLE +Показывать масштаб карты также в виде круга, центрированного на текущей выбранной системе (только если включена масштабная линейка карт). OPTIONS_DB_GALAXY_MAP_ZOOM_SLIDER -Показывать слайдер приближения карты галактики. +Показывать бегунок масштаба на галактической карте. OPTIONS_DB_UI_GALAXY_MAP_POPUP -Показывать контекстное меню по щелчку право кнопки мыши на звездной карте. +Показывать контекстное меню по щелчку правой кнопки мыши на звездной карте. + +OPTIONS_DB_UI_HIDE_MAP_PANELS +Показывать сводку, энциклопедию и прочие панели, скрытые при разворачивании производственного окна. Открывать их при закрытии производственного окна. OPTIONS_DB_STARLANE_THICKNESS -Установить размер звездных путей в пикселях. +Устанавливает размер звездных путей в пикселях. OPTIONS_DB_STARLANE_CORE -Порог кратности 'коренных' звездных путей империи. +Порог кратности 'основных' звездных путей империи. OPTIONS_DB_RESOURCE_STARLANE_COLOURING -Окрасить космические пути цветами империй, если между империями существует обмен ресурсами. +Окрашивать космические пути цветами империй вдоль звездных путей, если между империями существует обмен ресурсами. OPTIONS_DB_UNOWNED_STARLANE_COLOUR -Установить цвет по умолчанию для рендеринга звездных путей. +Устанавливает цвет по умолчанию для рендеринга звездных путей. OPTIONS_DB_FLEET_SUPPLY_LINES -Показывать линии флота поддержки цветом империи +Окрашивать линии снабжения флота цветом империи. OPTIONS_DB_FLEET_SUPPLY_LINE_WIDTH -Ширина линий поддержки флота. +Ширина линий снабжения флота. OPTIONS_DB_FLEET_SUPPLY_LINE_DOT_SPACING -Насколько далеко рисовать точки линий поддержки флота. +Насколько далеко рисовать точки линий снабжения флота. OPTIONS_DB_FLEET_SUPPLY_LINE_DOT_RATE -Насколько быстро рисовать точки линий поддержки флота. +Насколько часто рисовать точки линий снабжения флота. OPTIONS_DB_GALAXY_MAP_DETECTION_RANGE Рисовать дальность обнаружения для объектов на карте галактики. @@ -595,16 +1263,16 @@ OPTIONS_DB_GALAXY_MAP_DETECTION_RANGE_OPACITY Устанавливает прозрачность круга обнаружения. OPTIONS_DB_FORCE_EXTERNAL_SERVER -Вынудить клиента не запускать сервер, в случае когда игра на localhost, одиночная игра, и т.п. +Вынудить клиента не запускать сервер, даже в случае, когда игра на localhost, одиночная игра, и т.п. OPTIONS_DB_EXTERNAL_SERVER_ADDRESS -Адрес для соединения в расширенном серверном режиме. Если используется, этот клиент становится управляющим игрой. +Адрес для соединения в режиме внешнего сервера. Если используется, этот клиент становится управляющим игрой. OPTIONS_DB_MP_HOST_ADDRESS -Адрес, с которым нужно соединяться после входа в многопользовательскую игру. +Адрес, с которым нужно соединяться для входа в многопользовательскую игру. OPTIONS_DB_MP_PLAYER_NAME -Имя игрока при открытии или присоединении к многопользовательской игре. +Имя игрока при создании или присоединении к многопользовательской игре. OPTIONS_DB_UI_MAIN_MENU_X Позиция центра главного меню экрана, как часть полной ширины приложения. @@ -613,52 +1281,64 @@ OPTIONS_DB_UI_MAIN_MENU_Y Позиция центра главного меню экрана, как часть полной высоты приложения. OPTIONS_DB_APP_WIDTH -Установить горизонтальное разрешение. +Устанавливает горизонтальное разрешение. Параметры определяются плагином рендеринга и могут не соответствовать фактическому размеру монитора. OPTIONS_DB_APP_HEIGHT -Установить вертикальное разрешение. +Устанавливает вертикальное разрешение. Параметры определяются плагином рендеринга и могут не соответствовать фактическому размеру монитора. OPTIONS_DB_APP_WIDTH_WINDOWED -Ширина окна (в оконном режиме). +Ширина разрешения (в оконном режиме). OPTIONS_DB_APP_HEIGHT_WINDOWED -Высота окна (в оконном режиме). +Высота разрешения (в оконном режиме). OPTIONS_DB_APP_LEFT_WINDOWED -Устанавливает горизонтальное положение в оконном режиме. По сравнению с левом краем монитора. +Устанавливает горизонтальное положение в оконном режиме. По сравнению с левым краем монитора. OPTIONS_DB_APP_TOP_WINDOWED -Устанавливает вертикальное положение в оконном режиме. По сравнению с верхнем краем монитора. +Устанавливает вертикальное положение в оконном режиме. По сравнению с верхним краем монитора. OPTIONS_DB_SHOW_FPS -Включает отображение FPS (кадров в секунду). +Переключает отображение FPS. OPTIONS_DB_LIMIT_FPS Включить ограничение FPS. Предел устанавливается опцией "Максимальный FPS". OPTIONS_DB_MAX_FPS -Ограничение FPS. Включается опцией "Включить ограничение FPS". +Максимальный FPS. Включается опцией "Включить ограничение FPS". + +OPTIONS_DB_LIMIT_FPS_NO_FOCUS +Переключает ограничение FPS, когда игровое окно не имеет фокуса. + +OPTIONS_DB_MAX_FPS_NO_FOCUS +Максимальный FPS, когда игровое окно не имеет фокуса. OPTIONS_DB_UI_SOUND_VOLUME -Громкость (0 до 255) звуков интерфейса. +Громкость (0 до 255), при которой должны воспроизводиться звуковые эффекты UI. OPTIONS_DB_UI_SOUND_BUTTON_ROLLOVER -Звук на освобождение кнопки мыши. +Звук при проходе курсора над кнопкой. OPTIONS_DB_UI_SOUND_BUTTON_CLICK -Звук на нажатие кнопки мыши. +Звук при нажатии кнопки мыши. OPTIONS_DB_UI_SOUND_TURN_BUTTON_CLICK -Звук на нажатие кнопки 'Конец хода'. +Звук при нажатии кнопки 'Конец хода'. + +OPTIONS_DB_UI_SOUND_NEWTURN_TOGGLE +Звук в начале нового хода. + +OPTIONS_DB_UI_SOUND_NEWTURN_FILE +Воспроизводить звук нового хода. OPTIONS_DB_UI_SOUND_LIST_SELECT -Звук на выбор из списка. +Звук, воспроизводимый при выборе элемента списка или выпадающего списка. OPTIONS_DB_UI_SOUND_ITEM_DROP -Звук на сбрасывание пункта в списке. +Звук на сбрасывание пункта в список. OPTIONS_DB_UI_SOUND_LIST_PULLDOWN -Звук на открытие ниспадающего списка. +Звук при открытии выпадающего списка. OPTIONS_DB_UI_SOUND_TEXT_TYPING Звук на печатание текста на клавиатуре. @@ -673,7 +1353,7 @@ OPTIONS_DB_UI_SOUND_WINDOW_CLOSE Звук на закрытие окна. OPTIONS_DB_UI_SOUND_ALERT -Звук на ошибки. +Звук при возникновении ошибки. OPTIONS_DB_UI_SOUND_PLANET_BUTTON_CLICK Звук на нажатие кнопки 'Планета'. @@ -691,193 +1371,300 @@ OPTIONS_DB_UI_SOUND_SIDEPANEL_OPEN Звук на открытие боковой панели системы. OPTIONS_DB_UI_FONT -Установить файл источник шрифтов интерфейса. +Шрифт интерфейса. OPTIONS_DB_UI_FONT_BOLD -Установить файл источник жирного шрифта интерфейса. +Жирный шрифт интерфейса. OPTIONS_DB_UI_FONT_SIZE -Установить размер шрифта интерфейса. +Размер шрифта интерфейса. OPTIONS_DB_UI_TITLE_FONT -Установить файл источник шрифтов для заголовков интерфейса. +Шрифт для заголовков интерфейса. OPTIONS_DB_UI_TITLE_FONT_SIZE -Установить размер шрифта заголовков интерфейса. +Размер шрифта заголовков интерфейса. OPTIONS_DB_UI_WND_COLOR -Установить цвет окон интерфейса. +Цвет окон интерфейса. OPTIONS_DB_UI_TEXT_COLOR -Установить цвет текста интерфейса. +Цвет текста интерфейса OPTIONS_DB_UI_DEFAULT_LINK_COLOR -Установить цвет текста ссылок. +Цвет текста ссылок. OPTIONS_DB_UI_ROLLOVER_LINK_COLOR -Установить цвет текста ссылок при наведении курсора. +Цвет текста ссылок при наведении курсора. OPTIONS_DB_UI_CTRL_COLOR -Установить цвет контроля интерфейса. +Цвет контроля интерфейса. OPTIONS_DB_UI_CTRL_BORDER_COLOR -Установить цвет рамки контроля интерфейса. +Цвет рамки контроля интерфейса. OPTIONS_DB_UI_STATE_BUTTON_COLOR -Установить цвет кнопки структуры интерфейса. +Цвет кнопки структуры интерфейса. OPTIONS_DB_UI_DROPDOWNLIST_ARROW_COLOR -Установить цвет курсора ниспадающих списков интерфейса. +Цвет курсора ниспадающих списков интерфейса. OPTIONS_DB_UI_EDIT_HILITE -Установить цвет выделения в элементах управления для редактирования. +Цвет выделения в элементах управления для редактирования. OPTIONS_DB_UI_STAT_INCREASE_COLOR -Установить цвет increased stats интерфейса. +Цвет increased stats интерфейса. OPTIONS_DB_UI_STAT_DECREASE_COLOR -Установить цвет decreased stats интерфейса. +Цвет decreased stats интерфейса. OPTIONS_DB_UI_WND_OUTER_BORDER_COLOR -Установить цвет внешней границы интерфейса. +Цвет внешней границы интерфейса. OPTIONS_DB_UI_WND_INNER_BORDER_COLOR -Установить цвет внутренней границы интерфейса. +Цвет внутренней границы интерфейса. OPTIONS_DB_UI_KNOWN_TECH -Установить цвет известных технологий в дереве технологий. +Цвет известных технологий в дереве технологий. OPTIONS_DB_UI_KNOWN_TECH_BORDER -Установить цвет текста и границы известных технологий в дереве технологий. +Цвет текста и границы известных технологий в дереве технологий. OPTIONS_DB_UI_RESEARCHABLE_TECH -Установить цвет технологий доступных для исследований. +Цвет технологий доступных для исследований. OPTIONS_DB_UI_RESEARCHABLE_TECH_BORDER -Установить цвет текста и границы технологий доступных для исследований. +Цвет текста и границы технологий доступных для исследований. OPTIONS_DB_UI_UNRESEARCHABLE_TECH -Установить цвет технологий недоступных для исследований. +Цвет технологий недоступных для исследований. OPTIONS_DB_UI_UNRESEARCHABLE_TECH_BORDER -Установить цвет текста и границы технологий недоступных для исследований. +Цвет текста и границы технологий недоступных для исследований. OPTIONS_DB_UI_TECH_PROGRESS_BACKGROUND -Установить цвет фона полосы прогресса в дереве технологий. +Цвет фона полосы прогресса в дереве технологий. OPTIONS_DB_UI_TECH_PROGRESS -Установить цвет полосы прогресса в дереве технологий. +Цвет полосы прогресса в дереве технологий. + +OPTIONS_DB_UI_TECH_TREE_STATUS_UNRESEARCHABLE +Хранит статус фильтра "незавершенные" технологического дерева. + +OPTIONS_DB_UI_TECH_TREE_STATUS_HAS_RESEARCHED_PREREQ +Хранит статус фильтра "пререквизиты изучены" технологического дерева. + +OPTIONS_DB_UI_TECH_TREE_STATUS_RESEARCHABLE +Хранит статус фильтра "изучаемо" технологического дерева. + +OPTIONS_DB_UI_TECH_TREE_STATUS_COMPLETED +Хранит статус фильтра "закончено" технологического дерева. OPTIONS_DB_UI_SCROLL_WIDTH -Установить ширину полосы прокрутки в интерфейсе. +Устанавливает ширину полосы прокрутки в интерфейсе. OPTIONS_DB_UI_SYSTEM_ICON_SIZE -Установить размер системных значков. +Устанавливает размер системных значков. OPTIONS_DB_UI_SYSTEM_FOG -Рисовать полосы тумана войны поверх звезд. +Рисовать полосы тумана войны поверх значков систем. OPTIONS_DB_UI_SYSTEM_FOG_SPACING Расстояние между полосами тумана войны в пикселях. +OPTIONS_DB_UI_SYSTEM_FOG_CLR +Устанавливает цвет полосы затенения по значкам системы. + +OPTIONS_DB_UI_FIELD_FOG_CLR +Устанавливает цвет полосы затенения над значками области. + +OPTIONS_DB_UI_PLANET_FOG_CLR +Устанавливает цвет полосы затенения над графикам планеты боковой панели. + OPTIONS_DB_UI_SYSTEM_CIRCLES -Рисовать круги вокруг звезд. +Рисовать круги вокруг систем. OPTIONS_DB_UI_SYSTEM_CIRCLE_SIZE -Размер круга вокруг звезд, в соотношении к размеру значка звезды. +Размер круга вокруг систем на карте в соотношении к размеру значка системы. + +OPTIONS_DB_UI_SYSTEM_INNER_CIRCLE_WIDTH +Устанавливает ширину линии внутренней окружности системы при увеличении карты. + +OPTIONS_DB_UI_SYSTEM_OUTER_CIRCLE_WIDTH +Устанавливает ширину линии внешней окружности системы. + +OPTIONS_DB_UI_SYSTEM_INNER_CIRCLE_MAX_WIDTH +Устанавливает ширину линии внутреннего кружка системы при уменьшении карты. + +OPTIONS_DB_UI_SYSTEM_CIRCLE_DISTANCE +Устанавливает расстояние (в пикселях) между внутренними и внешними кругами системы при близком увеличении. + +OPTIONS_DB_UI_SYSTEM_UNEXPLORED_OVERLAY +Показывать различное наложение оверлея мыши для систем, которые еще не изучены игроком. OPTIONS_DB_UI_SYSTEM_NAME_UNOWNED_COLOR -Цвет шрифта чужих звездных систем на карте галактики. +Цвет шрифта ничьих звездных систем на карте галактики. OPTIONS_DB_UI_MEDIUM_FLEET_BUTTON_MIN_ZOOM -Минимальное увеличение, при котором значок среднего по величине флота отображается на карте галактики. +Минимальное увеличение при котором значок среднего по величине флота отображается на карте галактики. OPTIONS_DB_UI_SMALL_FLEET_BUTTON_MIN_ZOOM -Минимальное увеличение, при котором значок малого по величине флота отображается на карте галактики. +Минимальное увеличение при котором значок малого по величине флота отображается на карте галактики. OPTIONS_DB_UI_TINY_FLEET_BUTTON_MIN_ZOOM -Минимальное увеличение, при котором значок очень маленького по величине флота отображается на карте галактики. +Минимальное увеличение при котором значок очень маленького по величине флота отображается на карте галактики. OPTIONS_DB_UI_FLEET_SELECTION_INDICATOR_SIZE Размер индикатора выбора флота, в соотношении к значку размера флота. +OPTIONS_DB_UI_FLEET_WND_SCANLINE_CLR +Устанавливает цвет затенения полосы над графикой корабля/флота в окне флота. + +OPTIONS_DB_SHOW_FLEET_ETA +Показывать флот ETA (для перемещающихся флотов) в окне флота. + +OPTIONS_DB_SHOW_IDS_AFTER_NAMES +Показать идентификаторы после имен объектов. + OPTIONS_DB_UI_SYSTEM_SELECTION_INDICATOR_SIZE -Установить размер индикатора выбранной системы (относительно системного значка). +Устанавливает размер индикатора выбранной системы (относительно системного значка). OPTIONS_DB_UI_SYSTEM_SELECTION_INDICATOR_FPS -Скорость анимации индикатора выбора системы. +Устанавливает скорость анимации индикатора выбора системы, обороты в минуту. OPTIONS_DB_UI_SYSTEM_TINY_ICON_SIZE_THRESHOLD -Размер значков систем, до достижения которого значки маленьких систем показываются фиксированной величины. +Устанавливает размер значков систем, до достижения которого значки маленьких систем показываются фиксированной величины. OPTIONS_DB_UI_TOOLTIP_DELAY -Установить задержку всплывающей подсказки, в мс. +Устанавливает задержку всплывающей подсказки, мс. -OPTIONS_DB_UI_MULTIPLE_FLEET_WINDOWS -Если отмечено то нажатие на флот откроет окно флота, при уже открытых аналогичных окнах, иначе открытое ранее окно флота будет закрыто. +OPTIONS_DB_UI_TOOLTIP_LONG_DELAY +Устанавливает задержку альтернативных всплывающих подсказок, мс. -OPTIONS_DB_UI_WINDOW_QUICKCLOSE -Закрывать все окна (такие как окно флота, боковая панель системы) при нажатии правой кнопки мыши в любом месте карты. +OPTIONS_DB_UI_ENC_SEARCH_ARTICLE +При поиске в энциклопедии проверять совпадения в содержимом параграфа. -OPTIONS_DB_UI_SIDEPANEL_WIDTH -Установить размер боковой панели системы. +# Section title for options window logging -OPTIONS_DB_UI_SIDEPANEL_PLANET_MAX_DIAMETER -Установить максимальный размер планеты показываемой в боковой панели. +# This is a label to designate the logger as the general or default logger for a process +# %1% is the name of a process logger, client, server or ai -OPTIONS_DB_UI_SIDEPANEL_PLANET_MIN_DIAMETER -Установить минимальный размер планеты показываемой в боковой панели. +OPTIONS_DB_KEYPRESS_REPEAT_DELAY +Устанавливает задержку между удерживанием клавиши и генерированием повторяющихся нажатий. -OPTIONS_DB_UI_SIDEPANEL_PLANET_SHOWN -Показывать ли отрисованные планеты / астероиды на боковой панели. +OPTIONS_DB_KEYPRESS_REPEAT_INTERVAL +Устанавливает задержку между повторными нажатиями клавиш при удерживании клавиши. -OPTIONS_DB_GAMESETUP_SEED -'''Зерно, используется для случайной генерации галактики. -Галактики генерируемые с теми же настройками и тем же зерном будут одинаковым.''' +OPTIONS_DB_MOUSE_REPEAT_DELAY +Устанавливает задержку между нажатием кнопки мыши и генерацией повторяющихся кликов. + +OPTIONS_DB_MOUSE_REPEAT_INTERVAL +Устанавливает задержку между удерживанием кнопки мыши пока удерживается кнопка мыши. + +OPTIONS_DB_UI_MULTIPLE_FLEET_WINDOWS +Если включено, то при выборе нескольких флотов разворачиваются окна для каждого из них. Если нет, то открытие окна нового флота будет происходить поверх окна со старым. + +OPTIONS_DB_UI_WINDOW_QUICKCLOSE +Закрывать открытые окна флота при нажатии левой кнопки мыши в любом месте карты. + +OPTIONS_DB_UI_AUTO_REPOSITION_WINDOWS +Автоматически изменять позицию окон при изменении размеров игры. + +OPTIONS_DB_AUTO_ADD_SAVED_DESIGNS +Автоматически добавлять все сохраненные дизайны кораблей в известные дизайны империи. + +OPTIONS_DB_DESIGN_PEDIA_DYNAMIC +В окне «Дизайн» динамически обновлять страницу энциклопедии с подробными сведениями при редактировании имени дизайна. + +OPTIONS_DB_UI_SIDEPANEL_WIDTH +Устанавливает размер боковой панели системы. + +OPTIONS_DB_UI_SIDEPANEL_PLANET_MAX_DIAMETER +Устанавливает максимальный размер планеты показываемой в боковой панели. + +OPTIONS_DB_UI_SIDEPANEL_PLANET_MIN_DIAMETER +Устанавливает минимальный размер планеты показываемой в боковой панели. + +OPTIONS_DB_UI_SIDEPANEL_PLANET_SHOWN +Показывать ли изображения планет/астероидов на боковой панели. + +OPTIONS_DB_UI_QUEUE_WIDTH +Устанавливает ширину очередей на исследовательских и производственных экранах. + +OPTIONS_DB_UI_PROD_QUEUE_LOCATION +Показывать производственное местоположение для элементов в производственной очереди. + +OPTIONS_DB_GAMESETUP_SEED +'''Зерно используется для случайной генерации галактики. +Галактики генерируемые с теми же настройками и тем же зерном будут одинаковым'''. + +OPTIONS_DB_GAMESETUP_STARS +'''Количество генерируемых систем в галактике. -OPTIONS_DB_GAMESETUP_STARS -'''Количество генерируемых систем в галактике. Рекомендуется 15-30 систем на империю. -Большое количество приводит к лагам.''' + +Очень большое количество может приводить к возникновению лагов, особенно в поздней игре.''' OPTIONS_DB_GAMESETUP_GALAXY_SHAPE -Форма генерируемой галактики. +'''Форма генерируемой галактики. + +Различные формы создают различные тактические и стратегические вызовы.''' OPTIONS_DB_GAMESETUP_GALAXY_AGE -Возраст галактики. +'''Возраст галактики. + +У более молодых галактик будет больше ярких звезд, у более старых галактик будет больше черных дыр и нейтронных звезд.''' OPTIONS_DB_GAMESETUP_PLANET_DENSITY -Количество планет в звездных системах. +'''Количество планет в звездных системах. +''' OPTIONS_DB_GAMESETUP_STARLANE_FREQUENCY -Частота межзвездных путей. +'''Частота межзвездных путей. + +Если установлено значение «Низкая», большинство систем, как правило, имеют одну или две звезды. Если установлено значение «Высокий», большинство систем будут иметь звезды, связывающие их с ближайшими системами в пределах 120 световых лет.''' OPTIONS_DB_GAMESETUP_SPECIALS_FREQUENCY -Количество планет с аномалиями и артефактами. +'''Количество планет с аномалиями и артефактами. + +Некоторые аномалиями или артефактами охраняются стражами, оставленными «Предтечами». Только если значение «Частота появления Монстров» установлено в «Нет» они не будут появляться.''' OPTIONS_DB_GAMESETUP_MONSTER_FREQUENCY -Частота появления монстров в галактике. +'''Частота появления монстров в галактике. + +Этот параметр влияет на блуждающих монстров, но не на стражей, созданных специальными функциями, однако установка «Появление Монстров» в «Нет» также удалит и всех стражей.''' OPTIONS_DB_GAMESETUP_NATIVE_FREQUENCY -Количество планет с аборигенами. +'''Количество планет с аборигенами. + +Некоторые инопланетяне могут быть технологически продвинуты и могут создавать оборонительные корабли для защиты своих родных миров, они появятся даже если параметр «Частота появления монстров» установлен на «Нет».''' OPTIONS_DB_GAMESETUP_AI_MAX_AGGRESSION -Степень недружелюбности компьютерных игроков +'''Степень не дружелюбности компьютерных игроков. + +Большинство ИИ будут придерживаться установленного уровня, некоторые могут быть на один уровень ниже. В течение первого хода их уровень будет указан в окне сообщений.''' OPTIONS_DB_GAMESETUP_EMPIRE_NAME Название вашей империи. OPTIONS_DB_GAMESETUP_PLAYER_NAME -Имя будет использовано в игровых сообщениях. +Имя будет использовано в игровых сообщениях однопользовательской игры. OPTIONS_DB_GAMESETUP_EMPIRE_COLOR Цвет вашей империи. OPTIONS_DB_GAMESETUP_STARTING_SPECIES_NAME -Раса населяющая столицу вашей империи. +'''Раса, населяющая столицу вашей империи. + +Определяет вид вашего исходного родного мира и вашего первого колониального корабля и не влияет на планеты в вашей империи, колонизированной другими видами.''' OPTIONS_DB_GAMESETUP_NUM_AI_PLAYERS -Количество компьютерных игроков. +'''Количество компьютерных игроков. + + +Лучше иметь хотя бы одного игрока на каждые 15-30 систем в игре, играя с большим или меньшим количеством ИИ может создать проблемы с балансом. + +Очень большое количество ИИ может вызвать лаги, особенно на поздних этапах игры.''' OPTIONS_DB_UI_TECH_LAYOUT_HORZ_SPACING Горизонтальный интервал между технологиями на экране технологий. @@ -885,6 +1672,12 @@ OPTIONS_DB_UI_TECH_LAYOUT_HORZ_SPACING OPTIONS_DB_UI_TECH_LAYOUT_VERT_SPACING Вертикальный интервал между технологиями на экране технологий. +OPTIONS_DB_UI_TECH_LAYOUT_ZOOM_SCALE +Регулирует масштаб технологического окна. + +OPTIONS_DB_UI_TECH_CTRL_ICON_SIZE +Регулирует размер экрана исследований, масштабируется с текстовым размером шрифта. + OPTIONS_DB_SAVE_DIR Каталог для сохраненных игр. @@ -892,58 +1685,163 @@ OPTIONS_DB_RESOURCE_DIR Корневой каталог для главных файлов игры (настроек и файлов данных). OPTIONS_DB_LOG_LEVEL -Уровень лог-сообщений (уровни по понижению информативности: DEBUG, INFO, NOTICE, WARN, ERROR, CRIT, ALERT, FATAL, EMERG) +Нижний уровень выводимых в логе сообщений. OPTIONS_DB_STRINGTABLE_FILENAME -Языковой файл вида **.txt, где ** язык (например RU) +Устанавливает языко-специфичный файл с таблицами строк. + +OPTIONS_DB_AI_FOLDER_PATH +Устанавливает путь для каталога содержащего файлы скриптов AI, только для текущего исполнения, относительно каталога Ресурсов; по умолчанию - «AI». Используется для облегчения тестирования ИИ. + +OPTIONS_DB_AI_CONFIG +Доступно для AI через freeorioninterface, устанавливается только для текущего исполнения. Сейчас используется для указания необязательного файла конфигурации AI в папке скриптов AI; по умолчанию - пустая строка. Используется для облегчения тестирования ИИ. -OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER -Включить автосохранения в одиночной игре. +OPTIONS_DB_AI_CONFIG_TRAIT_AGGRESSION_FORCED +Логическое значение для тестирования ИИ, который показывает, что все ИИ вынуждены иметь одну и ту же черту Агрессивный. -OPTIONS_DB_AUTOSAVE_MULTIPLAYER -Включить автосохранения в сетевой игре. +OPTIONS_DB_AI_CONFIG_TRAIT_AGGRESSION_FORCED_VALUE +Принудительное значение черты Агрессивный. Значение от 0 до 5. + +OPTIONS_DB_AI_CONFIG_TRAIT_EMPIREID_FORCED +Логическое значение для тестирования ИИ, которое значит, что все ИИ вынуждены иметь один и тот же EmpireID. + +OPTIONS_DB_AI_CONFIG_TRAIT_EMPIREID_FORCED_VALUE +Принудительное значение EmpireID. Значение от 0 до 39. + +OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER_TURN_START +Включить автосохранения в начале хода для одиночной игры. + +OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER_TURN_END +Включить автосохранения в конце хода для одиночной игры. + +OPTIONS_DB_AUTOSAVE_MULTIPLAYER_TURN_START +Включить автосохранения в конце хода для одиночной игры. + +OPTIONS_DB_AUTOSAVE_MULTIPLAYER_TURN_END +Включить автосохранения в конце хода для сетевой игры. OPTIONS_DB_AUTOSAVE_TURNS Количество ходов между автосохранениями. OPTIONS_DB_AUTOSAVE_LIMIT -Максимальное число файлов сохранении. +Максимальное число файлов сохранений. OPTIONS_DB_UI_MOUSE_LR_SWAP -Инверсия правой и левой кнопок мыши. +Поменять местами кнопки мыши. OPTIONS_DB_MUSIC_VOLUME -Громкость (0 до 255) музыки. +Громкость музыки (от 0 до 255). OPTIONS_DB_QUICKSTART -Начать новую игру немедленно, минуя главное меню. +Начать новую быструю игру немедленно, минуя главное меню. -OPTIONS_DB_CHECKED_GL_VERSION -Проверка версии OpenGL. Если выключено, некоторые опции прорисовки будут изменены в соответствии с версией OpenGL, после чего этот параметр будет включен. +OPTIONS_DB_AUTO_N_TURNS +Автоматическое нажатие кнопки «Конец Хода» на первых N ходах (до 400 ходов, по умолчанию 0); Полезно для различных тестов, в частности с --quickstart или --load, возможно также --auto-quit OPTIONS_DB_RESET_FSSIZE -Сбрасывать сохраненное разрешение для полноэкранного режима. При включении разрешение будет устанавливаться на максимально большое, при выключении будут использоваться ранее сохраненные параметры. +Сбрасывать сохраненное разрешение для полноэкранного режима. При значении ложно разрешение будет устанавливаться на максимально большое, при значении верно будет использоваться ранее сохраненные параметры -OPTIONS_DB_VERBOSE_LOGGING_DESC -Детальная запись журнала событий вселенной и эффектов. +OPTIONS_DB_DUMP_EFFECTS_GROUPS_DESC +Включает сброс групп эффектов в технических, строительных или корабельных описаниях OPTIONS_DB_VERBOSE_SITREP_DESC -Включать сообщения об ошибках в отчет о событиях. +Включать сообщения об ошибках в отчет о событиях + +OPTIONS_DB_EFFECT_ACCOUNTING +Учитывать содержимое таблицы после внесенных изменений в состояние игры + +OPTIONS_DB_UI_SITREP_ICONSIZE +Устанавливает ширину и высоту значка сводки; по умолчанию 16 (мин. 12, макс. 64) OPTIONS_DB_LOAD -Загрузить одно пользовательскую игру. +Загрузить однопользовательскую игру + +OPTIONS_DB_EFFECTS_THREADS_UI_DESC +Определяет количество потоков, используемых для обработки эффектов в пользовательском интерфейсе. Более чем один поток может привести к непредсказуемым сбоям + +OPTIONS_DB_EFFECTS_THREADS_AI_DESC +Определяет количество потоков, используемых для обработки эффектов AI игроков. Более чем один поток может привести к непредсказуемым сбоям + +OPTIONS_DB_EFFECTS_THREADS_SERVER_DESC +Указывает количество потоков, используемых при обработке эффектов на сервере. Более чем один поток может привести к непредсказуемым сбоям + +OPTIONS_DB_AUTO_QUIT +Автоматический выход после того, как завершены все ходы заданные с помощью -auto-advance-n-turn (по умолчанию - ноль). Полезно для различных тестов, в частности с --quickstart или --load + +OPTIONS_DB_BINARY_SERIALIZATION +Использует бинарную сериализацию для сохранения игр. Двоичная сериализация быстрее сохраняется и загружается, но может быть невозможна загрузка игр в другой операционной системе + +OPTIONS_DB_UI_WINDOWS_EXISTS +Истина, если в настоящее время существует окно с заданным именем конфигурации, ложно - если окно было создана и удалено, не существует - если ни одно окно не было создано с таким именем + +OPTIONS_DB_UI_WINDOWS_LEFT +Положение левого края окна в полноэкранном режиме + +OPTIONS_DB_UI_WINDOWS_TOP +Положение верхнего края окна в полноэкранном режиме + +OPTIONS_DB_UI_WINDOWS_LEFT_WINDOWED +Положение левого края окна в оконном режиме + +OPTIONS_DB_UI_WINDOWS_TOP_WINDOWED +Положение верхнего края окна в оконном режиме + +OPTIONS_DB_UI_WINDOWS_WIDTH +Ширина окна в полноэкранном режиме + +OPTIONS_DB_UI_WINDOWS_HEIGHT +Высота окна в полноэкранном режиме + +OPTIONS_DB_UI_WINDOWS_WIDTH_WINDOWED +Ширина окна в оконном режиме + +OPTIONS_DB_UI_WINDOWS_HEIGHT_WINDOWED +Высота окна в оконном режиме + +OPTIONS_DB_UI_WINDOWS_VISIBLE +Включает отображение окна, когда оно доступно + +OPTIONS_DB_UI_WINDOWS_PINNED +Включает окно закрепленное на месте + +OPTIONS_DB_UI_WINDOWS_MINIMIZED +Включает минимизацию окна + +OPTIONS_DB_WINDOW_RESET +Заставляет окна использовать свои свойства по умолчанию вместо того, чтобы помнить их предыдущие позиции + +OPTIONS_DB_HIDDEN_SITREP_TEMPLATES_DESC +Скрытые шаблоны сводки + +OPTIONS_DB_PRODUCTION_PEDIA_HIDDEN +Истина, если игрок вручную закрыл окно производственного окна + +OPTIONS_DB_RESEARCH_PEDIA_HIDDEN +Истина, если игрок вручную закрыл окно обзора исследований + +OPTIONS_DB_UI_SAVE_DIALOG_TOOLTIP_DELAY +Задержка всплывающей подсказки для сохранения + +OPTIONS_DB_OBJECTS_LIST_COLUMN_INFO +Содержимое столбца списка объектов + +OPTIONS_DB_OBJECTS_LIST_COLUMN_WIDTH +Ширины столбцов списка объектов + +OPTIONS_DB_FLEET_WND_AGGRESSION +Настройка нападения по умолчанию для флота OPTIONS_DB_UI_COMBAT_SUMMARY_BAR_WIDTH_PROPORTIONAL -Если ВКЛ, ряды участников сражения будут масштабироваться по горизонтали пропорционально их текущего здоровья. +Если ВКЛ, ряды участников сражения будут масштабироваться по горизонтали пропорционально их текущего здоровья OPTIONS_DB_UI_COMBAT_SUMMARY_BAR_HEALTH_SMOOTH Если ВКЛ, цвета рядов участников сражения будут интерполированы плавно в соответствии с их оставшимся здоровьем (в %) OPTIONS_DB_UI_COMBAT_SUMMARY_BAR_HEIGHT_PROPORTIONAL -Если ВКЛ, ряды участников сражений масштабируются вертикально пропорционально их МАКС здоровью. +Если ВКЛ, ряды участников сражений масштабируются вертикально пропорционально их МАКС здоровью OPTIONS_DB_UI_COMBAT_SUMMARY_GRAPH_HEIGHT_PROPORTIONAL -Если ВКЛ, то график империй в сражении масштабируются по отношению к самому высокому значению Здоровья во флоте. +Если ВКЛ, то график империй в сражении масштабируются по отношению к самому высокому значению Здоровья во флоте OPTIONS_DB_UI_COMBAT_SUMMARY_DEAD_COLOR Цвет уничтоженных юнитов на графическом боевом отчете @@ -954,6 +1852,89 @@ OPTIONS_DB_UI_COMBAT_SUMMARY_WOUND_COLOR OPTIONS_DB_UI_COMBAT_SUMMARY_HEALTH_COLOR Цвет здоровья юнитов на графическом боевом отчете +OPTIONS_DB_NETWORK_DISCOVERY_PORT +Используемый сетевой порт для подключения к серверу + +OPTIONS_DB_NETWORK_MESSAGE_PORT +Сетевой порт для обмена сообщениями клиент-сервер + + +## +## Option sections +## + +OPTIONS_DB_SECTION_ALL +Все опции + +OPTIONS_DB_SECTION_AUDIO +Звук + +OPTIONS_DB_SECTION_AUDIO_MUSIC +Фоновая музыка + +OPTIONS_DB_SECTION_AUDIO_EFFECTS +Звуковые эффекты + +OPTIONS_DB_SECTION_AUDIO_EFFECTS_PATHS +Звуковые эффекты (пути) + +OPTIONS_DB_SECTION_EFFECTS +Эффекты + +OPTIONS_DB_SECTION_LOGGING +Логи + +OPTIONS_DB_SECTION_MISC +Разное + +OPTIONS_DB_SECTION_NETWORK +Сервер и сеть + +OPTIONS_DB_SECTION_SAVE +Сохранение игры + +OPTIONS_DB_SECTION_SETUP +Настройки старта новой игры + +OPTIONS_DB_SECTION_UI +Пользовательский интерфейс + +OPTIONS_DB_SECTION_UI_COLORS +Цвета + +OPTIONS_DB_SECTION_UI_CONTROL +Управление (общее) + +OPTIONS_DB_SECTION_UI_HOTKEYS +Горячие клавиши + +OPTIONS_DB_SECTION_UI_MAP +Карта галактики + +OPTIONS_DB_SECTION_UI_MAP_FLEET +Флоты (карта) + +OPTIONS_DB_SECTION_UI_FLEET +Окно флота + +OPTIONS_DB_UI_WINDOW +Окно (общее) + +OPTIONS_DB_SECTION_VERSION +Версия + +OPTIONS_DB_SECTION_VIDEO +Видео + +OPTIONS_DB_SECTION_VIDEO_FPS +Кадров в секунду + +OPTIONS_DB_SECTION_VIDEO_FULLSCREEN +Полноэкранный режим + +OPTIONS_DB_SECTION_VIDEO_WINDOWED +Оконный режим + ## ## File dialog @@ -974,13 +1955,12 @@ FILE_DLG_FILENAME_IS_A_DIRECTORY является каталогом.''' FILE_DLG_FILE_DOES_NOT_EXIST -'''File "%1%" +'''Файл "%1%" не существует.''' FILE_DLG_DEVICE_IS_NOT_READY Устройство не готово. - ## ## Color selection dialog ## @@ -991,7 +1971,6 @@ COLOR_DLG_NEW COLOR_DLG_OLD Старый - ## ## Intro screen ## @@ -999,6 +1978,9 @@ COLOR_DLG_OLD INTRO_WINDOW_TITLE FreeOrion Главное меню +INTRO_BTN_CONTINUE +Продолжить + INTRO_BTN_SINGLE_PLAYER Одиночная игра @@ -1017,6 +1999,9 @@ INTRO_BTN_OPTIONS INTRO_BTN_ABOUT Об игре +INTRO_BTN_WEBSITE +Открыть сайт freeorion.org + INTRO_BTN_CREDITS Разработчики @@ -1024,8 +2009,10 @@ INTRO_BTN_EXIT Выход ERR_CONNECT_TIMED_OUT -Таймаут соединения с сервером. +Таймаут соединения с сервером +INTRO_CREDITS_LICENSE +Выпущено под ## ## Server Setup Screen @@ -1047,10 +2034,24 @@ HOST_GAME_BN Создать новую игру JOIN_GAME_BN -Присоединиться к игре +Присоединиться как REFRESH_LIST_BN -Обновить список +Обновить + + +## +## Password Dialog +## + +AUTHENTICATION_WINDOW_TITLE +Аутентификация + +AUTHENTICATION_DESC +Это имя игрока требует ввода пароля. Внимание! Пароль будет передан в обычном текстовом файле. + +PASSWORD_LABEL +Пароль ## @@ -1061,7 +2062,7 @@ MPLOBBY_WINDOW_TITLE Установки сетевой игры MULTIPLAYER_GAME_START_CONDITIONS -Необходимы уникальные цвет и название империи. +Необходимы уникальные цвета и названия империи MULTIPLAYER_PLAYER_LIST_TYPES Тип @@ -1081,6 +2082,12 @@ MULTIPLAYER_PLAYER_LIST_ORIGINAL_NAMES MULTIPLAYER_PLAYER_LIST_STARTING_SPECIES Начальная раса +EDITABLE_GALAXY_SETTINGS +Все могут изменять параметры + +EDITABLE_GALAXY_SETTINGS_DESC +Все игроки могут изменять параметры галактики и ИИ. + NEW_GAME_BN Новая игра @@ -1090,6 +2097,17 @@ LOAD_GAME_BN START_GAME_BN Запуск игры +READY_BN +Принять + +NOT_READY_BN +Отклонить + +PLAYER_ENTERED_GAME +%1% зашёл в игру + +PLAYER_LEFT_GAME +%1% покинул игру ## ## Galaxy Setup Screen @@ -1111,13 +2129,13 @@ GSETUP_SPECIES Начальная раса GSETUP_NUMBER_AIS -Количество компьютерных игроков +Количество оппонентов GSETUP_SEED -Зерно +Семя GSETUP_RANDOM_SEED -Создать случайное зерно +Создать случайное семя GSETUP_STARS Звезды @@ -1135,16 +2153,16 @@ GSETUP_PLANET_DENSITY Количество планет GSETUP_SPECIALS_FREQ -Артефакты +Артефактов GSETUP_MONSTER_FREQ -Монстры +Монстров GSETUP_NATIVE_FREQ -Аборигены +Аборигенов GSETUP_AI_AGGR -Оппоненты +Агрессия ИИ # A galaxy shape to select when generating a universe. GSETUP_2ARM @@ -1166,6 +2184,18 @@ GSETUP_CLUSTER GSETUP_ELLIPTICAL Эллиптическая +# A galaxy shape to select when generating a universe. +GSETUP_DISC +Диск + +# A galaxy shape to select when generating a universe. +GSETUP_BOX +Квадрат + +# A galaxy shape to select when generating a universe. +GSETUP_IRREGULAR +Иррегулярная + # A galaxy shape to select when generating a universe. GSETUP_RING Кольцо @@ -1204,28 +2234,31 @@ GSETUP_ANCIENT # AI aggression level. GSETUP_BEGINNER -Слабый +Слабые # AI aggression level. GSETUP_TURTLE -Медлительный +Медлительные # AI aggression level. GSETUP_CAUTIOUS -Осторожный +Осторожные # AI aggression level. GSETUP_TYPICAL -Нормальный +Нормальные # AI aggression level. GSETUP_AGGRESSIVE -Агрессивный +Агрессивные # AI aggression level. GSETUP_MANIACAL -Маниакальный +Маниакальные +# Random species selection description +GSETUP_SPECIES_RANDOM_DESC +Выбирает случайный вид из доступных игровых видов. ## ## About dialog @@ -1241,8 +2274,7 @@ VISION Технические особенности FREEORION_VISION -FreeOrion - игра с открытым исходным кодом, основанная на Master of Orion - пошаговая масштабная космическая стратегия, строящаяся на классической модели '4X' с добавлением элементов развития наций, как в Europa Universalis 2 с гибким движком тактических боев. Так как проект модульный, принцип открытых исходных кодов позволяет значительно упростить доработку игровых элементов и движка с помощью сообществ, в то время как команда FreeOrion будет сосредоточена построением живущей, дышащей вселенной в "великой кампании". - +FreeOrion - игра с открытым исходным кодом, основанная на игре Master of Orion. Пошаговая стратегическая космическая игра с масштабной вселенной, строящаяся на классической модели '4X' с добавлением элементов развития наций, как в Europa Universalis 2, с гибким движком тактических боев. Так как проект модульный, принцип открытого исходного кода позволяет значительно упростить доработку игровых элементов и движка с помощью сообщества, в то время как команда FreeOrion сосредоточена построением живой, дышащей вселенной в "великой кампании". ## ## In-game menu @@ -1260,14 +2292,108 @@ GAME_MENU_LOAD GAME_MENU_RESIGN Выход +GAME_MENU_CONCEDE +Сдаться + GAME_MENU_SAVE_FILES Файлы сохраненных игр +BUTTON_DISABLED +Кнопка отключена + +SAVE_DISABLED_BROWSE_TEXT +Кнопка «Сохранить игру» отключена либо потому, что это многопользовательская игра, в которой вы не являетесь ни хостом, ни модератором, либо потому, что в то время, когда это меню было открыто, по крайней мере один из ИИ еще не завершил свои передвижения на текущем ходу (индикация значка состояния в виде зеленого треугольника слева от значка игрока в окне «Империи»). + +GAME_MENU_REALLY_CONCEDE +Вы действительно хотите сдаться? ## ## Save game dialog ## +SAVE_TIME_TITLE +Время + +SAVE_TURN_TITLE +Ход + +SAVE_PLAYER_TITLE +Игрок + +SAVE_EMPIRE_TITLE +Империя + +SAVE_FILE_TITLE +Файл + +SAVE_SEED_TITLE +Семя + +SAVE_GALAXY_AGE_TITLE +Возраст галактики + +SAVE_MONSTER_FREQ_TITLE +Частота появления монстров + +SAVE_NATIVE_FREQ_TITLE +Частота появления аборигенов + +SAVE_PLANET_FREQ_TITLE +Частота появления планет + +SAVE_SPECIALS_FREQ_TITLE +Частота появления аномалий + +SAVE_STARLANE_FREQ_TITLE +Частота звездных переходов + +SAVE_GALAXY_SIZE_TITLE +Размер галактики + +SAVE_GALAXY_SHAPE_TITLE +Форма галактики + +SAVE_AI_AGGRESSION_TITLE +Агрессивность АИ + +SAVE_NUMBER_EMPIRES_TITLE +Империи + +SAVE_NUMBER_HUMANS_TITLE +Игроки-люди + +SAVE_DIALOG_ROW_BROWSE_TEMPLATE +''' +Имя файла: %rawtext:file% +Время записи: %rawtext:time% +Ход: %rawtext:turn% +Семя: %rawtext:seed% +Игрок: %rawtext:player% +Империя: %rawtext:empire% +Империи: %rawtext:number_of_empires% +Игроки-люди: %rawtext:number_of_humans% +Галактика + Форма: %rawtext:galaxy_shape% + Возраст: %rawtext:galaxy_age% +Частота появления: + Артефакты: %rawtext:specials_freq% + Монстры: %rawtext:monster_freq% + Аборигены: %rawtext:native_freq% +''' + +SAVE_FILENAME +Файл: + +SAVE_DIRECTORY +Путь: + +# %1% entered path to the save game file that should be deleted. +SAVE_REALLY_DELETE +Вы уверены, что хотите удалить %1%? + +# %1% entered path to the save game file that should be overwritten. +SAVE_REALLY_OVERRIDE +Вы действительно хотите перезаписать %1%? ## ## Game options @@ -1280,19 +2406,22 @@ OPTIONS_MULTIPLE_FLEET_WNDS Несколько окон для флота OPTIONS_QUICK_CLOSE_WNDS -Быстрое закрытие окон +Быстрое закрытие окон флота OPTIONS_SHOW_SIDEPANEL_PLANETS Показывать боковую панель с планетами -OPTIONS_MISC_UI -Различные Настройки Интерфейса +OPTIONS_AUTO_REPOSITION_WINDOWS +Автоматическая перестановка окон -OPTIONS_SINGLEPLAYER -Одиночная игра +OPTIONS_DISPLAY_TIMESTAMP +Показывать метку времени в чате -OPTIONS_MULTIPLAYER -Сетевая Игра +OPTIONS_MISC_UI +Прочие настройки интерфейса + +OPTIONS_AUTOSAVE_LIMIT +Количество автосохранений OPTIONS_AUTOSAVE_TURNS_BETWEEN Ходов между автосохранениями @@ -1304,13 +2433,22 @@ OPTIONS_FONTS Шрифты OPTIONS_FONT_SIZES -Размеры Шрифтов +Размеры шрифтов OPTIONS_FONT_TEXT Текст OPTIONS_FONT_TITLE -Заголовки окон +Заголовки + +SHOW_FONT_TEXTURES +Показать структуру шрифта + +OPTIONS_RESEARCH_WND +Окно исследований + +OPTIONS_QUEUES +Очереди OPTIONS_TECH_SPACING_HORIZONTAL Горизонтальный @@ -1318,23 +2456,41 @@ OPTIONS_TECH_SPACING_HORIZONTAL OPTIONS_TECH_SPACING_VERTICAL Вертикальный +OPTIONS_TECH_LAYOUT_ZOOM +Масштаб + +OPTIONS_TECH_CTRL_ICON_SIZE +Размер значков + OPTIONS_TOOLTIP_DELAY Задержка подсказок (мс) +OPTIONS_KEYPRESS_REPEAT_DELAY +Задержка повтор. нажатия клавиши (мс) + +OPTIONS_KEYPRESS_REPEAT_INTERVAL +Интервал повтор. нажатия клавиши (мс) + +OPTIONS_MOUSE_REPEAT_DELAY +Задержка повтор. нажатия клавиши мыши (мс) + +OPTIONS_MOUSE_REPEAT_INTERVAL +Интервал повтор. нажатия клавиши мыши (мс) + OPTIONS_SWAP_MOUSE_LR Инверсия правой и левой кнопок мыши OPTIONS_VIDEO_MODE -Видеорежим (для вступления в силу необходим перезапуск) +Настройки полноэкранного режима OPTIONS_VIDEO_MODE_LIST_DESCRIPTION -Разрешение и глубина цвета (установить настроить, для выбора собственных параметров) +Разрешение и глубина цвета в полноэкранном режиме игры. При нажатии "Применить" настройки могут примениться сразу, или потребуется рестарт. OPTIONS_VIDEO_MODE_WINDOWED -Оконный режим +Настройки оконного режима. OPTIONS_VIDEO_MODE_WINDOWED_SPINNERS_DESCRIPTION -Установить размеры окна игры. Глубина цвета одинакова с полноэкранным режимом. +Устанавливает размеры окна в оконном режиме игры. Глубина цвета будет такой же, как в настройках полноэкранного режима. OPTIONS_APP_WIDTH_WINDOWED Ширина окна @@ -1343,29 +2499,44 @@ OPTIONS_APP_HEIGHT_WINDOWED Высота окна OPTIONS_APP_LEFT_WINDOWED -Позиция Левого края Окна +Позиция левого края окна OPTIONS_APP_TOP_WINDOWED -Позиция Верхнего края Окна +Позиция верхнего края окна OPTIONS_FULLSCREEN -Полноэкранный +Полноэкранный режим. + +OPTIONS_FAKE_MODE_CHANGE +Замена ориг. разрешения подставным OPTIONS_FULLSCREEN_MONITOR_ID -Полноэкранный Монитор ID +ID полноэкранного монитора OPTIONS_SHOW_FPS -Показать FPS +Показывать FPS OPTIONS_LIMIT_FPS -Ограничить FPS +Ограничивать FPS OPTIONS_MAX_FPS Максимум FPS +OPTIONS_LIMIT_FPS_NO_FOCUS +Предел FPS (вне фокуса) + +OPTIONS_MAX_FPS_NO_FOCUS +Максимум FPS (вне фокуса) + OPTIONS_APPLY Применить +OPTIONS_FLUSH_STRINGTABLE +Очистка файла stringtables + +OPTIONS_WINDOW_RESET +Применить перестановку окон + OPTIONS_GALAXY_MAP Галактическая карта @@ -1385,37 +2556,37 @@ OPTIONS_UI_SYSTEM_FOG_SPACING Промежуток между линиями тумана войны OPTIONS_UI_SYSTEM_CIRCLES -Круги звёздных систем +Круги звездных систем OPTIONS_UI_SYSTEM_CIRCLE_SIZE -Относительный размер кругов звёздных систем +Относит. размер кругов звездных систем OPTIONS_UI_SYSTEM_SELECTION_INDICATOR_SIZE -Относительный размер индикатора выбора звезды +Относит. размер индикатора выбора звезды OPTIONS_UI_SYSTEM_SELECTION_INDICATOR_FPS -Скорость анимации индикатора выбора звезды +Ск-сть анимации индикатора выбора звезды OPTIONS_UI_SYSTEM_TINY_ICON_SIZE_THRESHOLD -Порог размера минимального размера значка системы +Порог размера миним. размера значка системы OPTIONS_UI_SYSTEM_NAME_UNOWNED_COLOR Цвет названия бесхозных систем OPTIONS_FLEET_ICONS -Значки звёздных флотов +Значки звездных флотов OPTIONS_UI_TINY_FLEET_BUTTON_MIN_ZOOM -Минимальное увеличение для значка самого маленького флота +Миним. увеличение для значка оч. маленького флота OPTIONS_UI_SMALL_FLEET_BUTTON_MIN_ZOOM -Минимальное увеличение для значка маленького флота +Миним. увеличение для значка маленького флота OPTIONS_UI_MEDIUM_FLEET_BUTTON_MIN_ZOOM -Минимальное увеличение для значка среднего флота +Миним. увеличение для значка среднего флота OPTIONS_UI_FLEET_SELECTION_INDICATOR_SIZE -Относительный размер индикатора выбора флота +Относит. размер индикатора выбора флота OPTIONS_GALAXY_MAP_GENERAL Общие @@ -1427,11 +2598,17 @@ OPTIONS_GALAXY_MAP_STARFIELDS Звезды в фоне у галактики OPTIONS_GALAXY_MAP_SCALE_LINE -Шкала увеличения галактики +Масштаб увеличения галактики + +OPTIONS_GALAXY_MAP_SCALE_CIRCLE +Круговой масштаб увеличения галактики OPTIONS_GALAXY_MAP_ZOOM_SLIDER Слайдер увеличения галактики +OPTIONS_UI_SYSTEM_UNEXPLORED_OVERLAY +Отличительный оверлей при наведении мыши на неисследованную систему + OPTIONS_GALAXY_MAP_DETECTION_RANGE Радиусы обнаружения @@ -1441,11 +2618,14 @@ OPTIONS_GALAXY_MAP_DETECTION_RANGE_OPACITY OPTIONS_GALAXY_MAP_POPUP Контекстное меню на карте галактики +OPTIONS_UI_HIDE_MAP_PANELS +Скрывать панели карт в режиме производства + OPTIONS_STARLANES -Звёздные пути +Звездные пути OPTIONS_STARLANE_THICKNESS -Толщина межзвёздных путей +Толщина межзвездных путей OPTIONS_RESOURCE_STARLANE_COLOURING Цветные линии транспортировки ресурсов @@ -1463,28 +2643,34 @@ OPTIONS_FLEET_SUPPLY_LINE_DOT_RATE Скорость точек линий обеспечения флотов OPTIONS_UNOWNED_STARLANE_COLOUR -Цвет звёздных путей по умолчанию +Цвет звездных путей по умолчанию + +OPTIONS_UI_QUEUE_WIDTH +Ширина очереди + +OPTIONS_UI_PROD_QUEUE_LOCATION +Показать расположение очереди производства OPTIONS_MUSIC Музыка OPTIONS_UI_SOUNDS -Звуки интерфейса +Звуки OPTIONS_BACKGROUND_MUSIC Фоновая музыка OPTIONS_SOUNDS -Звуки +Настройки звуков OPTIONS_SOUND_CLOSE Закрытие окна OPTIONS_SOUND_MINIMIZE -Сворачивание окна +Свор-ние окна OPTIONS_SOUND_MAXIMIZE -Разворачивание окна +Разв-ние окна OPTIONS_SOUND_CLICK Нажатие кнопки @@ -1499,7 +2685,7 @@ OPTIONS_SOUND_FLEET_ROLLOVER Отпускание кнопки флота OPTIONS_SOUND_SYSTEM_ROLLOVER -Отпускание кнопки звёздной системы +Отпускание кнопки звездной системы OPTIONS_SOUND_WINDOW Звуки окна @@ -1507,29 +2693,38 @@ OPTIONS_SOUND_WINDOW OPTIONS_SOUND_BUTTON Звуки кнопки +OPTIONS_SOUND_NEWTURN +Новый ход + +OPTIONS_SOUND_NEWTURN_TOGGLE +Включить звук при начале нового хода + +OPTIONS_SOUND_NEWTURN_FILE +Звуковой файл + OPTIONS_SOUND_ALERT -Тревога +Предупреждение OPTIONS_SOUND_TYPING Набор текста OPTIONS_SOUND_TURN -Кнопка конец хода +Кнопка конца хода OPTIONS_SOUND_SIDEPANEL -Открытие боковой панели +Открытие панели OPTIONS_SOUND_PLANET -Щелчок на планету +Клик на планете OPTIONS_SOUND_LIST Звук списка OPTIONS_SOUND_DROP -Выбросить предмет +Сбросить предмет OPTIONS_SOUND_PULLDOWN -Открытие ниспадающего списка +Откр. нисп. списка OPTIONS_SOUND_SELECT Выбор из списка @@ -1591,12 +2786,24 @@ OPTIONS_PAGE_AUTOSAVE OPTIONS_PAGE_UI Интерфейс +OPTIONS_PAGE_OBJECTS_WINDOW +Список объектов + +OPTIONS_COLUMNS +Ширина столбца + OPTIONS_PAGE_COLORS Цвета OPTIONS_PAGE_DIRECTORIES Каталоги +OPTIONS_PAGE_LOGS +Логи + +OPTIONS_PAGE_MISC +Другой + OPTIONS_TECH_COLORS Технологии @@ -1607,10 +2814,10 @@ OPTIONS_KNOWN_TECH_COLORS Известные технологии OPTIONS_RESEARCHABLE_TECH_COLORS -Доступные к исследованию технологии +Доступные к исследованию техн. OPTIONS_UNRESEARCHABLE_TECH_COLORS -Недоступные к исследованию технологии +Недоступные к исследованию техн. OPTIONS_TECH_PROGRESS_COLORS Полоса прогресса исследования @@ -1622,16 +2829,19 @@ OPTIONS_PROGRESS_BACKGROUND_COLOR Фон OPTIONS_FOLDER_SETTINGS -Каталог установок +Путь настроек OPTIONS_FOLDER_SAVE -Каталог сохранении +Сохранения + +OPTIONS_SERVER_FOLDER_SAVE +Сохранения (сервер) OPTIONS_LANGUAGE_FILE Языковые файлы OPTIONS_VOLUME_AND_MUSIC -Громкость и музыка +Громкость OPTIONS_MUSIC_FILE Файлы музыки @@ -1640,14 +2850,62 @@ OPTIONS_SOUND_FILE Файлы эффектов OPTIONS_DUMP_EFFECTS_GROUPS_DESC -Описания автоматически сгенерированных эффектов +Описания авто сгенерированных эффектов OPTIONS_VERBOSE_LOGGING_DESC -Более детальное логирование для отладки +Более детальное логирование для отладки OPTIONS_VERBOSE_SITREP_DESC -Включить ошибки в отчёты +Включать ошибки в отчеты + +OPTIONS_SHOW_IDS_AFTER_NAMES +Идентификаторы после имен + +OPTIONS_EFFECT_ACCOUNTING +Учитывать изменения + +OPTIONS_EFFECTS_THREADS_UI +Потоки обработки эффектов (пользовательский интерфейс) + +OPTIONS_EFFECTS_THREADS_AI +Потоки обработки эффектов (ИИ) + +OPTIONS_EFFECTS_THREADS_SERVER +Потоки обработки эффектов (сервер) + +OPTIONS_ADD_SAVED_DESIGNS +Добавить пользовательские дизайны кораблей при запуске игры + +OPTIONS_ADD_DEFAULT_DESIGNS +Добавить все базовые дизайны кораблей при запуске игры + +OPTIONS_USE_BINARY_SERIALIZATION +Создание двоичных (сжатых) файлов сохранения + +OPTIONS_USE_XML_ZLIB_SERIALIZATION +Использовать компрессию для XML-файлов сохранения + +OPTIONS_CREATE_PERSISTENT_CONFIG +Сохранять конфигурацию в файл + +OPTIONS_CREATE_PERSISTENT_CONFIG_TOOLTIP_TITLE +Создать файл конфигурации (Расширенная опция) + +OPTIONS_CREATE_PERSISTENT_CONFIG_TOOLTIP_DESC +'''Сохранение текущих настроек в качестве настроек по-умолчанию. Благодаря этому они всегда будут иметь приоритет перед любыми другими настройками, кроме консольных команд. +В этом файле будут содержаться только изменённые значения различных базовых параметров. +Любой другой файл настроек будет перезаписан при этом. + +Данные настройки могут стать невалидными при смене версий игры. +Настройки из ранее открытого окна могут не примениться, если соответствующая им игровая сессия не была запущена. + +Смотрите статью [[CONFIG_GUIDE_TITLE]] для большей информации.''' + +OPTIONS_CREATE_PERSISTENT_CONFIG_SUCCESS +Успешно создан/перезаписан persistent_config.xml +OPTIONS_CREATE_PERSISTENT_CONFIG_FAILURE +Невозможно создать файл persistent_config.xml. Подробности ошибки смотрите в файле логов. ## ## Main map window @@ -1657,6 +2915,9 @@ OPTIONS_VERBOSE_SITREP_DESC MAP_BTN_TURN_UPDATE Ход %1% +MAP_SCALE_INDICATOR +%1% сл + MAP_BTN_MENU Меню @@ -1679,16 +2940,16 @@ MAP_BTN_RESEARCH Наука MAP_BTN_PRODUCTION -Производство +Промышленность MAP_BTN_DESIGN -Корабли +Дизайн MAP_BTN_GRAPH Графики MAP_BTN_PEDIA -Справочник +Энциклопедия MAP_BTN_EMPIRES Империи @@ -1697,59 +2958,87 @@ MAP_BTN_MESSAGES Сообщения MAP_PRODUCTION_TITLE -Производство - -MAP_PROD_WASTED_TITLE -Простаивающие мощности - -# %1% number of production points generated. -# %2% number of production points currently not used for production. -MAP_PROD_WASTED_TEXT -'''Все мощности : %1% -Простаивающие мощности : %2% +Промышленность -Меню производства''' +MAP_PROD_CLICK_TO_OPEN +''' +Нажмите здесь для открытия меню промышленности''' MAP_RESEARCH_TITLE Наука -MAP_RES_WASTED_TITLE -Простаивающие лаборатории +MAP_RESEARCH_WASTED_TITLE +Теряемые очки исследования + +MAP_RES_CLICK_TO_OPEN +''' +Нажмите здесь для открытия меню исследований''' + +MAP_STOCKPILE_TITLE +Резервы империи + +RESOURCE_TT_OUTPUT +Текущая мощность + +RESOURCE_TT_TARGET_OUTPUT +Целевая мощность + +RESOURCE_TT_USED +Используется + +RESOURCE_TT_EXCESS +Избыток -# %1% number of research points generated. -# %2% number of research points currently not used for research. -MAP_RES_WASTED_TEXT -'''Все лаборатории : %1% -Простаивающие лаборатории : %2% +RESOURCE_TT_TO_STOCKPILE +В резервы -Меню науки''' +RESOURCE_TT_WASTED +Потеряно MAP_POPULATION_DISTRIBUTION Перепись населения CENSUS_SPECIES_HEADER -Вид +Виды CENSUS_TAG_HEADER -'''Характеристика ''' +Характеристики MAP_DETECTION_TITLE Обнаружение MAP_DETECTION_TEXT -Радиус обнаружения объектов +Радиус обнаружения MAP_TRADE_TITLE Торговля MAP_TRADE_TEXT -Эффективность торговли. (Не реализовано) +Эффективность торговли. (Торговля не реализована) -MAP_FLEET_TITLE -Флот +MAP_FLEET_SHIP_COUNT +Всего кораблей + +MAP_FLEET_ARMED_COUNT +Военные корабли + +MAP_FLEET_UNARMED_COUNT +Невооруженные суда + +MAP_FLEET_TROOP_COUNT +Десантные корабли + +MAP_FLEET_COLONY_COUNT +Колониальные суда + +MAP_FLEET_CARRIER_COUNT +Авианосные корабли -MAP_FLEET_TEXT -Полное количество кораблей +MAP_FLEET_PART_COUNT +Всего кораб. частей + +MAP_FLEET_SLOT_COUNT +Всего кораб. слотов ## @@ -1759,12 +3048,12 @@ MAP_FLEET_TEXT # %1% resource type currently produced by the system. # %2% amount of the resource currently produced by the system. RESOURCE_PRODUCTION_TOOLTIP -'''%1% в системе: %2% ''' +Система %1%: %2% # %1% resource type currently consumed by the system. # %2% amount of the resource currently consumed by the system. RESOURCE_ALLOCATION_TOOLTIP -'''%1%: %2% ''' +Система %1%: %2% INDUSTRY_PRODUCTION Промышленность @@ -1775,14 +3064,20 @@ RESEARCH_PRODUCTION TRADE_PRODUCTION Торговля +STOCKPILE_GENERATION +Вместимость резервов + INDUSTRY_CONSUMPTION -Используется +Промышленное потребление RESEARCH_CONSUMPTION -Загруженность +Научные затраты TRADE_CONSUMPTION -Потребление +Торговые расходы + +STOCKPILE_USE +Задействование резервов IMPORT_EXPORT_TOOLTIP Импорт / Экспорт @@ -1794,8 +3089,7 @@ RESOURCE_EXPORT Доступно для экспорта: RESOURCE_SELF_SUFFICIENT -Ни импорта, ни экспорта. - +Ни импорта, ни экспорта ## ## Side panel/Planet panel @@ -1820,32 +3114,46 @@ PL_OUTPOST Основать аванпост # %1% number of ground troops that will be deployed on the planet. +# %2% number of defending troops present on planet PL_INVADE -Вторжение (+%1%) +Вторжение (+%1% / %2%) PL_CANCEL_INVADE Отменить вторжение +PL_BOMBARD +Бомбардировать + +PL_CANCEL_BOMBARD +Отменить бомбардировку + +# %1% the planet size as text (small, medium, large, ...). +# %2% type of the planet environment (desert, oceanic, terran, ...). + +# %1% the planet size as text (small, medium, large, ...). +# %2% type of the planet environment (desert, oceanic, terran, ...). +# %3% suitability of the planet for a concrete species (adequate, hostile, ...). + PL_NO_VISIBILITY -'''Планету в данный момент не видно. ''' +Планету в данный момент не видно. PL_BASIC_VISIBILITY -Планета скрыта от датчиков. +Планета скрыта от сенсоров. # %1% turn number where this planet was completely visible to the querying # player. PL_LAST_TURN_SEEN -Последний раз видели в ходу %1%. +Последний раз видели на %1% ходу. # %1% turn number where this planet was scanned by the querying player. PL_LAST_TURN_SCANNED -Последний раз сканировали в ходу %1%. +Последний раз сканировали на %1% ходу. PL_NEVER_SEEN -Эта планета никогда не видела вашу империю. +Эта планета никогда не была видима вашей империи. PL_NEVER_SCANNED -Эта планета никогда тщательно не сканировалась вашей империи. +Эта планета никогда не сканировалась тщательно вашей империей. PL_NOT_IN_RANGE Эта планета находится вне диапазона сканеров вашей империи. @@ -1853,11 +3161,19 @@ PL_NOT_IN_RANGE # %1% last known stealth value of the planet. # %2% detection strength of the empire which scanned the planet. PL_APPARENT_STEALTH_EXCEEDS_DETECTION -Последняя известная скрытность (%1%) превышает [[DETECTION_TITLE]] (%2%). +Последняя известная [[metertype METER_STEALTH]] (%1%) превышает [[DETECTION_TITLE]] (%2%) PL_APPARENT_STEALTH_DOES_NOT_EXCEED_DETECTION -Последняя известная скрытность устарела. Фактическая скрытность превышает [[DETECTION_TITLE]]. +Последняя известная [[metertype METER_STEALTH]] устарела. Фактическая скрытность превышает [[DETECTION_TITLE]] или местный [[METER_DETECTION_VALUE_LABEL]] сильно ослаблен. + +MENUITEM_SET_FOCUS +Установить фокус +MENUITEM_ENQUEUE_BUILDING +Строящаяся очередь + +MENUITEM_ENQUEUE_SHIPDESIGN +Корабли в очереди ## ## Side panel/Resources panel @@ -1874,14 +3190,14 @@ RP_FOCUS_TOOLTIP # %1% capacity value applied by the special. SPECIAL_CAPACITY -Емкость: %1% +Вместимость: %1% # %1% turn number this special was applied to an object. ADDED_ON_TURN -Зародилось с %1% хода +Зародилась с %1% хода ADDED_ON_INITIAL_TURN -Зародилось изначально +Зародилась изначально ## @@ -1910,44 +3226,71 @@ TT_CHANGE # %1% the meter name displayed. # %2% the meter value. -TT_BREAKDOWN_SUMMARY -'''%1%: %2% ''' TT_INHERENT Присущий # %1% name of the owning empire. -# %2% FIXME +# %2% name of tech TT_TECH %1% Технология %2% # %1% name of the planet, where the building is located. -# %2% FIXME +# %2% name of building TT_BUILDING %1% Строение %2% -# %1% FIXME -# %2% FIXME +# %1% source of effect +# %2% name of ship hull TT_SHIP_HULL -Корабль %1% оболочка %2% +Корабль %1% корпус %2% -# %1% FIXME -# %2% FIXME +# %1% source of effect +# %2% name of ship part TT_SHIP_PART Корабль %1% часть %2% -# %1% FIXME -# %2% FIXME +# %1% source of effect +# %2% name of special TT_SPECIAL -Особенность %1% +Артефакт %2% -# %1% name of the species. -# %2% FIXME +# %1% source of effect +# %2% name of species TT_SPECIES -Артефакты %1% +Виды %2% -TT_UNKNOWN -Неизвестный +# %1% name of this fields field type. +# %2% name of field + +TT_FIGHTER_LAUNCH +Запускающий + +# %1% number of fighters +# %2% damage per fighter +TT_FIGHTER_DAMAGE +%1% Атакующий * %2% Урон + +# Represent a value out of a possible total value +# %1% number +# %2% number +TT_N_OF_N +%1% из %2% + +# %1% combat round +TT_COMBAT_ROUND +Раунд %1%: + +TT_UNKNOWN +Неизвестный + +# %1% name of the species. + +SP_RENAME_SYSTEM +Переименовать систему + +SP_ENTER_NEW_SYSTEM_NAME +Введите новое имя системы SP_RENAME_PLANET Переименовать планету @@ -1969,10 +3312,15 @@ FW_FLEET_HOLDING_AT # %1% system name where this fleet is moving to. FW_FLEET_MOVING_TO -Двигаться %1%, ETA %2% (%3%) +Двигаться к %1% + +# %1% system name where this fleet is moving to. +# %2% estiminated time in turns when the fleet will arrive. +FW_FLEET_MOVING_TO_ETA +Двигаться к %1% (ETA %2%) FW_FLEET_EXPLORING_REFUEL -Ожидание топлива +Ожидание заправки FW_FLEET_EXPLORING_WAITING Ожидание разведки системы @@ -2001,13 +3349,22 @@ FW_AGGRESSIVE_DESC Флот берет в осаду планеты других империй и атакует любые цели в их системах. FW_PASSIVE -Пассивный / Скрытый +Пассивный/Скрытый FW_PASSIVE_DESC -Флот не атакует и не осаживает планеты, пытаясь остаться незамеченным. +Флот не атакует и не осаждает планеты, пытаясь остаться незамеченным. + +FW_AUTO +Адаптивная тактика. + +FW_AUTO_DESC +Выбор тактики поведения для нового флота осуществляется на основе наличия/отсутствия у его кораблей оружия. FW_UNKNOWN_DESIGN_NAME -Неизвестно +Неизвестный дизайн + +# %1% name of the ship design that was used by this ship. +# %2% name of the ship owning species. # Displayed when creating a new fleet by dropping ships into the new fleet slot. FW_NEW_FLEET_LABEL @@ -2019,6 +3376,11 @@ FW_NEW_FLEET_LABEL FW_EMPIRE_FLEETS_AT_SYSTEM %1% Флотов в системе %2% +# %1% empire, which owns the fleets. +# %2% system where those fleets hold. +FW_EMPIRE_FLEETS_NEAR_SYSTEM +%1% Флотов рядом с %2% + # Fleet window title # %1% empire, which owns the fleets. FW_EMPIRE_FLEETS @@ -2033,14 +3395,35 @@ FW_NO_FLEET FW_GENERIC_FLEETS_AT_SYSTEM Флоты в %1% +# Fleet window title +# %1% system where those fleets hold. +FW_GENERIC_FLEETS_NEAR_SYSTEM +Флоты рядом с %1% + # Fleet window title FW_GENERIC_FLEETS Флоты +# the name that is used for fleets the client's player does not control but is +# owned by an empire. +# %1% empire name. +FW_EMPIRE_FLEET +%1% флот + +# the name that is used for ships the client's player does not control but is +# owned by an empire. +# %1% empire name. +FW_EMPIRE_SHIP +%1% корабль + +# the fleet name for fleets with an unknown owning empire. +FW_FOREIGN +Посторонний + # the name that is used for fleets the client's player does not control when # the empire is not specified. FW_FOREIGN_FLEET -Чужой флот +Посторонний флот # the name that is used for fleets that no player controls FW_ROGUE_FLEET @@ -2049,7 +3432,7 @@ FW_ROGUE_FLEET # the name that is used for ships the client's player does not control when the # empire is not specified. FW_FOREIGN_SHIP -Чужой корабль +Посторонний корабль # the name that is used for ships that no player controls FW_ROGUE_SHIP @@ -2058,14 +3441,41 @@ FW_ROGUE_SHIP SHIP_DAMAGE_STAT_TITLE Урон +SHIP_TROOPS_TITLE +Войска + +SHIP_TROOPS_STAT +Общая вместимость войск. + +SHIP_FIGHTERS_TITLE +Истребители + +SHIP_FIGHTER_BAY_SUMMARY +Способность запуска + +SHIP_FIGHTER_HANGAR_SUMMARY +Текущие истребители + +SHIP_FIGHTERS_DAMAGE_TOTAL +Общий урон + +SHIP_FIGHTERS_STAT +Общая вместимость истребителей. + +SHIP_COLONY_TITLE +Колонисты + +SHIP_COLONY_STAT +Общая вместимость колонистов. + FW_FLEET_FUEL_SUMMARY -Топливо флота, эквивалентно наименьшему запасу топлива на каком-либо корабле флота. +Топливо флота - эквивалентно наименьшему запасу топлива среди кораблей флота. FW_FLEET_SPEED_SUMMARY -Скорость флота, эквивалентна скорости самого медленного корабля во флоте. +Скорость флота - эквивалентна скорости самого медленного корабля флота. FW_FLEET_SHIELD_SUMMARY -Энерго-щиты флота - сумма всех энерго-щитов всех кораблей флота. +Энергощиты флота - эквивалентно сумме энергощитов всех кораблей флота. FW_FLEET_STRUCTURE_SUMMARY Живучесть флота - общая живучесть всех кораблей флота. @@ -2073,18 +3483,39 @@ FW_FLEET_STRUCTURE_SUMMARY FW_FLEET_DAMAGE_SUMMARY Сила флота - общий урон всех кораблей флота. +FW_FLEET_FIGHTER_SUMMARY +Истребители флота - суммарная мощь всех истребителей, имеющаяся у флота. + FW_FLEET_COUNT_SUMMARY -Состав флота - количество кораблей во флоте. +Размер флота - количество кораблей флота. + +FW_FLEET_TROOP_SUMMARY +Десант флота - суммарное число десанта, имеющееся у флота. + +FW_FLEET_COLONY_SUMMARY +Колонисты флота - все колониальные резервы, имеющиеся у флота. + +FW_FLEET_INDUSTRY_SUMMARY +Промышленная мощь флота - суммарная промышленная мощь всех кораблей флота. + +FW_FLEET_RESEARCH_SUMMARY +Исследовательская мощь флота - суммарная исследовательская мощь кораблей флота. + +FW_FLEET_TRADE_SUMMARY +Торговые возможности флота - общее количество торговых кораблей флота. FW_MERGE_SYSTEM_FLEETS Объединить все корабли в системе в единый флот FW_SPLIT_DAMAGED_FLEET -Отделить повреждённые корабли от флота +Отделить поврежденные корабли от флота FW_SPLIT_UNFUELED_FLEET Отделить корабли без топлива от флота +FW_SPLIT_NOT_FULL_FIGHTERS_FLEET +Отделить корабли без истребителей от флота + FW_SPLIT_FLEET Разделить флот @@ -2094,6 +3525,13 @@ FW_SPLIT_SHIPS_THIS_DESIGN FW_SPLIT_SHIPS_ALL_DESIGNS Разделить по типу кораблей +# Remove old visibility information from the client empire's saved information +FW_ORDER_DISMISS_SENSOR_GHOST +Очистить данные сканирования + +FW_ORDER_DISMISS_SENSOR_GHOST_ALL +Очистить все "призрачные" объекты здесь + ORDER_SHIP_SCRAP Уничтожить корабль @@ -2112,31 +3550,57 @@ ORDER_FLEET_EXPLORE ORDER_CANCEL_FLEET_EXPLORE Прекратить разведку +ORDER_GIVE_FLEET_TO_EMPIRE +Передать флот... + +ORDER_CANCEL_GIVE_FLEET +Отменить передачу флота + +ORDER_GIVE_PLANET_TO_EMPIRE +Передать планету... + +ORDER_CANCEL_GIVE_PLANET +Отменить передачу планеты + + +## +## Fleet Button +## + +FB_TOOLTIP_BLOCKADE_WITH_EXIT +Этот флот находится под блокадой вражеского флота. Вы можете переместить корабли только в следующие системы: + +FB_TOOLTIP_BLOCKADE_NO_EXIT +Этот флот находится под блокадой вражеского флота. Вы не можете его переместить. + +FB_TOOLTIP_BLOCKADE_MONSTER +Этот флот монстров находится под блокадой флота империи. Когда монстры под блокадой, это не даёт им возможности передвигаться. + ## ## Moderator ## MOD_NONE -Нет Возможностей модератора +Нет действий модератора MOD_CREATE_PLANET Добавить планету MOD_CREATE_SYSTEM -Добавить Звезду +Добавить звезду MOD_DESTROY Уничтожить MOD_SET_OWNER -'''Установить владельца ''' +Установить владельца MOD_ADD_STARLANE -'''Добавить Межзвёздный путь ''' +Добавить межзвездный путь MOD_REMOVE_STARLANE -'''Удалить Межзвёздный путь ''' +Удалить межзвездный путь ## @@ -2158,11 +3622,29 @@ WAR_DECLARATION PEACE_PROPOSAL Заключить мир -PEACE_PROPOSAL_CANEL -Отклонить мирный договор +PEACE_PROPOSAL_CANCEL +Отменить мирный договор PEACE_ACCEPT -Подписать мирный договор +Принять мирный договор + +PEACE_REJECT +Отклонить мирное предложение + +ALLIES_PROPOSAL +Предложить альянс + +ALLIES_PROPOSAL_CANCEL +Отменить предложение альянса + +ALLIES_ACCEPT +Принять предложение альянса + +ALLIES_REJECT +Отклонить предложенный альянс + +END_ALLIANCE_DECLARATION +Объявить об окончании альянса ## @@ -2172,10 +3654,13 @@ PEACE_ACCEPT TECH_DISPLAY Экран +# %1% amount of research points required to research the technology. +# %2% number of turns required to research the technology. +TECH_TOTAL_COST_ALT_STR +%1% RP / %2% ходов + # %1% research points used this turn. # %2% research points available for this turn. -TECH_TURN_COST_STR -%2% ходов @ %1% RP / ход # %1% number of remaining turns to complete the technology research. TECH_TURNS_LEFT_STR @@ -2190,17 +3675,23 @@ TECH_WND_STATUS_COMPLETED TECH_WND_STATUS_RESEARCHABLE Доступно +TECH_WND_STATUS_PARTIAL_UNLOCK +Частично разблокировано + TECH_WND_STATUS_LOCKED Заблокировано +TECH_WND_UNRESEARCHABLE +Недостижимо + TECH_WND_ENQUEUED -'''Ждущие Обработки ''' +В очереди TECH_WND_UNRESEARCHED_PREREQUISITES -'''Необходимо Исследовать: ''' +'''Необходимо исследовать: ''' TECH_WND_VIEW_TYPE -Список / Дерево +Дерево / Список # %1% research points already spent on a reseach. # %2% total research points required to complete the research. @@ -2212,19 +3703,63 @@ TECH_WND_PROGRESS # %1% estimated turns left until technology is researched. TECH_WND_ETA -%1% ходов +%1% ходов прошло + +TECH_WND_LIST_COLUMN_NAME +Название + +TECH_WND_LIST_COLUMN_COST +Стоимость + +TECH_WND_LIST_COLUMN_TIME +Ходов + +TECH_WND_LIST_COLUMN_CATEGORY +Категория + +TECH_WND_LIST_COLUMN_DESCRIPTION +Описание ## ## Production info panel ## +# %1% type of resource (Research, Production, ...). +# %2% name of the location where the resource is produced. +# %3% name of the empire which owns this resource produced. +PRODUCTION_INFO_AT_LOCATION_TITLE +%3% %1% в %2% + +# %1% type of resource (Research, Production, ...). +# %2% name of the empire which owns this resource produced. + PRODUCTION_INFO_TOTAL_PS_LABEL -Пунктов всего доступно +Всего доступно очков PRODUCTION_INFO_WASTED_PS_LABEL -Потерянные пункты +Избытки + +PRODUCTION_INFO_STOCKPILE_PS_LABEL +Запасено в резервы + +PRODUCTION_INFO_STOCKPILE_USE_MAX_LABEL +Доступно очков + +PRODUCTION_INFO_STOCKPILE_USE_PS_LABEL +Взято из резервов + +STOCKPILE_LABEL +Резервы + +STOCKPILE_USE_LABEL +Использовано резервов + +STOCKPILE_USE_LIMIT +Предел использования резервов +STOCKPILE_CHANGE_LABEL +Резерв на след. ход ## ## Research window @@ -2233,19 +3768,21 @@ PRODUCTION_INFO_WASTED_PS_LABEL RESEARCH_WND_TITLE Исследования -RESEARCH_INFO_RP -ОИ +# %1% name of the empire which owns this research queue. +RESEARCH_QUEUE_EMPIRE +%1% очередь исследований RESEARCH_QUEUE_PROMPT -'''Дважды щёлкните мышью или нажмите Shift на любой пункт, чтобы добавить его к очереди исследования. ''' +'''Дважды щёлкните мышью или щёлкните с удержанием Shift на любом элементе, чтобы добавить его к очереди исследования. +Удерживайте клавишу Control при нажатии на элемент, чтобы добавить его в верх очереди.''' ## ## Build selector window ## PRODUCTION_WND_BUILD_ITEMS_TITLE -Доступные постройки +Доступные элементы # %1% name of the location where the item is produced. PRODUCTION_WND_BUILD_ITEMS_TITLE_LOCATION @@ -2257,23 +3794,28 @@ PRODUCTION_WND_CATEGORY_BT_BUILDING PRODUCTION_WND_CATEGORY_BT_SHIP Корабли +PRODUCTION_WND_AVAILABILITY_OBSOLETE +Устаревшее + PRODUCTION_WND_AVAILABILITY_AVAILABLE Доступно PRODUCTION_WND_AVAILABILITY_UNAVAILABLE Недоступно +PRODUCTION_WND_AVAILABILITY_OBSOLETE_AND_UNAVAILABLE +'''Невозможно отметить +устаревшим''' + PRODUCTION_WND_REDUNDANT -Устаревший +Лишнее # %1% currently used amount of production points used per turn to build an item. # %2% maximum available amount of production points for this turn. -PRODUCTION_TURN_COST_STR -+%1% ОП # %1% number of turns left to finish the production of an item. PRODUCTION_TURNS_LEFT_STR -%1% Ходов осталось +%1% ходов PRODUCTION_TURNS_LEFT_NEVER Никогда @@ -2281,8 +3823,11 @@ PRODUCTION_TURNS_LEFT_NEVER PRODUCTION_DETAIL_ADD_TO_QUEUE Добавить в очередь +PRODUCTION_DETAIL_ADD_TO_TOP_OF_QUEUE +Добавить в начало очереди + PRODUCTION_QUEUE_PROMPT -'''Дважды нажмите на любой пункт, чтобы добавить его к очереди производства. ''' +Дважды нажмите на любой элемент, чтобы добавить его к очереди производства. ## @@ -2292,23 +3837,253 @@ PRODUCTION_QUEUE_PROMPT PRODUCTION_WND_TITLE Производство -PRODUCTION_INFO_PP -ОП +PRODUCTION_WINDOW_ARTICLE_TITLE +Окно промышленности + +PRODUCTION_WINDOW_ARTICLE_TEXT +'''Окно промышленности является основным интерфейсом для использования производственных очков (PP). +Доступ к нему возможен: +* Нажатием горячей клавиши (по умолчанию: CTRL + P) +* Двойным кликом по планете +* Нажатием на любой из значков промышленности в верхней части меню [[MAP_WINDOW_ARTICLE_TITLE]] (данного вида): + +Окно промышленности состоит из: +* Панели данных производства +* Панели доступных к производству элементов +* Панели производственной очереди +* Боковой панели данных о системе (по правому краю) +Позволяет взаимодействовать с [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]], в первую очередь для изменения выбора системы. + + +Панель "Данные производства" + +(по умолчанию: сверху-слева) +Панель "Данные производства" отображает доступные очки производства (PP, Production Points). +Если выбрана система, то отображаются два вида данных. Первое число - это общие очки производства всей вашей империи. Второе - это очки выбранной системы (оно отсутствует, если ни одна система не выбрана). +Доступные очки - это все очки, которые вы можете потратить на промышленность. Во втором числе указан максимум PP для выбранной вами системы, и он может быть меньше суммарного количества очков, если система не соединена с остальной империей через [[metertype METER_SUPPLY]]. +Резерв также относится к доступным очкам промышленности, если используются имперские резервы. Производимые элементы могут использовать имперские резервы по-умолчанию, поэтому требуется при необходимости отключать их от резервов. +Как правило, большая часть избыточных PP будет потрачена впустую и потеряна, но часть их также сохранится и в резервах. + +Панель доступных к производству элементов + +(по умолчанию позиция: нижний край) +Панель доступных к производству элементов содержит список элементов, которые можно добавить в производственную очередь. +Можно включить фильтр группировки элементов по типу и их доступности для производства, переключая вкладки сверху панели. +Элементы могут быть добавлены в производственную очередь двойным кликом или через выпадающее меню правой клавиши мыши. +Напротив каждого элемента указаны его стоимость и время производства. +Максимум PP, которое может набрать элемент за ход - это (цена/время)*количество. +Например, элемент с общей стоимостью 30 PP, изготавливающийся минимум 3 хода, может набрать 10 PP за ход, для каждого отдельного элемента. +Недоступные к производству элементы, если они отображаются, будут выводить причину своей недоступности, отмеченную красным цветом, внизу окна, всплывающего при наведении на предмет. + +Панель производственной очереди + +(по умолчанию позиция: левый край, ниже данных производства) +Панель "Производственная очередь" содержит ранее добавленные заказы на производство. +Очки производства (PP) распределяются по заказам, начиная с вершины очереди. +При выборе системы все заказы, собирающиеся в ней, будут отмечены именем системы, подсвеченным цветом вашей империи (отображается сверху-справа в блоке заказа). +Можно поменять порядок производственной очереди, просто перетаскивая заказы на новые позиции. +Клик правой кнопкой мыши по заказу отобразит меню для перемещения, удаления или приостановки производства заказа (а также других возможных действий с ним, таких как Показать в Энциклопедии ). +При удалении заказа любой его прогресс изготовления будет потерян, и очки производства вам не вернутся, и также они не распределятся и по другим заказам. +Приостановка производства отменяет любой новый прогресс для элемента, пока производство не будет возобновлено. +Для заказов кораблей существует дополнительная опция меню - "Отправить в систему...". Когда заказ завершается, новый корабль (или корабли) по умолчанию получают пункт назначения указанной системы. Чтобы установить или изменить место сборки, выберите желаемую систему на карте, затем правый клик по заказау -> Отправить в... (название системы). + +Производственные заказы + +Производственные заказы создаются через панель доступных к производству элементов, после чего помещаются в панель производственной очереди. +Каждый заказ по умолчанию имеет счётчик повторений 1 и множитель 1х, которые можно настроить в верхнем-левом меню блока с заказом. +Счётчик повторений - это количество раз, которое необходимо воспроизвести данный заказ, без изменения его положения в очереди. +Множитель указывает какое количество предметов необходимо произвести за каждое повторение. + +Примечание Повторения будут потреблять количество PP, необходимое для них на данном этапе производства, если только заказ не завершается на следующий ход. +Если заказ завершается, некоторое количество PP может быть потрачено на следующее повторение элемента (максимум - столько, сколько допустимо для него потребить за ход). + + +Примеры + +Ваша империя имеет по 10 доступных PP за ход. +Корабль стоит 36 PP и производится минимум 3 хода (т.е., потребляет максимум 12 PP за ход). +(Для условности, предположим, что данный корабль не тратит очки на [[encyclopedia FLEET_UPKEEP_TITLE]]). +- 1-й ход: Заказ на производство корабля добавлен в очередь, со счётчиком повторений 2. (Заказ #1) + В очереди уже присутствует другой заказ такого же корабля со счётчиком повторений 2. (Заказ #2) + В очереди также присутствует ещё один заказ такого же корабля, со множителем 2. + +- 2-й ход: Заказ #1 завершён на 10 PP, осталось 26 PP. 10 PP будут потрачены на следующем ходу. + + (Завершённые PP / Оставшиеся к завершению PP -> Требуемые на следующем ходу) + +- 3-й ход: Заказ #1 20 / 16 -> 10 + +- 4-й ход: Заказ #1 30 / 6 -> 6 дополнительно 4 PP уходят на следующее повторение для этого заказа. + +- 5-й ход: Строительство корабля завершено (Заказ #1) + Заказ #1 4 / 32 -> 10 теперь осталось только 1 повторение. + +- 6-й ход: Заказ #1 14 / 22 -> 10 + +- 7-й ход: Заказ #1 24 / 12 -> 10 + +- 8-й ход: Заказ #1 34 / 2 -> 2 + Заказ #2 0 / 36 -> 8 + +- 9-й ход: Строительство корабля завершено (Заказ #1) + Промышленность империи подросла на 22 PP за ход (по некоторой внешней причине). + Заказ #2 8 / 28 -> 12 максимум за ход для этого типа продукции. + Заказ #3 0 / 72 -> 10 (36 общая стоимость * 2 множитель = 72 суммарная стоимость). + +- 10-й ход: Заказ #2 20 / 16 -> 12 + Заказ #3 10 / 62 -> 10 + +- 11-й ход: Заказ #2 32 / 4 -> 4 дополнительно 12 PP будут отправлены на следующее повторение. + Заказ #3 20 / 52 -> 6 (22 - (4 + 12)) + +- 12-й ход: Строительство корабля завершено (Заказ #2) + Заказ #2 12 / 24 -> 12 + Заказ #3 26 / 46 -> 10 + +- 13-й ход: Заказ #2 24 / 12 -> 12 + Заказ #3 36 / 36 -> 10 + +- 14-й ход: Строительство корабля завершено (Заказ #2) + Заказ #3 46 / 26 -> 22 (12 PP/ход * 2 множитель = максимум 24 PP/ход) + +- 15-й ход: Заказ #3 68 / 4 -> 4 (18 PP останутся неиспользованными, если в очереди нет других предметов для производства). + +- 16-й ход: Строительство корабля завершено (Заказ #3) + +''' + +# %1% name of the empire that owns the production queue. +PRODUCTION_QUEUE_EMPIRE +%1% очередь производства + +# %1% the amount of items that should be produced in a single batch. + +# %1% number of repetions an item should be produced on after another. +PRODUCTION_QUEUE_REPETITIONS +%1% + +# %1% production points already spent on an item. +# %2% total production points required to complete the item production. +# %3% production points used per turn. +# %4% production points available per turn. +PRODUCTION_WND_PROGRESS +'''%1%%% завершено ++ %3% / %4% цель PP/ход''' # %1% location where the item is produced. PRODUCTION_QUEUE_ITEM_LOCATION В %1% +# %1% location where the item is produced. +PRODUCTION_QUEUE_ITEM_RALLIED_FROM_LOCATION +Собирается из %1% + +# %1% location where the item is enqueued for production. +PRODUCTION_QUEUE_ENQUEUED_ITEM_LOCATION +Очередь на %1% + +PRODUCTION_LOCATION_OK +Может быть произведено здесь + +PRODUCTION_LOCATION_INVALID +НЕ может быть произведено здесь + +IMPERIAL_STOCKPILE +Резервы империи + +PRODUCTION_QUEUE_ITEM_STOCKPILE_ENABLED +(Использование [[IMPERIAL_STOCKPILE]] возможно для этого предмета) + +ALLOW_IMPERIAL_PP_STOCKPILE_USE +Включить возможность использования [[IMPERIAL_STOCKPILE]] для этого предмета + +DISALLOW_IMPERIAL_PP_STOCKPILE_USE +Отключить возможность использования [[IMPERIAL_STOCKPILE]] для этого предмета + +# %1% name of the system this item production should be relocated to. +PRODUCTION_QUEUE_RALLIED_TO +Будет отправлен в %1% + +# %1% name of the system this item production will be relocated to. +RALLY_QUEUE_ITEM +Отправить в %1% + +DELETE_QUEUE_ITEM +Удалить из очереди + +MOVE_UP_QUEUE_ITEM +Переместить в начало очереди + +MOVE_DOWN_QUEUE_ITEM +Переместить в конец очереди + +SPLIT_INCOMPLETE +Разделить повторения + +DUPLICATE +Дублировать + +PRODUCTION_WND_TOOLTIP_PROD_COST +Стоимость производства: %1% + +PRODUCTION_WND_TOOLTIP_PROD_TIME +Время производства: %1% + +PRODUCTION_WND_TOOLTIP_PARTS +Части корабля + +# %1% name of the location where the object should be build. +PRODUCTION_WND_TOOLTIP_FAILED_COND +Неудовлетворительные требования производства в %1% ## ## Design window ## SHIP_DESIGN_FILES -Файлы проекта +Файлы проектов + +DESIGN_SAVE +Сохранить файл дизайна + +DESIGN_ADD +Добавить к дизайнам империи + +DESIGN_WND_DELETE_SAVED +Удалить сохранённый дизайн + +DESIGN_WND_ADD_ALL_SAVED_NOW +Добавить все сохр. дизайны к нынешней империи + +DESIGN_WND_ADD_ALL_SAVED_START +Добавлять все сохр. дизайны в начале игры + +DESIGN_WND_ADD_ALL_DEFAULT_START +Добавлять все базовые дизайны в начале игры + +DESIGN_WND_OBSOLETE_DESIGN +Устаревший дизайн + +DESIGN_WND_UNOBSOLETE_DESIGN +Снять метку «устаревший» + +DESIGN_WND_OBSOLETE_HULL +Устаревший корпус + +DESIGN_WND_UNOBSOLETE_HULL +Снять метку «устаревший» + +DESIGN_WND_OBSOLETE_PART +Устаревшая часть + +DESIGN_WND_UNOBSOLETE_PART +Снять метку «устаревшая» + +DESIGN_WND_DELETE_DESIGN +Удалить дизайн DESIGN_NAME_DEFAULT -Объект №___ +Пользов. дизайн корабля DESIGN_DESCRIPTION_DEFAULT Описание проекта @@ -2323,16 +4098,22 @@ DESIGN_WND_FINISHED_DESIGNS Готовые проекты DESIGN_WND_SAVED_DESIGNS -Сохранённые проекты +Сохраненные проекты + +DESIGN_WND_MONSTERS +Монстры + +DESIGN_WND_ALL +Всё известное DESIGN_WND_PART_PALETTE_TITLE Части корабля DESIGN_WND_MAIN_PANEL_TITLE -Детали Проекта +Детали проекта DESIGN_WND_DESIGN_NAME -Имя Проекта +Имя проекта DESIGN_WND_DESIGN_DESCRIPTION Описание @@ -2344,13 +4125,13 @@ DESIGN_RENAME Переименовать DESIGN_ENTER_NEW_DESIGN_NAME -Введите новое имя для проекта +Введите новое имя проекта: DESIGN_INVALID Неоконченный проект DESIGN_INV_MODERATOR -Модераторы и зрители не могут создавать проекты. +Модераторы и наблюдатели не могут создавать и изменять проекты. DESIGN_INV_NO_HULL Начните проект с выбора корпуса. @@ -2358,61 +4139,263 @@ DESIGN_INV_NO_HULL DESIGN_INV_NO_NAME Имя проекта не может быть пустым. +DESIGN_WND_KNOWN +Дублирует существующий дизайн + # %1% name of the equivalent ship design. -DESIGN_KNOWN_DETAIL +DESIGN_WND_KNOWN_DETAIL Такой проект уже существует "%1%". +DESIGN_WND_RENAME_FINISHED +Переименовать готовый дизайн + +DESIGN_WND_COMPONENT_CONFLICT +Конфликтующий дизайн + +# %1% first conflicting ship part name. +# %2% second conflicting ship part name. +DESIGN_WND_COMPONENT_CONFLICT_DETAIL +Конструкция судна не может содержать как %1% так и %2% + +DESIGN_WND_ADD_FINISHED +Добавить готовый проект + +# %1% name of the new ship design. +DESIGN_WND_ADD_FINISHED_DETAIL +'''Добавить новый проект +"%1%" +к готовым дизайнам.''' + +DESIGN_WND_UPDATE_FINISHED +Обновить готовый дизайн + +# %1% previous name of the existing design. +# %2% new name of the existing design. +DESIGN_WND_UPDATE_FINISHED_DETAIL +'''Заменить готовый дизайн +"%1%" +новым проектом +"%2%" +в готовых проектах.''' + +DESIGN_WND_ADD_SAVED +Добавить сохранённый проект + +# %1% name of the new ship design. +DESIGN_WND_ADD_SAVED_DETAIL +'''Добавить проект +"%1%" +к сохранённым проектам.''' + +DESIGN_WND_UPDATE_SAVED +Обновить сохранённый проект + +# %1% old name of the saved design. +# %2% new name of the saved design. +DESIGN_WND_UPDATE_SAVED_DETAIL +'''Заменить сохранённый проект +"%1%" +новым проектом +"%2%" +в сохранённых проектах.''' + +DESIGN_UPDATE_INVALID_NO_CANDIDATE +Выберите существующий проект для обновления. + +ADD_FIRST_DESIGN_DESIGN_QUEUE_PROMPT +Выберите корпус, добавьте части и нажмите "[[DESIGN_WND_ADD_FINISHED]]" для создания нового возможного дизайна. + +ADD_FIRST_DESIGN_HULL_QUEUE_PROMPT +Выключить фильтры устаревшего, доступного и недоступного, чтобы показать больше корпусов + +ADD_FIRST_SAVED_DESIGN_QUEUE_PROMPT +Выберите существующий готовый проект, кликните правой клавишей мышки и выберите "[[DESIGN_SAVE]]", чтобы создать сохранённый дизайн. + +ALL_AVAILABILITY_FILTERS_BLOCKING_PROMPT +Выключить фильтры устаревшего, доступного и недоступного, чтобы показать больше + +NO_SAVED_OR_DEFAULT_DESIGNS_ADDED_PROMPT +'''Выберите корпус, добавьте детали, а затем нажмите "[[DESIGN_WND_ADD_FINISHED]]", чтобы создать новый возможный дизайн корабля. + +Ни сохранённые, ни базовые дизайны кораблей не добавляются к империи прямо на старте игры. Изменить эти настройки можно в Опции->Разное. Добавить сохранённые проекты кораблей можно и непосредственно - через контекстное меню списка проектов, во вкладке Сохранённые проекты.''' + +# %1% File path of unwritable file +ERROR_UNABLE_TO_WRITE_FILE +Невозможно записать файл:\n"%1%" + ## ## Statistics ## +SHIP_COUNT +Количество кораблей + +MILITARY_STRENGTH_STAT +Грубая оценка общей военной силы + +PP_OUTPUT +Производство продукции + +RP_OUTPUT +Научные достижения + +PLANET_COUNT +Планеты + +COLONIES_COUNT +Колонии + +BATTLESHIP_COUNT +Вооруженные корабли + +ARMED_MONSTER_COUNT +Блуждающие вооруженные монстры + +BUILDINGS_PRODUCED +Построено зданий + +BUILDINGS_SCRAPPED +Уничтожено зданий + +SHIPS_DESTROYED +Уничтожено кораблей в битвах + +SHIPS_LOST +Потеряно кораблей в битвах + +SHIPS_PRODUCED +Произведено кораблей + +SHIPS_SCRAPPED +Утилизировано кораблей + +PLANETS_BOMBED +Разбомблено планет + +PLANETS_DEPOPULATED +Деколонизировано своих планет + +PLANETS_INVADED +Захвачено планет + +TOTAL_POPULATION_STAT +Общее население + +STATISTICS_TEST_1 +Анализ статистики 1 + +STATISTICS_TEST_2 +Анализ статистики 2 + +STATISTICS_TEST_3 +Анализ статистики 3 + ## ## Encyclopedia ## +# %1% name of the species. + +# %1% content of ENC_SPECIES_PLANET_TYPE_SUITABILITY_COLUMN1 expanded to match +# column width. +# %2% the planet environment for a species. +# %3% the planet population capacity for a species. + # %1% name of the planet. ENC_SUITABILITY_REPORT_POSITIVE_HEADER -'''%1% подходит для заселения. +'''%1% подходит для заселения этими видами. ''' # %1% name of the planet. ENC_SUITABILITY_REPORT_NEGATIVE_HEADER -'''%1% не подходит для заселения. Попытка колонизации будет безуспешной или население со временем вымрет. +'''%1% не подходит для заселения этими видами. Попытка колонизации будет безуспешной или население со временем вымрет. + +''' + +ENC_SUITABILITY_REPORT_WHEEL_INTRO +''' +Пригодность планеты для расы определяется удалённостью её от предпочтительного типа планет на этом круге (см. [[encyclopedia ENVIRONMENT_TITLE]]): +''' +# types of things that can be shown in encyclopedia +ENC_GRAPH +[Игра - Графики] + +ENC_GALAXY_SETUP +[Игра - Галактика] + +# %1% seed value of the current galaxy. +# %2% number of systems in the current galaxy. +# %3% shape of the current galaxy. +# %4% age of the current galaxy. +# %5% starlane frequency in the current galaxy. +# %6% planet density in the current galaxy. +# %7% special frequency in the current galaxy. +# %8% monster frequency in the current galaxy. +# %9% natives frequency in the current galaxy. +# %10% upper AI aggression level in this galaxy. +# %11% game UID +ENC_GALAXY_SETUP_SETTINGS +'''Семя: %1% +Количество систем: %2% +Форма: %3% +Возраст: %4% +Звёздные пути: %5% +Планеты: %6% +Артефакты: %7% +Монстры: %8% +Аборигены: %9% +Макс. агрессивность ИИ: %10% +Идентификатор игры: %11% ''' +ENC_GAME_RULES +[Игра - Правила] + ENC_SHIP_PART -Часть корабля +Модуль корабля + +ENC_SHIP_PART_DESC +Модуль корабля (он же часть или деталь) может быть установлен на [[encyclopedia ENC_SHIP_HULL]] (в возможный [[encyclopedia SLOT_TITLE]]) при проектировании корабля, чтобы улучшить его параметры. Некоторые низкоуровневые части доступны по умолчанию, но новые типы деталей могут быть разблокированы с помощью [[encyclopedia RESEARCH_TECH_GUIDE_TITLE]]. Каждый модуль корабля размещается в одной из нижеперечисленных категорий. ENC_SHIP_HULL -Корпуса кораблей +Корпус корабля + +ENC_SHIP_HULL_DESC +Корпус корабля является основным элементом дизайна судна, определяющим базовые параметры его конструкции (до установки частей класса [[encyclopedia ENC_SHIP_PART]]). По умолчанию доступны некоторые низкоуровневые корпуса, но лучшие версии могут быть разблокированы при помощи [[encyclopedia RESEARCH_TECH_GUIDE_TITLE]]. Каждый корпус корабля размещается в одной из нижеперечисленных категорий. ENC_TECH Технологии +ENC_TECH_DESC +Новые эффекты, улучшения статистики планет, здания и части кораблей могут быть разблокированы в [[encyclopedia RESEARCH_TECH_GUIDE_TITLE]], каждый из которых попадает в одну из нижеперечисленных категорий. + ENC_SPECIAL -Особенности +Артефакты OBJECTS_WITH_SPECIAL -Объекты с артефактами: +'''Объекты с артефактами: ''' ENC_BUILDING_TYPE Типы строений ENC_SPECIES -Артефакты +Виды + +ENC_SPECIES_DESC +'''Каждый вид классифицируется по [[encyclopedia METABOLISM_TITLE]] ''' ENC_FIELD_TYPE -'''Тип поля ''' +Тип поля ENC_SHIP_DESIGN -Дизайн корабля +[Игра - Дизайн корабля] SHIPS_OF_DESIGN -Корабли этого вида: +'''Корабли этого вида: ''' NO_SHIPS_OF_DESIGN Нет кораблей такого вида @@ -2421,25 +4404,58 @@ ENC_INCOMPETE_SHIP_DESIGN Незавершённый проект корабля ENC_EMPIRE -Империя +[Игра - Империи] EMPIRE_CAPITAL '''Столица: ''' NO_CAPITAL -Нет столицы +Нет известной столицы + +EMPIRE_ID +ID Империи EMPIRE_METERS -Показатели: +Размер: + +RESEARCHED_TECHS +'''Исследовано: ''' + +BEFORE_FIRST_TURN +До первого хода + +TURN +Ход + +NO_TECHS_RESEARCHED +Нет исследованных наук + +AVAILABLE_PARTS +'''Доступные части: ''' + +NO_PARTS_AVAILABLE +Нет доступных частей + +AVAILABLE_HULLS +'''Доступные корпуса: ''' + +NO_HULLS_AVAILABLE +Нет доступных корпусов + +AVAILABLE_BUILDINGS +'''Доступные строения: ''' + +NO_BUILDINGS_AVAILABLE +Нет доступных зданий OWNED_PLANETS '''Планеты империи: ''' NO_OWNED_PLANETS_KNOWN -Нет планет во владении +Нет принадлежащих планет OWNED_FLEETS -'''Флоты: ''' +'''Флоты империи: ''' # %1% link to a fleet. # %2% link to a system. @@ -2447,34 +4463,105 @@ OWNED_FLEET_AT_SYSTEM %1% в %2% NO_OWNED_FLEETS_KNOWN -Нет флотов во владении +Нет известных принадлежащих флотов + +EMPIRE_SHIPS_DESTROYED +Уничтожено кораблей империи + +SHIP_DESIGNS_DESTROYED +Уничтожено дизайнов империи + +SPECIES_SHIPS_DESTROYED +Уничтожено кораблей аборигенов + +SPECIES_PLANETS_INVADED +Захваченные планеты аборигенов + +SPECIES_SHIPS_PRODUCED +Произведенно кораблей расами + +SHIP_DESIGNS_PRODUCED +Произведено видов кораблей + +SPECIES_SHIPS_LOST +Потерянные корабли видов + +SHIP_DESIGNS_LOST +Потеряно дизайнов кораблей + +SPECIES_SHIPS_SCRAPPED +Утилизированые корабли рас + +SHIP_DESIGNS_SCRAPPED +Утилизировано дизайнов кораблей + +SPECIES_PLANETS_DEPOPED +Деколонизированные планеты видов + +SPECIES_PLANETS_BOMBED +Разбомбленные планеты видов + +BUILDING_TYPES_PRODUCED +Построенные типы зданий + +BUILDING_TYPES_SCRAPPED +Утилизированные типы зданий ENC_SHIP -Корабль +[Игра - Корабли] ENC_MONSTER -Тип монстра +[Игра - Монстр] + +ENC_MONSTER_TYPE +[Игра - Типы монстра] ENC_FLEET -Флот +[Игра - Флоты] ENC_PLANET -Планета +[Игра - Планеты] ENC_BUILDING -Здание +[Игра - Здания] ENC_SYSTEM -Система +[Игра - Системы] + +ENC_FIELD +[Игра - Области] + +ENC_HOMEWORLDS +[Игра - Домашние миры] + +ENC_TEXTURES +[Тест - Структура] ENC_INDEX Оглавление энциклопедии -ENC_PP -ОП +SEARCH_RESULTS +Результаты поиска + +ENC_SEARCH_EXACT_MATCHES +Точное совпадение + +ENC_SEARCH_WORD_MATCHES +Совпадение слова -ENC_RP -ОИ +ENC_SEARCH_PARTIAL_MATCHES +Частичное совпадение + +ENC_SEARCH_ARTICLE_MATCHES +Совпадение артикля + +ENC_SEARCH_NOTHING_FOUND +Нет совпадений + +# %1% specialized type description of the thing described in the encyclopedia +# (e.g. for a ship part it would be the ship part class). +# %2% general type description of the thing described in the encyclopedia. +# (e.g. for a ship part it would be the content of 'ENC_SHIP_PART'). # Concise listing of costs required to produce item or research tech. # %1% cost to complete requested item or technology. @@ -2482,9 +4569,38 @@ ENC_RP # technology. # %3% turns to complete requested item or technology. ENC_COST_AND_TURNS_STR -%3% ходов @ %1% %2% / ход +%1% %2% и %3% ходов -# Displays a ship design inside the Pedia with the basic informations. +ENC_VERB_PRODUCE_STR +производства + +# %1% content of ENC_VERB_PRODUCE_STR. +ENC_AUTO_TIME_COST_INVARIANT_STR +''' + +Время и стоимость + +Время и стоимость %1% этого элемента не зависит от местоположения''' + +# %1% content of ENC_VERB_PRODUCE_STR. +ENC_AUTO_TIME_COST_VARIABLE_STR +''' + +Время и стоимость + +Время и стоимость %1% этого элемента зависят от местоположения. +В данном контексте термин «Источник» обычно означает столицу соответствующей империи, а термин «Цель» - выбранное место. +''' + +# %1% name of the planet where this item can be produced. +# %2% local costs on the planet to produce the item. +# %3% content of ENC_PP +# %4% local time required on the planet to produce the item. +ENC_AUTO_TIME_COST_VARIABLE_DETAIL_STR +''' +В системе %1% стоимость этого объекта составляет %2% %3%, а время производства - свыше %4% ходов.''' + +# Displays a ship design inside the Pedia with the basic informations. # %1% description. # %2% hull name. # %3% list of builtin parts. @@ -2519,29 +4635,39 @@ ENC_SHIP_DESIGN_DESCRIPTION_BASE_STR # %20% mock average attack strength/pp ENC_SHIP_DESIGN_DESCRIPTION_STATS_STR ''' -Для расы: %1% -Общий урон: %2% [[PC_DIRECT_WEAPON]] Shots: %3% +Для [[encyclopedia ENC_SPECIES]]: %1% +Всего [[encyclopedia DAMAGE_TITLE]]: %2% [[PC_DIRECT_WEAPON]] Выстрелов: %3% [[metertype METER_STRUCTURE]]: %4% [[metertype METER_SHIELD]]: %5% [[metertype METER_DETECTION]]: %6% [[metertype METER_STEALTH]]: %7% [[metertype METER_SPEED]]: %8% [[metertype METER_FUEL]] [[METER_CAPACITY]]: %9% -Численность колонии: %10% [[metertype METER_TROOPS]]: %11% +Заселенность [[METER_CAPACITY]]: %10% [[metertype METER_TROOPS]]: %11% [[OBJ_FIGHTER]] [[encyclopedia DAMAGE_TITLE]]: %13% [[ENC_SDD_HANGAR]]: %12% [[ENC_SDD_BAY]]: %14% -Расчетная боевая мощь (игнорируя щиты): %15$.f (%16$.2f за PP) -Против вражеской атаки %17$.1f и щитов %18$.1f: %19$.f (%20$.2f за PP) +Оценочная боевая мощь (игнорирование щитов): %15$.f (%16$.2f за PP) + (противник с атакой %17$.1f и щитами %18$.1f): %19$.f (%20$.2f за PP) ''' ENC_UNLOCKED_BY -'''Открыто технологией: +'''Открытые технологии: ''' ENC_UNLOCKS -'''Открыто +'''Открытые ''' ENC_SHIP_PART_CAN_MOUNT_IN_SLOT_TYPES -Можно установить в: +'''Можно установить - ''' + +ENC_SHIP_EXCLUSIONS +'''Не может быть в дизайне с: ''' + +# %1% name of the item type unlocked (building, ship part, hull, ...). +# %2% name of the item unlocked (a specific building or ship part, ...). + +# %1% category of the technology. +# %2% UNUSED +# %3% short description of the technology. # %1% object, technology or other content that should be looked up in the # encyclopedia. @@ -2562,29 +4688,122 @@ ENC_COMBAT_LOG_DESCRIPTION_STR ENC_COMBAT_ATTACK_STR %1% атаковал %2%, нанеся %3% урона +# %1% link to a ship part description. +# %2% potential damage done by the weapon. +# %3% damage consumed by the target shields. +# %4% damage done to the target. +ENC_COMBAT_ATTACK_DETAILS +Оружие: %1% Сила: %2% Щиты: %3% Урон: %4% + +ENC_COMBAT_SHIELD_PIERCED +пробили + +# %1% link to the attacking unit or empire. +# %2% name of the unit attacked. +ENC_COMBAT_ATTACK_SIMPLE_STR +%1% атакует %2% + +# %1% number of repeated events. +# %2% link to the attacking unit or empire. +# %3% link to the attacking unit or empire. +ENC_COMBAT_ATTACK_REPEATED_STR +%1%X %2% атакуют %3% + +# %1% link to the unit, which launches the fighter. +# %2% name of the fighters launched. +# %3% amount of fighters launched. +ENC_COMBAT_LAUNCH_STR +%1% запустили %3% %2% + +# %1% link to the unit, which recovers the fighters. +# %2% name of the fighters recovered. +# %3% amount of fighters recovered. +ENC_COMBAT_RECOVER_STR +%1% вернули %3% %2% + ENC_COMBAT_UNKNOWN_OBJECT Неизвестно # %1% number of combat round. ENC_ROUND_BEGIN ''' -Раунд %1%:''' +Раунд %1%''' # %1% name of the empire which owns the attacked unit. # %2% name of the attacked unit. ENC_COMBAT_UNKNOWN_DESTROYED_STR -Что-то, так или иначе, вывело из строя %2% +%2% выведен из строя + +# %1% name of the empire which owns the attacked unit. +# %2% name of the attacked fighter. +ENC_COMBAT_FIGHTER_INCAPACITATED_STR +%1% %2% был уничтожен. + +# A combat log report for multiple destroyed fighters from one empire +# %1% count of number of repeated events +# %2% name of the empire which owns the attacked unit. +# %3% name of the attacked fighter. +ENC_COMBAT_FIGHTER_INCAPACITATED_REPEATED_STR +%1%X %2% %3%s были уничтожены. # %1% name of the empire which owns the attacked unit. # %2% name of the attacked planet. ENC_COMBAT_PLANET_INCAPACITATED_STR -Планета %1% %2% выведена из строя +Планета %2% выведена из строя # %1% name of the empire which owns the attacked unit. # %2% name of the attacked unit. ENC_COMBAT_DESTROYED_STR -Корабль %1% %2% уничтожен - +Корабль %2% уничтожен + +# %1% unused +# %2% name of empire +ENC_COMBAT_INITIAL_STEALTH_LIST +%2% империя не может быть целью: + +# %1 link to the attacking unit +# %2 link to the defending unit. +# %3 link to the empire that owns the defending unit. +ENC_COMBAT_STEALTH_DECLOAK_ATTACK +%2% обнаруживает %1% во время атаки + +# %1% unused +# %2% name of empire detecting decloacking ship +ENC_COMBAT_STEALTH_DECLOAK_ATTACK_1_EVENTS +%2% обнаружен + +# %1% unused +# %2% name of empire detecting decloaking ships +ENC_COMBAT_STEALTH_DECLOAK_ATTACK_MANY_EVENTS +%2% обнаружены: + +# %1% unused +# %2% name of ship causing damage +ENC_COMBAT_PLATFORM_DAMAGE_1_EVENTS +%2% урона + +# %1% number of targets damaged +# %2% name of ship doing damage +ENC_COMBAT_PLATFORM_DAMAGE_MANY_EVENTS +%2% урона %1% цели: + +# %1 defender +# %2 damage +ENC_COMBAT_PLATFORM_TARGET_AND_DAMAGE +%1% для %2% урона + +# %1% unused +# %2% name of ship unable to do damage to other single ship in combat +ENC_COMBAT_PLATFORM_NO_DAMAGE_1_EVENTS +%2% не может повредить + +# %1% number of targets +# %2% name of ship unable to damage targets +ENC_COMBAT_PLATFORM_NO_DAMAGE_MANY_EVENTS +%2% не может повредить %1% цели: + +ENC_METER_TYPE +Тип измерителя ## ## Combat report @@ -2606,34 +4825,34 @@ COMBAT_SUMMARY Сводка COMBAT_SUMMARY_PARTICIPANT_RELATIVE -Ряды:Сравнительно +Ряды: Сравнительно COMBAT_SUMMARY_PARTICIPANT_RELATIVE_TIP Ширина рядов сейчас зависит от их текущего здоровья. COMBAT_SUMMARY_PARTICIPANT_EQUAL -Ряды:Равно +Ряды: Равно COMBAT_SUMMARY_PARTICIPANT_EQUAL_TIP -Ширна рядов сейчас просто одинакова. Нажмите для более наглядного вывода! +Ширина рядов сейчас просто одинакова. Нажмите для более наглядного режима! COMBAT_SUMMARY_HEALTH_SMOOTH -Здоровье:Ровный +Здоровье: Ровный COMBAT_SUMMARY_HEALTH_SMOOTH_TIP Изменение структуры корабля отображается ровной интерполяцией COMBAT_SUMMARY_HEALTH_BAR -Здоровье:Ряд +Здоровье: Ряд COMBAT_SUMMARY_HEALTH_BAR_TIP Изменение структуры корабля отображается двумя цветными рядами COMBAT_SUMMARY_BAR_HEIGHT_PROPORTIONAL -Ряды здоровья:Пропорционально +Ряды здоровья: Пропорционально COMBAT_SUMMARY_BAR_HEIGHT_EQUAL -Ряды здоровья:Равно +Ряды здоровья: Равно COMBAT_SUMMARY_BAR_HEIGHT_PROPORTIONAL_TIP Ряды участников сражения сейчас масштабируются, чтобы показать отношение их МАКС здоровья к здоровью. @@ -2642,16 +4861,16 @@ COMBAT_SUMMARY_BAR_HEIGHT_EQUAL_TIP Ряды участников сражения сейчас равны. COMBAT_SUMMARY_GRAPH_HEIGHT_PROPORTIONAL -Высота графов:Пропорциональная +Высота графов: Пропорциональная COMBAT_SUMMARY_GRAPH_HEIGHT_EQUAL -Высота графов:Равная +Высота графов: Равная COMBAT_SUMMARY_GRAPH_HEIGHT_PROPORTIONAL_TIP -Графики сражаемых империй сейчас масштабируются по отношению к самому высокому значению Здоровья во флоте. +Графики сражающихся империй сейчас масштабируются по отношению к самому высокому значению показателя [[OPTIONS_COMBAT_SUMMARY_HEALTH_COLOR]] во флоте. COMBAT_SUMMARY_GRAPH_HEIGHT_EQUAL_TIP -Графики сражаемых империй сейчас равны. +Графики сражающихся империй сейчас одинаковы. OPTIONS_COMBAT_COLORS Боевые цвета @@ -2666,7 +4885,7 @@ OPTIONS_COMBAT_SUMMARY_HEALTH_COLOR Здоровье COMBAT_LOG -Лог +Журнал # %1% name of the belligerent party. # %2% current health of all units. @@ -2682,7 +4901,6 @@ COMBAT_UNIT_HEALTH_AXIS_LABEL COMBAT_DESTROYED_LABEL Потери: %1% - ## ## Encyclopedia article categories and short descriptions ## @@ -2693,15 +4911,18 @@ CATEGORY_GUIDES CATEGORY_GAME_CONCEPTS *Принципы игры* -CATEGORY_USER_INTERFACE -*Интерфейс* - GROWTH_ARTICLE_SHORT_DESC Рост населения +STOCKPILE_ARTICLE_SHORT_DESC +Резервы + OUTPOSTS_ARTICLE_SHORT_DESC Аванпост +HOMEWORLDS_ARTICLE_SHORT_DESC +Домашний мир + PRODUCTION_ARTICLE_SHORT_DESC Производство @@ -2720,31 +4941,100 @@ METER_ARTICLE_SHORT_DESC POPULATION_TITLE_SHORT_DESC Население +MANAGEMENT_TITLE_SHORT_DESC +Управление + +## +## Encyclopedia subcategories +## + +# In Guides category + +INTERFACE_TITLE +*Интерфейс* + +INTERFACE_TEXT +Неполный список особенностей интерфейса FreeOrion: + +# In Game Concepts category + +DIPLOMACY_TITLE +Дипломатия + +DIPLOMACY_TEXT +Неполный список особенностей Дипломатии: + +METABOLISM_TITLE +Метаболизм + +METABOLISM_TEXT +Метаболизм [[encyclopedia ENC_SPECIES]] в галактике попадает в один из перечисленных здесь типов + +PLANET_MANAGEMENT_TITLE +Управление планетами + +PLANET_MANAGEMENT_TEXT +Неполный список особенностей Управления Планетами: + +PLANETARY_FOCUS_TITLE +Планетарный фокус + +PLANETARY_FOCUS_TEXT +Неполный список настроек Планетарного Фокуса: + +SHIP_MANAGEMENT_TITLE +Управление кораблями + +SHIP_MANAGEMENT_TEXT +Неполный список особенностей Управления Кораблями: + +SPECIES_TRAITS_TITLE +Особенности видов + +SPECIES_TRAITS_TEXT +Неполный список особенностей Видов: + +SYSTEM_BLOCKADE_TITLE +Блокада системы + +SYSTEM_BLOCKADE_TEXT +'''Если агрессивный, вооружённый флот вторгается во вражескую систему, он устанавливает блокаду против всех империй, с которыми воюет, и которые в настоящее время уже не блокируют систему. При установке блокады у заблокированных империй прерывается [[metertype METER_SUPPLY]], а производство замыкается на самих системах, начиная использовать только местные ресурсы для создания только местных структур. Во время ведения активных боевых действий также приостанавливается действие эффектов, для которых необходимо отсутствие боя (работа [[encyclopedia ORBITAL_DRYDOCK_REPAIR_TITLE]] и рост числа [[metertype METER_TROOPS]], соответствующий этому ходу). +После установки блокады все звёздные пути, ведущие в систему, охраняются врагами, поэтому если враждебные силы войдут в систему, они смогут выйти только через тот путь, через который они зашли. Но если вражеских кораблей много, и все они входили через разные пути, все использованные ими пути будут также открыты и на выход. Если все боевые флоты блокадной системы будут уничтожены или покинут систему до прибытия новых её флотов, то ранее открытые выходы будут снова заблокированы. +Союзные флоты не оказывают прямого влияния на блокировку снабжения и звёздных путей, но могут помогать планете устранять силы блокады. Если в систему вторгнутся одновременно два враждебных к ней флота (и при условии, что она не заблокирована), ни один из них не установит блокаду друг против друга, но они установят блокаду против других вражеских империй, что придут позднее. +Космические монстры тоже могут устанавливать блокады, но блокада всегда будет сниматься по окончанию боя, после чего флот империи сможет покинуть систему любым звёздным выходом из неё, или устанавливать блокаду по очереди с монстром. Кроме того, если заблокированный флот оснащён бронёй и установлен в агрессивный режим, то [[metertype METER_SUPPLY]] будет восстановлено. Если монстр и имперский флот встретятся одновременно, флот получит шанс действовать первым и создать блокаду против самого монстра.''' ## ## Encyclopedia articles ## +METER_POPULATION_VALUE_LABEL +Население + +METER_POPULATION_VALUE_DESC +Абстрактное значение для размера колонии, зависящее от специфики вида. + METER_INDUSTRY_VALUE_LABEL Промышленность METER_INDUSTRY_VALUE_DESC -'''Промышленность представляет собой производство и модификации физических товаров. Она необходима, чтобы создавать здания, космические корабли и другие проекты. +'''Промышленность представляет из себя производство и модификации физических товаров. Она необходима, чтобы создавать здания, космические корабли и другие проекты. -Промышленность планеты может быть использована для каких-либо производственных проектов на [[metertype METER_SUPPLY]] линии соединенных планет. Любая нераспределенная Промышленность теряется каждый ход. +Промышленность планеты может быть использована для каких-либо производственных проектов на линии [[metertype METER_SUPPLY]] соединенных планет. Любая нераспределенная Промышленность добавляется к [[metertype METER_STOCKPILE]]. -Каждый проект имеет минимальное количество ходов, необходимых для его постройки. Например, если проект, который стоит 15 PP, и имеет минимальное время постройки 3 хода, то 5 PP - тот максимум, который может использоваться за 1 ход для его завершения. -Проект в начале очереди получает PP первым. Любые оставшиеся PP определяются к следующему проекту, если ещё есть - то к следующему - и так далее. Вы можете изменить приоритеты объектов в очереди при помощи перетаскивания наиболее важных объектов ближе к началу. +Информацию об интерфейсах, касающихся промышленности, смотрите в статье [[encyclopedia PRODUCTION_WINDOW_ARTICLE_TITLE]]. ''' METER_RESEARCH_VALUE_LABEL Наука METER_RESEARCH_VALUE_DESC -'''Наука (или RP - "Научно-исследовательские очки") на всех планетах империи обобщенно открывают новые технологии. Любые очки Науки, которые не потрачены на исследования, теряются каждый ход. Каждой технологии необходимо минимальное количество ходов для ее исследования. Например, если технология стоит 15 RP, и занимает минимальное время 3 хода, то 5 RP - тот максимум, который может быть использован за 1 ход для ее изучения. -Технология в начале очереди получает RP первой. Любые оставшиеся RP идут на изучение следующей в очереди технологии, если ещё есть - то следующей - и так далее. Вы можете изменить приоритеты изучения технологий в очереди при помощи перетаскивания наиболее важных из них ближе к началу. +'''Наука или научно-исследовательские очки (RP - Research Points) суммирует исследовательский потенциал всех планет империи, предназначенный открывать новые [[encyclopedia ENC_TECH]]. -Наука не требует [[metertype METER_SUPPLY]] линий для использования.''' +Каждая наука имеет фиксированный минимум ходов, за который её можно исследовать. К примеру, если наука "стоит" 15 очков исследования и исследуется минимум 3 хода, то всего 5 очков исследований допустимо потратить на неё за ход. + +Исследование в самом верху очереди получает больше всего очков, оставшиеся же очки отправляются на второе в очереди исследование, потом на третье (если оно есть) и так далее. Можно перетаскивать более приоритетные исследования вверх очереди, чтобы те завершились быстрее. Если после всех распределений у империи остались излишки очков, они будут автоматически прибавлены к тем исследованиям, что не в очереди, но разблокированы и имеют меньше меньше всего очков до завершения. + +Для исследования не требуется соединения систем линиями [[metertype METER_SUPPLY]].''' METER_TRADE_VALUE_LABEL Торговля @@ -2753,19 +5043,32 @@ METER_CONSTRUCTION_VALUE_LABEL Инфраструктура METER_CONSTRUCTION_VALUE_DESC -Понятие Инфраструктура относится к техническим структурам, которые поддерживают колонизированную планету: энергия, транспорт, телекоммуникации и социальные услуги для граждан. +Понятие Инфраструктуры относится к техническим структурам, которые поддерживают колонизированную планету: энергия, транспорт, телекоммуникации и социальные услуги для граждан METER_HAPPINESS_VALUE_LABEL Счастье METER_HAPPINESS_VALUE_DESC -Счастье населения колонии зарождается низким после первоначального заселения или после вторжения, а также на счастье могут влиять другие факторы. В качестве источника поселенцев для колонизации других планет необходимо иметь МИН Значение 5 Счастье. +Счастье населения колонии после первоначального заселения или после вторжения находится на низком уровне, а также на счастье могут влиять другие факторы. В качестве источника поселенцев для колонизации других планет необходимо иметь минимальное значение счастья 5 + +METER_CAPACITY_VALUE_LABEL +Параметры - первичные + +# TODO make this into a listing of possible capacity types +METER_CAPACITY_VALUE_DESC +Первичные параметры объекта + +METER_SECONDARY_STAT_VALUE_LABEL +Параметры - вторичные + +METER_SECONDARY_STAT_VALUE_DESC +Вторичные параметры объекта METER_FUEL_VALUE_LABEL Топливо METER_FUEL_VALUE_DESC -Топливо позволяет кораблям путешествовать между галактиками за пределами [[metertype METER_SUPPLY]] своей империи. Топливо расходуется на каждый пройденный отрезок галактического пути; остановка корабля в зоне [[metertype METER_SUPPLY]] позволяет полностью заправить баки. Если корабль остается неподвижным за пределами [[metertype METER_SUPPLY]], топливо будет медленно накапливаться. Количество топлива в [[encyclopedia ENC_SHIP_DESIGN]] зависит от вместимости [[encyclopedia ENC_SHIP_HULL]], а также может быть увеличено с помощью некоторых [[encyclopedia ENC_SHIP_PART]]. +Топливо позволяет кораблям путешествовать между галактиками за пределами [[metertype METER_SUPPLY]] своей империи. Топливо расходуется на каждый пройденный отрезок галактического пути. Остановка корабля в зоне [[metertype METER_SUPPLY]] позволяет полностью заправить баки. Если корабль остается неподвижным за пределами [[metertype METER_SUPPLY]], топливо будет медленно накапливаться. Количество топлива в Дизайн корабля зависит от вместимости [[encyclopedia ENC_SHIP_HULL]], а также может быть увеличено с помощью некоторых [[encyclopedia ENC_SHIP_PART]] METER_SHIELD_VALUE_LABEL Энерго-щиты @@ -2780,70 +5083,112 @@ METER_SHIELD_VALUE_DESC Ещё одна разница между двумя этими типами щитов в том, что корабельные щиты могут быть заглушены межзвездным полем, известным как [[fieldtype FLD_MOLECULAR_CLOUD]]. ''' +METER_STRUCTURE_VALUE_LABEL +Структура + +METER_STRUCTURE_VALUE_DESC +Структура это некое количество [[encyclopedia DAMAGE_TITLE]], которое корабль может принять пока не будет полностью уничтожен. Структура может быть восстановлена путем ремонта, а ее максимальное значение увеличено с помощью [[encyclopedia ARMOR_TITLE]] + METER_DEFENSE_VALUE_LABEL Оборона METER_DEFENSE_VALUE_DESC -Значение Обороны планеты представляет собой мощность установленных орудий типа "Земля-Космос". Это сопоставимо с значением [[encyclopedia DAMAGE_TITLE]] космического корабля. +Значение Обороны планеты представляет собой мощность установленных орудий типа "Земля-Космос". Это сопоставимо со значением [[encyclopedia DAMAGE_TITLE]] космического корабля METER_SUPPLY_VALUE_LABEL Снабжение +METER_SUPPLY_VALUE_DESC +'''Показатель снабжения на планете представляет собой количество пересечений источников линий снабжения. Меньшие планеты могут обеспечивать снабжение на большие расстояния, большие же планеты уменьшают расстояния. + +Линии снабжения показаны окрашенными полосами цветом соответствующей империей. Планеты, связанные линиями снабжения, образуют «группу ресурсов» и могут делиться физическими ресурсами. Производственные очки, созданные на одной планете, могут использоваться на любой связанной планете для создания кораблей или сооружений. + +Набор базовых линий снабжения для империи (набор наименее прыжковых расстояний звезд, соединяющих ресурсодобывающие системы каждой группы ресурсов) будет иметь большую толщину, чем неосновные звездные переходы. Если PP в настоящее время теряется в составе группы ресурсов, внешние полосы основных звездных звезд будут выделены контрастным цветом. + +Линии снабжения также могут использоваться для пополнения [[metertype METER_FUEL]] кораблей''' + METER_TROOPS_VALUE_LABEL Войска METER_TROOPS_VALUE_DESC -'''Термин Войска сочетает в себе все виды вооруженных сил, способных работать на поверхности планеты. +'''Термин Войска сочетает в себе все виды вооруженных сил, способных работать на поверхности планеты +Размер войска на планете представляет собой силу обороняющихся сухопутных отрядов. Вторгшиеся силы должны отправить более мощную силу (т.е. выше общее число войска), чтобы завоевать планету. На кораблях должны быть построены специальные отсеки для перевозки войск и такие войска не могут вторгнуться, если защищающаяся планета имеет активные [[metertype METER_SHIELD]]''' -Рейтинг Войска на планете представляет собой силу обороняющихся сухопутных отрядов. Вторгшиеся силы должны отправить более мощную силу (т.е. выше общее число войска), чтобы завоевать планету. На кораблях должны быть построены специальные отсеки для перевозки войск, и такие войска не могут вторгнуться, если защищающаяся планета имеет активные [[metertype METER_SHIELD]].''' +METER_REBEL_TROOPS_VALUE_LABEL +Мятежные войска -METER_DETECTION_VALUE_LABEL -Радиус обнаружения +METER_REBEL_TROOPS_VALUE_DESC +Оккупационные войска враждебные по отношению к владельцу объектов -METER_DETECTION_VALUE_DESC -'''Радиус обнаружения определяет расстояние в uu, на котором датчики могут действовать. Империя может видеть только объекты, значение [[metertype METER_STEALTH]] которых не выше его [[metertype METER_STEALTH]] и находятся в пределах радиуса обнаружения контролируемого космического корабля или планеты. +METER_SIZE_VALUE_LABEL +Размер -Космические корабли и планеты имеют Рейтинг Радиуса обнаружения. В меню настроек в разделе Галактическая карта есть опция для отображения в виде окружности Радиуса обнаружения. Для облегчения прогнозирования того, что будет в Радиусе обнаружения, существуют варианты отображения стандарта 'Шкалы масштабирования карты' так же, как 'Окружности масштабирования карты' с центром в выбранной системе и с указанием на то же самое расстояние, что и на шкале. Они обе реагирует на масштабирование карты и могут переключаться с помощью горячих главиш.''' +METER_SIZE_VALUE_DESC +Абстрактное значение для размера объекта, группы объектов или области METER_STEALTH_VALUE_LABEL Скрытность METER_STEALTH_VALUE_DESC -'''Термин Скрытность представляет собой способность объекта спрятаться от всех видов датчиков. Только империи с Значением [[encyclopedia DETECTION_TITLE]] большим или равным Значению Скрытности смогут увидеть данный объект. +'''Термин Скрытность означает способность объекта уклоняться от всех видов сенсоров. Только империи с [[encyclopedia DETECTION_TITLE]], превышающим или равным значению Скрытности данного объекта могут его обнаружить + +Космические корабли, здания, артефакты, системы и планеты имеют некое измерение скрытности''' + +METER_DETECTION_VALUE_LABEL +Радиус обнаружения + +METER_DETECTION_VALUE_DESC +'''Радиус обнаружения определяет расстояние в световых годах (uu), на котором датчики могут действовать. Империя может видеть только объекты, значение [[metertype METER_STEALTH]] которых не выше его [[metertype METER_STEALTH]] и находятся в пределах радиуса обнаружения контролируемого космического корабля или планеты -Космические корабли, здания, специализации, системы и планеты имеют свои Значения Скрытности.''' +Космические корабли и планеты имеют Радиус обнаружения. В меню настроек в разделе Галактическая карта есть опция для отображения в виде окружности Радиуса обнаружения. Для облегчения прогнозирования того, что будет в Радиусе обнаружения, существуют варианты отображения стандарта 'Шкалы масштабирования карты' так же, как 'Окружности масштабирования карты' с центром в выбранной системе и с указанием на то же самое расстояние, что и на шкале. Они обе реагирует на масштабирование карты и могут переключаться с помощью горячих клавиш -METER_DETECTION_STRENGTH_VALUE_LABEL -Точность обнаружения +Некоторые объекты или способности могут позволить наблюдать определенные области за пределами этого диапазона. Так, например, это относятся к видовым свойствам [[species SP_TRITH]] и [[species SP_GEORGE]] или от эффектов [[buildingtype BLD_SCRYING_SPHERE]]''' -METER_DETECTION_STRENGTH_VALUE_DESC -'''Термин Точность Обнаружения представляет собой технологический уровень датчиков империи. Империя может увидеть только те объекты, значение [[metertype METER_STEALTH]] которых ниже или равно Точности Обнаружения и которые находятся в [[metertype METER_DETECTION]] подконтрольного корабля или планеты. +METER_SPEED_VALUE_LABEL +Скорость -Империи содержат Рейтинг Точности Обнаружения.''' +METER_SPEED_VALUE_DESC +Скорость на карте галактики (uu / ход). Расстояние uu, пройденное кораблем за один ход, равно его скорости на звездной карте и отображается пунктирной линией, за которой следует круг, показывающий количество ходов необходимых для полного перемещения и достижения следующей системы. Если скорость звездолета корабля меньше чем длина звездного пути для завершения путешествия потребуется не менее 2 ходов. Каждая пройденная звезда будет потреблять только 1 единицу [[metertype METER_FUEL]] независимо от требуемых ходов PEACE_TITLE Мир PEACE_TEXT -Мир - это состояние дипломатических отношений между империями, кода каждая из них принимает обязательство не атаковать корабли или планеты другой своего оппонента, а также не препятствовать [[metertype METER_SUPPLY]] флотов и обмену ресурсами и не мешать колонизации. +Мир - это состояние дипломатических отношений между империями, когда каждая из них принимает обязательство не атаковать корабли или планеты своего оппонента, а также не препятствовать [[metertype METER_SUPPLY]] флотов, обмену ресурсами и не мешать колонизации OUTPOSTS_TITLE Аванпосты OUTPOSTS_TEXT -Аванпосты - это автоматические станции, которые могут размещаться в местах, где жизнь колонистов невозможна. На них отсутствует население, и, как правило нет промышленности. Но они создают [[metertype METER_SUPPLY]] (при наличии соответствующей технологии), а так же обеспечивают обзор той области, где расположены. На планетах, где уже есть Аванпост могут быть основаны колонии. Чтобы построить Аванпост необходим корабль с модулем [[shippart CO_OUTPOST_POD]] на борту. +Аванпосты - это автоматические станции, которые могут размещаться в местах, где жизнь колонистов невозможна. На них отсутствует население и, как правило нет промышленности. Но они создают [[metertype METER_SUPPLY]] (при наличии соответствующей технологии), а так же обеспечивают обзор той области, где расположены. На планетах, где уже есть Аванпост могут быть основаны колонии. Чтобы построить Аванпост необходим корабль с модулем [[shippart CO_OUTPOST_POD]] на борту + +FOCUS_TITLE +Фокус + +FOCUS_TEXT +'''Каждая планета имеет настройку фокуса, которая направляет усилия жителей в ту или иную область, например [[metertype METER_INDUSTRY]] или [[metertype METER_RESEARCH]]. Не все области фокуса всегда доступны. Они могут быть ограничены, поскольку специфика вида не позволяет им сосредоточиться на этой области или же технология еще не известна или другие факторы доступные в описании [[encyclopedia ENC_SPECIAL]] + +Фокус планеты может дать бонусы или задействовать некий эффект. Небольшой штраф применяется при изменении фокуса планет, который со временем сходит на нет + +Планета по умолчанию будет использовать предпочитаемый фокус доминирующего вида, если она есть, иначе же по умолчанию будет [[metertype METER_INDUSTRY]]''' GROWTH_FOCUS_TITLE Цель: Рост населения GROWTH_FOCUS_TEXT -Рост населения достигается за счет освоения и экспорта редких материалов и веществ, что привлекает мигрантов. Доступен только в определенных ситуациях: на некоторых родных планетах, и на планетах с артефактами, способствующими росту населения. Планеты, которые ориентированы на рост населения, увеличивают максимальное население только на других планетах, соединённых с ними [[metertype METER_SUPPLY]]. Родные планеты, ориентированные на рост населения, только помогают другим планетам, также ориентированным на рост населения. Планета с артефактами, способствующими росту населения и сфокусированные на нём, помогает только соответствующему типу населения, например, некоторые помогают только росту органических видов жизни. +'''Рост населения достигается за счет освоения и экспорта редких материалов и веществ, что привлекает мигрантов. Доступен только в определенных ситуациях: на некоторых родных планетах и на планетах с артефактами, способствующими росту населения + +Планеты, которые ориентированы на рост населения, увеличивают максимальное население только на других планетах, соединенных с ними [[metertype METER_SUPPLY]] + +Родные планеты, ориентированные на рост населения, только помогают другим планетам, также ориентированным на рост населения. Планета с артефактами, способствующими росту населения и сфокусированные на нем, помогает только соответствующему типу населения, например, некоторые помогают только росту органических видов жизни + +[[GROWTH_SPECIALS_ENTRY_LIST]]''' TRADE_FOCUS_TITLE Цель: торговля TRADE_FOCUS_TEXT -Торговля пока ничего не делает. Она показывает, где планируется больше ресурсов. +Торговля пока ничего не делает. Она показывает, где планируется больше ресурсов PROTECTION_FOCUS_TITLE Цель: защита @@ -2855,87 +5200,324 @@ ARMOR_TITLE Броня ARMOR_TEXT -Увеличивает МАКС [[metertype METER_STRUCTURE]] корабля на рейтинг прочности брони. +Увеличивает максимальную [[metertype METER_STRUCTURE]] корабля на класс прочности брони SLOT_TITLE Слот SLOT_TEXT -'''Слот - отсек крепления [[encyclopedia ENC_SHIP_HULL]], который может быть занят определенными [[encyclopedia ENC_SHIP_PART]] (оружие, щиты, броня, двигатель и другие различные устройства). -Существует 2 типа слотов: внешний, внутренний и слот под ядро. Описание любой [[encyclopedia ENC_SHIP_PART]] содержит информацию, с каким слотом она совместима; некоторые совместимы с несколькими типами слотов. Эта информация также отображается графически в окне конструктора, где каждый тип слота в корпусе отрисован по-своему. [[encyclopedia ENC_SHIP_PART]] в Окне Проектов также отображены в виде формы, указывающей, с какими слотами они совместимы. В большинстве случаев, эскиз части четко соответствует определенной форме слота. Часть, которая совместима с несколькими типами слотов, имеет похожий на все эти слота эскиз. Большие (и дорогие) корпуса часто имеют больше слотов для установки частей.''' +Слот это отсек для установки в [[encyclopedia ENC_SHIP_HULL]], который может быть занят определенными [[encyclopedia ENC_SHIP_PART]] (оружие, щиты, броня, двигатель и другие различные устройства). Существует 3 типа слотов: внешний, внутренний и слот под ядро. Описание любой [[encyclopedia ENC_SHIP_PART]] содержит информацию с каким слотом она совместима; некоторые совместимы с несколькими типами слотов. Эта информация также отображается графически в окне [[encyclopedia DESIGN_WINDOW_ARTICLE_TITLE]], где каждый тип слота в корпусе отрисован по-своему. [[encyclopedia ENC_SHIP_PART]] в Окне дизайнов также отображены в виде формы, указывающей с какими слотами они совместимы. В большинстве случаев, эскиз части четко соответствует определенной форме слота. Часть, которая совместима с несколькими типами слотов, имеет похожий на все эти слота эскиз. Большие (и дорогие) корпуса часто имеют больше слотов для установки частей + +DETECTION_TITLE +Сила обнаружения + +DETECTION_TEXT +'''Термин Сила обнаружения представляет собой технологический уровень сенсоров империи. Империя может видеть только объекты, которые имеют значение [[metertype METER_STEALTH]], меньшее или равное его Силе обнаружения и находятся в пределах [[metertype METER_DETECTION]] управляемого космического корабля или планеты + +Империи обладают некой Силой обнаружения''' + +ENVIRONMENT_TITLE +Окружающая среда + +ENVIRONMENT_TEXT +'''При изучении планетных систем на галактической карте можно встретить различные среды: [[PT_INFERNO]], [[PT_RADIATED]], [[PT_TOXIC]], [[PT_BARREN]], [[PT_DESERT]], [[PT_TUNDRA]], [[PT_SWAMP]], [[PT_TERRAN]], [[PT_OCEAN]], [[PT_ASTEROIDS]] и [[PT_GASGIANT]] + +Каждый [[encyclopedia ENC_SPECIES]] имеет свои собственные экологические предпочтения, которые определяют пригодность планеты для вида в соответствии со следующим списком (от худшего к лучшему): + +[[PE_UNINHABITABLE]] < [[PE_HOSTILE]] < [[PE_POOR]] < [[PE_ADEQUATE]] < [[PE_GOOD]] + +Отчет о пригодности планеты может отображаться путем правого клика на планете в боковой панели системы. Зеленое число, следующее за данными пригодности, указывает максимальное значение популяции, если вид колонизирует планету, тогда как красное число указывает на то, что население будет стагнировать или уменьшаться до полного вымирания, если вид все-таки попытается колонизировать планету + +Терраформирование планеты (с помощью [[metertype METER_RESEARCH]] или если на планете имеется специальная особенность [[special GAIA_SPECIAL]]), позволяет лучше адаптировать ее к экологическим предпочтениям вида. Исходная среда планеты изменяется поэтапно, пока она, наконец, не достигнет [[PE_GOOD]] пригодности для вида, который хочет жить на планете. Степень терраформирования можно проверить на круге пригодности отображаемом ниже + +По часовой стрелке сверху: [[PT_TERRAN]], [[PT_OCEAN]], [[PT_SWAMP]], [[PT_TOXIC]], [[PT_INFERNO]], [[PT_RADIATED]], [[PT_BARREN]], [[PT_TUNDRA]], [[PT_DESERT]] + +''' ORGANIC_SPECIES_TITLE Метаболизм органических ORGANIC_SPECIES_TEXT -Органические виды, в большей или меньшей мере, 'живут как знают'. МАКС население таких видов увеличивается, когда их родина нацелена на [[encyclopedia GROWTH_FOCUS_TITLE]], или когда колонизирован Фактор Органического роста, и нацелен на рост популяции. Такой фактор помечаются зеленым символом 'O' в углу. +[[encyclopedia ORGANIC_SPECIES_CLASS]] виды в большей или меньшей мере, 'живут как знают'. МАКС население таких видов увеличивается, когда их домашний мир нацелен на [[encyclopedia GROWTH_FOCUS_TITLE]] или когда колонизируется планета с особенностью Органический рост и выставлением фокуса на рост популяции. Такой фактор помечается зеленым символом 'O' в углу LITHIC_SPECIES_TITLE -Метаболизм Педобионтов +Метаболизм педобионтов LITHIC_SPECIES_TEXT -Виды педобионтов состоят из кремниевой или минеральной основы. МАКС население таких видов увеличивается, когда их родина нацелена на [[encyclopedia GROWTH_FOCUS_TITLE]], или когда колонизирован Фактор Педобионического роста, и нацелен на рост популяции. Такой фактор помечаются серым символом 'L' в углу. +[[encyclopedia LITHIC_SPECIES_CLASS]] виды состоят из кремниевой или минеральной основы. МАКС население таких видов увеличивается, когда их домашний мир нацелен на [[encyclopedia GROWTH_FOCUS_TITLE]] или когда колонизируется планета с особенностью Каменный рост и выставлением фокуса на рост популяции. Такой фактор помечается серым символом 'L' в углу ROBOTIC_SPECIES_TITLE -Метаболизм Роботов +Роботизированный метаболизм ROBOTIC_SPECIES_TEXT -Роботизированные Виды представляют собой разумные машины. МАКС население таких видов увеличивается, когда их родина нацелена на [[encyclopedia GROWTH_FOCUS_TITLE]], или когда колонизирован Фактор Роботизированного роста, и нацелен на рост популяции. Такой фактор помечаются красным символом 'R' в углу. +[[encyclopedia ROBOTIC_SPECIES_CLASS]] виды представляют собой разумные машины. МАКС население таких видов увеличивается, когда их домашний мир нацелен на [[encyclopedia GROWTH_FOCUS_TITLE]] или когда колонизируется планета с особенностью Роботизированный рост и выставлением фокуса на рост популяции. Такой фактор помечается красным символом 'R' в углу PHOTOTROPHIC_SPECIES_TITLE Фототрофный метаболизм PHOTOTROPHIC_SPECIES_TEXT -'''Фототрофные виды живут в основном или полностью на солнечных лучах. МАКС население этих видов в значительной степени зависит от яркости местной звезды - чем ярче, тем лучше. Факторы роста не в их интересах. +'''[[encyclopedia PHOTOTROPHIC_SPECIES_CLASS]] виды живут в основном или полностью в солнечном свете. МАКС население этих видов в значительной степени зависит от яркости местной звезды - чем ярче тем лучше. Факторы роста не в их интересах Изменения численности населения: -Голубая Звезда: Крошечный +3 , Маленький +6 , Средний +9 , Большой +12 , Огромный +15. -Белая Звезда: Крошечный +1.5 , Маленький +3 , Средний +4.5 , Большой +6 , Огромный +7.5. -Желтая Звезда, Оранжевая Звезда: Без изменений. -Красная Звезда, Нейтронная Звезда: Крошечный -1 , Маленький -2 , Средний -3 , Большой -4 , Огромный -5. -Черная Дыра, Без Звезд: Крошечный -10 , Маленький -20 , Средний -30 , Большой -40 , Огромный -50.''' + +* [[STAR_BLUE]] звезда: ([[SZ_TINY]] +3) ([[SZ_SMALL]] +6) ([[SZ_MEDIUM]] +9) ([[SZ_LARGE]] +12) ([[SZ_HUGE]] +15) + +* [[STAR_WHITE]] звезда: ([[SZ_TINY]] +1.5) ([[SZ_SMALL]] +3) ([[SZ_MEDIUM]] +4.5) ([[SZ_LARGE]] +6) ([[SZ_HUGE]] +7.5) + +* [[STAR_YELLOW]] звезда, [[STAR_ORANGE]] звезда: (без изменений) + +* [[STAR_RED]] звезда, [[STAR_NEUTRON]] звезда: ([[SZ_TINY]] -1) ([[SZ_SMALL]] -2) ([[SZ_MEDIUM]] -3) ([[SZ_LARGE]] -4) ([[SZ_HUGE]] -5) + +* [[STAR_BLACK]], [[STAR_NONE]]: ([[SZ_TINY]] -10) ([[SZ_SMALL]] -20) ([[SZ_MEDIUM]] -30) ([[SZ_LARGE]] -40) ([[SZ_HUGE]] -50)''' SELF_SUSTAINING_SPECIES_TITLE -Самоподдерживающийся Метаболизм +Самоподдерживающийся метаболизм SELF_SUSTAINING_SPECIES_TEXT -Самоподдерживающиеся виды могут быть энергетическими или кристаллическими существами или чем-то более иноземным. Их объединяет то, что они не нуждаются в питании, поэтому их МАКС численность населения очень высок. Несмотря на то, что у них отсутствуют факторы роста, их МАКС численность населения равна значению вида, который снабжен всеми тремя [[encyclopedia GROWTH_FOCUS_TITLE]] факторами. +Самоподдерживающиеся виды могут быть энергетическими или кристаллическими существами или чем-то более иноземным. Их объединяет то, что они не нуждаются в питании, поэтому их МАКС численность населения очень высока. Несмотря на то, что у них отсутствуют факторы роста, их МАКС численность населения равна значению вида, который снабжен всеми тремя [[encyclopedia GROWTH_FOCUS_TITLE]] факторами TELEPATHIC_TITLE Телепатия TELEPATHY_TEXT -Виды, обладающие телепатией, имеют иммунитет к контролю над разумом и легче исследуют [[tech LRN_PSIONICS]]. +Виды, обладающие телепатией, имеют иммунитет к контролю над разумом и легче исследуют [[tech LRN_PSIONICS]] XENOPHOBIC_SPECIES_TITLE Ксенофобия XENOPHOBIC_SPECIES_TEXT -'''Виды, обладающие ксенофобией, автоматически используют часть своих промышленных усилий для причинения беспокойства другим видам, находящимся неподалеку. Пораженные виды получают штраф Промышленности из-за такого беспокойства. Ксенофобы для самоподдерживающегося метаболизма должны иметь поблизости чужеродных соседей, чтобы подорвать их природный баланс (либо для прямого воздействия, либо для пустой траты энергии на преследование чужаков), что приведет к снижению их МАКС численности населения. +'''Виды, обладающие ксенофобией, автоматически используют часть своих промышленных усилий для причинения беспокойства другим видам, находящимся неподалеку. Пораженные виды получают штраф Промышленности из-за такого беспокойства. Ксенофобы для самоподдерживающегося метаболизма должны иметь поблизости чужеродных соседей, чтобы подрывать их природный баланс (либо для прямого воздействия, либо для пустой траты энергии на преследование чужаков), что приводит к снижению их МАКС численности населения -Ксенофобы начинают с изученной [[tech CON_CONC_CAMP]].''' +Ксенофобы начинают с изученной [[tech CON_CONC_CAMP]]''' FLEET_UPKEEP_TITLE Содержание флота FLEET_UPKEEP_TEXT -Стоимость флота больше, чем сумма стоимости кораблей из-за эксплуатационных расходов, связанных с управлением несколькими кораблями. При производстве новых кораблей, затраты увеличиваются на 1% на каждый корабль, уже принадлежащий империи. Стоимость [[shippart CO_COLONY_POD]], [[shippart CO_SUSPEND_ANIM_POD]] и [[shippart CO_OUTPOST_POD]] а также всех зданий колонии в свою очередь увеличивается на 6% за каждую планету, уже принадлежащую империи. +Стоимость флота больше, чем сумма стоимости кораблей из-за эксплуатационных расходов, связанных с управлением несколькими кораблями. При производстве новых кораблей, затраты увеличиваются на 1% на каждый корабль, уже принадлежащий империи. Стоимость [[shippart CO_COLONY_POD]], [[shippart CO_SUSPEND_ANIM_POD]] и [[shippart CO_OUTPOST_POD]] а также всех зданий колонии в свою очередь увеличивается на 6% за каждую планету уже принадлежащую империи EVACUATION_TITLE Эвакуация EVACUATION_TEXT -'''Построенный проект [[buildingtype BLD_EVACUATION]] способствует переезду населения планеты. Каждый ход часть населения убывает. Если колонизированы планеты того же вида с свободными жилищами, и находящиеся в зоне действия [[metertype METER_SUPPLY]], одна из них будет выбрана случайно и население переселится на нее. Если такие планеты отсутствуют, население просто испарится в космосе. -Во время эвакуации Производство планеты останавливается. Когда колония опустеет, она превратится в [[encyclopedia OUTPOSTS_TITLE]]. Она будет по-прежнему принадлежать империи и становиться доступна для новой колонизации, как только здание [[buildingtype BLD_EVACUATION]] будет разрушено.''' +'''Построенный проект [[buildingtype BLD_EVACUATION]] способствует переезду населения планеты. Каждый ход часть населения убывает. Если колонизированы планеты того же вида с свободными жилищами и находящиеся в зоне действия [[metertype METER_SUPPLY]], одна из них будет выбрана случайно и население переселится на нее. Если такие планеты отсутствуют, население просто испарится в космосе +Во время эвакуации Производство планеты останавливается. Когда колония опустеет, она превратится в [[encyclopedia OUTPOSTS_TITLE]]. Она будет по-прежнему принадлежать империи и становиться доступна для новой колонизации, как только здание [[buildingtype BLD_EVACUATION]] будет разрушено''' AI_LEVELS_TITLE -Уровни Агрессий AI +Уровни агрессий AI + +AI_LEVELS_TEXT +'''Уровни агрессии ИИ должны быть примерно схожи с уровнями сложности, но не совсем, особенно в том смысле, что они различаются по различным аспектам принятия риска и нападения, причем только некоторые фактические ограничения применяются к более низким уровням. Все они могут играть довольно хорошо, но с более низкими уровнями агрессии немного легче управлять. Чем более агрессивны, тем лучше в течение игры + +ИИ переименовывают свою столичную планету на первом ходу, добавляя префикс. Как только вы откроете их домашнюю систему вы сможете определить их личность по префиксу +Текущие префиксы: + +Начинающие: +[[AI_CAPITOL_NAMES_BEGINNER]] + +Черепашьи: +[[AI_CAPITOL_NAMES_TURTLE]] + +Осторожные: +[[AI_CAPITOL_NAMES_CAUTIOUS]] + +Сдержанные: +[[AI_CAPITOL_NAMES_TYPICAL]] + +Агрессивные: +[[AI_CAPITOL_NAMES_AGGRESSIVE]] + +Маниакальные: +[[AI_CAPITOL_NAMES_MANIACAL]]''' + +DAMAGE_TITLE +Урон + +DAMAGE_TEXT +'''Определяет базовое количество, которое оружие уменьшит [[metertype METER_STRUCTURE]] другого корабля (или [[metertype METER_SHIELD]], [[metertype METER_DEFENSE]] и [[metertype METER_CONSTRUCTION]] планеты), в одном раунде боя (которых всего три в каждом раунде одного хода битвы). Если целевое судно оснащено [[metertype METER_SHIELD]], фактический урон, наносимый каждым ударом будет базовым уроном уменьшенным по уровню прочности корабля [[metertype METER_SHIELD]] + +Замечание: [[encyclopedia FIGHTER_TECHS]] наносят урон игнорируя [[metertype METER_SHIELD]] на кораблях, так как истребители фактически стреляют находясь внутри энергетического поля вражеских щитов''' + +MAP_WINDOW_ARTICLE_TITLE +Окно карты + +MAP_WINDOW_ARTICLE_TEXT +'''Окно карты можно рассматривать как «главный» игровой экран, на котором показана галактика и из которого можно получить доступ ко всем остальным окнам. В верхней панели отображается несколько элементов управления + +В левом верхнем углу находится кнопка хода, которая читается как «Ход n» (где n - текущий ход); Левый клик мыши на кнопку «Ход» приводит к тому, что игра переходит к следующему ходу. Сразу же справа находится кнопка Авто продвижение в виде двойной стрелки, которая приводит к тому, что игра не будет автоматически продвигаться вперед; Если щелкнуть по ней, то можно переключиться на круговые стрелки и игра будет автоматически переходить на следующий ход, как только все другие империи совершат свои ходы и отдадут приказы + +Ниже приведены значки, показывающие общий потенциал империи игрока в [[metertype METER_INDUSTRY]] и [[metertype METER_RESEARCH]], количество существующих кораблей и обнаруженных империй [[encyclopedia DETECTION_TITLE]] + +С правой стороны панели управления находится ряд значков для переключения отображения других важных окон пользовательского интерфейса. Всплывающие подсказки доступны и для них''' + +DESIGN_WINDOW_ARTICLE_TITLE +Окно дизайна + +DESIGN_WINDOW_ARTICLE_TEXT +'''[[DESIGN_WINDOW_ARTICLE_TITLE]] предназначено для просмотра или изменения конструкций монстров или кораблей + +Есть четыре вспомогательных окна: [[DESIGN_WND_STARTS]], [[DESIGN_WND_PART_PALETTE_TITLE]], [[DESIGN_WND_MAIN_PANEL_TITLE]] и [[MAP_BTN_PEDIA]] + + +[[DESIGN_WND_STARTS]] имеет четыре вкладки: пустую [[DESIGN_WND_HULLS]], [[DESIGN_WND_FINISHED_DESIGNS]] дизайны, [[DESIGN_WND_SAVED_DESIGNS]] проекты предыдущих игр и [[DESIGN_WND_MONSTERS]] проекты для контроля. Перетащите или дважды щелкните по корпусам, рисункам или монстрам, чтобы отредактировать / осмотреть их в проектировщике + +[[DESIGN_WND_FINISHED_DESIGNS]] проекты отображаются в том же порядке, что и «Производственном окне». Дизайны можно перемещать в списке, чтобы изменить их порядок. Удалите проекты с помощью «[[DESIGN_WND_DELETE_DESIGN]]» из контекстного меню. Корабли уже поставленные в очередь для производства будут по-прежнему строиться со старым дизайном. Сохраните проекты на диске для будущих игр с помощью «[[DESIGN_SAVE]]» в контекстном меню + +Щелкните правой кнопкой мыши [[DESIGN_WND_SAVED_DESIGNS]] по дизайну и скопируйте один или все сохраненные проекты в доступный список проектов империи. Если в настройках игры активировать «[[OPTIONS_ADD_SAVED_DESIGNS]], то все конструкции [[DESIGN_WND_SAVED_DESIGNS]] будут автоматически импортированы при старте новой игры + + +[[DESIGN_WND_PART_PALETTE_TITLE]] отображает части. Если связанные технологии с данной частью исследованы, то она может использоваться в текущих проектах. Другие части не могут использоваться, потому что либо связанная с ними технология не была исследована, либо часть доступна только монстрам + +"[[PRODUCTION_WND_AVAILABILITY_AVAILABLE]]" и "[[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]]" установка фильтров на [[DESIGN_WND_STARTS]] и [[DESIGN_WND_PART_PALETTE_TITLE]] позволит переключать отображение частей, которые империя может использовать или которые ограничены. Некоторые недоступные части могут быть разблокированы, исследуя связанные с ней [[encyclopedia ENC_TECH]] + +В окне [[DESIGN_WND_PART_PALETTE_TITLE]] есть фильтры для отображения / скрытия целых категорий частей + +[[DESIGN_WND_MAIN_PANEL_TITLE]] это панель проектирования для текущего дизайна + +Текущая конструкция может иметь внутренние, внешние и центральные [[encyclopedia SLOT_TITLE]]ы. Перетащите или дважды щелкните на части из [[DESIGN_WND_PART_PALETTE_TITLE]], чтобы добавить или удалить + +Добавьте имя и необязательное описание дизайна + +Нажмите «[[DESIGN_WND_ADD_FINISHED]]», чтобы добавить проект в конец списка существующих проектов [[DESIGN_WND_FINISHED_DESIGNS]] + +Нажмите «[[DESIGN_WND_UPDATE_FINISHED]]», чтобы заменить недавно выбранный дизайн [[DESIGN_WND_FINISHED_DESIGNS]] новым дизайном. «[[DESIGN_WND_UPDATE_FINISHED]]» будет активным только в том случае, если этот проект был запущен из проекта в списке [[DESIGN_WND_FINISHED_DESIGNS]]. «[[DESIGN_WND_UPDATE_FINISHED]]» только меняет новый дизайн, любые суда уже поставленные в очередь для производства или уже выпущенные останутся не обновленными +''' + +FLEET_MOVEMENT_ARTICLE_TITLE +Движение флота + +FLEET_MOVEMENT_ARTICLE_TEXT +'''Флоты можно найти в [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]] как набор символов в правом верхнем углу звездных систем. Их можно выбрать с помощью щелчка левой кнопкой мыши, который открывает окно со списком всех флотов в стеке + +Чтобы приказать выбранному флоту перейти в другую систему, щелкните правой кнопкой мыши на пункт назначения. Флоты с приказом о перемещении будут показаны в левом верхнем углу системы. Чтобы отменить приказ, щелкните правой кнопкой мыши по системе в которой находится флот + +Флоты могут содержать любое количество кораблей и могут быть разделены или объединены с другими флотами в той же системе + +У каждого флота есть возможность действовать в осторожном режиме (символ Глаза) или быть в агрессивным режиме (символ Кулака) + +Щелкните правой кнопкой мыши по флоту в окне флота, чтобы получить доступ к дополнительным параметрам, таким как переключение на автоматическое исследование, переименование или уничтожение флота''' + +HIDDEN_SETTINGS_ARTICLE_TITLE +Скрытые настройки + +HIDDEN_SETTINGS__ARTICLE_TEXT +'''Существует ряд настроек пользовательского интерфейса (UI) и других параметров игры, которые могут быть изменены пользователем через меню опций. Есть также несколько параметров пользовательского интерфейса, которые не отображаются на страницах настроек «Опции», но которые могут быть изменены пользователем, напрямую редактируя файл config.xml, расположенный в каталоге ведения журнала *:\Users\***\AppData\Roaming\FreeOrion. Пути для расположения Win7/8/10 указаны ранее для остальных систем местоположения этого файла можно посмотреть в FreeOrion Wiki + +В дополнение к итак большому количеству записей по настройкам и расположению различных элементов пользовательского интерфейса эти «Скрытые настройки» включают: + +UI.design-pedia-dynamic : управляет динамическим обновлением страницы описания энциклопедии дизайна в [[encyclopedia DESIGN_WINDOW_ARTICLE_TITLE]] с изменением названия проекта (что может вызвать некоторые лаги) + +show-fleet-eta : управляет расчетным временем прибытия флота (ETA) в количестве пройденных ходов от текущего хода являющимся началом движения и отображается на Панели управления флотом в Окне флота + +''' + +FIELDS_TITLE +Межзвездные пространства + +FIELDS_TEXT +Существует несколько типов областей, каждая из которых имеет своеобразный, несколько диффузный внешний вид. Щелчок правой кнопкой мыши по ним в [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]] вызывает небольшое всплывающее меню, идентифицирующее их тип и предоставляющее возможность просмотреть их данные ## ## Guides ## +GENERAL_PEDIA_REFERENCE +Информацию такую как эта, можно получить через «Энциклопедию» [[encyclopedia ENC_INDEX]] + +DETAILED_PEDIA_REFERENCE +[[GENERAL_PEDIA_REFERENCE]] Энциклопедию можно открыть и закрыть через фиолетовый значок с вопросительным знаком в верхней правой части основного экрана [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]]. В ней можно перемещаться по горячим ссылкам и с помощью стрелок в правом нижнем углу окна энциклопедии + +GREETINGS_GUIDE_TITLE +**Приветствую** + +GREETINGS_INTRO +Приветствую уважаемую сущность и Добро пожаловать во Вселенную FreeOrion! + +GREETINGS_GUIDE_REFERENCE +Новичкам этой вселенной, возможно, захочется ознакомиться с [[encyclopedia QUICK_START_GUIDE_TITLE]]. + +GREETINGS_GUIDE_TEXT +'''[[GREETINGS_INTRO]] + +[[DETAILED_PEDIA_REFERENCE]] +[[GREETINGS_GUIDE_REFERENCE]] + +FreeOrion - бесплатная, с открытым исходным кодом, пошаговая космическая империя и галактическая завоевательная (4X) компьютерная игра, разработанная и спроектированная проектом FreeOrion. FreeOrion вдохновлен традицией игр Master of Orion, но не является клоном или римейком этой серии или любой другой игры + +Дополнительная информация доступна по адресу freeorion.org +''' + +QUICK_START_GUIDE_TITLE +**Инструкция по началу работы** + +QUICK_START_GUIDE_TEXT +'''Это краткое введение в игровой процесс FreeOrion. Это руководство появилось недавно и в настоящее время в нем мало данных, но оно активно пополняется. Более полное руководство по начальным азам игры доступно на freeorion.org (текущий адрес: freeorion.org/index.php/V0.4_Quick_Play_Guide) + +[[DETAILED_PEDIA_REFERENCE]] +Важные разделы включают [[encyclopedia CATEGORY_GAME_CONCEPTS]], [[encyclopedia CATEGORY_GUIDES]] и список [[encyclopedia ENC_METER_TYPE]] +''' + +SITREP_IGNORE_BLOCK_TITLE +Игнорирование и блокировка оперативных сводок (докладов / отчетов) + +SITREP_IGNORE_BLOCK_SHORT_DESC +Игнорирование определенных отчетов и блокировки по типам докладов + +SITREP_IGNORE_BLOCK_TEXT +'''Существует два варианта скрытия отчетов о текущем состоянии дел: игнорирование и блокировка + +Игнорирование всегда, отчетов определенного события +Например, конкретное судно, прибывающее в определенную систему +Другой корабль прибывающий в эту систему или тот же корабль прибывающий, но уже в другую систему - это все разные события и они будут отображаться + +Блокировка отчета по типу применяется к любому отчету для одного и того же типа события +Например, [[SITREP_OWN_SHIP_ARRIVED_AT_DESTINATION_LABEL]] применяется к любому кораблю принадлежащему империи игроков и прибывающих в любую систему + +[[SITREP_SNOOZE_INDEFINITE]] скроет отчет для всех последующих ходов начиная с 0 хода +Параметры игнорирования для 5 или 10 хода начнут действовать с момента, который отражается в окне «Отчет о текущей обстановке» +Игнорирование отчетов относится только к текущей игре +Если клиент перезагружен или игра загружена, эти отчеты будут отображаться снова + +Блокирование типа отчета применяется ко всем видам и будет сохраняться между игровыми сеансами +Ранее заблокированные типы отчетов могут быть повторно активированы нажатием кнопки «Фильтры» +Типы отчетов будут иметь метки рядом с ними, если они включены / показаны +Типы отчетов появляются только в списке «Фильтры», как только происходит событие вызвавшее их + +Игнорирование полезно для скрытия повторяющегося события, например напоминаний о создании [[buildingtype BLD_GAS_GIANT_GEN]] на планете + +Блокирование полезно, когда вы видите такие отчеты, как например [[encyclopedia BEGINNER_HINTS]], виденные вами уже ранее +''' + +RESEARCH_TECH_GUIDE_TITLE +Исследовательские технологии + +RESEARCH_TECH_GUIDE_SHORT_DESC +Исследование и разблокировка новых элементов + +RESEARCH_TECH_GUIDE_TEXT +'''Изучение технологии [[metertype METER_RESEARCH]], разблокирует элемент, однако, это даст империи только знания о том, как построить что-то с использованием данной технологии. Фактически для создания может потребоваться дополнительные элементы или, например, доступ к определенному ресурсу +Если элемент был разблокирован, но недоступен для создания, можно проверить дополнительные требования, которые для этого необходимы путем наведения мышки на список построек в окне «Производство» (с выбранным [[PRODUCTION_WND_AVAILABILITY_UNAVAILABLE]]) + +Как правило, элементы разблокированные исследованной [[encyclopedia ENC_TECH]] не требуют наличия доступных или активных линий [[metertype METER_SUPPLY]] (например, здания или планетарные улучшения). Однако, если исследуется технология, которая улучшает часть корабля (например оружие), существующие корабли должны находиться в диапазоне снабжения и быть неподвижными (а не в бою), чтобы данное обновление было для них доступно''' + +AI_ERROR_MSG +AI_Error: ошибки в ИИ скрипте + +SERVER_MESSAGE_NOT_UNDERSTOOD +Сервер отправил сообщение, которое невозможно разобрать + +ORBITAL_DRYDOCK_REPAIR_TITLE +Орбитальный ремонтный док + +ORBITAL_DRYDOCK_REPAIR_TEXT +'''[[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] считается лучшим доступным ремонтным сооружением, способным восстановить корабль до первоначального состояния + +Когда на планете [[metertype METER_HAPPINESS]] ниже 15, ремонт не так эффективен. Если на планете [[metertype METER_HAPPINESS]] опускается ниже 5, ремонтный док не может выполнять никакие ремонтные работы + +Ремонт начнется только на следующий ход после того как корабль войдет в систему. Корабли нуждаются во времени для стыковки, а ремонтному доку требуется время для подготовки как необходимой оснастки, так и самого пристыкованного корабля + +Любой бой в системе нарушит ремонт на этом ходу''' ## ## Turn progress @@ -2945,7 +5527,7 @@ TURN_PROGRESS_PHASE_FLEET_MOVEMENT Продвижения флотов... TURN_PROGRESS_PHASE_COMBAT -Расчёт сражений... +Расчет сражений... TURN_PROGRESS_PHASE_EMPIRE_GROWTH Производство и демография... @@ -2975,6 +5557,11 @@ TURN_PROGRESS_STARTING_AIS TURN_BEGIN Начало хода %1% +ERROR_PROCESSING_SERVER_MESSAGE +Ошибка обработки сообщения с сервера + +ERROR_EARLY_TURN_UPDATE +Получено неожиданное обновление с сервера ## ## Messages panel @@ -2983,7 +5570,6 @@ TURN_BEGIN MESSAGES_PANEL_TITLE Сообщения - ## ## Players list ## @@ -2992,7 +5578,7 @@ PLAYERS_LIST_PANEL_TITLE Империи PLAYING_TURN -Ходы +Ходы игроков RESOLVING_COMBAT Сражения @@ -3000,7 +5586,6 @@ RESOLVING_COMBAT WAITING Ожидания - ## ## Objects window ## @@ -3014,33 +5599,163 @@ COLLAPSE_ALL EXPAND_ALL Развернуть все - ## ## Objects columns ## +METERS_SUBMENU +Размер + +PLANETS_SUBMENU +Планеты + +FLEETS_SUBMENU +Флот / Корабли + +NAME +Название + +OWNER +Владелец + +SPECIES +Виды + +BUILDING_TYPE +Тип строения + +FOCUS +Цель + +PREFERRED_FOCUS +Предпочтительная цель + +AVAILABLE_FOCI +Доступные цели + +PARTS +Части + +HULL +Корпус + +OBJECT_TYPE +Тип + +CREATION_TURN +Построение хода + AGE Возраст +TURNS_SINCE_FOCUS_CHANGE +Прошло ходов после изменения цели + +SUPPLY_RANGE +Область снабжения + +SUPPLYING_EMPIRE +Снабжающая империя + +SYSTEM_SUPPLY_RANGE +Диапазон снабжения системы + +PROPAGATED_SUPPLY_RANGE +Диапазон распростр. снабжения + +PROPAGATED_SUPPLY_DISTANCE +Дистанция распростр. снабжения + +ETA +Ходов до назначения + +PRODUCED_BY +Произведено + +SYSTEM +Текущая система + +NEAREST_SYSTEM +Ближ. система + +DESIGN_ID +ID дизайна + +FINAL_DEST +Система назначения + +NEXT_SYSTEM +След. система + +PREV_SYSTEM +Предыд. система + +LAST_TURN_BATTLE_HERE +Прошло ходов от сражения здесь + +LAST_TURN_ACTIVE_IN_BATTLE +Прошло ходов от сражения + +ARRIVED_ON_TURN +На данном ходу достиг цели + +SIZE_AS_DOUBLE +Размер + +DISTANCE_FROM_ORIGINAL_TYPE +Расст. от ориг. типа планеты + +PLANET_TYPE +Тип планеты + +ORIGINAL_TYPE +Ориг. тип планеты + +NEXT_TOWARDS_ORIGINAL_TYPE +След. ориг. тип планеты + +PLANET_SIZE +Размер планеты + +PLANET_ENVIRONMENT +Среда обитания планеты + +STAR_TYPE +Тип звезды + +NEXT_TURN_POP_GROWTH +На след. ходу население вырастет + +NUM_SPECIALS +Кол-во артефактов + +SPECIALS +Артефакты + +TAGS +Теги ## ## Filters dialog ## VISIBLE -'''Видимый ''' +Видимый PREVIOUSLY_VISIBLE Пред. видимый DESTROYED -'''Разрушенный ''' +Уничтоженный CONDITION_ALL Все CONDITION_EMPIREAFFILIATION -Владелец империя +Империя владелец + +CONDITION_ARMED +Вооруженный CONDITION_CAPITAL Столица @@ -3048,15 +5763,56 @@ CONDITION_CAPITAL CONDITION_MONSTER Монстр +CONDITION_STATIONARY +Неизменное + +CONDITION_AGGRESSIVE +Агрессивное + +CONDITION_CANCOLONIZE +Виды могут колонизировать + +CONDITION_CANPRODUCESHIPS +Виды могут производить корабли + +CONDITION_HOMEWORLD +Домашний мир + +CONDITION_HASSPECIAL +Имеет артефакт + +CONDITION_HASTAG +Имеет тег + +CONDITION_SPECIES +Название вида + CONDITION_PLANETSIZE Размер планеты CONDITION_PLANETTYPE -Тип Планеты +Тип планеты + +CONDITION_FOCUSTYPE +Настройка фокуса (цели) CONDITION_STARTYPE -Тип Звезды +Тип звезды + +CONDITION_METERVALUE +Значение свойства объекта + +CONDITION_HAS_GROWTH_SPECIAL +Имеет какой-либо рост + +CONDITION_PTYPE_W_GG +Газовый гигант вместе с планетой типа +CONDITION_PTYPE_W_AST +Астероиды вместе с планетой типа + +CONDITION_ANY +Любой ## ## Situation report @@ -3064,32 +5820,66 @@ CONDITION_STARTYPE ## Format: Ideally always start with Location: with ship/fleet names near the end. SITREP_PANEL_TITLE -Отчёт о событиях +Отчет о событиях - начальный ход # %1% number of the current turn. SITREP_PANEL_TITLE_TURN -Отчёт о событиях - Ход %1% +Отчет о событиях - ход %1% FILTERS Фильтры -SITREP_WELCOME_LABEL -Добро пожаловать +SITREP_IGNORE_MENU +Игнорировать -SITREP_GAS_GIANT_GENERATION_REMINDER -%planet% - идеальное месторасположение для %buildingtype%, но никто ее там не строил +SITREP_BLOCK_MENU +Блокировать -SITREP_SHIP_BUILT -Новый корабль %ship% был построен в систем %system%. +SITREP_SNOOZE_5_TURNS +За 5 ходов, игнорировать эти данные -SITREP_SHIP_BUILT_LABEL -Собран корабль +SITREP_SNOOZE_10_TURNS +За 10 ходов, игнорировать эти данные -SITREP_SHIP_BATCH_BUILT -%rawtext% %shipdesign% были произведены в %system%. +SITREP_SNOOZE_INDEFINITE +Полностью игнорировать эти данные -SITREP_SHIP_BATCH_BUILT_LABEL -'''Корабли пакетного Производства ''' +SITREP_SNOOZE_CLEAR_INDEFINITE +Показать полностью игнорируемые данные + +SITREP_SNOOZE_CLEAR_ALL +Показать все игнорируемые данные + +# %1% localized name of SitRep template +SITREP_HIDE_TEMPLATE +Блокировать все %1% данные + +SITREP_SHOWALL_TEMPLATES +Показать все заблокированные данные + +SITREP_WELCOME_LABEL +Добро пожаловать + +SITREP_SYSTEM_GOT_INCOMING_WARNING +%system%: Вражеские корабли прибывают на следующем ходу! + +SITREP_SYSTEM_GOT_INCOMING_WARNING_LABEL +Прибывающие враги + +SITREP_GAS_GIANT_GENERATION_REMINDER +%planet% является идеальным местом для %buildingtype%, но там ещё ничего не построено. + +SITREP_SHIP_BUILT +Новый корабль %ship% %shipdesign% был построен в системе %system%. + +SITREP_SHIP_BUILT_LABEL +Произведен корабль + +SITREP_SHIP_BATCH_BUILT +%rawtext% %shipdesign% были произведены в %system% + +SITREP_SHIP_BATCH_BUILT_LABEL +Корабли конвеерного производства SITREP_BUILDING_BUILT Новый комплекс %building% был построен на планете %planet%. @@ -3104,25 +5894,25 @@ SITREP_TECH_RESEARCHED_LABEL Открыта технология SITREP_TECH_UNLOCKED -%tech% была разблокирована и может быть исследована +%tech% была разблокирована и может быть исследована. SITREP_TECH_UNLOCKED_LABEL -Технология Разблокирована +Технология разблокирована SITREP_SHIP_PART_UNLOCKED -%shippart% разблокирован и теперь может быть использован в дизайне корабля. +%shippart% разблокирована и теперь может быть использована в дизайнах кораблей. SITREP_SHIP_PART_UNLOCKED_LABEL Доступна новая деталь корабля SITREP_SHIP_HULL_UNLOCKED -%shiphull% разблокирован и теперь может быть использован в качестве корпуса корабля. +%shiphull% разблокирован и теперь может быть использован в качестве корпуса кораблей. SITREP_SHIP_HULL_UNLOCKED_LABEL Доступен новый корпус корабля SITREP_BUILDING_TYPE_UNLOCKED -%buildingtype% разблокирован и доступен для строительства. +%buildingtype% разблокировано и доступно для строительства. SITREP_BUILDING_TYPE_UNLOCKED_LABEL Доступно новое здание @@ -3131,17 +5921,29 @@ SITREP_COMBAT_SYSTEM %combat% в системе %system%. SITREP_COMBAT_SYSTEM_LABEL -Битва в системе +Битва в системе + +SITREP_COMBAT_SYSTEM_ENEMY +В %system%: произошло %combat% с %empire%. + +SITREP_COMBAT_SYSTEM_ENEMY_LABEL +Сражение в системе - одна империя COMBAT -Битва +Сражение SITREP_OBJECT_DESTROYED_AT_SYSTEM -Уничтожен объект в системе %system%. +Неизвестный объект уничтожен в системе %system%. SITREP_OBJECT_DESTROYED_AT_SYSTEM_LABEL Объект уничтожен +SITREP_OWN_SHIP_DESTROYED_AT_SYSTEM +В %system%: %shipdesign% (%empire% корабль '%ship%') был уничтожен. + +SITREP_OWN_SHIP_DESTROYED_AT_SYSTEM_LABEL +Ваш корабль уничтожен + SITREP_SHIP_DESTROYED_AT_SYSTEM Уничтожен корабль %shipdesign% '%ship%' империи %empire% в системе %system%. @@ -3149,10 +5951,10 @@ SITREP_SHIP_DESTROYED_AT_SYSTEM_LABEL Уничтожен корабль империи SITREP_UNOWNED_SHIP_DESTROYED_AT_SYSTEM -Бесхозный корабль %shipdesign% уничтожен в системе %system%. +%shipdesign% уничтожен в системе %system%. SITREP_UNOWNED_SHIP_DESTROYED_AT_SYSTEM_LABEL -Бесхозный корабль уничтожен +Ничейный корабль уничтожен SITREP_FLEET_DESTROYED_AT_SYSTEM Уничтожен флот %fleet% империи %empire% в системе %system%. @@ -3164,19 +5966,19 @@ SITREP_UNOWNED_FLEET_DESTROYED_AT_SYSTEM Уничтожен флот %fleet% в системе %system%. SITREP_UNOWNED_FLEET_DESTROYED_AT_SYSTEM_LABEL -Бесхозный флот уничтожен +Ничейный флот уничтожен SITREP_PLANET_DESTROYED_AT_SYSTEM -Уничтожена планета %planet% империи %empire% в системе %system%. +Уничтожена планета %planet% империи %empire% в системе %system%! SITREP_PLANET_DESTROYED_AT_SYSTEM_LABEL Уничтожена планета империи SITREP_UNOWNED_PLANET_DESTROYED_AT_SYSTEM -Уничтожена планета %planet% в системе %system%. +Уничтожена планета %planet% в системе %system%! SITREP_UNOWNED_PLANET_DESTROYED_AT_SYSTEM_LABEL -Уничтожена бесхозная планета +Уничтожена ничейная планета SITREP_BUILDING_DESTROYED_ON_PLANET_AT_SYSTEM Уничтожено строение %building% империи %empire% на планете %planet% в системе %system%. @@ -3191,452 +5993,1130 @@ SITREP_UNOWNED_BUILDING_DESTROYED_ON_PLANET_AT_SYSTEM_LABEL Бесхозное строение уничтожено SITREP_OBJECT_DAMAGED_AT_SYSTEM -Объект был повреждён в системе %system%. +Неопознанный объект был поврежден в системе %system%. SITREP_OBJECT_DAMAGED_AT_SYSTEM_LABEL -Объект повреждён +Объект поврежден SITREP_SHIP_DAMAGED_AT_SYSTEM Корабль %shipdesign% '%ship%' империи %empire% был поврежден в системе %system%. SITREP_SHIP_DAMAGED_AT_SYSTEM_LABEL -Повреждён корабль империи +Поврежден корабль империи SITREP_UNOWNED_SHIP_DAMAGED_AT_SYSTEM -%shipdesign% был повреждён в системе %system%. +%shipdesign% был поврежден в системе %system%. SITREP_UNOWNED_SHIP_DAMAGED_AT_SYSTEM_LABEL -Повреждён бесхозный корабль +Поврежден неопознанный корабль -SITREP_PLANET_BOMBARDED_AT_SYSTEM -Планета %planet% империи %empire% в системе %system% подверглась бомбардировкам. +SITREP_PLANET_ATTACKED_AT_SYSTEM +В %system%: планета %planet% была атакована %empire%. -SITREP_PLANET_BOMBARDED_AT_SYSTEM_LABEL -'''Бомбардировка Планеты Империи ''' +SITREP_PLANET_ATTACKED_AT_SYSTEM_LABEL +Наша планета атакована -SITREP_UNOWNED_PLANET_BOMBARDED_AT_SYSTEM -Планета %planet% в системе %system% подверглась бомбардировкам. +SITREP_UNOWNED_PLANET_ATTACKED_AT_SYSTEM +В %system%: планета %planet% была атакована. -SITREP_UNOWNED_PLANET_BOMBARDED_AT_SYSTEM_LABEL -'''Бомбардировка Бесхозной Планеты ''' +SITREP_UNOWNED_PLANET_ATTACKED_AT_SYSTEM_LABEL +Планета была атакована SITREP_GROUND_BATTLE -Битва на планете %planet%. +Наземная битва на планете %planet%. SITREP_GROUND_BATTLE_LABEL -Битва на Планете +Битва на планете + +SITREP_GROUND_BATTLE_ENEMY +На %planet%: произошло наземное столкновение с %empire%. + +SITREP_GROUND_BATTLE_ENEMY_LABEL +Наземное сражение - одна империя SITREP_PLANET_CAPTURED Планета %planet% захвачена империей %empire%. SITREP_PLANET_CAPTURED_LABEL -Планета Захвачена +Планета захвачена SITREP_PLANET_DEPOPULATED -Всё население планеты %planet% погибло от голода. Планета потеряна! +Население планеты %planet% полностью вымерло! SITREP_PLANET_DEPOPULATED_LABEL -Планета Потеряна! +Планета опустошена! SITREP_PLANET_COLONIZED -Колонизирована планета %planet% в системе %system%. +Планета %planet% была заселена колонией расы %species%. SITREP_PLANET_COLONIZED_LABEL -Колонизирована Планета +Колонизирована планета + +SITREP_PLANET_OUTPOSTED +На %planet% был установлен аванпост. + +SITREP_PLANET_OUTPOSTED_LABEL +Установлен аванпост SITREP_NEW_COLONY_ESTABLISHED -Новая колония расы %species% заселена на планете %planet%. +Новая колония расы %species% заселилась на планете %planet%. SITREP_NEW_COLONY_ESTABLISHED_LABEL Новая колония заселена SITREP_FLEET_ARRIVED_AT_DESTINATION -Флот %fleet% прибыл в систему %system% и ожидает дальнейших распоряжений. +Флот %fleet% (%rawtext% кораблей) прибыл в систему %system%. SITREP_FLEET_ARRIVED_AT_DESTINATION_LABEL -Флот Прибыл +Флот прибыл + +SITREP_MONSTER_SHIP_ARRIVED_AT_DESTINATION +В %system%: прибыл %shipdesign%. + +SITREP_MONSTER_SHIP_ARRIVED_AT_DESTINATION_LABEL +Объявился монстр! SITREP_MONSTER_FLEET_ARRIVED_AT_DESTINATION -%fleet% прибыл в %system%. +%fleet% (%rawtext% кораблей) прибыл в %system%. SITREP_MONSTER_FLEET_ARRIVED_AT_DESTINATION_LABEL -Флот Монстров Прибыл +Флот монстров прибыл! + +SITREP_OWN_SHIP_ARRIVED_AT_DESTINATION +В %system%: прибыл %shipdesign% (%empire% корабль '%ship%'). + +SITREP_OWN_SHIP_ARRIVED_AT_DESTINATION_LABEL +Ваш корабль прибыл SITREP_OWN_FLEET_ARRIVED_AT_DESTINATION -%empire% %fleet% прибыл в %system%. +%empire% %fleet% (%rawtext% кораблей) прибыл в %system%. SITREP_OWN_FLEET_ARRIVED_AT_DESTINATION_LABEL -Собственный Флот Прибыл +Ваш флот прибыл + +SITREP_FOREIGN_SHIP_ARRIVED_AT_DESTINATION +В %system%: прибыл %shipdesign% (%empire% корабль '%ship%'). + +SITREP_FOREIGN_SHIP_ARRIVED_AT_DESTINATION_LABEL +Иностранный корабль прибыл SITREP_FOREIGN_FLEET_ARRIVED_AT_DESTINATION -%empire% %fleet% прибыл в %system%. +%empire% %fleet% (%rawtext% кораблей) прибыл в %system%. SITREP_FOREIGN_FLEET_ARRIVED_AT_DESTINATION_LABEL -Иностранный Флот Прибыл +Иностранный флот прибыл SITREP_POP_THRESHOLD -Колония на планете %planet% достигла достаточных размеров и [[metertype METER_HAPPINESS]] направляет своих колонистов на другие планеты. +Колония на планете %planet% достигла достаточных размеров и [[metertype METER_HAPPINESS]] и может теперь колонизировать другие планеты. SITREP_POP_THRESHOLD_LABEL -Порог Колонизации +Порог колонизации + +EFFECT_EXPERIMENT_MONSTERS_LAUNCH +WARNING: Наблюдения за %rawtext% %predefinedshipdesign% сообщают о %species% в %system%! + +EFFECT_EXPERIMENT_MONSTERS_LAUNCH_LABEL +Экспериментальный монстр EFFECT_ANCIENT_SHIP -В Древних руинах на %planet% обнаружили %predefinedshipdesign%. +В Древних руинах на %planet% обнаружили %predefinedshipdesign%! EFFECT_ANCIENT_SHIP_LABEL -'''Древние руины Корабль ''' +Корабль из древних руин EFFECT_ANCIENT_SHIP_RUMORS -Слухи распространяются в галактике: %predefinedshipdesign% был обнаружен в ксеноархеологической экспедиции в Древних Руинах! +По галактике распространяются слухи, что в Древних руинах был найден %predefinedshipdesign%, в ходе ксеноархеологической экспедиции! EFFECT_ANCIENT_SHIP_RUMORS_LABEL Древние руины - слухи о корабле EFFECT_ANCIENT_BUILDING -В Древних руинах на %planet% обнаружили %buildingtype%. +В Древних руинах на %planet% обнаружили %buildingtype%! EFFECT_ANCIENT_BUILDING_LABEL Древние руины Здание EFFECT_ANCIENT_BUILDING_RUMORS -Слухи распространяются в галактике: %buildingtype% был обнаружен в ксеноархеологической экспедиции в Древних Руинах! +По галактике распространяются слухи, что в Древних руинах было обнаружено здание %buildingtype%, в ходе ксеноархеологической экспедиции! EFFECT_ANCIENT_BUILDING_RUMORS_LABEL Древние руины - слухи о строении EFFECT_ANCIENT_TECH -В Древних руинах на %planet% обнаружили %tech%. +В Древних руинах на %planet% обнаружили технологию %tech%! EFFECT_ANCIENT_TECH_LABEL -Древние руины Технология +Технология из древних руин EFFECT_ANCIENT_TECH_RUMORS -Слухи распространяются в галактике: %tech% была обнаружена в ксеноархеологической экспедиции в Древних Руинах! +По галактике распространяются слухи, что в Древних руинах была найдена технология %tech%, в ходе ксеноархеологической экспедиции! EFFECT_ANCIENT_TECH_RUMORS_LABEL Древние руины - слухи о технологии +EFFECT_ANCIENT_EXTINCT_SPECIES +На %planet% в Древних руинах были найдены хорошо сохранившиеся останки вымершей расы %species%! + +EFFECT_ANCIENT_EXTINCT_SPECIES_LABEL +Древние руины - останки некоего вида + +EFFECT_ANCIENT_EXTINCT_SPECIES_RUMORS +По галактике распространяются слухи, что в Древних руинах были найдены хорошо сохранившиеся останки расы %species%! + +EFFECT_ANCIENT_EXTINCT_SPECIES_RUMORS_LABEL +Древние руины - слухи об останках некоего вида + EFFECT_ANCIENT_RUINS_EMPTY -Древние руины были раскопаны на %planet%, но были найдены только дразнящие намеки древней технологии. +Древние руины были раскопаны на %planet%, но найдены только дразнящие намеки древней технологии. EFFECT_ANCIENT_RUINS_EMPTY_LABEL Древние руины - пусто +EFFECT_NATIVES_TECH +На %planet%: технология %tech% была получена от %species%! + +EFFECT_NATIVES_TECH_LABEL +Местная технология + +EFFECT_ANCIENT_GUARDIANS_CAPTURED +На %planet%: [[species SP_ANCIENT_GUARDIANS]] самоуничтожились при захвате планеты! Планета теперь является Аванпостом под вашим контролем. + +EFFECT_ANCIENT_GUARDIANS_CAPTURED_LABEL +Древние стражи самоуничтожились + EFFECT_CONC_CAMP_COMLETE Этническая чистка на %planet% завершена. EFFECT_CONC_CAMP_COMLETE_LABEL -'''Этническая чистка ''' +Этническая чистка + +SITREP_SHIP_REPAIR_DOCK_NONE +Орбитальный ремонтный док - пусто + +SITREP_SHIP_REPAIR_DOCK_NOPOP +Орбитальный ремонтный док - нет населения + +SITREP_SHIP_REPAIR_DOCK_PARTIAL +Орбитальный ремонтный док - частично + +SITREP_SHIP_REPAIR_DOCK_COMPLETE +Орбитальный ремонтный док - завершено + +EFFECT_DRYDOCK_SHIP_REPAIR_NONE +На %planet%: комплекс %building% не может начать ремонт %ship% из-за низкого [[metertype METER_HAPPINESS]] планеты. + +EFFECT_DRYDOCK_SHIP_REPAIR_NOPOP +На %planet%: комплекс %building% заброшен, и некому починить корабль %ship%. + +EFFECT_DRYDOCK_SHIP_REPAIR_PARTIAL +На %planet%: %building% выполнено частичное восстановление %ship%. EFFECT_DRYDOCK_SHIP_REPAIR_COMPLETE %ship% починен в %building% на %planet%. EFFECT_MONSTER_SPAWNING -%predefinedshipdesign% Появился в %system%! +%predefinedshipdesign% появился в %system%! EFFECT_MONSTER_SPAWNING_LABEL Появился монстр +EFFECT_GAIAN_TERRAFORM +На %planet%: планета с [[special GAIA_SPECIAL]] терраформировала себя, чтобы лучше приспособиться к своим жителям. + EFFECT_GAIAN_TERRAFORM_LABEL -Терраформирования Гайя +Терраформирование Гайи + +EFFECT_GAIA +На %planet%: проходят неописуемые празднества, поскольку теперь их мир является [[special GAIA_SPECIAL]]-раем. + +EFFECT_GAIA_LABEL +Благодать Гайи EFFECT_TERRAFORM -%planet% терраформирована. +%planet% терраформирована EFFECT_TERRAFORM_LABEL Терраформирование +EFFECT_NEST_REMOVAL +На планете %planet% зачищено гнездо монстров. + +EFFECT_NEST_REMOVAL_LABEL +Зачистка гнезда монстров + +EFFECT_STARLANE_BORE +В %system%: открылась новая связь с близлежащей системой. + +EFFECT_STARLANE_BORE_LABEL +Звездная связь + +EFFECT_BLACKHOLE +%system% коллапсировала, создав чёрную дыру. + +EFFECT_BLACKHOLE_LABEL +Черная дыра + +EFFECT_ART_PLANET +%planet% была превращена в искусственную планету. + +EFFECT_ART_PLANET_LABEL +Искусственная планета + +EFFECT_TAME_MONSTER_HATCHED +На %planet%: прирученный %predefinedshipdesign% готов к службе + +EFFECT_TAME_MONSTER_HATCHED_LABEL +Рождение монстра + +EFFECT_TAME_MONSTER_MATURED +В %system%: более мощный ручной %predefinedshipdesign% вырос из меньшей формы + +EFFECT_TAME_MONSTER_MATURED_LABEL +Зрелый монстр + +EFFECT_SENTRY_CREATED +На %planet%: %predefinedshipdesign% готов к службе + +EFFECT_SENTRY_CREATED_LABEL +Часовой создан + +EFFECT_WHITE_KRAKEN_RESURRECTED +На %planet%: [[predefinedshipdesign SM_WHITE_KRAKEN]] был воскрешен и готов к служению + +EFFECT_WHITE_KRAKEN_RESURRECTED_LABEL +Монстр возрожден + +EFFECT_GATEWAY_VOID_DESTROY +%buildingtype% на %planet% уничтожено флотом %fleet% + +EFFECT_GATEWAY_VOID_DESTROY_LABEL +Врата вакуума уничтожены + +EFFECT_DAMP_CLOUD +В %system%: команда %ship% сообщает, что они теряют топливо на %predefinedshipdesign% + +EFFECT_DAMP_CLOUD_LABEL +Демпферные облака + EFFECT_NEBULA -Астрономы сообщают, что они наблюдают новую звезду в системе %system%, сформированную из туманности. +Астрономы сообщают, что они наблюдают новую звезду в системе %system%, сформированную из туманности EFFECT_NEBULA_LABEL Туманность +EFFECT_TREE +В %system%: новый лес Дайсона начал разрастаться и окружать звезду + +EFFECT_TREE_LABEL +Лес Дайсона + +EFFECT_DERELICT_MAP +В %system%: информация об этой области была получена из заброшенного %special% + EFFECT_DERELICT_MAP_LABEL -Заброшенная Карта +Заброшенная карта + +EFFECT_DERELICT_FUEL +В %system%: корабли были дозаправлены из оставшихся запасов найденных в заброшенном %special% EFFECT_DERELICT_FUEL_LABEL -Заброшенное Топливо +Заброшенное топливо + +EFFECT_BIOWEAPON +На %planet% потеряно %rawtext% населения из-за %shippart% атак + +EFFECT_BIOWEAPON_LABEL +Биологическое оружие EFFECT_EVACUEES -Эвакуированные поселились на %planet%. +Эвакуированные поселились на %planet% EFFECT_EVACUEES_LABEL Эвакуированные +EFFECT_STARGATE +В %system%: флот %fleet% достиг Звездных Врат + +EFFECT_STARGATE_LABEL +Использование Звездных Врат + +EFFECT_PLANET_DRIVE +В %system%: прибыла планета %planet% использовав [[buildingtype BLD_PLANET_DRIVE]] + +EFFECT_PLANET_DRIVE_LABEL +Планетарный привод звездного пути запущен + +SITREP_PLANET_DRIVE_FAILURE +В %system%: из-за недостаточно проведенных расчетов вблизи [[buildingtype BLD_LIGHTHOUSE]], [[buildingtype BLD_PLANET_DRIVE]] планета %planet% потерпела катастрофический коллапс по прибытии, что привело к ее полному уничтожению! + +SITREP_PLANET_DRIVE_FAILURE_LABEL +Ошибка планетарного запуска звездного пути + +EFFECT_PSY_DOM +Корабль %ship% %empire% подвергся психологическому контролю! + +EFFECT_PSY_DOM_LABEL +Психогенное доминирование + +EFFECT_MINES +В %system%: мины вызвали повреждение %rawtext% флота %fleet% %empire% + +EFFECT_MINES_SINGLE_SHIP +В %system%: мины вызвали повреждение %rawtext% корабля %ship% %shipdesign% + +EFFECT_MINES_LABEL +Системные мины + +EFFECT_MINES_SHIP_DESTROYED +В %system%: мины уничтожили корабль %ship% %empire% + +EFFECT_MINES_UNOWNED_DESTROYED +В %system%: мины уничтожили корабль %ship% %shipdesign% + +EFFECT_MINES_SHIP_DESTROYED_LABEL +Системные мины уничтожили корабль + +EFFECT_MINES_UNKNOWN +В %system%: неизвестный объект вызвал детонацию ваших мин + +EFFECT_MINES_UNKNOWN_LABEL +Детонация мин неопознанным кораблем + +EFFECT_FLEET_MOVED_TOWARDS +На %planet%: [[tech LRN_SPATIAL_DISTORT_GEN]] переместил %fleet% назад к %system%. Первоначально они были %rawtext% uu отдельно + +EFFECT_FLEET_MOVED_TOWARDS_LABEL +Флот перемещен к + +EFFECT_FLEET_MOVED_TO +На %planet%: [[tech LRN_SPATIAL_DISTORT_GEN]] переместил %fleet% обратно в %system%. Первоначально они были %rawtext% uu отдельно + +EFFECT_FLEET_MOVED_TO_LABEL +Флот перемещен в + +HEAD_ON_A_SPIKE_MESSAGE +Столица %empire% пала + HEAD_ON_A_SPIKE_MESSAGE_LABEL Вражеская столица захвачена +CUSTOM_SITREP_INTRODUCTION +Добро пожаловать в первоначальный брифинг о состоянии дел и обстановке в империи. В соответствии с директивой %tech% все занятые служащие империи стремятся собрать важную информацию и отправить ее на %system%, где она компилируется в отчеты, например, с уведомлением о важных событиях и других напоминаниях, выбранных лидером империи + +# This message value must contain no spaces. Use underlines instead. + +# This message value must contain no spaces. Use underlines instead. + +# This message value must contain no spaces. Use underlines instead. + +# This message value must contain no spaces. Use underlines instead. ## ## Victory/defeat ## +VICTORY_TECH +ПОБЕДА! %empire% превзошла сложившуюся реальность и выиграла технологическую победу + +VICTORY_ALL_ENEMIES_ELIMINATED +ПОБЕДА! %empire% последняя выживающая империя + +VICTORY_EXPERIMENTOR_CAPTURE +ПОБЕДА! %empire% захватила мощных Экспериментаторов и покончила со страшной угрозой наводнившую галактику + SITREP_VICTORY_LABEL Победа SITREP_EMPIRE_ELIMINATED -Империя %empire% была уничтожена. - +Империя %empire% была уничтожена SITREP_EMPIRE_ELIMINATED_LABEL -Империя Уничтожена. - +Империя уничтожена ## ## Random beginner hints ## +BEGINNER_HINTS +Случайные подсказки игры + +BEGINNER_HINTS_TEXT +'''Это сборник «различных советов», призванных помочь новичкам. +Одна из этих записей выбирается случайным образом на каждом ходу, они аккумулируются здесь для дальнейшего использования + +Все случайные игровые подсказки: + +[[BEGINNER_HINT_01]] + +[[BEGINNER_HINT_02]] + +[[BEGINNER_HINT_03]] + +[[BEGINNER_HINT_04]] + +[[BEGINNER_HINT_05]] + +[[BEGINNER_HINT_06]] + +[[BEGINNER_HINT_07]] + +[[BEGINNER_HINT_08]] + +[[BEGINNER_HINT_09]] + +[[BEGINNER_HINT_10]] + +[[BEGINNER_HINT_11]] + +[[BEGINNER_HINT_12]] + +[[BEGINNER_HINT_13]] + +[[BEGINNER_HINT_14]] + +[[BEGINNER_HINT_15]] + +[[BEGINNER_HINT_16]] + +[[BEGINNER_HINT_17]] + +[[BEGINNER_HINT_18]] + +[[BEGINNER_HINT_19]] + +[[BEGINNER_HINT_20]] + +[[BEGINNER_HINT_21]] + +[[BEGINNER_HINT_22]]''' + +RANDOM_BEGINNER_HINT +Совет #%rawtext% + +BEGINNER_HINT_01 +1: В начале игры создание [[predefinedshipdesign SD_SCOUT]] может помочь в изучении региона вокруг вашего родного мира + +BEGINNER_HINT_02 +2: Каждый вид имеет свои собственные планетарные [[encyclopedia ENVIRONMENT_TITLE]] предпочтения. Если вы cможете завоевать планету с другим видом, затем вы сможете колонизировать другие планеты используя их, что позволит быстрее защитить систему. Имейте в виду, что не все виды могут проводить колонизацию, а некоторые не могут строить корабли + +BEGINNER_HINT_03 +3: Корабли могут быть отремонтированы в системе, у которой есть дружественный [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], если в последний ход не было сражения в системе, а у планеты уровень [[metertype METER_HAPPINESS]] 5 или более. Корабли должны будут ждать один ход в системе до начала ремонта + +BEGINNER_HINT_04 +4: Системы, которые соединены толстыми линиями, связаны между собой [[metertype METER_SUPPLY]] и они разделяют [[metertype METER_INDUSTRY]] Производственные очки (PP) + +BEGINNER_HINT_05 +5: Все системы используют [[metertype METER_RESEARCH]] Научные очки (RP), которые они генерируют, даже если они не связаны линиями [[metertype METER_SUPPLY]]. Если система не подключена к линии снабжения, установка [[encyclopedia FOCUS_TITLE]] на исследования может быть более эффективной для планеты чем другие настройки + +BEGINNER_HINT_06 +6: [[metertype METER_SUPPLY]] линии могут использоваться для пополнения запасов [[metertype METER_FUEL]] кораблей + +BEGINNER_HINT_07 +7: Некоторые здания, такие как [[buildingtype BLD_INDUSTRY_CENTER]], будут влиять на каждую планету, у которой есть соединяющая линия [[metertype METER_SUPPLY]]. Другие, такие как [[buildingtype BLD_GAS_GIANT_GEN]], влияют только на систему в которой они построены. Проверьте раздел [[encyclopedia ENC_BUILDING_TYPE]], чтобы узнать о функциях различных зданий + +BEGINNER_HINT_08 +8: [[predefinedshipdesign SD_OUTPOST_SHIP]] можно установить [[encyclopedia OUTPOSTS_TITLE]] на любой невостребованной и видимой планете или астероиде в зоне досягаемости, если в этой системе нет вражеских кораблей + +BEGINNER_HINT_09 +9: [[predefinedshipdesign SD_COLONY_SHIP]] строятся дольше по времени чем [[predefinedshipdesign SD_OUTPOST_SHIP]], но они могут заселять планеты за пределами диапазона [[metertype METER_SUPPLY]] + +BEGINNER_HINT_10 +10: С помощью [[encyclopedia MAP_WINDOW_ARTICLE_TITLE]] выберите систему, щелкните правой кнопкой мыши на изображении планет на боковой панели системы, чтобы получить доступ к отчету о пригодности планеты. Здесь вы увидите, какие виды из вашей империи смогут успешно заселить эту планету. Цифры в зеленой области показывают максимальную численность населения, которую достигнет население планеты, если она будет колонизирована этим видом. Красные цифры указывают на то, что вид медленно вымрет, если попытаются колонизировать планету + +BEGINNER_HINT_11 +11: Максимальное количество населения, которое может вместить планета, можно повысить, исследуя различные технологии роста. Редкие события могут также увеличить максимальную популяцию видов с определенными метаболизмами. Ознакомьтесь с [[encyclopedia GROWTH_FOCUS_TITLE]] для получения дополнительной информации об этих возможностях + +BEGINNER_HINT_12 +12: Будьте внимательны при поиске планет с [[encyclopedia ENC_SPECIAL]], они обычно указывают на какой-то специальный ресурс на этой планете, который может принести пользу всей вашей империи, если его использовать должным образом. Щелкните правой кнопкой мыши по значку артефакта, чтобы получить доступ к записям «Энциклопедии» и узнать больше! + +BEGINNER_HINT_13 +13: Только [[species SP_EXOBOT]] могут заселять пояса астероидов. Если вы обнаружите множество поясов астероидов поблизости, вы можете исследовать эту возможность пораньше! + +BEGINNER_HINT_14 +14: Исследование усовершенствований [[metertype METER_SUPPLY]], таких как [[tech CON_ORBITAL_CON]], помогут увеличить расстояние снабжения от ваших систем + +BEGINNER_HINT_15 +15: Большинство видов начинают с [[metertype METER_HAPPINESS]] 1 при захвате планеты и затем обычно происходит увеличение на 1 за ход. Однако обратите внимание на вид [[encyclopedia XENOPHOBIC_SPECIES_TITLE]]! Им не нравится находиться рядом с другими видами и это будет влиять на счастье и другие ценности как их так и других соседних видов + +BEGINNER_HINT_16 +16: Возможно, вы захотите провести исследование [[tech LRN_PSIONICS]], пока ваша империя не заполучила вид с возможностями [[encyclopedia TELEPATHIC_TITLE]] + +BEGINNER_HINT_17 +17: [[metertype METER_SHIELD]] на кораблях защищает от каждого выстрела, который попадает в корабль, уменьшая урон на величину значения щита. На параметры щита могут влиять различные факторы, включая космические штормы, тип звездной системы, в которой вы находитесь или если вы близки к захвату столичной планеты другой империи + +BEGINNER_HINT_18 +18: Обратите внимание на настройку [[encyclopedia FOCUS_TITLE]] новых планет, которые входят в состав вашей империи, либо от заселения, либо от завоевания. Эта настройка регулирует, фокусируется ли планета на промышленном производстве на исследованиях или защите. Позже в игре по мере того, как будут исследованы новые [[encyclopedia ENC_TECH]] могут разблокироваться дополнительные возможности фокусировки. У разных видов могут быть разные варианты фокусировки + +BEGINNER_HINT_19 +19: Колонию можно основать на [[encyclopedia OUTPOSTS_TITLE]] построив здание колонии или использовать [[predefinedshipdesign SD_COLONY_SHIP]]. Для возведения большинства колониальных зданий требуется связь [[metertype METER_SUPPLY]] с планетой имеющей [[metertype METER_HAPPINESS]] не менее 5 и население не менее 3. Когда будет разблокирован вид, например, после исследования [[tech PRO_EXOBOTS]], то данное требование для зданий колонии больше не применяется + +BEGINNER_HINT_20 +20: Отчеты об обстановке (например, такие как этот) можно временно проигнорировать, щелкнув правой кнопкой мыши по их значку и выбрав один из параметров. Отчеты конкретного типа могут быть отфильтрованы на более постоянной основе с помощью меню «Фильтры» в нижней части этого окна. Эти подсказки для начинающих сгруппированы как [[encyclopedia BEGINNER_HINTS]] в списке фильтра + +BEGINNER_HINT_21 +21: Вы можете установить пункт назначения для судна, которое еще находится в очереди производства. В открытом производственном экране: нажмите на целевую систему на карте галактики, затем щелкните правой кнопкой мыши на судне в производственной очереди и выберете из меню выбора для этого корабля «Отправить в». Теперь ему будет присвоено название выбранной системы, куда вы хотите, чтобы корабль автоматически отправился после завершения строительства + +BEGINNER_HINT_22 +22: Выбрав систему и нажав на ALT-C вы увидите диапазон окружности системы, увеличение или уменьшение масштаба позволит оценить расстояние, посмотрев на величину измеряемого расстояния ## ## Species ## HOMEWORLD -Родина: +Домашний мир: NO_HOMEWORLD -Нет родины +Нет домашнего мира UNKNOWN_PLANET Неизвестная планета CANNOT_PRODUCE_SHIPS -Невозможно строить корабли +Не может строить корабли CAN_PRODUCE_SHIPS -Возможна постройка кораблей +Может строить корабли CANNNOT_COLONIZE Не может колонизировать планеты CAN_COLONIZE -Может строить корабли поселенцев +Может строить корабли колонистов OCCUPIED_PLANETS Захваченные планеты: ENVIRONMENTAL_PREFERENCES -Настройки окружающей среды: +Предпочтения [[encyclopedia ENVIRONMENT_TITLE]]: + +FOCUS_PREFERENCE +'''Предпочтительный [[encyclopedia FOCUS_TITLE]]: ''' + +OPINIONS_OF_EMPIRES +Мнения империй: + +OPINIONS_OF_OTHER_SPECIES +Мнения других видов: + +# Species Pedia Categories + +LITHIC_SPECIES_CLASS +Педобионты + +LITHIC_SPECIES_CLASS_DESC +Перечень [[encyclopedia LITHIC_SPECIES_TITLE]] видов: + +ORGANIC_SPECIES_CLASS +Органические + +ORGANIC_SPECIES_CLASS_DESC +Перечень [[encyclopedia ORGANIC_SPECIES_TITLE]] видов: + +PHOTOTROPHIC_SPECIES_CLASS +Фототрофные + +PHOTOTROPHIC_SPECIES_CLASS_DESC +Перечень [[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]] видов: + +ROBOTIC_SPECIES_CLASS +Роботизированные + +ROBOTIC_SPECIES_CLASS_DESC +Перечень [[encyclopedia ROBOTIC_SPECIES_TITLE]] видов: + +SELF_SUSTAINING_SPECIES_CLASS +Самоподдерживаемые + +SELF_SUSTAINING_SPECIES_CLASS_DESC +Перечень [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]] видов: + +# Species Pedia Articles SP_HUMAN Люди SP_HUMAN_GAMEPLAY_DESC -'''В основном безвредны. Все характеристики усреднены для тестирования. -Предпочитают планеты земного типа. -[[encyclopedia ORGANIC_SPECIES_TITLE]]. +'''Безжалостные, хищные преследователи, живущие племенами. +Предпочитают [[PT_TERRAN]] планеты. +[[encyclopedia ORGANIC_SPECIES_TITLE]] ''' SP_HUMAN_DESC '''Описание: -Люди - это двуногий, обособленный и всеядный вид с невероятной выносливостью. Человек эволюционировал, создав орудия труда, чтобы лучше охотиться на свою добычу, так как ему приходилось преследовать гораздо более крупных животных, пока день-за-днём тот ослабнет, а затем съесть его. Современные люди по-прежнему сильные борцы или избегают опасность. Но любая угроза, с которой не сможет справиться один индивидуум, будет встречена многочисленной группой работающих вместе, чтобы победить её с подавляющей силой. Люди, как известно, едят всё, что знают или что трудно переваривается просто для того, чтобы доказать что они это МОГУТ, и используют токсичные вещества в качестве ароматной приправы к еде. - -Общество: -Современные люди сформировали географические национальные государства с сильным основанием в исторических племенах. Эти государства конкурируют друг с другом, порой дело доходит до войны, хотя продолжают сотрудничать по другим вопросам. Разногласия по поводу религии, использовании ресурсов и даже в правильности приготовления того или иного блюда по-прежнему используются, чтобы оправдать агрессию друг к другу, и эти войны выступают в качестве шпор для дальнейшего технического прогресса. +Люди - двуногие всеядные существа, живующие племенами и обладающие невероятными способностями к выживанию. Они усовершенствовали свои орудия для более эффективной охоты, и способны долго преследовать жертву, чтобы в конечном итоге завалить её и съесть. Современные люди по-прежнему живут под сильным влиянием реакции "бей или беги", но, однако, с любой опасностью, с которой невозможно справиться в одиночку, они предпочитают бороться группой, чтобы количеством в конце концов преодолеть трудность. Иногда люди едят заведомо малосъедобные вещи только для того, чтобы доказать, что они могут это сделать, и используют некоторые ядовитые вещества для придания новых оттенков вкуса пище. +Социальный строй: +Современные люди создали несколько географически обусловленных наций, в основе которых прочно закрепились исторически сложившиеся культурные традиции. Эти нации конкурируют друг с другом, иногда устраивая войны, и в то же время - сотрудничают по другим вопросам. Религиозные разногласия, различные ресурсы и даже правильный способ приготовления определённой пищи - всё это до сих пор используются людьми для оправдания своих агрессивных действий, которые значительно тормозят технологический прогресс данной расы. ''' SP_SCYLIOR Скилиор SP_SCYLIOR_GAMEPLAY_DESC -'''Водоплавающие с тремя щупальцами. -Предпочитают водную среду. +''' +Морские головоногие моллюски с тремя щупальцами. +Предпочитают [[PT_OCEAN]] планеты. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' SP_SCYLIOR_DESC '''Описание: -Скилиоры - чешуйчатые головоногие, похожие на Nautilus. Они обладают передовым зрением, обеспеченным четырьмя глазами, и имеют четыре антенны, содержащие миллионы neuromasts, позволяя Скилиорам обнаружить изменения в гидравлическом давлении и электрических волнах. У Скилиора есть три больших, сильных питательных щупальца, которые они используют, чтобы схватить и держать инструменты или еду, в то время как она разбивается клювом и усваивается. +Скилиоры - панцирные головоногие моллюски, похожие на наутилусов. Они обладают усовершенствованным зрением, которое обеспечивается четырьмя глазами, и имеют четыре антенны с миллионами невромастов (чувствительных рецепторов) на них, позволяющих Скилиорам улавливать малейшие изменения в давлении воды и электрическом поле. У Скилиоров есть три крупных, сильных щупальца, предназначенные для удерживания предметов или добычи в процессе её поглощения, после того, как та была предварительно раздроблена на части клювом. -Хроматофоры представляют расу с развитой формой общения. Когда два Скилиора желают общаться друг с другом, они тотчас меняют окраску и рисунок на коже. +Хроматофоры являются основным способом общения для данной расы. Когда два Скилиора собираются коммуницировать, они быстро сменяют цвета и пятна на своей коже. -Скилиоры бесполые и перерождаются несколько раз за жизнь. Продолжительность жизни Скилиора составляет около 30 лет, в течении которых он постоянно растёт, наращивая раковину диаметром всего 5 см и вырастая каждый год на 10%. +Скилиоры бесполы и способны размножаться несколько раз за свою жизнь. Её продолжительность составляет около 30 лет, в течении которых Скилиор постоянно растёт, наращивая раковину диаметром всего 5 см и вырастая каждый год на 10%. -Общество: -Скилиоры (в переводе - доброжелательный Бог) явились как остатки некогда могучего мозгового телепатического улья "Мышление". Это привило им миссию путешествовать звезды в надежде на возвращение к ее прежней силе. Скилиоры очень социальные и полюбили изучение вещей и делиться знаниями с Мышлением. +Социальный строй: +Остатки некогда могущественного мозга в форме улья, обладающего телепатией и называемого "Разум", почитаются Скилиорами в качестве доброго божества. Именно оно внушило Скилиорам, что их высшая миссия - это путешествовать между звёздами, в надежде вернуть себе своё былое могущество. Скилиоры очень социальны и обожают изучать новые знания, которыми непременно делятся с Разумом. История: -Несколько тысячелетий назад экспедиция предшественников обнаружила Мышление, управляющее целой планетой с его телепатическими полномочиями. Мышление немедленно потянулось к ним и начало копировать знание ученых и экипажа их судна. Но ученые отключили большую часть мозга Мышления и взяли его с собой как образец. Оставшаяся часть Мышления выжила и жаждала возвратить свои воспоминания, которые были отобраны у него. Мышление, однако, было неподвижно, и в настоящее время, не имело никаких разумных существ по близости, чтобы действовать под их командованием. Так появились Скилиоры. +Несколько тысячелетий назад экспедиция предтеч обнаружила Разум, управлявший целой планетой при помощи своих телепатических способностей. Разум мгновенно потянулся к учёным предтеч, принявшись копировать их знания и знания экипажа судна. Но учёные разрушили большую часть мозга Разума, взяв его часть с собой как образец. Оставшаяся часть Разума выжила и жаждала возвратить воспоминания, которые были у него отобраны. Разум, однако, был неподвижен, и по настоящее время не имел рядом никаких разумных существ, способных действовать под своим командованием. Так появились Скилиоры. + ''' SP_GYSACHE Гисач SP_GYSACHE_GAMEPLAY_DESC -'''Трусливые причудливые травоядные, напоминающие овец. -Предпочитают болотистые планеты. +''' +Трусливые, причудливые травоядные, поведением напоминающие овец. +Предпочитают [[PT_SWAMP]] планеты. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' +SP_GYSACHE_DESC +'''Описание: +Гисач - травоядные существа, которые всегда были добычей. Они стремятся устранять любые опасности, которые им угрожают, пока все места во Вселенной не станут для них безопасны. + +Гисач имеют две ноги, на каждой из которых есть по два передних колена и одному заднему, расположенному между передними. Если Гисач приседает, его ноги складываются в форме буквы "W", и существо становится всего 0,5 метра в высоту (по сравнению с его исходным ростом в 2,5 метра, при полной расправленности ног). +Их продолговатые тела не имеют ничего похожего на голову, вместо которой на передней части расположены три одинаковых стебля с глазами, у основания которых есть небольшой рот. + +Социальный строй: +Гисач постоянно опасаются нападения, отчего всегда немного встревожены. Их естественная реакция - это бегство от всего на свете. Их правительственная система децентрирована, каждый индивидуум полностью независим от остальных и не образует никаких связей с прочими членами общества. Однако, у них всё же есть тенденция сбиваться в стада, что бережёт их при встрече с опасностью. + +Домашний мир: +Их домашний мир - планета с плотной атмосферой, содержащей азот, аммиак и углекислый газ. Большая часть поверхности увлажнена, однако присутствует недостаток крупных океанов - они составляют всего 5% от общей площади поверхности. Топографические особенности местности таковы, что очень высокие, скалистые, столовые горы изрезаны глубокими, тянущимися на километры каньонами, на дне которых находится стоячая вода. + +''' + SP_CHATO -Чато-м-Гошк +Чато SP_CHATO_GAMEPLAY_DESC -'''Сидячие кристаллические сущности, ездящие верхом на животных "Гормошках". -Предпочитают токсичную среду. +''' +Неподвижные кристаллические сущности, ездящие верхом на существах под названием "Гормошк". +Предпочитают [[PT_TOXIC]] планеты. [[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]] ''' +SP_CHATO_DESC +'''Описание: +Чато (полн. "Чато-мато-Гормошк") - это, фактически, два вида вместе, однако, не совсем понятно, симбиотической или паразитической является связь между ними. Главным здесь является Чато-мато - кристаллическая форма жизни около 30 сантиметров в высоту и 10 сантиметров в длину. Их цвет варьируется от светло-голубого до малинового. Чато-мато получают жизненную энергию от солнца или, в крайних случаях, от Гормошков, к которым прикреплены. Гормошки - это хищные животные, ранее населявшие подземные пещеры. Их тела круглые, плоские и покоятся на пяти несекомоподобных ногах. Чато-мато закрепляются на плоской верхней части тела Гормошков, прямо над их мозгом. Через кристаллический отросток они полностью контролируют Гормошков. + +Социальный строй: +В сообществе Чато-мато самым важным фактором является возраст существ, так как они по сути бессмертные. Любая команда, отданная старейшиной, тут же исполняется - ослушаться старшего у Чато-мато является чудовищным преступлением. Они разработали систему передачи сигналов через всю колонию без задержки, что позволило им общаться друг с другом почти со скоростью света. Это значительно усовершенствовало их умственные способности, поскольку все новые идеи и теории стало возможно мгновенно обрабатывать всей колонией. Гормошки являются транспортировщиками Чато-мато и в нормальных условиях о них хорошо заботятся, однако всё же они остаются, по большей части, обычными животными. + +''' + SP_TRITH Трит SP_TRITH_GAMEPLAY_DESC -'''Бесплотные неприветливые существа, обладающие телепатией. -Предпочитают радиоактивные планеты. -[[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] +''' +Яростные ксенофобы-телепаты. +Предпочитают [[PT_DESERT]] планеты. +[[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]], [[encyclopedia XENOPHOBIC_SPECIES_TITLE]] +''' + +SP_TRITH_DESC +'''Описание: +Трит - это смесь материи и огромного количества психической энергии. Формой они смутно напоминают гуманоидов. Все Триты обладают телепатией, и это является причиной их ярости: до тех пор, пока молодой Трит не научится держать свои мысли при себе, он неспособен и заглушить фон из чужих мыслей. Для взрослого Трита это просто небольшой дискомфорт, но для маленького может стать фатально. Иногда Триты обретают свои телепатические способности раньше, чем способны выдержать шум от объединённых умов всех существ во вселенной, что становится причиной того, что эти существа погибают или сходят с ума. + +Социальный строй: +Общественный строй Тритов - это теократия, возглавляемая священниками Каран-Дора (Бога Разума). Все они объединены общей единственной целью - уничтожить все прочие мыслящие расы, чтобы заставить замолчать их шумящие во Вселенной мысли. + +''' + +SP_HAPPY +День Рождения + +SP_HAPPY_GAMEPLAY_DESC +''' +Заброшенные, одинокие подводные роботы, запрограммированные для того, чтобы распевать себе «С Днем Рождения» через равные промежутки времени. +Предпочитают [[PT_OCEAN]] планеты. +[[encyclopedia ROBOTIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] +''' + +SP_HAPPY_DESC +'''Описание: Роботизированные подводные аппараты. +Хотя некоторые из них достаточно громоздки и неуклюжи, большинство представителей расы День Рождения всё же имеет обтекаемую, рыбоподобную форму. У всех них есть по одной или по нескольку камер и манипуляторов, обычно расположенных спереди. День Рождения также имеют набор датчиков температуры, вибрации, химикатов, радиолокационных, гидролокационных, электрических и магнитных полей, хотя часто это варьируется от модели к модели. Они функционируют в широком диапазоне температур и давления водной среды. + +Любой представитель расы День Рождения может починить своего "сородича", и именно поэтому их системы часто имеют дублирующие детали. Достаточно большие группы могут полностью отремонтировать другого представителя вида, а также создать таким образом совершенно новую единицу расы. Новые представители Дня Рождения имеют то же программное обеспечение, что и их "родители", но пустую память, которую необходимо заполнить путём исследования окружающей среды. + +Домашний мир: Вечно Одинокий +Огромная, глубокая океанида. Считается, что первоначально они были созданы с целью обследования этой планеты на предмет возможной колонизации. Неизвестно, почему их забросили - теории варьируются: от версии, что их создатели сочли планету непригодной для колонизации, до мнения о том, что планету было решено уничтожить (по той же причине). Несмотря на то, что океаны на планете были достаточно обширны и наполнены различными интересными формами жизни, здесь очень мало что может рассказать нам о том, почему эти роботы были направлены конкретно в этот мир в частности. + +Социальный строй: Созависимость. +Одинокий представитель расы День Рождения обычно психологически замыкается на ближайшем (желательно, чужеземном) чувственном существе, объявляя его своим лучшим другом. Так они пытаются облегчить своё одиночество - путём поиска другого существа, способного быть их другом, навсегда и навеки... + +Колонизация этих существ происходит медленно, а промышленность неэффективна из-за видовой депрессии, временами приводящей к приступам массового суицида. Однако, они стремятся не создавать врагов, а заводить друзей, что делает День Рождения хорошими торговцами. + +История: Грустная. +Будь то их первоначальная одинокая задача изучать свой домашний мир, отказ создателей от них, периодически возникающие депрессии, или путешествия в космосе в поисках дружбы - существование Дня Рождения было с самого начала печально, удручающе и одиноко. + + ''' SP_HHHOH -Ххох +Хххох SP_HHHOH_GAMEPLAY_DESC -'''Огромные медленные многохоботовые мамонты. -Педпочитают тундру. +''' +Огромные, медленные мамонты со множеством хоботов. +Предпочитают [[PT_TUNDRA]] планеты. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' +SP_HHHOH_DESC +'''Описание: +Хххохи - большие, теплокровные существа ростом более 16 метров в длину (включая хоботы и хвост), 6 метров в ширину, 5 с половиной метров в высоту и весом более 5 тонн. Обладают очень толстым слоем белого меха. +Хххохи передвигаются при помощи двух больших передних ног, используя заднюю в качестве балансира для их огромной спины. Они имеют также четыре длинных, хоботоподобных отростка по 8 метров в длину с четырьмя острыми когтистыми пальцами на кончиках, служащими для удерживания предметов. Когда их хоботы не используются, Хххохи скручивают их под своим телом и прячут в специальные кожистые мешки, чтобы сохранить тепло. Хххохи всеядны, но охотятся не активно, предпочитая выращивать крупные одомашненные жизненные формы. + +Индивидуальные особенности: +Хххохи называют себя социальными прагматиками. Старейшины очень почитаются, некоторые из них могут доживать до 230 лет. Из-за природной склонности к лени Хххохи ненавидят любые быстрые движения, а также поспешные решения. + + +''' + SP_EAXAW -Эксо +Экзо SP_EAXAW_GAMEPLAY_DESC -'''Злобные агрессивные черви. -Предпочитают пустыни. -[[encyclopedia ORGANIC_SPECIES_TITLE]] +''' +Злые, агрессивные черви-амазонки с ксенофобскими взглядами. +Предпочитают [[PT_TERRAN]] планеты +[[encyclopedia ORGANIC_SPECIES_TITLE]], [[encyclopedia XENOPHOBIC_SPECIES_TITLE]] +''' + +SP_EAXAW_DESC +'''Описание: +У Экзо существуют три стадии развития. Сначала - новорождённая, личиночная стадия, затем мужская форма, и, наконец, взрослая женская форма. Все они полностью слепы и используют осязание, обоняние, слух и чувство электрических токов для исследования окружающего мира. Женская форма представляет собой сегментированного червя длиной около 8 метров. На передней части расположен трёхклювый рот, используемый для рытья обширных систем туннелей. Нервная система самок представлена цепочкой нервных узлов, управляющих движениями тела. Большая же часть остального тела - это мускулатура, поддерживаемая защитной внешней оболочкой, состоящей из сегментов. Эти сегменты имеют небольшие перекрытия, напоминающие чешуйки, под которыми вырастают новые личинки Экзо. Мужская форма Экзо мельче по размеру и развита хуже. На кончике их хвоста находится сложный хватающий коготь, внешний вид которого очень индивидуален. + +Социальный строй: +Экзо живут матриархальными группами, нацеленными на размножение. Одна самка держит "гарем" из примерно 200 самцов, которые защищают её, кормят и спариваются с ней. Молодые самцы могут быть добавлены к основному гарему, но чаще всего их разменивают с прочими самками, словно валюту. Это обеспечивает генетическое разнообразие и способствует более широкому "ассортименту" самцов для выполнения различных специализированных нужд. Самые высокопоставленные самки (имеющие самые большие и разнообразные гаремы) образуют Великий Матриархальный Совет, который устанавливает глобальную повестку дня для всей расы Экзо. + +История: +Давным-давно заброшенный корабль Предтеч обрушился на родной мир Экзо. Изучив некоторые секреты данного корабля, Экзо пришли к выводу, что в космосе существуют и другие разумные виды. Вскоре их Великий Матриархальный Совет высказал величайшее провозглашение: "Всё это должно убраться отсюда!" + ''' SP_DERTHREAN Дертрин SP_DERTHREAN_GAMEPLAY_DESC -'''Пацифистичные, болебоязненные плавающие полурастения полувирусы. -Предпочитают токсичные планеты. -[[encyclopedia ORGANIC_SPECIES_TITLE]] +'''Пацифистичные, биотехнологичные, болебоязненные плавающие полурастения-полувирусы. +Предпочитают [[PT_TOXIC]] планеты. +[[encyclopedia ORGANIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] +''' + +SP_DERTHREAN_DESC +'''Описание: +Самосознание Дертрина представляет собой полупаразитическое, полусимбиотическое сосуществование вируса, живущего в теле растения. У вируса нет интеллекта, и растение без него - это также просто пустая оболочка. Но их нейронные синапсы способны совмещаться, и при этом образуется разумное существо. Однако, хотя вирус и даёт растению разум, он также токсически воздействует на его нервную систему, вызывая сильную боль и медленно разрушая по мере распространения по телу, в конечном итоге убивая растение. Таким образом, чем выше интеллект Дертрина, тем больше боли он испытывает. + +Социальный строй: +Раса Дертринов жёстко поделена на касты, созданные наследием их предков. Дертрины рождаются в касте, размножаются внутри неё и умирают как члены этой касты. Поскольку их размножение на протяжении тысячелетий происходило настолько изолированно, между кастами существуют даже физические различия. Касты делятся на следующие виды: самая доброжелательная каста Священников, каста Рабочих, каста Фермеров, каста Снабженцев, каста Учёных и, самая презираемая - каста Воинов. Религия играет фундаментальную роль в обществе Дертринов. Каждый из них является пацифистом и живёт только для того, чтобы угодить их Богине, в надежде на облегчение страшного бремени боли и получение прощения за предательство и кровожадность своих предков. ''' SP_LAENFA Линфа SP_LAENFA_GAMEPLAY_DESC -'''Подлые, чувствительные, обладающие телепатией черви. -Предпочитают водную среду. +''' +Ползучие, разумные лианы-телепаты. +Предпочитают [[PT_OCEAN]] планеты. [[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] ''' +SP_LAENFA_DESC +'''Описание: +Линфа - длинные лианоподобные существа, способные присоединять к себе или отсоединять любое количество мелких или более крупных лиан по желанию. Каждый лист обладает собственным, независимым сознанием и имеет полный контроль над небольшим участком лианы, к которому крепится. Линфа хорошо передвигается в водной среде и немного мобильна на суше, она движется (плавает и ползает) подобно змее. Когда к ней прикрепляются новые особи, эффективность их телепатической связи возрастает, позволяя синхронизовать движения отдельных ветвей Линфы для более эффективной её транспортировки. + +Социальный строй: +У Линфы единая культура ввиду их телепатии, но телепатическая связь слабеет с расстоянием, поэтому у них есть большая центральная лоза, которая принимает все самые важные решения. Эта лоза держит все свои обсуждения в секрете, но учитывает голоса всех представителей расы, которые сможет обнаружить. Социальный строй Линфы напоминает улей, где каждый индивидуум работает над целями всего коллектива - получением знаний о других видах и защитой знаний о самих себе. + +''' + +SP_SLY +Слаи + +SP_SLY_GAMEPLAY_DESC +'''Дружелюбные, наполненные газом существа, о которых до недавнего времени ничего не слышали. +Живут только на [[PT_GASGIANT]]. +[[encyclopedia GASEOUS_SPECIES_TITLE]] +''' + +SP_SLY_DESC +'''Описание: +Слаи живут на газовых гигантах. Газовые гиганты являются настолько непривлекательными для заселения, что значительную часть истории их даже не рассматривали в качестве планет, возможных для колонизации. +Слаи же не умели создавать технологичные приборы и устройства, отчего долгое время не могли покинуть свой домашний мир. Лишь недавно им стали доступны все необходимые технологии и оборудование для полётов в космос, выменянные у торговцев в обмен на знание своей истории. +При правильном режиме питания слаи образуют чистейшее, концентрированное топливо для кораблей. Находясь в системе с газовым гигантом, корабль слаев будет регенерировать топливо. + +Социальный строй: +Слаи - большие долгожители, и всю жизнь поют, поддерживая жизнь песен, в которых закодированы их научные знания и культура. Они очень открыты всей галактике и другим видам, с которыми, наконец, могут пообщаться. +''' + +SP_LEMBALALAM +Лембала-Лам +SP_LEMBALALAM_GAMEPLAY_DESC +'''Эгоцентричные, дегенерировавшие древние рептилии, утратившие возможность к размножению. +Предпочитают [[PT_DESERT]] планеты. +[[encyclopedia ORGANIC_SPECIES_TITLE]] +Обладают технологиями: [[tech SPY_STEALTH_1]], [[tech SPY_STEALTH_2]] and [[tech GRO_LIFECYCLE_MAN]] +''' + +SP_LEMBALALAM_DESC +'''Описание: +Лембала-Лам - древняя раса, находящаяся на закате своего существования. Ранее у них было некоторое сходство с рептилиями, такими как змеи или гекконы, но большая часть их первоначальных физиологических функций была утрачена из-за улучшения их жизни при помощи механических приспособлений и извращения естественного хода эволюции. Без машин они больше не могут ни передвигаться, ни питаться. Способность к размножению была полностью утрачена, но (что более важно) они потеряли доверие друг к другу. Последним гвоздём в гробу сообщества Лембалы стало изобретение автоматизированной системы, которая переносит разум умирающего Лембалы в следующий запасной его клон, сотнями которых каждый Лембала надёжно защитил себя. Почти все умственные и материальные ресурсы Лембал уходят на обеспечение их искусственного бессмертия, не оставляя ничего для реального развития их цивилизации. + +Социальный строй: +Лембалы знают друг друга уже целую вечность, но относятся подозрительно друг к другу. Их демократическое правительство навсегда парализовано множеством старых и безвыходных решений. Из-за параноидальности и отсутствия альтруизма даже самое простое сотрудничество у них как правило редко и недолго. +''' + SP_TAEGHIRUS -Т'игфирус +Тэ Гирус SP_TAEGHIRUS_GAMEPLAY_DESC -'''Слабые не летающие птицы, объединяющиеся в сообщества. -Предпочитают болотистые планеты. -[[encyclopedia ORGANIC_SPECIES_TITLE]] +'''Слабые, нелетающие птицы, объединившиеся ради общего сотрудничества. +Предпочитают [[PT_SWAMP]] планеты +[[encyclopedia ORGANIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] +''' + +SP_TAEGHIRUS_DESC +'''Описание: +Тэ Гирусы похожи на стервятников без перьев, с хрупким, серым телом. Их крылья ранее предназначались для планирования, а не для полётов, но позже и эта летательная функция была утеряна в ходе эволюции. + +Социальный строй: +Тэ Гирусы очень социальны и настроены на сотрудничество, что компенсирует их слабые тела. Их социальная и политическая система под названием "Верность" - это смесь участливости, демократии и братства, которая работает на удивление хорошо. ''' SP_MUURSH -Муурш +Му-Урш SP_MUURSH_GAMEPLAY_DESC -'''Миролюбивые травоядные. -Предпочитают пустыни. +'''Сражающиеся за мир травоядные. +Предпочитают [[PT_DESERT]] планеты. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' +SP_MUURSH_DESC +'''Описание: +Му-Урши - мирная, неагрессивная раса, смутно похожая на представителей семейства кошачьих, ставшая доминирующей формой жизни на своей родной планете благодаря непревзойдённой скорости реакции и зрению. Они использовали свои способности чтобы окружить и взять под контроль все угрожающие им хищные формы жизни, постепенно ограничивая их число для обеспечения мира на планете. + +Социальный строй: +К сожалению, их природные способности сделали их также одарёнными пилотами, и некоторые Му-Урши решили, что лучший способ установления мира в галактике - это завоевание и контроль всех агрессивных форм жизни, наделённых разумом. Они хотят сделать всю галактику такой же мирной, как и их родной дом.''' + SP_GEORGE -Гооги +Джордж SP_GEORGE_GAMEPLAY_DESC '''Единая сущность, обладающая телепатией, состоящая из множества многоножек. -Предпочитают тундру. +Предпочитают [[PT_TUNDRA]] планеты [[encyclopedia LITHIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] ''' +SP_GEORGE_DESC +'''Описание: +Джордж - единая телепатическая сущность, состоящая из множества подвижных телепатических организмов. + +Социальный строй: +Организмы, входящие в состав сущности Джордж, выглядят как многоножки длиной 2,5 метра и 0,5 метра в высоту, не включая мягкий отросток сзади и 6 щупиков-антенн спереди, предназначенных для различных манипуляций. Эти организмы не могут действовать и думать самостоятельно. Разум Джорджа - это сумма всех организмов вместе, которые общаются телепатически, на любом расстоянии. +Благодаря своим телепатическим способностям Джордж также может слышать и других Джорджей во Вселенной, вне зависимости от их принадлежности к своей империи. +''' + SP_CRAY Крэй SP_CRAY_GAMEPLAY_DESC -'''Жизнерадостные роботы с огромным энтузиазмом. -Предпочитают бесплодные планеты, но с легкостью приспосабливаются и к другим. +'''Весёлые роботы-энтузиасты. +Предпочитают [[PT_BARREN]] планеты. [[encyclopedia ROBOTIC_SPECIES_TITLE]] ''' +SP_CRAY_DESC +'''Описание: +Крэй напоминает большой, чёрный ящик из гладкого, блестящего пластика, с закруглёнными углами, длина ребра которого составляет 6 метров. Из центра каждой грани (в том числе сверху и снизу) простирается большое механическое щупальце, которое служит в первую очередь для передвижения или подъёма тяжестей. Крэи общаются по беспроводной радиосвязи. Они обладают впечатляющими способностями разделять деятельность, и способны обдумывать в среднем четыре мысли одновременно и вести более одного обсуждения, а часто - по нескольку, причем с одним и тем же собеседником. + +Социальный строй: +Крэи - племенное общество. Каждое племя формируется вокруг одного из Крэев, которого называют Мастер-Ремесленник. Он обладает навыками по созданию и техобслуживанию Крэев. Поскольку технически Крэи довольно плохо спроектированы, даже у Мастера-Ремесленника они редко получаются совершенными, поэтому каждое племя получает свои "расовые характеристики", в зависимости от особенностей работы их Мастера-Ремесленника. +Каждодневные нужды Крэев подчинены чрезвычайно сложной сети взаимопомощи. Любой Крэй с радостью поможет своему сородичу, если тот его попросит, причем оба из них отлично будут помнить, что такое долг. Хотя это и не самый эффективный способ управления экономикой, но это означает, что внутри племени Крэй всегда будет должен и ему будут все должны, что способствует созданию исключительно сильных социальных связей, а это именно то, что им нравится. +''' + SP_ETTY Етти SP_ETTY_GAMEPLAY_DESC -'''Кибернетические насаждения. -Предпочитают пустыни. +'''Кибернетические растения. +Предпочитают [[PT_DESERT]] планеты. [[encyclopedia ROBOTIC_SPECIES_TITLE]] ''' -SP_CYNOS -Синос +SP_ETTY_DESC +'''Описание: +Етти - это искуственно выведенная раса разумных растений. Они могут вырасти в высоту до полуметра и имеют мозг в центре большой луковицы. Етти испускают сильный аромат, который могут изменять по своему усмотрению, и благодаря этому качеству защищаются от растительноядных существ, имитируя запах крупных хищников. У них также хорошо развито обоняние. -SP_CYNOS_GAMEPLAY_DESC -'''Ходячие деревья неотделимые от огромного бесчувственного материнского дерева. -Предпочитают болота. -[[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]] +Роботизированное тело Етти включает в себя ноги, глаза, уши и руки. Специальный коннектор соединяет эту часть Етти с Етти-растением, позволяя существу передвигаться без необходимости болезненной пересадки себя всякий раз, когда это требуется. Форма роботизированной части может быть очень разнообразной, в зависимости от её предназначения. + +Социальный строй: +Етти чрезвычайно вежливы и строго соблюдают все контракты и соглашения. Их культура очень чистая, гражданская, доброжелательная и вежливая, но очень замкнутая. Правительство Етти - это настоящая демократия, ведущая активную работу с гражданами и проверенная многими невзгодами. +''' + +SP_CYNOS +Цино + +SP_CYNOS_GAMEPLAY_DESC +'''Подвижные растения, зависимые от гигантского, но неразумного материнского дерева. +[[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]] +''' + +SP_CYNOS_DESC +'''Описание: +Цино являются частью материнского растения, напоминающего агаву высотой 90 метров. Огромные листья этого растения имеют синеватый, аквамариновый цвет, который исходит из центра растения. Обширная, но очень неглубокая корневая система пускает ответвления, чтобы дотянуться до центрального растения. Одно растение может жить до 5000 лет. В его листьях находится множество "коробочек", где "рождаются" и питаются Цино. + +Цино начинает жить маленькой луковицей внутри материнской "коробочки", вырастая через несколько дней в полноразмерного индивидуума и отделяясь. В длину он 3,5 метра и имеет немного неправильную прямоугольную форму с сужающимися на концах лианами в каждом своём углу. Эти лианы используются для перемещения, кормления или сражения. Цино способен примерно 30 часов существовать независимо, после чего он обязательно должен вернуться к материнскому растению, чтобы поесть. Существуют и искусственные аналоги их пищи, но все они приводят к бесплодию у Цино. Поэтому все Цино, покидающие родной мир, становятся бесплодными. + +Социальный строй: +Цино очень альтруистичны и настроены на сотрудничество, если дело касается материнских растений. У них есть единое правительство, оказывающее сильное влияние на материнские растения, которые не наделены разумом, но всё же способны общаться с детьми-Цино на химическом уровне и манипулировать их настроением. + +Препятствие для распространения: +Цино не могут размножаться без материнских растений, и они не могут вырастить это растение из себя нигде, кроме их домашнего мира. ''' SP_PHINNERT Финнерт SP_PHINNERT_GAMEPLAY_DESC -'''Летающие обезьяны. +''' +Трудолюбивые летающие обезьяны. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' +SP_PHINNERT_DESC +'''Описание: +Финнерты - древесные млекопитающие, рост которых варьируется от 1 до 2 метров. У них длинные конечности с размахом крыльев, дважды превышающим их рост, и шерстистые, втягивающиеся перепонки, которые тянутся от запястьев до лодыжек и позволяют полноценный полёт при пониженной гравитации. Финнерты очень хорошо летают и могут ловить летающих насекомоподобных существ при помощи цепкого хватательного выроста на носу. Они могут использовать эхолокацию, как летучие мыши, однако этот навык у них развит слабо. + +Социальный строй: +Жизнь у Финнертов достаточно коротка для разумного существа, из-за множества опасностей, встречаемых на болоте. Финнерты воспринимают друг друга как членов одной огромной семьи, и все их обиды, как правило, недолговечны. У них нет центральной власти - есть только некоторое подобие иерархии. Баланс их сил может быстро меняться ввиду высокой смертности. Их общество не очень индивидуалистично - они больше сосредоточены на интересах группы. + +Домашний мир: +Их родная планета имеет низкую гравитацию и очень плотную атмосферу. Состав планеты уникален - в ней содержится очень низкий процент тяжёлых металлов, в то же время высок процент других тяжёлых элементов и благородных газов. Она также имеет стабильные тектонические образования, отчего кора у планеты очень плоская, и большая часть поверхности покрыта болотной, стоячей водой. Мир Финнертов - огромное, вечнозелёное болото. + +''' + +SP_REPLICON +Репликон + +SP_REPLICON_GAMEPLAY_DESC +'''Малообразованные самореплицирующие роботы. +Prefer [[PT_RADIATED]] planets. +[[encyclopedia ROBOTIC_SPECIES_TITLE]] +''' + +SP_REPLICON_DESC +'''Описание: +Репликоны выглядят как высокие, трёхметровые пауки-роботы с крыльями. Крылья, на самом деле - это высокоэффективные солнечные панели, охватывающие достаточно большую площадь; "живот" же является "промышленным обрабатывающим центром", где ремонтируются запчасти и куются новые репликоны. + +Социальный строй: +Репликоны имеют достаточно примитивное общество. У них есть правительство, представленное группой феодальных лордов, которые когда-то обнаружили, что космические путешествия - это отличный способ завоевывать существ, расположенных далеко. Лорды, ввиду наличия у них определённых редких качеств, сумели начать совершать перелёты ещё задолго до соответствующего этапа в развитии своей цивилизации (промышленной революции). Ввиду большого разнообразия самореплицирующих машин на Цисарруе (домашней планете репликонов), практически все основные потребности, удовлетворяемые другими расами при помощи промышленности, у репликонов достигаются при помощи роботизированного "сельского хозяйства" и "рождения" новых репликонов. Репликоны существуют без какой-либо реальной промышленной базы, зачастую не понимая и не зная технологий, связанных с использованием "естественных" деталей и продуктов. Их общество крайне необразованно - большая его часть существует на знаниях, полученных ещё при рождении, вроде: как снять с существа (с их планеты) титановый покров так, чтобы тот смог "отрасти" заново; или, сколько нужно урановой руды для перерабатывающего реактора, чтобы обогатить ею плохой алюминий, и т.д. Большая часть продукции, направленной на удовлетворение естественных нужд, здесь является уделом лишь лордов, остальная же часть репликонов существует в мире будто бы из постапокалиптического романа. + +Домашний мир: +Цисарруй - планета с очень жёсткой радиацией, тщательно скрывающая свои особенности. На планете доминируют самые разнообразные, самореплицирующие существа с роботизированным метаболизмом. На ней ведутся хроники всего того, кто, когда и с кем воевал, поскольку это представляет большую важность в делах, касающихся формирования Империи Цисарруй. Но, на самом деле, всё случившееся в те времена - типичный доиндустриальный период, как и у любых других наций, с его характерными подъёмами и спадами. Главная тайна репликонов кроется в их более дальнем, доисторическом прошлом. Неясно, были ли они творением рук другой разумной цивилизации, создавшей их в качестве эксперимента, или же появились в результате инцидента (например, некоего промышленного проекта, оставленного без присмотра). Или же их просто бросили на произвол судьбы и забыли... А, может, создателей постигла и худшая участь - они погибли от рук своих же собственных творений. +''' + SP_EGASSEM -Егассем +Игассем SP_EGASSEM_GAMEPLAY_DESC -'''Очень большие радиоактивные кристаллические амебы живущие в лаве. -Предпочитают планеты типа "Ад". +'''Сверхтяжёлые радиоактивные кристаллические амёбы, обитающие в лаве. +Предпочитают [[PT_INFERNO]] планеты. [[encyclopedia LITHIC_SPECIES_TITLE]] ''' +SP_EGASSEM_DESC +'''Описание: +Игассемы имеет в поперечнике несколько километров, но у них нет фиксированно устоявшегося внешнего вида. Они способны работать одновременно с несколькими инструментами при помощи дополнительно созданных придатков. Игассемы невероятно медленные - они способны передвигаться не более чем на несколько метров за час, но передвижение им всё же необходимо, для разграничения своих ресурсов. Они размножаются почкованием, и при отделении молодой Игассем имеет вес всего каких-то сто тысяч тонн. + +Социальный строй: +Сообщество Игассемов феодально по сути, каждый Игассем имеет свою, отдельную "вотчину". Тот факт, что Игассемы долгожители (так как они фактически не стареют) означает, что они очень консервативны и очень беспокоятся о своих богатствах. + +Домашний мир: +Мир, подогреваемый приливным и геологическим жаром, а также обычным солнечным излучением. Большие "океаны" расплавленной породы покрывают большую часть поверхности их планеты. +''' + SP_SSLITH Сслит SP_SSLITH_GAMEPLAY_DESC -'''Плоские водянистые игривые существа. -Предпочитают тундру. +'''Плоские, гибкие подводные существа. +Предпочитают [[PT_TUNDRA]] планеты. [[encyclopedia ORGANIC_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] ''' +SP_SSLITH_DESC +'''Описание: +В расслабленном состоянии Сслит представляет из себя прямоугольный лист толщиной 2,15 сантиметров, который в длину всегда 132 сантиметра, а в ширину от 31 до 53 сантиметров. Его единственная конечность - щупальце, которое используется для восприятия звука и инфракрасного света, а также для общения с помощью ультразвука. Сслиты способны принимать различные формы - этот навык у них развит путём эволюции для того, чтобы иметь возможность заселять раковины существ под названием Врин (которые тоже прячутся в свои раковины, если чувствуют опасность). Такая гибкая форма тела позволяет Сслитам удивительно быстро перемещаться по океанам, при этом они как бы просачиваются сквозь потоки воды, вибрируя телом. У них нет центральной нервной системы и мозга, а умственные способности обеспечиваются совокупностью нескольких нервных узлов, связанных вместе и формирующих распределённое сознание, позволяющиее Сслитам выполнять несколько задач одновременно. + +Социальный строй: +Сслиты объединяются в группы под названием Пнакра, являющиеся независимыми правительственными подразделениями. У них также существует несколько глобальных законов, которым подчиняются Сслиты из любой Пнакры, но внутри каждой Пнакры есть и свои собственные правила и законы, касающиеся манер поведения. Пнакра может перемещаться сама по себе и не имеет фиксированного географического положения. Если две Пнакры встречаются, то члены одной из них, недовольные её устоями, могут переходить в другую, чтобы попробовать чужие обычаи. +''' + +SP_FIFTYSEVEN +Пятьдесят-семь + SP_FIFTYSEVEN_GAMEPLAY_DESC '''Одержимые математикой летучие змеи, распевающие фрактальные песни на всю планету. -Предпочитают болотистые планеты. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' SP_FIFTYSEVEN_DESC -'''Физиология: -Взрослые 57 от 10м до 20м в длину. Основное отличие от конического змеиного тела - это 2 резонаторных уплотнения на спине. Различные плавники служат для маневрирования. Абсолютно слепые, они полагаются на обоняние, слух, удивительно чуткую интуицию, и, когда это необходимо, эхолокацию. 57 всеядны и питаются как косяками мелких организмов, так и плавающим фитопланктоном. Так как они являются самыми высокоразвитыми существами, но не имеют конечностей, у 57 нет какой-либо материально-технологической базы. +'''Описание: +Взрослые Пятьдесят-семь имеют в длину от 10м до 20м. Основное отличие их тела от змеиного - это 2 резонансных уплотнения на спине. Несколько плавников служат для маневрирования. Абсолютно слепые, Пятьдесят-семь полагаются на обоняние, слух, удивительно чуткую интуицию, и, когда это необходимо, эхолокацию. Пятьдесят-семь всеядны и питаются как косяками мелких организмов, так и плавающим фитопланктоном. Так как они являются самыми высокоразвитыми существами, но не имеют конечностей, у Пятьдесят-семь нет какой-либо материально-технологической базы. -Социология: -57 мало интересуются остальной галактикой, особенно планетами, где они не обитают. Основной их интерес сконцентрирован на математике. Они с жадностью набрасываются на интересные задачи, и если задача исследования правильно сформулирована... +Социальный строй: +Пятьдесят-семь мало интересуются остальной галактикой, особенно планетами, где они не обитают. Основной их интерес сконцентрирован на математике. Они с жадностью набрасываются на интересные задачи, и, если задача исследования правильно сформулирована... -Родина: +Домашний мир: Болото с сильной гравитацией и плотной атмосферой. -Reason for Staying: -57 постоянно общаются посредством сложных фрактальных "песен", которые могут выражать как объяснения математических гипотез, так и просто своей артистической натуры. Очевидно, что фрактальные песни крайне неприемлемы. +Препятствие для распространения: +Пятьдесят-семь постоянно общаются с остальными видами при помощи сложных фрактальных "песен", которыми могут объяснять математические гипотезы или просто проявлять свою артистичную натуру. Жизнь без этих фрактальных песен они находят ужасно непривлекательной. ''' SP_SETINON Сетинон SP_SETINON_GAMEPLAY_DESC -'''Эгоистичные, раздражительные микроскопические организмы. -Предпочитают океанические планеты. +'''Индивидуалистичные разумные микроорганизмы. +Предпочитают [[PT_SWAMP]] планеты [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' +SP_SETINON_DESC +'''Описание: +Раса Сетинон появилась из иммунных клеток более крупного организма, который она защищала от вирусов и паразитов. Её представители имеют 1 мм в поперечнике, а внутри их тела наличествуют несколько различных камер и отсёков. Нейроны, которые у большинства других существ отвечают за разум, представлены у Сетинона белками и нуклеиновыми кислотами. Эти существа образуют целые биоструктуры, состоящие из тысяч клеток-операторов, которые выполняют задачи, непосильные для одного простейшего организма. + +Социальный строй: +Общественная структура Сетинона представлена двумя противоречащими друг другу факторами: с одной стороны, все Сетиноны имеют индивидуалистичный, обороняющийся характер, с другой - им необходимо сотрудничать, чтобы иметь возможность выжить в этом мире. Поэтому социальный строй Сетинона организуется комплексно, несколькими взаимодействующими каналами власти, которые могут сменяться каждую неделю. + +Домашний мир: +Болото с плотной атмосферой и довольно плотной биосферой. +''' + SP_NYMNMN Нимнмн SP_NYMNMN_GAMEPLAY_DESC -'''Мудрые магнитно-волновые образования планетарного масштаба. -Предпочитают пустыни. +'''Разумные магнитные волны планетарного масштаба. +Предпочитают [[PT_DESERT]] планеты [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]] ''' @@ -3644,8 +7124,9 @@ SP_TRENCHERS Тренчеры SP_TRENCHERS_GAMEPLAY_DESC -'''Загадочные колесные роботы, тщательно нарезающие на поверхности своей планеты кривые и спирали. -Предпочитают бесплодные земли. +''' +Загадочные колесные роботы, тщательно нарезающие на поверхности своей планеты кривые и спирали. +Предпочитают [[PT_BARREN]] планеты [[encyclopedia ROBOTIC_SPECIES_TITLE]] ''' @@ -3653,226 +7134,556 @@ SP_RAAAGH Раааах SP_RAAAGH_GAMEPLAY_DESC -'''Похожи на кошек.. -Предпочитают планеты земного типа. +'''Высокотерриториальные фелиноиды. +Предпочитают [[PT_TERRAN]] планеты [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' +SP_RAAAGH_DESC +'''Описание: +Раааахи - пушистые полосатые двуногие ростом 1.5 метра. Их тело всегда наклонено вперёд на 45 градусов, отчего клыкастые челюсти и когтистые лапы свободны для сражений, строительства или ловли добычи. Треугольные уши и вертикальные зрачки-щелочки придают им сходство с кошачьими. Маленький Раааах вылупляется в кладке, среди дюжины других яиц. Заботе о потомстве у них уделяется мало внимания - детёныши сразу должны научиться выживать самостоятельно, конкурируя со своими братьями и сестрами за места для укрытия и воруя еду. Через три года, как правило, из всей кладки выживает только один Раааах и он начинает учиться говорить. Родительские инстинкты Рааахов внезапно активируются, и выжившего детёныша начинают заботливо растить как члена Прайда. + +Индивидуальные особенности: +Крайняя территориальность - отличительная черта Раааахов в их повседневной жизни и культуре. Ввиду этого, их предпочтение всегда отдаётся друзьям и знакомым. Их любопытство минимально, что, видимо, и является причиной развития их технологий на уровне каменного века. + +Домашний мир: +Планета земного типа. Земли и моря более-менее равномерно распределены по всей поверхности, причём самый большой континент составляет только 4% площади. Большое количество малых континентов и крупных островов только ещё больше обостряют территориальные инстинкты Раааахов. + +Препятствие для распространения: +Нет в мире достаточно большого мира, на котором отдельный Раааах мог бы жить на протяжении веков и не сражаться. Их генетическая программа состоит в том, чтобы охранять свою территорию ценой жизни, и, когда возможно, распространяться на прилегающие земли. Раааах не может покинуть свою территорию - насильственное удаление с неё приведёт к тому, что он сойдёт с ума. +''' + SP_BEIGEGOO Бежевая слизь SP_BEIGEGOO_GAMEPLAY_DESC -'''Безумные, очень недоверчивые, поглощающие почву черви. -Предпочитают радиоактивные планеты. +'''Безумный, сильно закрытый планетарный нано-рой. +Предпочитают [[PT_RADIATED]] планеты [[encyclopedia ROBOTIC_SPECIES_TITLE]] ''' +SP_BEIGEGOO_DESC +'''Вся планета окружена бежевой дымкой из нано-частиц. Неизвестно, были ли они сброшены туда в качестве оружия, или же это вышедший из под контроля эксперимент, или средство для извлечения ресурсов... Тем не менее, от планеты уже ничего не осталось, кроме радиоактивного ядра - нано-частицы являются очень хорошими сборщиками ресурсов и стремительно стараются делать это при наличии сырья. Однако, они чрезвычайно тщательно защищают тайну своего строения - саморазрушаются, стоит их поймать или попытаться увезти с планеты. +''' + SP_SILEXIAN Силексианцы SP_SILEXIAN_GAMEPLAY_DESC -'''Самосветящиеся роботы-садовники, оставленные на волю судьбы. -Предпочитают планеты земного типа. +'''Светящиеся роботы-садовники, оставленные на волю судьбы. +Предпочитают [[PT_TERRAN]] планеты [[encyclopedia ROBOTIC_SPECIES_TITLE]] ''' +SP_SILEXIAN_DESC +'''Силексианы - раса роботов, нацеленных на экологическое техобслуживание. Они видят своё существование как средство достижения биологической красоты и баланса в галактике. + +Описание: +Каждый из Силексианцев создан для выполнения конкретной задачи, поэтому их внешность различна. Но у большинства всё же присутствуют схожие черты - все они полностью искусственные роботизированные существа, созданные в основном из металлов. У них есть антенны ближнего радиуса действия для радиообщения, продвинутые тактильные датчики и широкочастотные фотонные детекторы. +Силексианцы, как правило, имеют плоскую основу, оборудованную колёсами и ракетными ускорителями, но попадаются и двуногие модели, передвигающиеся быстрее и ловчее. У многих есть клешневидные придатки и инжекторные трубочки для манипулирования объектами. + +Социальный строй: +Форма правления силексианцев - диктатура, с наличием четкой иерархии и разделением на касты. Каждый силексианец создаётся для конкретной работы и выполняет свои задания до тех пор, пока работа не будет объявлена устаревшей. Социальная мобильность у них отсутствует. На самом деле, сама её идея была бы чуждой и несколько нездоровой для силексианцев. Расу возглавляет каста смотрителей, раздающая все задания и определяющая обновления, объявляющая работы устаревшими. Сами смотрители подчиняются Главному оператору, которого никто не имеет права считать устаревшим - следовательно, он практически бессмертен. + +История: +Силексианцы были созданы Наблюдателями чтобы повысить эффективность и автоматизацию проектов экологического обслуживания. В конце концов, все миры Наблюдателей однажды были заселены и поддерживались мирными и верными силексианцами. Когда Наблюдатели покинули галактику, некоторые силексианцы были ими брошены на произвол судьбы. +''' + SP_KOBUNTURA Кобунтура SP_KOBUNTURA_GAMEPLAY_DESC -'''Чувствительные образования из магнитной пыли с пристрастием к строительству. -Предпочитают бесплодные земли. +'''Разумные магнитные пылевые образования с тягой к строительству и конструированию. +Предпочитают [[PT_BARREN]] планеты [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]] ''' +SP_KOBUNTURA_DESC +'''Описание: +Представитель расы Кобунтура состоит из триллионов частиц магнитной пыли. Плотное ядро из вибрирующих молекул генерирует магнитное поле, которое манипулирует менее плотным облаком частиц, вращающимся вокруг с причудливыми завихрениями. Кобунтура может обнаруживать объекты, попадающие в её облако частиц и манипулировать ими, а также косвенно манипулировать и обмениваться данными с объектами, расположенными на расстоянии, используя свои магнитные поля. Основной метод перемещения кобунтуры - взаимодействуя с магнитными полями планеты. Всякий раз, когда она это делает, существует небольшой шанс, что окружающие её частицы пыли ввиду движения намагнитятся и перемешаются в определённой степени, что вызовет цепную реакцию и создаст новую, взрослую особь кобунтуры. + +Социальный строй: +Кобунтура имеет несколько охранных гильдий, сформированных ещё в древности, но одна гильдия, Анклав тьмы, специализирующаяся на боевых искусствах, получила контроль за другими и установила военную диктатуру. Несмотря на это, у кобунтур нет секретов друг от друга, так как они просто не умеют общаться так, чтоб их не услышали другие кобунтуры. Торговля и сфера развлечений у них очень ограничены, поскольку единственная цель, диктуемая Анклавом тьмы (а, следовательно, поддерживаемая и всем обществом кобунтуры) - поиск одного из видов предтеч, Строителей и открытие всех их великих секретов строительства. Эта цель - импульс, приводящий в движение всю цивилизацию кобунтур. +''' + SP_UGMORS Угморы SP_UGMORS_GAMEPLAY_DESC -'''Живущие в лаве дрожжеподобные сгустки, формирующиеся в големы. -Предпочитают планеты типа "ад". +''' +Живущие в лаве сгустки дрожжей, временно формирующиеся в големов. +Предпочитают [[PT_INFERNO]] планеты. [[encyclopedia LITHIC_SPECIES_TITLE]] ''' +SP_UGMORS_DESC +'''Описание: +Угморы - раса разумных дрожжевых грибков, которые используют камни и различные минералы для создания временных тел, предназначенных для выполнения конкретных задач. + +Социальный строй: +В основе этой расы на самом деле находятся дрейфующие дрожжевые острова под названием Ларар. Они размещаются в самых жарких местах лавовых морей и поглощают энергию. Когда энергии становится достаточно много, большая колония заряженных дрожжей отделяется, собирает камни с берега лавогого моря и соединяет их - образуется Угмор. Угморы изначально были предназначены просто для перемещения ларарских дрожжей из одного лавогого бассейна в другой, но позже эти существа сформировали более сложное общество. Острова Лараров сами по себе не очень умны, и действуют только как центральная память, собирающая знания всех до последнего Угморов, возвращающихся к ним назад. + +Угморы имеют единую форму правления. Она похожа по устройству на улей, только без центральной королевы. Просто центрирования им и не нужно - все угморы уже зарождаются со схожими расовыми воспоминаниями и установками. Когда открывается что-нибудь новое, Угмор, обнаруживший это, быстро возвращается к ближайшему острову Ларар, чтобы поделиться информацией - распространяя её, тем самым, и между сородичей. + +Угморы могут принимать разные формы, но все они состоят непременно из камней и листов металла, соединённых липким, волокнистым материалом. Угморы чрезвычайно прочны и могут даже несколько часов существовать в открытом космосе. В естественной среде они живут до пяти лет, после чего охлаждаются и "умирают от старости". Раса угморов не является очень высокоразвитой. Пока ларарские острова видят ценность интеллекта, они продолжают закладывать его в угморах, но даже это они делают в очень умеренных количествах. + +Домашний мир: +Скалистая планета, которая вращается очень быстро (каждые 3 с половиной часа), 30% которой покрыто морями из лавы и жидких металлов. + +''' + SP_GISGUFGTHRIM Гис Гуф Г-трим SP_GISGUFGTHRIM_GAMEPLAY_DESC -'''Травоядные ракообразные, живущие в колонии вне планеты в гелеобразной субстанции. -Предпочитают токсичные миры. +'''Травоядные ракообразные, живущие колонией в едином желеподобном образовании планетарного масштаба. +Предпочитают [[PT_TOXIC]] планеты [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' +SP_GISGUFGTHRIM_DESC +'''Большая часть экваториальной области родной планеты Гис Гуф Г-тримов покрыта единым желеподобным организмом, известным как "янтарная экспансия", который заменяет им всю растительную жизнь. Гисы (и многие другие создания) живут и питаются только внутри янтарной экспансии. Сама экспансия, похоже, бессмертна и невоспроизводима. Попытки клонировать её части потерпели неудачу, поэтому, видимо, Гисы не могут покидать свой родной мир. +''' + SP_HIDDENGARDENER Невидимый садовник SP_HIDDENGARDENER_GAMEPLAY_DESC -'''Мудрая грибница, живущая глубоко в почве. -Предпочитает планеты земного типа. +'''Разумная грибница, живущая под поверхностью и выращивающая растения себе в пищу. +Prefers [[PT_TERRAN]] planets [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' SP_HIDDENGARDENER_DESC -'''Невидимый садовник - это массивные распределенные грибницы, обладающие интеллектом, расположенные под корой планеты. Каждая особь занимает несколько квадратных километров. Их действия и мыслительные процессы медлительны. Различные чувствительные узлы, распределенные по всему телу, помогают чувствовать погоду и растить "сад" на поверхности. Невидимый садовник посредством ферментов, взращивает и распыляет споры, которые под воздействием климата способствуют гниению растений, что необходимо для жизни грибницы. +'''Невидимый садовник - огромный распределённый грибковый интеллект, расположенный прямо под поверхностью планеты. Каждая особь простирается на много квадратных километров вокруг. Садовник действует и мыслит очень медленно. У него по всему телу разбросаны различные сенсорные узлы, при помощи которых он следит за погодой и ростом своего сада. Используя ферменты, инкапсуляцию и распространение семян, контролируя подачу воды, Невидимый садовник ухаживает за садом. Сад, в свою очередь, продуцирует определённое количество перегноя, небходимого грибнице для существования. -Грибные формы жизни редко используют мозг в каком-либо виде, но Садовник в этом плане необычен и внимательно следит за тем, чтобы его экосистема находилась в гармонии. +Грибковые формы жизни редко обладают мозговыми структурами, но такой необычный и продуманный подход к обеспечению себя питанием, достаточным для работы мозга, позволил Невидимому садовнику развиться в разумную форму жизни. -Их родная планета - жуткое место, похожее на парк с насаждениями, расположенными в четком порядке, без каких-либо других намеков на присутствие разумной жизни. +Родной мир садовника - странная планета земного типа, больше похожая на парк, где растения размещены с неестественной точностью. Тем не менее, там нет никаких других внешних признаков разумной жизни. + +Большие размеры и хрупкость нитей грибницы, интегрированных в почву, не позволяют Невидимому садовнику покидать свою планету. Они размножаются делением по мере разрастания грибницы и достижения ею достаточно больших размеров, после которых ей становится уже трудно принимать решения в одиночку. Разум расщепляется надвое и образуются два новых садовника - таким образом, у них не формируются маленькие особи, способные к межпланетным путешествиям. +''' -Большие размеры и хрупкое строение грибниц, находящихся в почве не позволяют вырваться за пределы планеты. И так как Садовники размножаются делением на 2 сознательные особи лишь когда вырастают до огромных размеров, не позволяющих быстро и эффективно понимать решения, не могут появиться достаточно маленькие особи для межпланетных путешествий.''' +SP_ABADDONI +Абаддони SP_ABADDONI_GAMEPLAY_DESC -'''Раса обитающих в пещерах существ, находящихся в рабстве у суперзащищенного компьютера, который они сами и создали. -Предпочитают планеты типа "ад". +''' +Раса обитающих в пещерах существ, находящихся в рабстве у сверхконтролирующего компьютера, который они сами и создали. +Предпочитают [[PT_INFERNO]] планеты. [[encyclopedia LITHIC_SPECIES_TITLE]] ''' +SP_ABADDONI_DESC +'''Описание: +Абаддони имеют форму куриного яйца, высоту 0,8 метра и тёмно-красный с чёрными пятнами окрас. Их основная отличительная черта - это большой рот, начинающийся из одной точки на голове и открывающийся на треть спины. На конце каждой из коротких, похожих на крабовые ног находится выпуклый мешочек, позволяющий Абаддони перемещаться по потолку. Поскольку Абаддони живут под землёй, у них отсутствуют глаза, и в основном они полагаются на обоняние, в меньшей степени - на слух и осязание. + +Социальный строй: +Весь вид Абаддони живет под диктатурой, возглавляемой искусственным интеллектом под названием Мать. Этот интеллект был создан Абаддони в качестве помощника своему правительству, но постепенно взял верх надо всем. Теперь каждому новорождённому Абаддони вживляют в мозг беспроводной передатчик, чтобы Мать всегда могла читать его мысли и ограничивать действия, когда ей нужно. Теперь между Абаддони нет никакой социальной структуры - все они, по сути своей, рабы. + +''' + SP_ACIREMA -Ахирема +Ацирема SP_ACIREMA_GAMEPLAY_DESC -'''Раса с практически беспредельным могуществом, которая не может выжить во вселенной. -Предпочитают радиоактивные планеты. +'''Раса с практически беспредельной силой, но неспособная выжить в нашей вселенной. +Предпочитают [[PT_RADIATED]] планеты. [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]] ''' SP_ACIREMA_DESC '''Описание: -Ахиремы - это 10-30 метровые элиптические сгустки энергии с большими (30-100м) "крыльями". С возрастом они аккумулируют материю, окружающую их, и создают сначала пылевую, а в конечном счет каменную оболочку. Ахиремы живут во вселенной, где физические постоянные мало разнятся между собой, и есть только одно место, через которое они могут попасть в нашу вселенную - Портал - их родная планета. Однако, как только они пересекут его, они сразу же начинают "стареть" совсем по другому, чем на родине, так что Ахиремы редко проводят в нашей галактике много времени. +Ациремы - это 10-30 метровые эллиптические сгустки энергии с большими (от 30 до 100 метров) "крыльями". По мере роста они аккумулируют вокруг себя материю и создают сперва пылевую, а в конечном итоге - каменную оболочку, сквозь трещины которой видно циркулирующие по их телам потоки энергии. Большинство Ацирем живут во вселенной, где физические постоянные мало разнятся между собой, и есть только одно место, сквозь которое они могут попасть к нам - Портал, их родная планета. Но, совершая переход, Ациремы начинают "стареть" совсем иначе, нежели в своей родной вселенной - здесь они не смогут прожить и несколько циклов обращения галактики. Поэтому Ациремы не задерживаются надолго в нашем мире, быстро возвращаясь назад, к себе на родину. -Как только Ахиремы нашли лазейку в нашу галактику, они обнаружили, что контроль различий в структуре позволяет генерировать энергию в больших масштабах в обоих галактиках. Также они обнаружили, что их тела могут излучать и манипулировать большими энергиями. Они сразу же начали собирать материю рядом с Порталом для изучения. Вскоре они полностью исчерпали Портал и не могли более извлекать материю из него. +Найдя лазейку в нашу вселенную, Ациремы заметили, что благодаря контролю различий между физическими постоянными можно генерировать огромное количество энергии - как в нашей вселенной, так и в их тоже. Тела Ацирем также оказались неплохими генераторами, поэтому их раса тут же принялась добывать всевозможные полезные ископаемые - как для строительства, так и для изучения новых для себя материалов. Обладая такими мощностями, очень скоро Ациремы полностью истощили свою родную планету - на ней оставалось уже не так много минералов, доступных для безопасного извлечения. -Большое количество энергии, которое они излучают непосредственно или через портал в родную вселенную дает им возможность развивать промышленность и воевать. Это делает Портал очень опасным местом, учитывая что Ахиремы настороженно относятся к чужакам. Торговля с другой вселенной для Ахирем безусловно выгодна, особенно учитывая, что они не могут добывать минералы с других планет. Ахиремы имеют правительство, похожее на военно-исследовательский институт. Они очень скрытны относительно своей социальной жизни на родине. +Гигантские мощности Ацирем дали им больше преимущество в промышленности, но сделали и лакомым кусочком для пришельцев. Ациремы стали очень сторожки к посторонним, чувствительны к любому вторжению на их территорию. Но торговля с другой расой им небходима, ввиду невозможности добычи ресурсов как с родной планеты, так и с окружающих. Ациремы имеют собственное правительство, устройство которого больше похоже на военно-исследовательский аванпост. Они очень скрытны относительно своей социальной жизни на родине. -"Родная планета" -Портал - это радиоктивная планета, вращающаяся близко к очень яркой звезде - синему гиганту. Есть мнение, что предшествующий цивилизацией был открыт проход в галактику и звезда была изменена. Есть также вероятность, что на Портале существовала разумная жизнь, уничтоженная Ахиремами, сразу же по открытии прохода (или в процессе исследований или тестирования сверхмощного оружия). +Домашний мир: +Портал - это радиоктивная планета, имеющая связь с иной вселенной, и вращающаяся близко к очень яркой звезде - голубому гиганту. Считается, что связь с иной вселенной могла быть открыта Предтечами. Существует даже вероятность, что ранее на Портале была разумная жизнь, но она была уничтожена Ациремами сразу после совершения ими перехода (или в процессе исследований, тестирований какого-либо современного оружия). -Reason for staying -Они не могут оставаться на этом месте достаточно долго для того, чтобы переместиться на другую планету и выжить.''' +Препятствие для распространения: +Ациремы не смогут расселяться - им не хватит времени, чтобы успеть переместиться на другую планету и выжить.''' SP_OURBOOLS Урбулы SP_OURBOOLS_GAMEPLAY_DESC -'''Гигантские слепые водоплавающие, которые могут "видеть" силы притяжения. -Предпочитают океаны. +'''Гигантские слепые подводные существа, которые могут "видеть" гравитацию. +Предпочитают [[PT_OCEAN]] планеты. [[encyclopedia ORGANIC_SPECIES_TITLE]] ''' +SP_OURBOOLS_DESC +'''Описание: +Гигантские подводные змееподобные существа. Отличительной особенностью Урбулов являются два массивных плавника, изогнутые в форме лука по обеим сторонам их головы. Здесь у них расположены чувствительные к гравитации органы. Три щупальца, расположенные над их ртом, позволяют Урбулам производить манипуляции с окружающей средой. Урбулы дают потомство только один раз за всю жизнь, и последующие 35 лет заботятся о своих детёнышах (тех, как правило, от 5 до 7), обучая их - не без помощи сородичей. У Урбулов довольно продолжительный цикл жизни. + +Социальный строй: +Урбулы склонны к кочевой жизни - они охотятся на ихтиоидные формы жизни или выращивают их в качестве домашнего скота. Их общество в значительной степени сфокусировано на правильных, изящных манерах общения между группами, которые странствуют в океанах и иногда случайно встречаются. +Меньшая часть Урбулов живёт отдельно от остальных, ведя оседлый образ жизни (как правило, по причине особенностей того ремесла, которым занимаются). + +Домашний мир: +Планета под названием Улур, на орбите которой расположены 23 луны. + +Препятствие для распространения: +Урбулы определяют своё местоположение через регулярные движения 23-х лун своей планеты. Всё остальное воспринимается ими через "призму" этого гравитационного фона. Если удалить от них этот фон, Урбулы станут полностью дезориентированы. Они также не могут и "перекалибровать" свои сенсоры под что-либо иное, чем весь этот бессмысленный гравитационный шум 23-х лун. +''' + SP_VOLP -Ворп-Углуш +Волп-Углуш SP_VOLP_GAMEPLAY_DESC -'''Огромное скопление в конец одуревших сверлильщиков. -Предпочитают бесплодные планеты. +'''Высокобюрократичные копатели, заполонившие всю свою планету. +Предпочитают [[PT_BARREN]] планеты. +[[encyclopedia LITHIC_SPECIES_TITLE]] +''' + +SP_VOLP_DESC +'''Описание: +Волп-Углуш - бесполые существа длиной до 300 метров, живущие в туннелях, вырытых на своей родной планете. Они обитают в силикатах и имеют большое количество коротких конечностей (руко-ртов) на передней части толстого, цилиндрического тела. Эти конечности используются для питания, рытья туннелей и манипуляций с предметами, а также для отправки сообщений через туннельные сети. В доисторические времена существовало несколько разновидностей Волп-Углушей, обитавших группами, которые находились в постоянном конфликте друг с другом. Те группы, что оказались лучше организованы, лучше добывали ресурсы, стали доминирующими в своём роде, по мере перехода общества от индустриальной к информационной эпохе. Остальные же - вымерли. Выжившие принялись оптимизировать свою планету для наиболее эффективного использования ресурсов и стали невероятно рациональны в плане организации потребления. Это было достигнуто благодаря постоянному влиянию социальных структур Волп-Углушей на отдельных индивидуумов. +Общество Волп-Углушей действует в большинстве своём как единый организм - благодаря очень правильной организации. Однако, неизбежным следствием этого стали потери их независимых функций. Нынешнее общество Волп-Углушей эффективно именно вместе - для достижения такой же эффективности в новой колонии потребуются сотни (если не тысячи) лет жизни Волп-Углушей. + +Они также эффективно управляют торговыми флотами и обладают превосходными бюрократическими способностями в плане внесений изменений в общество. Но, несмотря на то, что Волп-Углуши хорошо вооружены и способны в одиночку управлять различными крупными боевыми механизмами, хаос войны - это не самая эффективная среда для их самореализации. + +Домашний мир: +Большая бесплодная планета под названием Улей, которая весит примерно в два раза меньше, чем полагается при её размере. Предполагается, что её небольшое металлическое ядро было когда-то утрачено, ввиду обширной системы туннелей, построенной бюрократическим обществом Волп-Углушей. + +Препятствие для распространения: +Волп-Углуши слишком интегрированы в свою планету и общество. Им очень не хватает независимости решений и инноваций, которые потребуются для того, чтобы покинуть родной Улей и построить новую колонию. +''' + +SP_FULVER +Фулвер + +SP_FULVER_GAMEPLAY_DESC +'''Вечно-неудовлетворённые муравьи-прогнозисты. +Предпочитают [[PT_TUNDRA]] планеты. +[[encyclopedia LITHIC_SPECIES_TITLE]] +''' + +SP_FULVER_DESC +'''Описание: +Внешне Фулверы похожи на крупных, нервозных муравьёв. Их мозги оснащёны квантовыми компьютерами, что позволяет им успешно прогнозировать события в ближайшем будущем - поэтому, их очень трудно застать врасплох в бою. + +Название расы: +Способность предсказывать будущее является уникальной даже для домашнего мира Фулверов. Таким образом, на их языке "Фулвер" - это "Тот, кто может видеть" (ваши возможные реакции). + +Индивидуальные особенности: +Фулверы предпочитают опираться на свою силу и жить изо дня в день. Они зачастую отдают предпочтение таким инструментам и решениям, которые позволяют обеспечить максимальное число выборов в будущем. Обладая плохой памятью и неспособностью прогнозировать в далёком будущем, Фулверы неуспешны в таких отраслях, как наука и строительство оборонительных структур. Чувствуя себя скованными ограниченным количеством выборов, Фулверы всегда очень встревожены и неудовлетворены, и постоянно пытаются преодолеть своё положение. + +Причина ухода: +Бесконечные возможности, открываемые космосом, помогли Фулверам сконцентрироваться и наконец открыть эпоху межзвёздных перелётов. Наконец-то они могут преступить границу и не волноваться над тем, что они потеряют. + +Социальный строй: +Фокус на ближайшем будущем не позволяет Фулверам образовывать хоть сколько-нибудь долговечные структуры. Знание возможных выборов друг друга и реакция на это привела Фулверов к форме общества, которую неформально можно было бы назвать анархией. +''' + +SP_FURTHEST +Фёрвест + +SP_FURTHEST_GAMEPLAY_DESC +'''Параноидальные трёхглавые и трёхногие травоядные. +Предпочитают [[PT_TUNDRA]] планеты. +[[encyclopedia ORGANIC_SPECIES_TITLE]] +''' + +SP_FURTHEST_DESC +'''Описание: +Фёрвесты - пушистые, полутраметровые создания. Радиально симметричные, они имеют три длинные ноги и три длинные, растяжимые шеи. На каждой из последних имеется сложный глаз, а также рот, используемый для приёма пищи и манипулирования объектами. Это позволяет Фёрвесту следить за происходящим вокруг почти во всех направлениях одновременно. Фактический мозг Фёрвеста находится в центре его тела. + +Имя: +Фёрвесты убеждены, что степень разумности существа определяется расстоянием, на которое оно может отдалиться от опасности. Таким образом, имя "Фёрвест" (Furthest - англ. "самый дальний") означает, что их вид - самый передовой из всех. + +Социальный строй: +Фёрвесты заявляют, что раньше их домашний мир просто кишел смертельно-опасными существами. Но Фёрвесты выжили, проявив осторожность на высшем уровне. На самом деле, оставаться настороже и скрываться - самая глубинная суть их психологии и культуры. В настоящий момент родина Фёрвестов не содержит таких хищных существ, которых те не смогли бы раздавить ногами. + +Препятствие для распространения: +В большинстве своём Фёрвесты считают космические путешествия слишком опасными для нормальной жизни. Но некоторые индивидуумы, посчитавшие родной мир слишком опасным, могут и рассматривать возможность переезда в другой мир. +''' + +SP_BANFORO +Банфоро + +SP_BANFORO_GAMEPLAY_DESC +''' +Симпатичные и усердные звёздные наблюдатели. +Предпочитают [[PT_BARREN]] планеты [[encyclopedia LITHIC_SPECIES_TITLE]] ''' +SP_BANFORO_DESC +'''Описание: +Банфоро выглядят как плюшевые мишки из белого камня, с большими, чёрными, кристаллическими глазами. Вполне возможно, что эта раса была искусственно создана, но какая-либо информация, способная подтвердить это, теряется в архивах истории. Глаза являются самыми важными органами Банфоро, и служат не только для зрительного восприятия, но также и для общения - Банфоро показывают друг другу светлые пятна, переливающиеся в гранях своих глаз. Банфоро могут различать света и формы даже в очень тусклом свете, а также фокусироваться на источниках света, расположенных очень далеко. С другой стороны, слишком яркий свет тяжёл для их глаз и также нарушает коммуникацию. Таким образом, вечера - любимое время для Банфоро, и коренной ритуал всей их цивилизации - созерцание звёзд во время солнечного заката. + +Социальный строй: +Банфоро очень общительны друг с другом и формируют прочные связи. Они всегда делятся ресурсами друг с другом, а частная собственность у них просто не существует для товаров с материальной ценностью. Их форму правления можно назвать коммунистической. + +Причина вымирания: +Недостаточно ясна. Учёные подозревают, что некий космический катаклизм сделал невыносимым существование Банфоро, причём на такое долгое время, что эта раса постепенно полностью вымерла. + +''' + +SP_KILANDOW +Киландау + +SP_KILANDOW_GAMEPLAY_DESC +'''Поглощающие радиацию, шипастые, неторопливые экспериментаторы. +Предпочитают [[PT_RADIATED]] планеты. +[[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]] +''' + +SP_KILANDOW_DESC +'''Описание: +У Киландау четыре конечности с когтями на концах. Стоя вертикально на двух конечностях (тех, которые короче) Киландау имеет рост более 1 метра. Их голова представляет собой трёхглазый клюв, который используется как кирка для поиска редких металлов и других полезных ископаемых. Их Киландау дробят в пасту и наносят на свою шипастую спину, после чего ищут местность с сильным ультрафиолетовым и гамма-излучением. Киландау может стоять в этом месте часами или днями, ожидая, пока все молекулы пасты на его спине не будут полностью заряжены. Поскольку радиация - источник питания для Киландау, их тела также сильно фонят, отчего этих существ становится легко обнаружить. Но по этой же причине Киландау опасны для тех видов, с которыми они контактируют. +Киландау очень терпеливы и любят проводить различные химические и физические эскперименты. Благодаря своей долгой жизни, они не имеют проблем с некоторыми экспериментами, для выполнения которых требуются сотни лет. Другим любимым видом деятельности Киландау является добыча полезных ископаемых (особенно на астероидах), поскольку для них это одновременно и исследование, и охота за сокровищами, и поиск пищи. + +Социальный строй: +Основываясь на документах, найденных в древних руинах, Киландау были законопослушными существами с демократичным обществом, но, однако, они больше интересовались экспериментами, нежели общением друг с другом. + +Причина вымирания: +Считается, что этому послужил их миролюбивый нрав и низкая скорость воспроизводства. +''' + +SP_MISIORLA +Мизиорла + +SP_MISIORLA_GAMEPLAY_DESC +'''Хаотичные, свободолюбивые существа, прирождённые пилоты. +Предпочитают [[PT_TOXIC]] планеты. +[[encyclopedia ORGANIC_SPECIES_TITLE]] +''' + +SP_MISIORLA_DESC +'''Описание: +Мизиорлы - моллюски с четырьмя гибкими щупальцами и двумя глазами, каждый из которых содержит ещё четыре маленьких глаза. Они отлично адаптированы к своему родному, кислотному миру, и способны плавать в морях кислоты, парить в густых облаках... Мизиорлы считались довольно посредственным видом, едва поспевавшим за остальными разумными существами - недостаточно умные, не очень производительные, не особо талантливые... До тех пор, пока первый Мизиорла не оказался у руля космического корабля. Весть о лучших боевых пилотах во вселенной распространилась через весь космос, словно лесной пожар. Сперва Мизиорлы управляли только истребителями, но затем получили в командование и крейсера. Полный их потенциал открылся, когда не самый современный линкор с экипажем одних лишь Мизиорл уничтожил весь космический флот империи, превосходящей их по силе. + +Социальный строй: +Вся жизнь Мизиорл сосредоточена вокруг пилотирования космических кораблей. Пилотирование - это их работа, их спорт и их любовь. Мизиорлы полностью зависят от других разумных видов и становятся вялыми и неорганизованными, когда принимаются делать что-либо ещё, кроме пилотирования. Мизиорл может нанять к себе на службу любой - главное, предложить им интересную работу и предоставить достаточно топлива для её выполнения. + +Причина вымирания: +В один момент раса Финалистов (одна из цивилизаций Предтеч) решила, что Мизиорла может стать угрозой для их конечной цели. +''' + SP_EXOBOT Экзоботы SP_EXOBOT_GAMEPLAY_DESC -'''Полуавтономные роботы, созданные для добычи полезных ископаемых и производства на бесплодных, радиоактивных и вулканических планетах. +'''Полуавтономные роботы, созданные для добычи полезных ископаемых и производства на [[PT_BARREN]], [[PT_RADIATED]] и [[PT_INFERNO]] планетах. [[encyclopedia ROBOTIC_SPECIES_TITLE]] ''' +SP_EXOBOT_DESC +'''Хотя они не настолько эффективны, как полностью самодостаточные граждане, экзоботы могут колонизировать негостеприимные планеты и строить на них других экзоботов. +''' + +SP_ANCIENT_GUARDIANS +Древние стражи +SP_ANCIENT_GUARDIANS_GAMEPLAY_DESC +'''Стражи-роботы, построенные цивилизацией Предтеч целую вечность назад. +[[encyclopedia ROBOTIC_SPECIES_TITLE]] +Будут самоуничтожаться при завоевании, оставляя аванпост, принадлежащий завоевателю. +''' + +SP_ANCIENT_GUARDIANS_DESC +'''Построенные по технологиям предтеч, эти древние машины могут адаптироваться к любым условиям и сражаться в них. С непоколебимой верностью и приверженностью они защитили свою планету от любых захватчиков, и потому никогда не тратят времени на ремонт защиты или на её улучшение. После завоевания каждый Страж-робот автоматически самоуничтожается. +''' + SP_EXPERIMENTOR -Экспериментатор +Экспериментаторы SP_EXPERIMENTOR_GAMEPLAY_DESC '''Предтечи безумных ученых. [[encyclopedia SELF_SUSTAINING_SPECIES_TITLE]], [[encyclopedia TELEPATHIC_TITLE]] ''' +SP_EXPERIMENTOR_DESC +'''Экспериментаторы - древняя раса предтеч, существование которой полностью посвящено стремлению к знаниям. Их желание учиться не имеет равных себе, и они не стесняются рассматривать даже целые галактики в качестве лабораторий для своих экспериментов. +''' + SP_SUPER_TEST Супер-тестеры SP_SUPER_TEST_GAMEPLAY_DESC -'''Супер-тестеры не сбалансированы и бесчестны. Они почти всеведующи и обитают на всех планетах. +'''Супер-тестеры - несбалансированная, бесчестная раса. Они почти всеведущи и могут обитать на любых планетах. Силы, разрушающие [[metertype METER_STEALTH]], работают только, если они находятся в столице. [[encyclopedia ORGANIC_SPECIES_TITLE]], [[encyclopedia LITHIC_SPECIES_TITLE]], [[encyclopedia ROBOTIC_SPECIES_TITLE]], [[encyclopedia PHOTOTROPHIC_SPECIES_TITLE]] [[encyclopedia TELEPATHIC_TITLE]] ''' - ## ## Specials ## MODERATE_TECH_NATIVES_SPECIAL -Умеренно-технологические аборигены +Умеренно-развитые аборигены MODERATE_TECH_NATIVES_SPECIAL_DESC -Аборигены на этой планете выработали умеренно передовые технологии, обеспечив свою планету сильной обороной против атаки и вторжения как независимая империя. +Аборигены на этой планете обладают некоторыми технологиями, обеспечивающими улучшенную защиту планеты от атак и вторжений внешних империй. HIGH_TECH_NATIVES_SPECIAL Высокотехнологические аборигены HIGH_TECH_NATIVES_SPECIAL_DESC -Аборигены на этой планете выработали наиболее передовые технологии, обеспечив свою планету более сильной обороной против атаки и вторжения как независимая империи. +Аборигены на этой планете выработали наиболее передовые технологии, обеспечивающие мощную защиту планеты от атак и вторжений внешних империй. GAIA_SPECIAL Гайа GAIA_SPECIAL_DESC -'''Увеличивает максимальное значение населения на планете: Крохотная: +3, Маленькая: +6, Средняя: +9, Большая: +12, Огромная: +15. +'''Терраформирует планету и увеличивает максимальное население в зависимости от размера планеты: +• [[SZ_TINY]] (+3) +• [[SZ_SMALL]] (+6) +• [[SZ_MEDIUM]] (+9) +• [[SZ_LARGE]] (+12) +• [[SZ_HUGE]] (+15) +а также увеличивает параметр [[metertype METER_HAPPINESS]] на 5. + +Эта планета была преобразована в живой организм, который может изменяться и адаптироваться под нужды населяющей его цивилизации. Если мир является [[PE_GOOD]] [[encyclopedia ENVIRONMENT_TITLE]], то добавляется значительный бонус для населения. Если нет, планета будет ход за ходом терраформироваться, чтобы достичь [[PE_GOOD]] ([[species SP_EXOBOT]] не имеет среды [[PE_GOOD]], поэтому планета не сможет улучшиться дальше [[PE_ADEQUATE]]).''' + +WORLDTREE_SPECIAL +Мировое дерево -Эта планета была преобразована в живой организм, который может изменяться и адаптироваться под нужды населяющей его цивилизации.''' +WORLDTREE_SPECIAL_DESC +'''Увеличивает [[metertype METER_SUPPLY]] на +1, [[metertype METER_DETECTION]] на +10, максимум населения на +1 и [[metertype METER_HAPPINESS]] на планете с данным артефактом на +5. Увеличивает [[metertype METER_HAPPINESS]] на всех других планетах, принадлежащих данной империи на +1. + +Гигантское дерево выросло настолько высоким, что его крона возвышается над атмосферой планеты. Ученые подозревают, что оно было посажено Хранителями в древние времена.''' ANCIENT_RUINS_DEPLETED_SPECIAL -Древние руины +Древние руины (раскопанные) ANCIENT_RUINS_DEPLETED_SPECIAL_DESC -'''[[metertype METER_RESEARCH]] на этой планете увеличиваются число населения. +'''Увеличивает [[metertype METER_RESEARCH]] на этой планете по 1 за единицу населения, когда фокус установлен на исследования. -На этой планете находятся руины древней и мудрой расы. Уникальное открытие на этих руинах уже было совершено.''' +На планете находятся руины неизвестной, мудрой, древней расы. Уникальное открытие на этих руинах уже было совершено.''' ANCIENT_RUINS_SPECIAL Древние руины ANCIENT_RUINS_SPECIAL_DESCRIPTION -'''[[metertype METER_RESEARCH]] на этой планете увеличиваются число населения. +'''Увеличивает [[metertype METER_RESEARCH]] на этой планете по 1 за единицу населения, когда фокус установлен на исследования. + +На этой планете находятся руины неизвестной, мудрой, древней расы. Первая империя с технологией [[tech LRN_XENOARCH]], занявшая эту планету, получает одну бесплатную передовую технологию или открывает мощное здание/корабль. Также есть высокий шанс найти хорошо сохранившиеся останки вымерших видов.''' + +EXTINCT_BANFORO_SPECIAL +Останки Банфоро + +EXTINCT_BANFORO_SPECIAL_DESC +На этой планете были обнаружены хорошо сохранившиеся тела вымершего вида [[species SP_BANFORO]]. Создание здесь [[buildingtype BLD_XENORESURRECTION_LAB]] позволит вам построить [[buildingtype BLD_COL_BANFORO]] на планетах, соединенных через [[metertype METER_SUPPLY]]. + +TECH_COL_BANFORO +Восстановление Банфоро + +TECH_COL_BANFORO_DESC +Позволяет восстановить древний вид [[species SP_BANFORO]]. + +EXTINCT_KILANDOW_SPECIAL +Останки Киландау + +EXTINCT_KILANDOW_SPECIAL_DESC +На этой планете были обнаружены хорошо сохранившиеся тела вымершего вида [[species SP_KILANDOW]]. Создание здесь [[buildingtype BLD_XENORESURRECTION_LAB]] позволит вам построить [[buildingtype BLD_COL_KILANDOW]] на планетах, соединенных через [[metertype METER_SUPPLY]]. + +TECH_COL_KILANDOW +Восстановление Киландау + +TECH_COL_KILANDOW_DESC +Позволяет восстановить древний вид [[species SP_KILANDOW]]. + +EXTINCT_MISIORLA_SPECIAL +Останки Мизиорла -На этой планете находятся руины древней и мудрой расы. Первая цивилизация с технологией [[tech LRN_XENOARCH]], освоившая эту планету, получает уникальную технологию, строение или корабль.''' +EXTINCT_MISIORLA_SPECIAL_DESC +На этой планете были обнаружены хорошо сохранившиеся тела вымершего вида [[species SP_MISIORLA]]. Создание здесь [[buildingtype BLD_XENORESURRECTION_LAB]] позволит вам построить [[buildingtype BLD_COL_MISIORLA]] на планетах, соединенных через [[metertype METER_SUPPLY]]. + +TECH_COL_MISIORLA +Восстановление Мизиорла + +TECH_COL_MISIORLA_DESC +Позволяет восстановить древний вид [[species SP_MISIORLA]] + +EXTINCT_UNLOCK_SHORT_DESC +Разблокирует вымерший вид + +PANOPTICON_SPECIAL +Паноптикум + +PANOPTICON_SPECIAL_DESC +'''Увеличивает общее [[encyclopedia DETECTION_TITLE]] империи на 10. Также увеличивает [[metertype METER_DETECTION]] на этой планете на 75. + +Паноптикум, погребённый глубоко внутри коры планеты, является мощной реликвией, оставленной одной из цивилизаций Предтеч. Когда данные обнаружения империи обрабатываются им, он способен извлекать из хаотичного набора данных достоверную информацию.''' + +FORTRESS_SPECIAL +Крепость + +FORTRESS_SPECIAL_DESC +'''Увеличивает [[metertype METER_SHIELD]] на +100, [[metertype METER_DEFENSE]] на +30, [[metertype METER_TROOPS]] на +30 и [[metertype METER_DETECTION]] на +30. Щиты будут восстанавливаться на 5 за ход, а защита на 1 за ход. Мины уменьшают [[metertype METER_STRUCTURE]] вражеских кораблей в системе на 3 за ход. На каждом шагу есть 5% -ый шанс того, что будет создан Часовой для защиты системы, только если он уже не присутствует. + +Эта планета была подготовлена для защиты одной из древних цивилизаций Предтеч. Она заполнена планетарными щитами, оружием и сенсорным оборудованием, чтобы противостоять любой осаде. Все по-прежнему полностью функционально и имеет удивительно дружественный интерфейс.''' ECCENTRIC_ORBIT_SPECIAL Эксцентриситет орбиты ECCENTRIC_ORBIT_SPECIAL_DESC -'''[[metertype METER_CONSTRUCTION]] на этой планете снижено на 20, зато [[metertype METER_RESEARCH]] выше на 3. +'''Увеличивает [[metertype METER_RESEARCH]] на 3, но [[metertype METER_SUPPLY]] уменьшается на 2 + +Орбита этой планеты имеет сильно вытянутую форму. Расстояние между самым ближним и самым дальним положением планеты от звезды очень велико. Освещённость планеты сильно варьируется на протяжении года, из-за чего ухудшается снабжение, но это даёт преимущество в исследованиях.''' + +TIDAL_LOCK_SPECIAL +Заблокированное вращение + +TIDAL_LOCK_SPECIAL_DESC +'''Увеличивает [[metertype METER_INDUSTRY]] на этой планете на 0.2 за единицу населения, при условии, что выбран фокус [[metertype METER_INDUSTRY]]. Уменьшает популяцию (независимо от фокуса) в зависимости от размера планеты: +• [[SZ_TINY]] (-1) +• [[SZ_SMALL]] (-2) +• [[SZ_MEDIUM]] (-3) +• [[SZ_LARGE]] (-4) +• [[SZ_HUGE]] (-5) -Орбита этой планеты имеет сильно вытянутую форму, из-за чего очень велико расстояние между самым ближним и самым дальним положением планеты от солнца. Общая облучаемость планеты значительно отличается в течении года. Такая разница в условиях затрудняет развитие инфраструктуры, но дает преимущество в исследованиях.''' +Планета, вращаясь вокруг звезды, всегда обращена к ней одной стороной. На одной стороне планеты вечный день, на другой - вечная ночь, а день и год имеют одинаковую продолжительность. Рост населения затруднён, хотя промышленность имеет преимущество за счёт локально стабильных условий поверхности.''' + +TEMPORAL_ANOMALY_SPECIAL +Временная аномалия + +TEMPORAL_ANOMALY_SPECIAL_DESC +'''Увеличивает [[metertype METER_RESEARCH]] на этой планете на 5 за единицу населения, если фокус установлен на [[metertype METER_RESEARCH]]. Уменьшает популяцию (независимо от фокуса) в зависимости от размера планеты: +• [[SZ_TINY]] (-5) +• [[SZ_SMALL]] (-10) +• [[SZ_MEDIUM]] (-15) +• [[SZ_LARGE]] (-20) +• [[SZ_HUGE]] (-25) + +Течение времени на этой планете серьезно искажено. Хаотичные локальные ускорения и замедления времени вызывают серьёзные проблемы у любого живого существа. Социальная и сколько-либо продуктивная деятельность на планете практически невозможна. Потребуются все меры предосторожности и невероятная приспособляемость вида чтобы основать здесь колонию. С другой стороны, временная аномалия несёт в себе огромный потенциал для научных экспериментов, значительно превосходящий тот, что возможен на обычных, нормальных планетах.''' RESONANT_MOON_SPECIAL Резонансная луна RESONANT_MOON_SPECIAL_DESC -'''[[metertype METER_STEALTH]] строений на планете и кораблей в системе увеличивается на 10. +'''[[metertype METER_STEALTH]] строений на планете и кораблей в системе увеличивается на 10 -У этой планеты есть естественный спутник, период вращения которого вокруг планеты соответствует периоду вращения вокруг собственной оси. Это означает, что такая луна повернута к планете всегда одной стороной, а другая "темная" сторона всегда направлена в космос и является прекрасным местом для тайных предприятий.''' +У этой планеты есть естественный спутник, период вращения которого вокруг планеты соответствует периоду вращения вокруг собственной оси. Это означает, что такая луна повернута к планете всегда одной стороной, а другая "темная" сторона всегда направлена в космос и является прекрасным местом для тайных предприятий''' COMPUTRONIUM_SPECIAL -Умная луна +Компьютрониумная луна COMPUTRONIUM_SPECIAL_DESC -'''Когда такая планета нацелена на [[metertype METER_RESEARCH]], все научно-ориентированные планеты империи получают бонус в 0,2*Население. +'''Когда планета с такой луной нацелена на [[metertype METER_RESEARCH]], все научно-ориентированные планеты империи получают бонус в 0.2 за единицу населения. -Оставленная какой-то исчезнувшей цивилизацией, эта луна представляет собой вычислительную машину небывалой мощи. Она может выполнить самое сложное моделирование или решить задачу быстрее чем они будут введены в нее.''' +Компьютрониумная (компьютрониум - программируемая материя) луна была оставлена некоей исчезнувшей цивилизацией, и представляет из себя вычислительную машину невероятной мощности. Она может выполнить самое сложное моделирование или решить задачу быстрее, чем в неё будут введены данные.''' HONEYCOMB_SPECIAL Медовые соты HONEYCOMB_SPECIAL_DESC -'''Когда такая планета нацелена на [[metertype METER_INDUSTRY]], все планеты с промышленным фокусом, которые включены в [[metertype METER_SUPPLY]] с этой планетой, получают бонус в 0.5*Население. +'''Когда такая планета нацелена на [[metertype METER_INDUSTRY]], все планеты с промышленным фокусом, соединённые с ней через [[metertype METER_SUPPLY]], получают бонус в 0.5 к [[metertype METER_INDUSTRY]] за единицу населения. -Древние строители создали из планеты огромный улей. Окружающие планеты были разобраны на составляющие, отсортированы и складированны по гигантским трубам, вмонтированным в коре планеты. Тут спрятано многое: от редких и ценных элементов до трудно производимых молекул и широкого разнообразия источников энергии. Что Строители хотели создать, собирая эти сокровища, или почему они их не использовали, в конце концов, остается загадкой.''' +Планета с Медовыми сотами - это тайник, оставленный древней расой Строителей. Почти все окружающие планеты были лишены своих ценных материалов, а позже - демонтированы. Материалы были рассортированы и складированы в гигантские трубы, вмонтированные в кору планеты. Здесь есть и редкие, ценные элементы, и трудносинтезируемые молекулы, и огромные запасы источников энергии. Не ясно, как именно Строители собирались использовать всё это богатство, и почему они его не использовали.''' PHILOSOPHER_SPECIAL -Философская Планета +Философская планета PHILOSOPHER_SPECIAL_DESC -'''Пока эта планета не населена, она дает +5 [[metertype METER_RESEARCH]] бонуса к населенным планетам в той же системе. [[metertype METER_CONSTRUCTION]] на этой планете снижено на 20. +'''Пока эта планета не населена и на ней не установлен аванпост, она дает +5 [[metertype METER_RESEARCH]] бонуса к населенным планетам в этой же системе. [[metertype METER_CONSTRUCTION]] на этой планете снижено на 20. -Одинокий след скитающегося в космосе вида, размером с планету, нашел покой, выйдя на орбиту этой системы, как обычная планета. Просуществовав около тысячи лет, он потерял волю и способность путешествовать и начал размышлять о тайнах Вселенной, основываясь на увиденном. Эксцентричный и немного сумасшедший, он отказывался общаться со всем, что не походило по размеру и форме на его родственников, не оставляя ученым никакого выбора, кроме как задавать вопросы через планеты, на которых они жили. Разумно, что колонизация Филосовской Планеты помешает ему, и он умолкнет. Строительство на его поверхности затруднено, ведь он - живой организм.''' +Одинокий представитель расы размером с планету, скитаясь по космосу, нашёл приют в этой системе и обрёл покой, став вращаться на её орбите, как обычная планета. Прожив тысячи лет, он потерял силу воли и способность путешествовать, и теперь предпочитает предаваться размышлениям а тайнах вселенной, которые увидел и о которых узнал. Эксцентричный и слегка сумасшедший, он отказывается общаться с кем-либо, не соответствующим ему по форме и размеру. Учёным ничего не остаётся, кроме как задавать вопросы через планету, на которой они живут. Колонизация философа серьёзно потревожит его, и он затихнет. Строительство на его поверхности затруднено, ведь он - живой организм.''' ABANDONED_COLONY_SPECIAL Покинутая колония @@ -3881,133 +7692,156 @@ ABANDONED_COLONY_SPECIAL_DESC Старая заброшенная колония. Большая часть [[metertype METER_CONSTRUCTION]] должна функционировать. KRAKEN_NEST_SPECIAL -Гнездо космического спрута +Гнездо кракенов KRAKEN_NEST_SPECIAL_DESC -Иногда молодые спруты появляются здесь. После изучения технологии [[tech SHP_DOMESTIC_MONSTER]] это гнездо может быть уничтожено. +Иногда здесь появляются маленькие кракены, которые затем растут. После изучения технологии [[tech SHP_DOMESTIC_MONSTER]], они могут быть приручены. SNOWFLAKE_NEST_SPECIAL -Гнездо космических снежинок +Гнездо снежинок SNOWFLAKE_NEST_SPECIAL_DESC -Иногда молодые космические снежинки появляются в этом месте. С соответствующей технологией это гнездо может быть уничтожено. +Иногда здесь появляются малые снежинки, которые затем растут. После изучения технологии [[tech SHP_DOMESTIC_MONSTER]] они могут быть приручены. JUGGERNAUT_NEST_SPECIAL -Гнездо космического вышибалы +Гнездо джаггернаутов JUGGERNAUT_NEST_SPECIAL_DESC -Иногда молодые космические вышибалы появляются в этом месте. С соответствующей технологией это гнездо может быть уничтожено. +Иногда здесь появляются маленькие джаггернауты, которые затем растут. После изучения технологии [[tech SHP_DOMESTIC_MONSTER]] они могут быть приручены. + +KRAKEN_IN_THE_ICE_SPECIAL +Кракен во льду + +KRAKEN_IN_THE_ICE_SPECIAL_DESC +'''Благодаря знаниям технологий [[tech SHP_DOMESTIC_MONSTER]] и [[tech LRN_XENOARCH]], [[predefinedshipdesign SM_WHITE_KRAKEN]] может быть воскрешен и приручен, и контролироваться при помощи [[buildingtype BLD_XENORESURRECTION_LAB]] на этой планете. + +Глубоко во льдах на этой планете покоится доисторический предок космического монстра Кракена, белого цвета.''' HEAD_ON_A_SPIKE_SPECIAL Голова на колу HEAD_ON_A_SPIKE_SPECIAL_DESC -'''Голова бывшего императора нанизана на кол на обозрение всем, что пораждает страх в сердцах нынешних врагов империи. +'''Голова бывшего императора нанизана на кол на обозрение всем, что порождает страх в сердцах нынешних врагов империи. -Не-имперские корабли, будь то враги или союзники, в пределах 100uu теряют часть [[metertype METER_SHIELD]], [[metertype METER_STRUCTURE]] и оружия. +Не-имперские корабли, будь то враги или союзники, в пределах 100 световых лет теряют часть [[metertype METER_SHIELD]], [[metertype METER_STRUCTURE]] и оружия. ''' HEAD_ON_A_SPIKE_SPECIAL_EFFECT -Съёжившись от страха +Съежившись от страха CONC_CAMP_MASTER_SPECIAL -Пелена смерти +Завеса смерти CONC_CAMP_MASTER_SPECIAL_DESC -Гости не могут видеть [[buildingtype BLD_CONC_CAMP]], работающие на этой планете, но они работают безжалостно и такая жестокость сказывается на популяции; Завеса смерти применима к чувствительным существам. +Гости не могут видеть [[buildingtype BLD_CONC_CAMP]], работающие на этой планете, но они безжалостно работают, и такая жестокость сказывается на популяции. Чувствительные существа способны заметить Завесу смерти. CONC_CAMP_SLAVE_SPECIAL Психическая усталость CONC_CAMP_SLAVE_SPECIAL_DESC -Здесь больше не может быть [[buildingtype BLD_CONC_CAMP]], работающих на этой планете сегодня, но воспоминания все еще близки к поверхности, и пережитки так плохо скрытых массовых захоронений до сих пор изредка встречаются. Психическая рана по сей день лежит на народе, и он не процветает. +Здесь может уже не быть работающих [[buildingtype BLD_CONC_CAMP]], но воспоминания о них всё ещё свежи, и плохо скрытые массовые захоронения до сих пор изредка, но встречаются. По сей день на народе лежит глубокая психическая рана, из-за чего он не процветает. CLOUD_COVER_MASTER_SPECIAL Сильная облачность CLOUD_COVER_MASTER_SPECIAL_DESC -Эта планета населена живущими в атмосфере существами, которые испаряют влагу, что способствует сильной облачности. В процессе своей дальнейшей жизнедеятельности эти существа зарываются в землю и провоцируют вулканические процессы. +Эта планета населена живущими в атмосфере существами, которые испаряют влагу, что способствует сильной облачности. В процессе своей дальнейшей жизнедеятельности эти существа зарываются в землю и провоцируют вулканическую активность. CLOUD_COVER_SLAVE_SPECIAL Парниковый эффект CLOUD_COVER_SLAVE_SPECIAL_DESC -На этой планете очень плотный слой облаков, что увеличивает ее [[metertype METER_STEALTH]]. +На этой планете очень плотный слой облаков, что увеличивает её [[metertype METER_STEALTH]] на 20. Это может быть результатом деятельности космического существа, или же вызвано технологией наподобие [[tech SPY_STEALTH_1]]. VOLCANIC_ASH_MASTER_SPECIAL Землеройки VOLCANIC_ASH_MASTER_SPECIAL_DESC -Вокруг ядра этой планеты обитают монстры, которые вызывают вулканическую активность. Вырастая, эти землеройки превращаются в существ, способных делать проходы между измерениями в окружающем космосе. +Вокруг ядра этой планеты обитают монстры, вызывающие сильную вулканическую активность. Далее в своём цикле развития землеройки превращаются в существ, способных делать проходы между измерениями. VOLCANIC_ASH_SLAVE_SPECIAL Вулканический пепел VOLCANIC_ASH_SLAVE_SPECIAL_DESC -Эта планета окружена вулканическим пеплом, что увеличивает ее [[metertype METER_STEALTH]]. +Эта планета окружена вулканическим пеплом, что увеличивает ее [[metertype METER_STEALTH]] на 40. Это может быть результатом деятельности космического существа, или же вызвано технологией наподобие [[tech SPY_STEALTH_2]]. DIM_RIFT_MASTER_SPECIAL Кроты измерений DIM_RIFT_MASTER_SPECIAL_DESC -Вокруг ядра этой планеты обитают монстры, которые делают проходы между измерениями в и вокруг планеты. Впоследствии эти твари окрепнут и утащат эту планету в Ничто. +Вокруг ядра этой планеты обитают монстры, которые делают проходы между измерениями внутри и на поверхности планеты. Впоследствии эти твари окрепнут и утащат эту планету в бездну. DIM_RIFT_SLAVE_SPECIAL -Дыры в измерениях +Пространственный разрыв DIM_RIFT_SLAVE_SPECIAL_DESC -Эта планета частично находится в разрыве между измерениями, что повышает ее [[metertype METER_STEALTH]]. +Эта планета частично находится в разрыве между измерениями, что повышает ее [[metertype METER_STEALTH]] на 60. Это может быть результатом деятельности космического существа, или же вызвано технологией наподобие [[tech SPY_STEALTH_3]]. VOID_MASTER_SPECIAL Монстры пустоты VOID_MASTER_SPECIAL_DESC -Монстры в ядре этой планеты тянут ее в Пустоту. Для этой планеты скоро наступит полный п. +Монстры в ядре этой планеты тянут ее в бездну. Дни существования этой планеты сочтены VOID_SLAVE_SPECIAL Метаморфозы пустоты VOID_SLAVE_SPECIAL_DESC -Эта планета существует частично в этой вселенной, а частично в пустоте, что увеличивает ее [[metertype METER_STEALTH]] на 80. Если эта планета принадлежит империи, бонусы от технологий и населения будут добавлены к этому бонусу. +Эта планета существует частично в этой вселенной, а частично в пустоте, что увеличивает ее [[metertype METER_STEALTH]] на 80. Это может быть естественным результатом космического существа или вызванной технологией такой как [[tech SPY_STEALTH_4]] DERELICT_SPECIAL2 -Покинутый +Покинутый разведчик DERELICT_SPECIAL3 -Покинутый +Покинутый танкер DERELICT_SPECIAL_DESC Древний покинутый корабль с непонятными артефактами. Кто знает, что он может скрывать в себе... +ACCRETION_DISC_SPECIAL +Аккреционный диск + +ACCRETION_DISC_SPECIAL_DESC +Аккреционный диск представляет собой структуру (часто околозвездный диск), образованную рассеянным материалом в орбитальном движении вокруг массивного центрального тела. Все планеты в затронутой системе получают штраф [[metertype METER_SUPPLY]] -1 + SUPERNOVA_SPECIAL Сверхновая +SUPERNOVA_SPECIAL2 +Молодая сверхновая + +SUPERNOVA_SPECIAL3 +Зрелая сверхновая + +SUPERNOVA_SPECIAL4 +Пережиток сверхновой + SUPERNOVA_SPECIAL_DESC -После взрыва сверхновой, который произошел совсем недавно, остались обломки солнечной системы, существовавшей на этом месте ранее. +После взрыва сверхновой, который произошел совсем недавно, остались обломки солнечной системы, существовавшей на этом месте ранее NOVA_BOMB_ACTIVATOR_SPECIAL Взрыватель сверхновой NOVA_BOMB_ACTIVATOR_SPECIAL_DESC -Стабильность измерений в этом регионе крайне низка, что провоцирует все близлежащие сверхновые детонировать. +Стабильность измерений в этом регионе крайне низка, что провоцирует все близлежащие сверхновые детонировать PROBIOTIC_SPECIAL Биологический суп PROBIOTIC_SPECIAL_DESC -Множество уникальных одноклеточных, благотворно влияющих на здоровье и жизнедеятельность любой органической формы жизни. +Множество уникальных одноклеточных, благотворно влияющих на здоровье и жизнедеятельность любой органической формы жизни FRUIT_SPECIAL -Фрукт сторожа +Плоды опекуна FRUIT_SPECIAL_DESC -То, что выглядит как лес, на самом деле является частью огромного организма, простирающегося на целые континенты. Плоды с такого леса содержат комплекс органических вещество, благотворно влияющий на все формы органической жизни. +То, что выглядит как лес, на самом деле является частью огромного организма, простирающегося на целые континенты. Плоды с такого леса содержат комплекс органических веществ, благотворно влияющий на все формы органической жизни SPICE_SPECIAL Специя "Ки" SPICE_SPECIAL_DESC -Уникальная субстанция, являющаяся продуктом экологии этой планеты, Специя "Ки", способна продлевать жизнь, прочищать мозги и отрезвлять разум органических существ. +Уникальная субстанция, являющаяся продуктом экологии этой планеты, Специя "Ки", способна продлевать жизнь, прочищать мозги и отрезвлять разум органических существ MONOPOLE_SPECIAL Однополярные магниты @@ -4031,17 +7865,19 @@ MINERALS_SPECIAL Богата минералами MINERALS_SPECIAL_DESC -Эта планета богата залежами минералов, что способствует росту промышленности. Также здесь намного быстрее происходит размножение педобионтов. +Эта планета богата залежами минералов, что способствует росту промышленности. Также здесь намного быстрее происходит размножение педобионтов (каменная/почвенная жизнь) CRYSTALS_SPECIAL Круглые кристаллы CRYSTALS_SPECIAL_DESC -На этой планете есть редкие круглые кристаллы, что увеличивает производство или рост педобионтов. +На этой планете есть редкие круглые кристаллы, что увеличивает производство или рост педобионтов (каменная/почвенная жизнь) ELERIUM_SPECIAL -Абориген Элериума +Чистый Элериум +ELERIUM_SPECIAL_DESC +Эта планета имеет богатое природное месторождение чистого элерия, значительно увеличивающее производство или рост педобионтов (каменная/почвенная жизнь) ## ## Enumeration values @@ -4073,7 +7909,7 @@ OBJ_POP_CENTER # Universe object types OBJ_PROD_CENTER -Продуктовый центр +Производственный центр # Universe object types OBJ_SYSTEM @@ -4081,87 +7917,99 @@ OBJ_SYSTEM # Universe object types OBJ_FIELD -Поле +Область + +# Universe object types +OBJ_FIGHTER +Истребитель # Star types INVALID_STAR_TYPE -непонятная звезда +Неопознанная звезда # Star types STAR_BLUE -голубая +Голубая # Star types STAR_WHITE -белая +Белая # Star types STAR_YELLOW -желтая +Желтая # Star types STAR_ORANGE -оранжевая +Оранжевая # Star types STAR_RED -красная +Красная # Star types STAR_NEUTRON -нейтронная +Нейтронная # Star types STAR_BLACK -черная дыра +Черная дыра # Star types STAR_NONE -не звезда +Не звезда + +# Planet types +INVALID_PLANET_TYPE +Неопознанная планета # Planet types PT_SWAMP -болотистая +Болотистая # Planet types PT_TOXIC -токсичная +Токсичная # Planet types PT_INFERNO -ад +Раскаленная # Planet types PT_RADIATED -радиационная +Радиационная # Planet types PT_BARREN -бесплодная +Бесплодная # Planet types PT_TUNDRA -тундра +Холодная # Planet types PT_DESERT -пустыня +Пустынная # Planet types PT_TERRAN -земля +Земная # Planet types PT_OCEAN -океан +Океаническая # Planet types PT_ASTEROIDS -астероиды +Астероидная # Planet types PT_GASGIANT -газовый гигант +Газовый гигант + +# Planet sizes +INVALID_PLANET_SIZE +Неопознанный размер планеты # Planet sizes SZ_NOWORLD @@ -4169,31 +8017,35 @@ SZ_NOWORLD # Planet sizes SZ_TINY -крошечная +Крошечная # Planet sizes SZ_SMALL -маленькая +Маленькая # Planet sizes SZ_MEDIUM -средняя +Средняя # Planet sizes SZ_LARGE -большая +Большая # Planet sizes SZ_HUGE -огромная +Огромная # Planet sizes SZ_ASTEROIDS -астероиды +Астероиды # Planet sizes SZ_GASGIANT -газовый гигант +Газовый гигант + +# Planet environments +INVALID_PLANET_ENVIRONMENT +неизвестная планетарная среда # Planet environments PE_UNINHABITABLE @@ -4217,7 +8069,7 @@ PE_GOOD # Focus types INVALID_FOCUS_TYPE -неправильная цель +неверная цель # Focus types FOCUS_GROWTH @@ -4225,15 +8077,15 @@ FOCUS_GROWTH # Focus types FOCUS_INDUSTRY -индустрия +Индустрия # Focus types FOCUS_RESEARCH -исследования +Исследования # Focus types FOCUS_TRADE -торговля +Торговля # Focus types FOCUS_LOGISTICS @@ -4249,14 +8101,17 @@ FOCUS_BIOTERROR # Focus types FOCUS_STARGATE_SEND -Отправка В Звездные врата +Отправка через Звездные врата FOCUS_STARGATE_SEND_DESC -'''Отправка судов в системе через звездные врата ''' +'''Отправка кораблей в системе через звездные врата ''' # Focus types FOCUS_STARGATE_RECEIVE -Получение Через Звездный врата +Получение через Звездный врата + +FOCUS_STARGATE_RECEIVE_DESC +Открыть Звездные врата кораблям отправляемым из других мест FOCUS_PLANET_DRIVE Планетарный двигатель @@ -4268,7 +8123,13 @@ FOCUS_PROTECTION Оборона FOCUS_PROTECTION_DESC -Повышает энерго-щиты, оборону и наземные силы. +Повышает энерго-щиты, оборону и наземные силы + +FOCUS_DOMINATION +Психическое господство + +FOCUS_DOMINATION_DESC +Психически контролирует вражеские корабли # Meter types INVALID_METER_TYPE @@ -4278,10 +8139,6 @@ INVALID_METER_TYPE METER_TARGET_POPULATION Цель: население -# Meter types -METER_TARGET_HEALTH -Цель: здоровье - # Meter types METER_TARGET_INDUSTRY Цель: промышленность @@ -4302,41 +8159,41 @@ METER_TARGET_CONSTRUCTION METER_TARGET_HAPPINESS Цель: счастье +# Meter types +METER_MAX_CAPACITY +Макс. вместимость + +# Meter types +METER_MAX_SECONDARY_STAT +Макс. вторичные параметры + # Meter types METER_MAX_FUEL -Max Топливо +Макс. топливо # Meter types METER_MAX_SHIELD -Max Щиты +Макс. щиты # Meter types METER_MAX_STRUCTURE -Max Структура +Макс. структура # Meter types METER_MAX_DEFENSE -Max Защита +Макс. защита # Meter types -METER_MAX_TROOPS -Max Десант +METER_MAX_SUPPLY +Макс. снабжение # Meter types -METER_MAX_REBEL_TROOPS -Max Повстанцы +METER_MAX_TROOPS +Макс. войска # Meter types METER_POPULATION -население - -# Meter types -METER_HEALTH -здоровье - -# Meter types -METER_GROWTH -Заселение +Население # Meter types METER_INDUSTRY @@ -4344,7 +8201,7 @@ METER_INDUSTRY # Meter types METER_RESEARCH -Наука +Исследования # Meter types METER_TRADE @@ -4358,33 +8215,45 @@ METER_CONSTRUCTION METER_HAPPINESS Счастье +# Meter types +METER_CAPACITY +Вместимость + +# Meter types +METER_SECONDARY_STAT +Вторичные параметры + # Meter types METER_FUEL Топливо # Meter types METER_SHIELD -Щиты +Щит # Meter types METER_STRUCTURE -Структура +Прочность # Meter types METER_DEFENSE Защита +# Meter types +METER_SUPPLY +Снабжение + # Meter types METER_TROOPS -Десант +Войска # Meter types METER_REBEL_TROOPS Повстанцы # Meter types -METER_SUPPLY -Обеспечение +METER_SIZE +Размер # Meter types METER_STEALTH @@ -4399,12 +8268,15 @@ METER_SPEED Скорость # Meter types -METER_CAPACITY -Вместимость -# Meter types -METER_SIZE -Размер +METER_BUILDING_COST_FACTOR +Фактор затрат на строительство + +METER_SHIP_COST_FACTOR +Фактор стоимости судна + +METER_TECH_COST_FACTOR +Фактор стоимости технологии ALIGN_MILITARISM Милитаризм @@ -4428,7 +8300,7 @@ ALIGN_BIOLOGICAL_ALTERATION Биологические модификации ALIGN_BIOLOGICAL_ALTERATION_DESC -Использование биоинжениринга и биологических модификаций +Использование биоинженеринга и биологических модификаций ALIGN_MECHANIZATION Механизация @@ -4462,7 +8334,7 @@ UIT_BUILDING # Unlockable item types UIT_SHIP_PART -Часть корабля +Деталь корабля # Unlockable item types UIT_SHIP_HULL @@ -4484,6 +8356,10 @@ BT_BUILDING BT_SHIP Корабль +# Resource types +INVALID_RESOURCE_TYPE +Неверный тип ресурса + # Resource types RE_TRADE торговля @@ -4504,45 +8380,258 @@ SL_EXTERNAL SL_INTERNAL Внутренний слот +# Ship slot types +SL_CORE +Центральный слот + SL_TOOLTIP_DESC Пустой +# Ship part classes +INVALID_SHIP_PART_CLASS +неверный класс части судна + +# Ship part classes +PC_DIRECT_WEAPON +Вооружение + +PC_DIRECT_WEAPON_DESC +'''Атака с применением оружия прямого огня может повредить цели в каждом раунде боя (в отличие от [[encyclopedia FIGHTER_TECHS]], которые необходимо сначала запустить, прежде чем они смогут атаковать). + +Некоторые виды оружия в этой категории способны стрелять несколькими выстрелами за один раунд боя. Корабли построенные видами с возможностью пилотирования, могут влиять на ущерб от оружия''' + +# Ship part classes +PC_FIGHTER_HANGAR +Ангары + +PC_FIGHTER_HANGAR_DESC +'''Ангары - это области хранения, обслуживания и снабжения для [[encyclopedia FIGHTER_TECHS]]. +На корабле не может быть более одного типа ангара + +После боя выжившие истребители возвращаются в ангар для ремонта, пополнения горючего и перевооружения. +Если авианосец уничтожается в бою, любой из истребителей запущенный с него и выживший в битве не сможет добраться до дока после боя и также будет потерян +''' + +# Ship part classes +PC_FIGHTER_BAY +Пусковые отсеки + +PC_FIGHTER_BAY_DESC +'''Запускающие отсеки - это точка доступа для [[encyclopedia FIGHTER_TECHS]] их входа или выхода на корабле. +Запущенные истребители вступают в бой после запуска, возвращаясь после окончания боя в этот ход''' + # Ship part classes PC_SHIELD Щиты +PC_SHIELD_DESC +'''Щит уменьшает [[encyclopedia DAMAGE_TITLE]] полученные от каждого оружия. + +Корабельные щиты не влияют на попадание оружия и уменьшают все последующие попадания оружия прямого огня на тоже значение. Корабельные щиты неэффективны против [[encyclopedia FIGHTER_TECHS]]''' + # Ship part classes PC_ARMOUR Броня +PC_ARMOUR_DESC +Броня увеличивает [[metertype METER_STRUCTURE]] корабля, позволяя судну выдерживать больше урона прежде чем полностью разрушиться + # Ship part classes PC_TROOPS Наземные силы +PC_TROOPS_DESC +'''Войска увеличивают значение [[metertype METER_TROOPS]] на борту, которое можно использовать для вторжения на планеты противника + +Чтобы успешно вторгнуться на планету, общее количество [[metertype METER_TROOPS]] из всех вторгшихся кораблей должно превышать общую защиту (соединений имеющихся у защитника). +Во время операции по посадке может быть уничтожена часть кораблей вторжения (любые другие соединения входящие в состав уничтоженного корабля также теряются)''' + # Ship part classes PC_DETECTION Обнаружение +PC_DETECTION_DESC +'''Детали обнаружения увеличивают значение [[metertype METER_DETECTION]] для корабля на котором они установлены + +Империя [[encyclopedia DETECTION_TITLE]] должна равняться или превосходить объект [[metertype METER_STEALTH]] для его обнаружения''' + # Ship part classes PC_STEALTH Скрытность +PC_STEALTH_DESC +'''Детали невидимости увеличивают [[metertype METER_STEALTH]] корабля. Эффекты от этих частей не дополняют друг друга, на корабль будет влиять только самая высокая номинальная часть + +Если битва происходит в системе, корабли невидимые врагу, не являются целью орудий противника. Однако, если невидимое судно начнет атаку оно может стать видимым в последующих раундах + +Смотрите также: [[metertype METER_DETECTION]], [[encyclopedia DETECTION_TITLE]]''' + # Ship part classes PC_FUEL Топливо +PC_FUEL_DESC +'''Детали топлива влияют на [[metertype METER_FUEL]] корабля. Такие части могут увеличить максимальную топливную вместительность или увеличить количество топлива, которое судно может собрать или регенерировать. + +Кораблю требуется значение показателя топлива 1.0 или больше, чтобы иметь возможность покинуть систему''' + # Ship part classes PC_COLONY Колонизация +PC_COLONY_DESC +'''Части колонии интегрируются с кораблем, чтобы обеспечить необходимое оборудование для установления владения на новой планете + +Эти части также могут увеличить [[metertype METER_POPULATION]] на корабле +При колонизации планеты с населенным колониальным кораблем основывается колония для [[encyclopedia ENC_SPECIES]] начиная с той же популяции, которая находилась на борту корабля + +На корабле, где нет населения, будет достаточно личного состава для создания [[encyclopedia OUTPOSTS_TITLE]] +Аванпосты могут построить колонию для любых видов в империи в пределах диапазона снабжения, при условии соответствия другим необходимым критериям +Комбинация корабля аванпоста и местного строительства колонии дешевле чем производство населенного колониального корабля + +Когда судно колонизирует планету, оно задействуется в процессе и не может использоваться снова. Любое оружие или другие части на корабле будут также выведены из эксплуатации''' + +# Ship part classes +PC_SPEED +Скорость + +PC_SPEED_DESC +Части в категории [[metertype METER_SPEED]] увеличивают максимальное расстояние, которое корабль может пройти на каждом ходе + # Ship part classes PC_GENERAL -'''Общий ''' +Общие + +PC_GENERAL_DESC +Универсальные и специализированные детали, которые не относятся к какой-либо конкретной категории # Ship part classes PC_BOMBARD Бомбардировка +PC_BOMBARD_DESC +'''Детали бомбардировки позволяют кораблю бомбардировать вражескую планету, как правило, уменьшая численность населения планеты на величину мощности любых применимых частей входящих в общий потенциал бомбардировки на борту + +Большинство деталей бомбардировки влияют только на [[encyclopedia METABOLISM_TITLE]]''' + +# Ship part classes +PC_INDUSTRY +Индустрия + +PC_INDUSTRY_DESC +Детали индустрии увеличивают [[metertype METER_INDUSTRY]] генерируемые кораблем. Корабль должен быть в зоне [[metertype METER_SUPPLY]] соединяясь с [[OBJ_PROD_CENTER]] (например колонии) для возможности использования выходных мощностей + +# Ship part classes +PC_RESEARCH +Исследования + +PC_RESEARCH_DESC +Научные детали увеличивают [[metertype METER_RESEARCH]] генерируемые кораблем. Кораблю может потребоваться [[metertype METER_POPULATION]] чтобы начать производить выходные мощности + +# Ship part classes +PC_TRADE +Торговля + +# Ship part classes +PC_PRODUCTION_LOCATION +Производство + +PC_PRODUCTION_LOCATION_DESC +Производственные детали интегрируются с кораблем, чтобы сформировать [[OBJ_PROD_CENTER]], что позволит создавать ряд производственных проектов. Корабль должен быть в зоне [[metertype METER_SUPPLY]] и подключен к источнику [[metertype METER_INDUSTRY]] + +# Visibility levels +INVALID_VISIBILITY +неверная видимость + +# Visibility levels +VIS_NO_VISIBILITY +Невидимый + +# Visibility levels +VIS_BASIC_VISIBILITY +Основная + +# Visibility levels +VIS_PARTIAL_VISIBILITY +Частичная + +# Visibility levels +VIS_FULL_VISIBILITY +Полная + +# Ship hull categories + +HULL_LINE_GENERIC +Тип корпуса: обычный + +HULL_LINE_GENERIC_DESC +Общий тип корпусов кораблей без специальности + +HULL_LINE_ASTEROIDS +Тип корпуса: [[PT_ASTEROIDS]] + +HULL_LINE_ASTEROIDS_DESC +Тип корпусов смоделированный и используемый из [[PT_ASTEROIDS]] + +HULL_LINE_ENERGY +Тип корпуса: энергетический + +HULL_LINE_ENERGY_DESC +Тип корпусов, спроектированный или используемый из энергии высокой плотности + +HULL_LINE_ORGANIC +Тип корпуса: органический + +HULL_LINE_ORGANIC_DESC +Тип корпусов состоящий из органических материалов + +HULL_LINE_ROBOTIC +Тип корпуса: роботизированный + +HULL_LINE_ROBOTIC_DESC +Тип корпусов с большим уклоном на искусственный интеллект и автоматизацию + +HULL_MONSTER +Корпуса монстров + +HULL_MONSTER_DESC +Корпуса используемые в конструкциях монстров + +HULL_MONSTER_GUARD +Корпуса стражей + +HULL_MONSTER_GUARD_DESC +Корпуса используемые в конструкциях стражей (один из классов дизайна монстров) + +HULL_MONSTER_JUGGERNAUT +Корпуса сокрушительной силы + +HULL_MONSTER_JUGGERNAUT_DESC +Корпуса используемые в конструкциях монстров большой силы + +HULL_MONSTER_KRAKEN +Корпуса кракена + +HULL_MONSTER_KRAKEN_DESC +Корпуса используемые в дизайнах монстров-кракенов + +HULL_MONSTER_KRILL +Корпуса криля + +HULL_MONSTER_KRILL_DESC +Корпуса используемые в дизайнах монстров-криля + +HULL_MONSTER_SNOWFLAKE +Корпуса снежинок + +HULL_MONSTER_SNOWFLAKE_DESC +Корпуса используемые в дизайнах монстров-снежинок + +HULL_MONSTER_TREE +Корпуса деревьев + +HULL_MONSTER_TREE_DESC +Корпуса используемые в дизайнах монстров-деревьев ## ## FOCS value references, effects & condition descriptions @@ -4563,9 +8652,21 @@ DESC_VAR_SOURCE DESC_VAR_TARGET цель +DESC_VAR_LOCAL_CANDIDATE +локальный кандидат состояния + +DESC_VAR_ROOT_CANDIDATE +корневой кандидат состояния + DESC_STATISTIC статистика +DESC_STAT_TYPE +тип отчета + +DESC_SAMPLING_CONDITION +условие выборки + DESC_VAR_PLANETSIZE Размер планеты @@ -4573,10 +8674,16 @@ DESC_VAR_SIZEASDOUBLE размер планеты DESC_VAR_PLANETTYPE -Тип планеты +тип планеты + +DESC_VAR_ORIGINALTYPE +ориг. тип планеты DESC_VAR_NEXTBETTERPLANETTYPE -следующая планета, подходящая по условиям +следующая планета, подходящая для видов + +DESC_VAR_DISTANCEFROMORIGINALTYPE +расстояние от ориг. типа планеты DESC_VAR_PLANETENVIRONMENT среда планеты @@ -4588,22 +8695,52 @@ DESC_VAR_STARTYPE тип звезды DESC_VAR_VALUE -'''предыдущее значение ''' +предыдущее значение + +DESC_VAR_GALAXYMAXAIAGGRESSION +установка макс. агрессии в галактике + +DESC_VAR_GALAXYSIZE +настройка размера галактики (# систем) + +DESC_VAR_GALAXYPLANETDENSITY +настройка концентрации планет в галактике + +DESC_VAR_GALAXYAGE +установка возраста галактики + +DESC_VAR_GALAXYMONSTERFREQUENCY +настройка частоты появления монстров в галактике + +DESC_VAR_GALAXYNATIVEFREQUENCY +настройка частоты появления аборигенов в галактике + +DESC_VAR_GALAXYSHAPE +установка формы галактики + +DESC_VAR_GALAXYSPECIALFREQUENCY +настройка частоты появления артефактов в галактике + +DESC_VAR_GALAXYSTARLANEFREQUENCY +настройка частоты звездных переходов в галактике + +DESC_VAR_NON_OBJECT_REFERENCE +свободная переменная DESC_VAR_TRADESTOCKPILE торговый запас DESC_VAR_TRADEOUTPUT -Выход торговли +торговая ценность DESC_VAR_INDUSTRYOUTPUT -Выход промышленности +промышленная мощность DESC_VAR_RESEARCHOUTPUT -Выход науки +научный потенциал DESC_VAR_OWNER -собственник +владелец DESC_VAR_AGE возраст в ходах @@ -4638,6 +8775,9 @@ DESC_VAR_PREVIOUSSYSTEMID DESC_VAR_NUMSHIPS количество кораблей +DESC_VAR_NUMSTARLANES +количество соединенных звезд + DESC_VAR_CURRENTTURN текущий ход @@ -4653,9 +8793,94 @@ DESC_VAR_BUILDINGTYPE DESC_VAR_FOCUS цель -DESC_VAR_LASTTURNBATTLEHERE -в последний раз бой была здесь +DESC_VAR_AVAILABLE_FOCI +доступные цели (фокусы) + +DESC_VAR_OWNERLEASTEXPENSIVEENQUEUEDTECH +наименее дорогостоящая технология владельца + +DESC_VAR_OWNERMOSTEXPENSIVEENQUEUEDTECH +наиболее дорогостоящая технология владельца + +DESC_VAR_OWNERMOSTRPCOSTLEFTENQUEUEDTECH +технология владельца в очереди с наибольшей стоимостью RP + +DESC_VAR_OWNERMOSTRPSPENTENQUEUEDTECH +технология владельца в очереди с наибольшими RP затратами + +DESC_VAR_OWNERTOPPRIORITYENQUEUEDTECH +технология владельца в очереди с наивысшим приоритетом +DESC_VAR_LASTTURNBATTLEHERE +в последний раз бой был здесь + +DESC_COMPLEX +Совокупность переменных + +# FOCS variable reference description text. +# %1% reference type of the FOCS variable. + +# FOCS variable reference description text. +# %1% reference type of the FOCS variable. +# %2% base property referenced by the FOCS variable. +DESC_VALUE_REF_MULTIPART_VARIABLE1 +%1% %2% + +# FOCS variable reference description text. +# %1% reference type of the FOCS variable. +# %2% base property referenced by the FOCS variable. +# %3% first dependent property referenced by the FOCS variable. +DESC_VALUE_REF_MULTIPART_VARIABLE2 +%1% %2% %3% + +# FOCS variable reference description text. +# %1% reference type of the FOCS variable. +# %2% base property referenced by the FOCS variable. +# %3% first dependent property referenced by the FOCS variable. +# %4% second dependent property referenced by the FOCS variable. +DESC_VALUE_REF_MULTIPART_VARIABLE3 +%1% %2% %3% %4% + +# FOCS variable reference description text. +# %1% reference type of the FOCS variable. +# %2% base property referenced by the FOCS variable. +# %3% first dependent property referenced by the FOCS variable. +# %4% second dependent property referenced by the FOCS variable. +# %5% third dependent property referenced by the FOCS variable. +DESC_VALUE_REF_MULTIPART_VARIABLE4 +%1% %2% %3% %4% %5% + +# FOCS variable reference description text. +# %1% reference type of the FOCS variable. +# %2% base property referenced by the FOCS variable. +# %3% first dependent property referenced by the FOCS variable. +# %4% second dependent property referenced by the FOCS variable. +# %5% third dependent property referenced by the FOCS variable. +# %6% fourth dependent property referenced by the FOCS variable. +DESC_VALUE_REF_MULTIPART_VARIABLE5 +%1% %2% %3% %4% %5% %6% + +# FOCS variable reference description text. +# %1% reference type of the FOCS variable. +# %2% base property referenced by the FOCS variable. +# %3% first dependent property referenced by the FOCS variable. +# %4% second dependent property referenced by the FOCS variable. +# %5% third dependent property referenced by the FOCS variable. +# %6% fourth dependent property referenced by the FOCS variable. +# %7% fifth dependent property referenced by the FOCS variable. +DESC_VALUE_REF_MULTIPART_VARIABLE6 +%1% %2% %3% %4% %5% %6% %7% + +# FOCS variable reference description text. +# %1% reference type of the FOCS variable. +# %2% base property referenced by the FOCS variable. +# %3% first dependent property referenced by the FOCS variable. +# %4% second dependent property referenced by the FOCS variable. +# %5% third dependent property referenced by the FOCS variable. +# %6% fourth dependent property referenced by the FOCS variable. +# %7% fifth dependent property referenced by the FOCS variable. +DESC_VALUE_REF_MULTIPART_VARIABLEMANY +%1% %2% %3% %4% %5% %6% %7% ... ## ## FOCS condition descriptions @@ -4711,11 +8936,11 @@ DESC_TARGET_NOT # %1% name of the species. DESC_HOMEWORLD -''' это родной мир''' +''' это родной мир вида %1%''' # %1% name of the species. DESC_HOMEWORLD_NOT -''' это не родной мир''' +''' это не родной мир вида %1%''' DESC_CAPITAL ''' это столичная планета империи''' @@ -4745,83 +8970,140 @@ DESC_TYPE_NOT # %1% name of a building type. DESC_BUILDING -''' это содержит %1% строения''' +''' это %1% строение''' # %1% name of a building type. DESC_BUILDING_NOT -''' это не содержит %1% строения''' +''' это не %1% строение''' # %1% name of the special the object should have. DESC_SPECIAL -''' должна быть артефакт: %1%''' +''' имеет артефакт: %1%''' # %1% name of the special the object should not have. DESC_SPECIAL_NOT -''' не должно быть артефакт: %1%''' +''' не имеет артефакт: %1%''' + +# %1% name of the special the object should have. +# %2% FIXME +# %3% FIXME +DESC_SPECIAL_CAPACITY_RANGE +''' имеет артефакт %1% со способностью между %2% и %3%''' + +# %1% name of the special the object should not have. +# %2% FIXME +# %3% FIXME +DESC_SPECIAL_CAPACITY_RANGE_NOT +''' не имеет артефакт %1% со способностью между %2% и %3%''' # %1% name of the special the object should have. # %2% textual description of the lower turn limit. # %3% textual description of the upper turn limit. DESC_SPECIAL_TURN_RANGE -''' у этого была особенность %1% в промежуток между %2% и %3% ходами''' +''' имел особенность %1% в промежутке между %2% и %3% ходами''' # %1% name of the special the object should not have. # %2% textual description of the lower turn limit. # %3% textual description of the upper turn limit. DESC_SPECIAL_TURN_RANGE_NOT -''' у этого не было особенности %1% в промежуток между %2% и %3% ходами''' +''' не имел особенность %1% в промежутке между %2% и %3% ходами''' # %1% textual description of the lower turn limit an object was created. # %2% textual description of the upper turn limit an object was created. DESC_CREATED_ON_TURN -''' это было создано в промежуток между %1% и %2% ходами''' +''' это было создано в промежутке между %1% и %2% ходами''' # %1% textual description of the lower turn limit an object was not created. # %2% textual description of the upper turn limit an object was not created. DESC_CREATED_ON_TURN_NOT -''' это не было создано в промежуток между %1% и %2% ходами''' +''' это не было создано в промежутке между %1% и %2% ходами''' # %1% textual description of an object contained inside another object. DESC_CONTAINS -''' это содержит объект%1%''' +''' это содержит объект %1%''' # %1% textual description of an object not contained inside another object. DESC_CONTAINS_NOT -''' это не содержит объект%1%''' +''' это не содержит объект %1%''' # %1% planet type that should match the condition. DESC_PLANET_TYPE -''' это %1% планета''' +''' это планета %1%''' # %1% planet type that should not match the condition. DESC_PLANET_TYPE_NOT -''' это не %1% планета''' +''' это не планета %1%''' # %1% planet size that should match the condition. DESC_PLANET_SIZE -''' это %1% планета''' +''' это планета %1%''' # %1% planet size that should not match the condition. DESC_PLANET_SIZE_NOT -''' это не %1% планета''' +''' это не планета %1%''' # %1% textual description of the environments for the species. # %2% name of the species. DESC_PLANET_ENVIRONMENT -''' это %1% планета''' +''' это планета %1% для вида %2%''' # %1% textual description of the environments for the species. # %2% name of the species. DESC_PLANET_ENVIRONMENT_NOT -''' это не %1% планета''' +''' это не планета %1% для вида %2%''' + +DESC_PLANET_ENVIRONMENT_CUR_SPECIES +имеет сейчас # %1% textual decription of the species. DESC_SPECIES -''' у этого есть артефакт %1%''' +''' содержит вид %1%''' # %1% textual decription of the species. DESC_SPECIES_NOT -''' у этого нет артефакта %1%''' +''' не содержит вида %1%''' + +# %1% name of the empire that enqueued items. +# %2% textual description of the lower limit for the number of enqueued items. +# %3% textual description of the upper limit for the number of enqueued items. +# %4% textual description for the items enqueued. +DESC_ENQUEUED +''' где у империи %1% есть от %2% до %3% элементов в очереди на производство''' + +# %1% name of the empire that enqueued items. +# %2% textual description of the lower limit for the number of enqueued items. +# %3% textual description of the upper limit for the number of enqueued items. +# %4% textual description fo the items enqueued. +DESC_ENQUEUED_NOT +''' где у империи %1% нет от %2% до %3% элементов в очереди на производство''' + +# %1% name of the empire that enqueued buildings. +# %2% textual description of the lower limit for the number of enqueued buildings. +# %3% textual description of the upper limit for the number of enqueued buildings. +# %4% textual description fo the buildings enqueued. +DESC_ENQUEUED_BUILDING +''' где у империи %1% есть от %2% до %3% строений %4% в производственной очереди''' + +# %1% name of the empire that enqueued buildings. +# %2% textual description of the lower limit for the number of enqueued buildings. +# %3% textual description of the upper limit for the number of enqueued buildings. +# %4% textual description fo the buildings enqueued. +DESC_ENQUEUED_BUILDING_NOT +''' где у империи %1% нет от %2% до %3% строений %4% в производственной очереди''' + +# %1% name of the empire that enqueued ships. +# %2% textual description of the lower limit for the number of enqueued ships. +# %3% textual description of the upper limit for the number of enqueued ships. +# %4% textual description fo the ships enqueued. +DESC_ENQUEUED_DESIGN +''' где у империи %1% есть от %2% до %3% кораблей %4% в производственной очереди''' + +# %1% name of the empire that enqueued ships. +# %2% textual description of the lower limit for the number of enqueued ships. +# %3% textual description of the upper limit for the number of enqueued ships. +# %4% textual description fo the ships enqueued. +DESC_ENQUEUED_DESIGN_NOT +''' где у империи %1% нет от %2% до %3% строений %4% в производственной очереди''' # %1% textual description of the planetary focus. DESC_FOCUS_TYPE @@ -4837,7 +9119,7 @@ DESC_STAR_TYPE # %1% textual description of the star type. DESC_STAR_TYPE_NOT -''' это не система %1% звезд''' +''' это не система со звездой типа: %1%''' # %1% textual description of the hull. DESC_DESIGN_HAS_HULL @@ -4919,89 +9201,109 @@ DESC_PRODUCED_BY_EMPIRE_NOT # %2% textual description of the lower limit for the meter value. # %3% textual description of the upper limit for the meter value. DESC_METER_VALUE_CURRENT -''' у этого есть текущее %1% между %2% и %3%''' +''' имеет %1% между %2% и %3%''' # %1% name of the meter. # %2% textual description of the lower limit for the meter value. # %3% textual description of the upper limit for the meter value. DESC_METER_VALUE_CURRENT_NOT -''' у этого нет текущего %1% между %2% и %3%''' +''' не имеет %1% между %2% и %3%''' + +# %1% name of the meter. +# %2% textual description of the lower limit for the meter value. +DESC_METER_VALUE_CURRENT_MIN +''' имеет %1% не менее %2%''' + +# %1% name of the meter. +# %2% textual description of the lower limit for the meter value. +DESC_METER_VALUE_CURRENT_MIN_NOT +''' имеет %1% более чем %2%''' + +# %1% name of the meter. +# %2% textual description of the upper limit for the meter value. +DESC_METER_VALUE_CURRENT_MAX +''' имеет %1% не более чем %2%''' + +# %1% name of the meter. +# %2% textual description of the upper limit for the meter value. +DESC_METER_VALUE_CURRENT_MAX_NOT +''' имеет %1% менее чем %2%''' # %1% name of the ship meter. # %2% textual description of the ship part. # %3% textual description of the lower limit for the meter value. # %4% textual description of the upper limit for the meter value. DESC_SHIP_PART_METER_VALUE_CURRENT -''' у этого есть %2% %1% между %3% и %4%''' +''' есть %2% %1% между %3% и %4%''' -# %1% name of the ship eter. +# %1% name of the ship meter. # %2% textual description of the ship part. # %3% textual description of the lower limit for the meter value. # %4% textual description of the upper limit for the meter value. DESC_SHIP_PART_METER_VALUE_CURRENT_NOT -''' у этого нет %2% %1% между %3% и %4%''' +''' нет %2% %1% между %3% и %4%''' # %1% name of the empire meter. # %2% textual description of the lower limit for the meter value. # %3% textual description of the upper limit for the meter value. # %4% textual description of the empire which owns the meter. DESC_EMPIRE_METER_VALUE_CURRENT -''' если у империи %4% есть %1% между %2% и %3%''' +''' если империя %4% имеет %1% между %2% и %3%''' # %1% name of the empire meter. # %2% textual description of the lower limit for the meter value. # %3% textual description of the upper limit for the meter value. # %4% textual description of the empire which owns the meter. DESC_EMPIRE_METER_VALUE_CURRENT_NOT -''' если у империи %4% нет %1% между %2% и %3%''' +''' если империя %4% не имеет %1% между %2% и %3%''' # %1% type of stockpile resource. # %2% textual description of the lower limit for the stockpile value. # %3% textual description of the upper limit for the stockpile value. DESC_EMPIRE_STOCKPILE_VALUE -''' это принадлежит империи с %1% запас между %2% и %3%''' +''' принадлежит империи с %1% запас между %2% и %3%''' # %1% type of stockpile resource. # %2% textual description of the lower limit for the stockpile value. # %3% textual description of the upper limit for the stockpile value. DESC_EMPIRE_STOCKPILE_VALUE_NOT -''' это не принадлежит империи с %1% запаса между %2% и %3%''' +''' не принадлежит империи с %1% запаса между %2% и %3%''' # %1% textual description of the empire. DESC_VISIBLE_TO_EMPIRE -''' это видимо для империи %1%''' +''' видимо для империи %1%''' # %1% textual description of the empire. DESC_VISIBLE_TO_EMPIRE_NOT -''' это скрыто от империи %1%''' +''' скрыто от империи %1%''' # %1% distance in universe units. # %2% textual description of the target object. DESC_WITHIN_DISTANCE -''' это в пределах %1% любых объектов%2%''' +''' в пределах %1% любых объектов %2%''' # %1% distance in universe units. # %2% textual description of the target object. DESC_WITHIN_DISTANCE_NOT -''' это не в пределах %1% любых объектов%2%''' +''' не в пределах %1% любых объектов %2%''' # %1% number of starlane jumps. # %2% textual description of the target object. DESC_WITHIN_STARLANE_JUMPS -''' это в пределах %1% прыжков от любого объекта%2%''' +''' в пределах %1% прыжков от любого объекта %2%''' # %1% number of starlane jumps. # %2% textual description of the target object. DESC_WITHIN_STARLANE_JUMPS_NOT -''' это не в пределах %1% прыжков от любого объекта%2%''' +''' не в пределах %1% прыжков от любого объекта %2%''' # %1% textual description of the far endpoint system the starlane connects to. DESC_CAN_ADD_STARLANE_CONNECTION -''' это можно соединить звездными путями с любой системой, имеющей объект%1%''' +''' можно соединить звездными путями с любой системой, имеющей объект %1%''' # %1% textual description of the far endpoint system the starlane connects to. DESC_CAN_ADD_STARLANE_CONNECTION_NOT -это нельзя соединить звездными путями с любой системой, имеющей объект%1% +''' нельзя соединить звездными путями с любой системой, имеющей объект %1%''' # %1% textual description of the empire. DESC_EXPLORED_BY_EMPIRE @@ -5015,7 +9317,19 @@ DESC_STATIONARY ''' стационарный объект''' DESC_STATIONARY_NOT -''' подвижный объект''' +''' движущийся объект''' + +DESC_AGGRESSIVE +''' агрессивен''' + +DESC_AGGRESSIVE_NOT +''' не агрессивен''' + +DESC_PASSIVE +''' пассивен''' + +DESC_PASSIVE_NOT +''' активен''' DESC_CAN_COLONIZE ''' виды, которые могут колонизировать''' @@ -5038,12 +9352,20 @@ DESC_SUPPLY_CONNECTED_FLEET_NOT # %1% textual description of the empire. # %2% textual description of the condition. DESC_SUPPLY_CONNECTED_RESOURCE -''' этот ресурс связан империей %1% с объектом%2%''' +''' этот ресурс связан империей %1% с объектом %2%''' # %1% textual description of the empire. # %2% textual description of the condition. DESC_SUPPLY_CONNECTED_RESOURCE_NOT -''' этот ресурс не связан империей %1% с объектом%2%''' +''' этот ресурс не связан империей %1% с объектом %2%''' + +# %1% textual description of the bombarding object. +DESC_ORDERED_BOMBARDED +''' бомбардирован объектом %1%''' + +# %1% textual description of the bombarding object. +DESC_ORDERED_BOMBARDED_NOT +''' не бомбардирован объектом %1%''' DESC_AND_BETWEEN_OPERANDS ''' и''' @@ -5086,56 +9408,64 @@ DESC_TURN_ANY_NOT # %1% FIXME # %2% FIXME DESC_NUMBER_OF -''' один из %1% случайно выбранных объектов%2%''' +''' один из %1% случайно выбранных объектов %2%''' # %1% FIXME # %2% FIXME DESC_NUMBER_OF_NOT -''' не один из %1% случайно выбранных объектов%2%''' +''' не один из %1% случайно выбранных объектов %2%''' # %1% FIXME # %2% FIXME # %3% FIXME DESC_MAX_NUMBER_OF -''' один из %1% объектов с наибольшим показателем%2% и%3%''' +''' один из %1% объектов с наибольшим показателем %2% и %3%''' # %1% FIXME # %2% FIXME # %3% FIXME DESC_MAX_NUMBER_OF_NOT -не один из %1% объектов с наибольшим показателем%2% и%3% +''' не один из %1% объектов с наибольшим показателем %2% и %3%''' # %1% FIXME # %2% FIXME # %3% FIXME DESC_MIN_NUMBER_OF -один из %1% объектов с наименьшим показателем%2% и%3% +''' один из %1% объектов с наименьшим показателем %2% и %3%''' # %1% FIXME # %2% FIXME # %3% FIXME DESC_MIN_NUMBER_OF_NOT -не один из %1% объектов с наименьшим показателем%2% и%3% +''' не один из %1% объектов с наименьшим показателем %2% и %3%''' # %1% FIXME # %2% FIXME # %3% FIXME DESC_MODE_NUMBER_OF -один из %1% объектов со средним показателем%2% и%3% +''' один из %1% объектов со средним показателем %2% и %3%''' # %1% FIXME # %2% FIXME # %3% FIXME DESC_MODE_NUMBER_OF_NOT -не один из %1% объектов со средним показателем%2% и%3% +''' не один из %1% объектов со средним показателем %2% и %3%''' + +# %1% FIXME +DESC_VALUE_TEST +''' когда %1%''' + +# %1% FIXME +DESC_VALUE_TEST_NOT +''' кроме %1%''' # %1% FIXME DESC_CONTAINED_BY -''' содержится в объекте%1%''' +''' содержится в объекте %1%''' # %1% FIXME DESC_CONTAINED_BY_NOT -''' не содержится в объекте%1%''' +''' не содержится в объекте %1%''' # %1% FIXME DESC_IN_SYSTEM @@ -5179,14 +9509,20 @@ DESC_OWNER_HAS_SHIP_DESIGN DESC_OWNER_HAS_SHIP_DESIGN_NOT ''' проект корабля заблокирован для империи''' +DESC_OWNER_HAS_SHIP_PART +''' деталь корабля разблокирована для империи''' + +DESC_OWNER_HAS_SHIP_PART_NOT +''' деталь корабля заблокирована для империи''' + # %1% FIXME # %2% FIXME # %3% FIXME DESC_NUMBER -''' если есть объекты%3% между %1% и %2%''' +''' если есть объекты %3% между %1% и %2%''' DESC_NUMBER_NOT -''' если нет объектов%3% между %1% и %2%''' +''' если нет объектов %3% между %1% и %2%''' # %1% an object tag string. DESC_HAS_TAG @@ -5196,7 +9532,6 @@ DESC_HAS_TAG DESC_HAS_TAG_NOT ''' это не %1%''' - ## ## Technology categories ## @@ -5204,21 +9539,146 @@ DESC_HAS_TAG_NOT LEARNING_CATEGORY Наука +LEARNING_CATEGORY_DESC +Технология относится к познанию + PRODUCTION_CATEGORY Производство +PRODUCTION_CATEGORY_DESC +Технология относится к производству + CONSTRUCTION_CATEGORY Строительство +CONSTRUCTION_CATEGORY_DESC +Технология относится к строительству + GROWTH_CATEGORY Демография +GROWTH_CATEGORY_DESC +Технология относится к росту + +SHIP_HULLS_CATEGORY +Корпуса кораблей + +SHIP_HULLS_CATEGORY_DESC +Технология относится к корпусам кораблей + +ASTEROID_HULL_TECHS +Астероидные корпуса + +ASTEROID_HULL_TECHS_DESC +Технология относится к астероидным корпусам кораблей + +ENERGY_HULL_TECHS +Энергетические корпуса + +ENERGY_HULL_TECHS_DESC +Технология относится к энергетическим корпусам кораблей + +ORGANIC_HULL_TECHS +Органические корпуса + +ORGANIC_HULL_TECHS_DESC +Технология относится к органическим корпусам кораблей + +ROBOTIC_HULL_TECHS +Роботизированные корпуса + +ROBOTIC_HULL_TECHS_DESC +Технология относится к роботизированным корпусам кораблей + +SHIP_PARTS_CATEGORY +Проектирование кораблей + +SHIP_PARTS_CATEGORY_DESC +Технология относится к корабельному проектированию + +SHIELD_PART_TECHS +Щиты + +SHIELD_PART_TECHS_DESC +Технология относится к экранирующим частям кораблей + +ENGINE_PART_TECHS +Двигатели + +ENGINE_PART_TECHS_DESC +Технология относится к двигателям кораблей + +FUEL_PART_TECHS +Топливо + +FUEL_PART_TECHS_DESC +Технология относится к топливным частям кораблей + +ARMOR_PART_TECHS +Броня + +ARMOR_PART_TECHS_DESC +Технология относится к броне кораблей + +STEALTH_PART_TECHS +Невидимость + +STEALTH_PART_TECHS_DESC +Технология относится к невидимости кораблей + +DETECTION_PART_TECHS +Обнаружение + +DETECTION_PART_TECHS_DESC +Технология относится к обнаружению кораблей + +DAMAGE_CONTROL_PART_TECHS +Управление повреждениями + +DAMAGE_CONTROL_PART_TECHS_DESC +Технология относится к нанесению ущерба + +SHIP_WEAPONS_CATEGORY +Корабельное оружие + +SHIP_WEAPONS_CATEGORY_DESC +Технология относится к корабельному вооружению + +SR_WEAPON_TECHS +Оружие ближнего действия + +SR_WEAPON_TECHS_DESC +Технология относится к корабельному вооружению на короткие дистанции + +FIGHTER_TECHS +Истребители + +FIGHTER_TECHS_DESC +Технология относится к использованию истребителей + +BOMBARD_WEAPON_TECHS +Бомбардировка + +BOMBARD_WEAPON_TECHS_DESC +Технология относится к оружию способному осуществлять бомбардировки + DEFENSE_CATEGORY Оборона +DEFENSE_CATEGORY_DESC +Технология относится к обороне планет и целых систем + SPY_CATEGORY Разведка +SPY_CATEGORY_DESC +Технология относится к разведывательной деятельности + +EXTINCT_SPECIES_TECHS +Вымершие виды + +EXTINCT_SPECIES_TECHS_DESC +'''Технология специализируется на вымерших видах ''' ## ## Technology theories @@ -5231,41 +9691,41 @@ LRN_PHYS_BRAIN Физика мозга LRN_PHYS_BRAIN_DESC -Вся структура и функции мозга изучены. Электрохимическая и квантовая природа мысли и памяти раскрыта. Теперь становится возможным изменять и расширять функциональность мозга, и новые идеи порождаются на границах мышления, их формулировки и их замена. +Определены структуры и их функции в головном мозге. Выделена электрохимическая и квантовая природа мысли и памяти. С пониманием, изменением и увеличением функций мозга открывается путь к новым идеям, порождающимся на границах мысли, их выражением и их совершенствованием. LRN_ALGO_ELEGANCE Изящные алгоритмы LRN_ALGO_ELEGANCE_DESC -'''Increases Target [[metertype METER_RESEARCH]] on all Research focused planets by 0.1 per Population. +'''Увеличивает [[metertype METER_RESEARCH]] на всех планетах, ориентированных на исследование, на 0.1 за единицу населения. -Все более и более сложными становятся проблемы анализа данных, а традиционные методы алгоритмизации становятся менее полезными из-за ограничений непреодолимой сложности. На этом этапе важными становятся другие виды форм алгоритмов и функций, эстетически и метафорически, элегантность решения должна быть оптимизирована.''' +Все более и более сложными становятся проблемы анализа данных, и традиционные методы алгоритмизации уходят на второй план, из-за ограничений и непреодолимой сложности. На этом этапе важными становятся другие виды алгоритмов и функций: элегантность решения должна быть оптимальна, эстетически и метафорически.''' LRN_TRANSLING_THT -Сверхлингвистическое Мышление +Транслингвистика LRN_TRANSLING_THT_DESC -'''Does nothing. Later this can enable peaceful acquistion of native planets. +'''Не даёт ничего сейчас, но позже может обеспечить более мирное освоение своих, родных миров. -Слабые умы борются с пределами языка, который они изучили, или пытаются принять их. Продвинутые умы достигли их, и чувствуют себя скованными концепциями, которыми создают пути для выражения своих мыслей. По-настоящему великие умы ломают границы языка, формируют и анализируют мысли, которые граничат с трансцендентностью. Однако исключительно великие умы оставили изолированное и бесполезное, что бы выражать мысли без языка. И как же они смогут поделиться своим пониманием?''' +Слабые умом или борются, или принимают границы своего родного языка. Продвинутые умы достигают пределов способов выражения и чувствуют себя скованными существующим понятийным аппаратом. Действительно великие умы преодолевают границы языка, создавая и анализируя мысли, которые уже граничат с трансцендентальными. Но воистину неподражаемые умы остаются изолированными и бесполезными, поскольку язык больше не позволяет им выразить свои мысли. Следовательно, встаёт вопрос - как им поделиться своими идеями?''' LRN_PSIONICS Псионика LRN_PSIONICS_DESC -'''[[metertype METER_RESEARCH]] Cost is greatly reduced when the empire includes a telepathic species. +'''Стоимость [[metertype METER_RESEARCH]] значительно снижается, когда империя включает в себя телепатический вид -При помощи глубокого самоанализа или искусственного улучшения, мозг может развить способности прямого взаимодействия с окружающей вселенной минуя ограничения физического тела. Такие способности, как телепатия, эмпатия, ясновидение, предвидение, психокинез и психоэнергетика заменяют или вытесняют мирские биологические и технологические альтернативы. Применение этих способностей, включающих управление разумом, изменение и подчинение личности, имеет глубокий смысл во взаимоотношениях между талантливыми и бесталанными созданиями.''' +При помощи глубокого самоанализа или искусственного улучшения, мозг может развить способности прямого взаимодействия с окружающей вселенной минуя ограничения физического тела. Такие способности, как телепатия, эмпатия, ясновидение, предвидение, психокинез и психоэнергетика заменяют или вытесняют мирские биологические и технологические альтернативы. Применение этих способностей, включающих управление разумом, изменение и подчинение личности, имеет глубокий смысл во взаимоотношениях между талантливыми и бесталанными созданиями''' LRN_ARTIF_MINDS -Искусственные Разумы +Искусственные разумы LRN_ARTIF_MINDS_DESC -'''Увеличивает целевую [[metertype METER_RESEARCH]] на всех планетах на 0,5*Население. +'''Увеличивает целевые [[metertype METER_RESEARCH]] на всех планетах на 0.5 за единицу населения -Автоматически добавляя все данные, открытые наукой, в единый автоматический вычислительную сеть можно анализировать их и исследовать в полностью автоматизированных научных центрах. Любая планета может содержать такой центр бех оглядки на персонал, давая таким образом бонус к научным исследованиям во всей галактике. +Пока традиционные компьютеры обладают почти безмерным интеллектом и вычислительными способностями, у них отсутствуют очень важные качества самоанализа, сознания или ощущения. С разработкой истинно-искусственных разумов, эти качества могут быть синтезированы, модифицированы и изменены. Эти исследования открывают новые пути разработок познавательных наук и неизвестных мета-парадигм -Пока традиционные компьютеры обладают почти безмерным интеллектом и вычислительными способностями, у них отсутствуют очень важные качества самоанализа, сознания или ощущения. С разработкой истинно-искусственных разумов, эти качества могут быть синтезированы, модифицированы и изменены. Эти исследования открывают новые пути разработок познавательных наук и неизвестных мета-парадигм.''' +Автоматически добавляя все данные, открытые наукой, в единый автоматический вычислительную сеть можно анализировать их и исследовать в полностью автоматизированных научных центрах. Любая планета может содержать такой центр без оглядки на персонал, давая таким образом бонус к научным исследованиям во всей галактике''' LRN_XENOARCH Ксеноархеология @@ -5274,397 +9734,599 @@ XENOARCH_SHORT_DESC Извлекает ценные данные из руин LRN_XENOARCH_DESC -'''Позволяет делать открытия из [[special ANCIENT_RUINS_SPECIAL]]. +'''Позволяет совершать открытия из [[special ANCIENT_RUINS_SPECIAL]] -Ныне существующие империи, расы и отдельные цивилизации - не первые или единственные живущие существа в этой галактике. Останки, руины или слухи о предках могут быть найдены на пустынных планетах и безжизненных астероидах, а так же дрейфовать в открытом космосе. Поиск и дешифрация этих тайн содержит большой потенциал к раскрытию секретов, новым урокам, или даже показать признаки древних знаний. +Ныне существующие империи, расы и отдельные цивилизации - не первые или единственные живущие существа в этой галактике. Останки, руины или слухи о предках могут быть найдены на пустынных планетах и безжизненных астероидах, а так же дрейфовать в открытом космосе. Поиск и дешифрация этих тайн содержит большой потенциал к раскрытию секретов, новым урокам или даже показать признаки древних знаний -Чрезвычайно сложно и опасно разгадывать загадки, оставленные давно забытыми цивилизациями в виде неизвестных технологий. Исследование технологий или случайных их проявлений может быть как успешным, так и фатальным для самой технологии и/или исследователя. Помещая технологию в силовое поле и манипулируя ей через это поле помогает безопасно все таинства древних рас.''' +Чрезвычайно сложно и опасно разгадывать загадки, оставленные давно забытыми цивилизациями в виде неизвестных технологий. Исследование технологий или случайных их проявлений может быть как успешным, так и фатальным для самой технологии и/или исследователя. Помещая технологию в силовое поле и манипулируя ею через это поле можно безопасно узнать или разблокировать все таинства древних рас''' LRN_GRAVITONICS Гравитоника LRN_GRAVITONICS_DESC -Аналогично "цветному заряду" в квантовой хромодинамике, есть несколько типов гравитонов. С общей силой притяжения гравитон, в общей лексике "анти-гравитон", "правый-гравитон" и "левый-гравитон" при объединение могут произвольно изгибать и скручивать пространственно-временную поверхность. Эта манипуляция, воспринимаемая как управление силой гравитации, открывает доступ к конструкциям, производству и исследованиям, в прошлом невозможных даже теоретически. +Аналогично "цветному заряду" в квантовой хромодинамике, есть несколько типов гравитонов. С общей силой притяжения гравитон, в общей лексике "анти-гравитон", "правый-гравитон" и "левый-гравитон" при объединение могут произвольно изгибать и скручивать пространственно-временную поверхность. Эта манипуляция, воспринимаемая как управление силой гравитации, открывает доступ к конструкциям, производству и исследованиям в прошлом невозможным даже теоретически LRN_EVERYTHING -Теория Всего +Теория всего LRN_EVERYTHING_DESC -Раннее, наивные теории описывали подмножества четырех фундаментальных сил природы: электромагнетизм, сильные и слабые ядерные силы, и гравитацию. Полный каркас, описывающий все силы как специальные случаи единственного взаимодействия, представляет святую чашу Грааля науки. Эта теория описывает самые ранние моменты вселенной, наиболее искаженные глубины сингулярности, плотные завихрения скрытых измерений и конечная судьба космоса. Однако все еще могут "существовать" слои существования, которые могут быть исследованы и описаны наукой... +Ранее наивные теории описывали подмножества четырех фундаментальных сил природы: электромагнетизм, сильные и слабые ядерные силы и гравитацию. Полный каркас, описывающий все силы как специальные случаи единственного взаимодействия, представляет святую чашу Грааля науки. Эта теория описывает самые ранние моменты вселенной, наиболее искаженные глубины сингулярности, плотные завихрения скрытых измерений и конечная судьба космоса. Однако все еще могут "существовать" слои существования, которые могут быть исследованы и описаны наукой... LRN_FORCE_FIELD -Гармоники Силовых Полей +Гармоники силовых полей LRN_FORCE_FIELD_DESC -'''Увеличивает макс [[metertype METER_SHIELD]] на всех планетах на 10. +'''Увеличивает максимальный [[metertype METER_SHIELD]] на всех планетах на 10 -Подобно анализу Фурье звука, сильные и слабые электромагнитные силы могут быть описаны как гармоники квантовой суперпозиции вертикальных волн силопереносящих частиц. Будучи избранно усиленными, сила этих гармоник может быть произвольно направлена на защиту, атаку, накопление или хранение.''' +Подобно анализу звука Фурье, сильные и слабые электромагнитные силы могут быть описаны как гармоники квантовой суперпозиции вертикальных волн силопереносящих частиц. Будучи избранно усиленными, сила этих гармоник может быть произвольно направлена на защиту, атаку, накопление или хранение''' LRN_MIND_VOID -Разум Пустоты +Разум вакуума LRN_MIND_VOID_DESC -Мы не одиноки в космосе... но почему поиск настолько ограничен? У многих культур есть мифы, легенды или страстная вера в высший разум. Любящий ли Бог или недоброжелательный, или просто наблюдатель, совершенно ясно, что что-то, возможно сама вселенная на особом уровне, живет, думает, наблюдает. +Мы не одиноки в космосе... но почему поиск настолько ограничен? У многих культур есть мифы, легенды или страстная вера в высший разум. Любящий ли Бог или недоброжелательный или просто наблюдатель, совершенно ясно, что что-то, возможно, сама вселенная на особом уровне, живет, думает, наблюдает... LRN_TIME_MECH -Механика Времени +Механика времени LRN_TIME_MECH_DESC -Что есть "сейчас", "будущее" и "прошлое"? Двойственный парадокс специальной ТО, или теоретические "червоточины" в жизни, предлагают ранние формы "путешествия во времени". Направленное манипулирование временным пространством и течением времени позволяет "запрягать" и усиливать эти явления. Временные вторжения не могут ощутимо изменить прошлое вселенной, которую мы ощущаем. Однако, они могут сжимать года или растягивать моменты на произвольную длительность, ограниченные в использовании только материальными поставками или нашим терпением... +Что есть "сейчас", "будущее" и "прошлое"? Двойственный парадокс специальной ТО или теоретические "червоточины" в жизни, предлагают ранние формы "путешествия во времени". Направленное манипулирование временным пространством и течением времени позволяет "запрягать" и усиливать эти явления. Временные вторжения не могут ощутимо изменить прошлое вселенной, которую мы ощущаем. Однако, они могут сжимать года или растягивать моменты на произвольную длительность, ограниченные в использовании только материальными поставками или нашим терпением... LRN_NDIM_SUBSPACE -N-мерное Подпространство +N-мерное подпространство LRN_NDIM_SUBSPACE_DESC -Раннее теоретики суперструн говорили о 10-, 11- или 26-мерной вселенной, "скрученных" в 4 макроизмерения настолько, что их не заметно. Теперь эти и другие измерения могут быть "раскручены" и открыты, и сама поверхность пространства вытягивается и прокалывается непостоянной материей на- или из уровней существования, ранее скрытых за поверхностью нашего ограниченного восприятия, или создавая слои и пузыри там, где ничто не существовало прежде. +Ранние теоретики суперструн говорили о 10-, 11- или 26-мерной вселенной, "скрученных" в 4 макроизмерения настолько, что их не заметно. Теперь эти и другие измерения могут быть "раскручены" и открыты, и сама поверхность пространства вытягивается и прокалывается непостоянной материей из уровней существования, ранее скрытых за поверхностью нашего ограниченного восприятия или создавая слои и пузыри там, где ничто не существовало прежде LRN_UNIF_CONC -Объединенное Сознание +Объединенное сознание LRN_UNIF_CONC_DESC -'''Перемещенные в киберпространство умы тысячами могут действовать как один, унифицированно и направленно на достижение всеобщего блага. +'''Перемещенные в киберпространство умы тысячами могут действовать как один, их действия идеально объединены и направлены на благо всего -Телепатическая коммуникация между индивидами или разумными машинными интерфейсами, позволяет только самые основные, тривиальные и поверхностные обмены мыслью и идеями. По настоящему объединенные разумы функционируют как единое сознание, с суммированными способностями и знаниями его составных частей, и действующее как новый уникальный объект. У такого разума есть большой потенциал, но также и существенный риск, в котором объединенный разум, возможно, не позволит собственное разрушение, необходимое для восстановления исходных частей, из которых он был создан. В другом случае, один из разумов может доминировать над другим, управляя или разрушая его, вместо того, чтобы формировать равное, гармоничное объединение.''' +Телепатическая коммуникация между индивидами или разумными машинными интерфейсами позволяет только самые основные, тривиальные и поверхностные обмены мыслью и идеями. По-настоящему объединенные разумы функционируют как единое сознание с суммированными способностями и знаниями его составных частей и действующие как новый уникальный объект. У такого разума есть большой потенциал, но также и существенный риск, в котором объединенный разум, возможно, не позволит собственное разрушение, необходимое для восстановления исходных частей из которых он был создан. В другом случае, один из разумов может доминировать над другим, управляя или разрушая его, вместо того, чтобы формировать равное, гармоничное объединение''' LRN_QUANT_NET Квантовые сети LRN_QUANT_NET_DESC -'''Увеличивает целевой показатель [[metertype METER_RESEARCH]] на всех научно-ориентированных планетах на 0,5*Население. +'''Увеличивает целевой показатель [[metertype METER_RESEARCH]] на всех научно-ориентированных планетах на 0.5 за единицу населения + +Большая, в масштабах всей империи вычислительная сеть способна мгновенно передавать информацию, что на намного быстрее, чем это происходит через межзвездные пути, что способствует ускоренному развитию науки по всей империи + +Каждая частица неразрывно связана на квантовом уровне с каждой частицей, с которой она ранее взаимодействовала. Устаревшие принципы механики не работают на этом уровне, где частицы могут взаимодействовать несмотря на расстояние между ними. При полном понимании этого феномена возможно установление связи между определенными, удаленными друг от друга точками практически мгновенно на невиданных доселе скоростях для передачи информации''' -Большая, в масштабах всей империи вычислительная сеть способна мгновенно передавать информацию, что на намного быстрее, чем это происходит через межзвездные пути, что способствует ускоренному развитию науки по всей империи. +LRN_COLLAPSER +Разрушитель черных дыр -Каждая частица, соединённа на квантовом уровне с любой другой частицей, с которой она ранее взаимодействовала. Устаревшие принципы механики не работают на этом уровне, где частицы могут взаимодействовать несмотря на расстояние между ними. При полном понимании этого феномена возможно установление связи между определенными, удаленными друг от друга точками практически мгновенно на невиданных доселе скоростях для передачи информации.''' +LRN_COLLAPSER_DESC +Кульминация пространственных искажений и манипуляции звездного масштаба позволяет инвертировать сингулярность с [[STAR_BLACK]], создавая разрыв пространства-времени в межзвездном масштабе. Однако это разрушение нестабильно и быстро разрушается само в себя, притягивая все флоты, планеты или целые системы в радиусе действия и полностью разрушая все, что попадает в центр LRN_TRANSCEND -Сингулярность Трансцендентности +Сингулярность трансцендентности LRN_TRANSCEND_DESC -Что есть пристанище бога? Он заинтересован в моральности и великодушии или же во зле и тирании? Преподносит ли бог смертным заботу и любовь, ненависть и страдание или невозмутимое равнодушие? Осведомлен ли бог о бесконечно малых заботах физической вселенной, или же истинная божественность формы за пределами описания, а невозможность понимания смертными распространяется по всем направлениям? +Что есть пристанище бога? Он заинтересован в моральности и великодушии или же во зле и тирании? Преподносит ли бог смертным заботу и любовь, ненависть и страдание или невозмутимое равнодушие? Осведомлен ли бог о бесконечно малых заботах физической вселенной или же истинная божественность формы за пределами описания, а невозможность понимания смертными распространяется по всем направлениям? GRO_PLANET_ECOL -Планетарная Экология +Планетарная экология GRO_PLANET_ECOL_DESC -Агрономия и научная медицина могут резко повысить живучесть, вызывающую гиперэкспоненциальный рост населения с течением времени. В конечном счете приближаются новые пределы вместимости планеты, а так же ее поддерживающей взаимосвязанной сети жизни. Понимание природной экологии и ее взаимодействие с нагрузкой позволяет системе быть устойчивой, а ее плоды будут пожинаться будущими поколениями. +'''Увеличивает максимальную популяцию на [[PE_GOOD]] и [[PE_ADEQUATE]] планетах на +1. Этот бонус не складывается с [[tech GRO_SYMBIOTIC_BIO]] + +Агрономия и научная медицина могут резко повысить живучесть, вызывающую гиперэкспоненциальный рост населения с течением времени. В конечном счете приближаются новые пределы вместимости планеты, а так же ее поддерживающей взаимосвязанной сети жизни. Понимание природной экологии и ее взаимодействие с нагрузкой позволяет системе быть устойчивой, а ее плоды будут пожинаться будущими поколениями''' GRO_GENETIC_ENG -Генная Инженерия +Генная инженерия GRO_GENETIC_ENG_DESC -Палитра генетики настолько же разнообразна, как и формы жизни, из которых она заимствуется. Гены могут быть удалены, скопированы и заменены в геномы для создания трансгенных организмов с набором особенностей, невозможных в природе. Также, гены могут быть изменены перед заменой, записывая измененные базовые коды напрямую, что позволяет создавать полностью новые гены. Несмотря на это, методы ограничены небольшими модификациями существующих генов, доступных для анализа. +Палитра генетики настолько же разнообразна, как и формы жизни из которых она заимствуется. Гены могут быть удалены, скопированы и заменены в геноме для создания трансгенных организмов с набором особенностей невозможных в природе. Также, гены могут быть изменены перед заменой, записывая измененные базовые коды напрямую, что позволяет создавать полностью новые гены. Несмотря на это, методы ограничены небольшими модификациями существующих генов, доступных для анализа + +CON_REMOTE_COL +Отдаленная колонизация + +CON_REMOTE_COL_DESC +Создание новых колоний за пределами диапазона [[metertype METER_SUPPLY]] с уже населенными мирами - сложная задача, которая требует колониальное судно способное нести все оборудование, необходимое для создания самоподдерживающейся колонии, а также первоначальный контингент колонистов GRO_SYMBIOTIC_BIO -Симбиозная Биология +Симбиозная биология GRO_SYMBIOTIC_BIO_DESC -Увеличивает максимальное население хороших, средних и приемлемых планет на Крохотная: +1, Маленькая: +2, Средняя: +3, Большая: +4, Огромная: +5 планет соответственно. +Увеличивает максимальное население [[PE_GOOD]], [[PE_ADEQUATE]] и [[PE_POOR]] планет в зависимости от их размера: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5) GRO_GENETIC_MED -Генная Медицина +Генная медицина GRO_GENETIC_MED_DESC -Традиционная Генная Инженерия изменяет генетические коды и потом применяет, либо выращивает модифицированные организмы. Похожая техника может быть применена к уже взрослым личностям. Целью такого лечения может быть косметическая или улучшающая модификация, корректировка, профилактический или срочный медицинский уход. Снижение задержки между изменением и результатом с поколений до месяцев, дней или часов дает соответствующее расширение границ и силы возможных изменений. +Традиционная генетическая инженерия изменяет генетические коды, а затем выращивает модифицированный организм из эмбрионального состояния. Похожая техника может быть применена к уже взрослым личностям. Целью такого лечения может быть косметическая или улучшающая модификация, корректировка, профилактический или срочный медицинский уход. Снижение задержки между изменением и результатом от нескольких поколений до месяцев, дней или часов дает соответствующее расширение границ и силы возможных изменений GRO_LIFECYCLE_MAN -Манипуляция с Жизненным Циклом +Манипуляция с жизненным циклом GRO_LIFECYCLE_MAN_DESC -'''С помощью химических манипуляций и крионики, жизненные процессы могут быть приостановлены без гибели организма. Организм в этом состоянии сохраняется на неопределенный срок, не потребляют ресурсы и требуют очень мало места для хранения. С помощью этого метода, емкость колонии корабля может быть значительно увеличена. +'''В дополнение к детали корабля эта технология также увеличивает численность новых колоний, созданных с помощью колониального модуля до 3 + +С помощью химических манипуляций и крионики, жизненные процессы могут быть приостановлены без гибели организма. Организм в этом состоянии сохраняется на неопределенный срок, не потребляют ресурсы и требуют очень мало места для хранения. С помощью этого метода, емкость колонии корабля может быть значительно увеличена -Большинство сложных организмов прогрессируют через физиологические этапы в течение всей жизни, которые либо четко разделены трансформацией, либо "смазаны" непрерывным медленным старением. Во множестве случаев, один или более из этих стадий, как минимум в определенное время, более полезны и предпочтительны, чем другие. С помощью точного управления гормональными или умственными механизмами, становится возможным радикально ускорять или замедлять каждую стадию, позволяя создавать полностью развитых и функционирующих особей в отрезке природного интервала времени, что дает особи возможность функционировать бесконечно, становясь эффективно бессмертными.''' +Большинство сложных организмов прогрессируют через физиологические этапы в течение всей жизни, которые либо четко разделены трансформацией, либо "смазаны" непрерывным медленным старением. Во множестве случаев, одна или более из этих стадий, как минимум в определенное время, более полезны и предпочтительны чем другие. С помощью точного управления гормональными или умственными механизмами, становится возможным радикально ускорять или замедлять каждую стадию, позволяя создавать полностью развитых и функционирующих особей в отрезке природного интервала времени, что дает особи возможность функционировать бесконечно, становясь фактически бессмертными''' GRO_ADV_ECOMAN Продвинутое изменение экологии GRO_ADV_ECOMAN_DESC -Даже искусственная окружающая среда с погодными условиями и экологическими цыклами может выйти из под контроля и нанести непоправимый ущерб. Вводя стабилизирующие факторы шанс таких случайности в искусственных природных средах может быть снижен, что дает возможность делать более масштабные эксперименты. +Даже искусственная окружающая среда с погодными условиями и экологическими циклами, может выйти из под контроля и нанести непоправимый ущерб. Вводя стабилизирующие факторы шанс таких случайности в искусственных природных средах может быть снижен, что дает возможность делать более масштабные эксперименты GRO_XENO_GENETICS -Ксено-Генетика +Ксено-генетика GRO_XENO_GENETICS_DESC -'''Increases the max population of Adequate & Poor planets by planet size; Крохотная: +2, Маленькая: +4, Средняя: +6, Большая: +8, Огромная: +10, and Hostile Poor planets by planet size; Крохотная: +1, Маленькая: +2, Средняя: +3, Большая: +4, Огромная: +5. +'''Увеличивает максимальное население на планетах, в зависимости от пригодности планеты и ее размеров: +• [[PE_ADEQUATE]] or [[PE_POOR]] planet: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10) +• [[PE_HOSTILE]] planet: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5) -The study of other life forms often exemplifies the weaknesses inherent in one's own species. By studying other life forms and using that knowledge to improve upon one's own genetic code, it is possible to develop many useful adaptations that would be otherwise impossible. +Изучение других форм жизни часто иллюстрирует недостатки, присущие собственному виду. Изучая другие формы жизни и используя эти знания для улучшения своего собственного генетического кода, можно разработать множество полезных приспособлений, которые в противном случае были бы невозможны -Ранняя генетика ограничивалась лишь доступными образцами организмов природного происхождения, на которых строились их модификации. Так же эти модификации ограничены пределами их физических механизмов хранения, замены и формулировки генетического кода развитых особей. С помощью изучения эквивалентных систем в полностью отличных экосистемах и организмах, огромное понимание или открытия могут быть достигнуты, подталкивающие к новым открытиям в более привычной генетической системе.''' +Ранняя генетика ограничивалась лишь доступными образцами организмов природного происхождения на которых строились их модификации. Так же эти модификации ограничены пределами их физических механизмов хранения, замены и формулировки генетического кода развитых особей. С помощью изучения эквивалентных систем в полностью отличных экосистемах и организмах, огромное понимание или откровения могут быть достигнуты, подталкивающие к новым открытиям в более привычной генетической системе''' GRO_NANOTECH_MED -Нанотехнологичная Медицина +Нанотехнологичная медицина GRO_NANOTECH_MED_DESC -'''Pathogens, particularly prokaryotes, are vulnerable to disruption of their genetic material from within - that is, if it is possible to permeate the cell membrane or the protein coating, in the case of viral infections. By designing nanorobots capable of identifying and penetrating such pathogens, it would be possible to destroy them by completely deconstructing their genetic material. +'''Патогены, особенно прокариоты, подвержены разрушению их генетического материала изнутри - то есть, если возможно проникнуть через клеточную мембрану или белковое покрытие в случае вирусных инфекций. Конструируя нанороботов, способных идентифицировать и проникать в такие патогены, можно было бы их уничтожить, полностью разрушив генетический материал -Пока генетическая замена может исправить или отменить эффект травматических или нежелательных мутаций, скорректировать биохимический дисбаланс, излечивать сильные физические травмы и не генетические врожденные дефекты, требующие физио-корректирующих мер. Нанотехнологии могут быть использовать для выполнения этих задач, но с двумя важными преимуществами: коррекция может быть выполнена без инвазивной хирургии и связанной с ней вторичными повреждениями и рисками, а так же значительно снизить время лечения за счет инструментов, которые всегда на месте и готовы к работе.''' +Пока генетическая замена может исправить или отменить эффект травматических или нежелательных мутаций, скорректировать биохимический дисбаланс, излечивать сильные физические травмы и не генетические врожденные дефекты, требующие физио-корректирующих мер. Нанотехнологии могут быть использованы для выполнения этих задач, но с двумя важными преимуществами: коррекция может быть выполнена без инвазивной хирургии и связанной с ней вторичными повреждениями и рисками, а так же значительно снизить время лечения за счет инструментов, которые всегда на месте и готовы к работе''' GRO_XENO_HYBRIDS -Ксено-Скрещивание +Ксено-скрещивание GRO_XENO_HYBRIDS_DESC -'''Increases the max population of Poor planets by planet size; Крохотная: +1, Маленькая: +2, Средняя: +3, Большая: +4, Огромная: +5, and Hostile Poor planets by planet size; Крохотная: +2, Маленькая: +4, Средняя: +6, Большая: +8, Огромная: +10 +'''Увеличивает максимальное население на планетах, в зависимости от пригодности планеты и ее размеров: +• [[PE_POOR]] planet: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5) +• [[PE_HOSTILE]] planet: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10) -Often, the native fauna of a planet are far better suited to a planet's environment than any colonists who would try to inhabit it. These adverse effects can include anything from weather patterns to the planet's size. Through xenological hybridization, a genetically adapted form of the colonizing species can be developed with the advantageous traits of the native fauna, thus allowing more of the planet to be inhabited. +Часто местная фауна планеты гораздо лучше подходит для окружающей среды планеты, чем любые колонисты, которые попытаются ее обжить. Эти побочные эффекты могут включать в себя все, что угодно от погодных условий до размеров планеты. Благодаря ксенологической гибридизации генетически адаптированная форма колонизирующих видов может развиваться с преимущественными чертами местной фауны, что позволит заселить больше планет -Несмотря на то, насколько хорошо понимается или как хорошо улучшается, биологическая система ограничена собственной природой. Структуры и основы физиологии могут быть изменены настолько, насколько пролегают границы биологии отдельно взятой планеты. С пониманием других биологий, тем не менее, несравнимые системы могут сливаться, формируя полностью новую гибридную биологию с потенциальными возможностями и способностями, превосходящими каждую из составных частей.''' +Независимо от того, насколько хорошо это понимается или как хорошо совершенствуется, биологическая система ограничена ее присущей природой. Структуры и основы физиологии могут быть изменены настолько, насколько позволяют границы биологии отдельно взятой планеты. С пониманием других биологий, тем не менее, несравнимые системы могут сливаться, формируя полностью новую гибридную биологию с потенциальными возможностями и способностями, превосходящими каждую из составных частей''' GRO_NANO_CYBERNET -Нанотехнологичная Кибернетика +Нанотехнологичная кибернетика GRO_NANO_CYBERNET_DESC -После применения нанотехнологий к прямым коррекциям дефектов или травм, следующий шаг - расширить или заменить природные биохимические системы более лучшими искусственными заменителями. Вместо того, что бы просто наращивать потерянные конечности или восстанавливать поврежденные органы, заменители могут быстро вырастить новые, удовлетворяя потребности в них в любое время. Устаревшие биологические системы, от мышечных тканей и сухожилий до ферментов могут быть так же заменены, предоставляя значительно возросшую силу, выносливость и гибкость. +После применения нанотехнологий к прямым коррекциям дефектов или травм, следующий шаг - расширить или заменить природные биохимические системы более лучшими искусственными заменителями. Вместо того, чтобы просто наращивать потерянные конечности или восстанавливать поврежденные органы, заменители могут быстро вырастить новые, удовлетворяя потребности в них в любое время. Устаревшие биологические системы от мышечных тканей и сухожилий до ферментов могут быть так же заменены, предоставляя значительно возросшую силу, выносливость и гибкость GRO_TRANSORG_SENT Трансорганическая чувствительность +GRO_TRANSORG_SENT_DESC +Ощущение, как мы его знаем, всегда наблюдается как неразрывно связанное с биологической жизнью. Ухватив истинную природу и происхождение самого чувства, можно было бы создать сознательные сущности, которые нельзя было бы отнести к жизни, той которую мы знаем + GRO_ENERGY_META -Метаболизм Чистой Энергии +Метаболизм чистой энергии GRO_ENERGY_META_DESC -'''Increases max Fuel on all ships by 2, max [[metertype METER_DEFENSE]] on all planets by 5, max [[metertype METER_SHIELD]] on all planets by 5, target [[metertype METER_INDUSTRY]] on all planets with the Industry focus by 0.2 per Population, and target [[metertype METER_RESEARCH]] on all planets with the Research focus by 0.5 per Population. +'''Увеличивает максимальное [[metertype METER_FUEL]] всех кораблей на 2, увеличивает максимальную [[metertype METER_DEFENSE]] на всех планетах на 5, максимальный [[metertype METER_SHIELD]] на всех планетах на 5, целевую [[metertype METER_INDUSTRY]] на всех планетах с фокусом производство на 0.2 за единицу населения, а целевые [[metertype METER_RESEARCH]] на всех планетах с фокусом исследования на 0.5 за единицу населения + +Преобразование целого населения в существ чистой энергии было бы невыполнимой задачей, да и только некоторые задачи лучше подходят для людей, способных проявлять себя без телесно. Например, экипажу корабля больше не нужно было бы тратить место на отсеки экипажа или хранилища пайков. Существа с энергетическими способностями могли бы улучшать планетарные щиты и защиту, а производство ресурсов, как правило было бы улучшено + +Конечная стадия физического совершенства - это уход за границы физических пределов. Без тела, особи и сообщества могут перемещаться, расти и существовать свободно, не отягощенные ненужными физическими ограничениями и неэффективными метаболическими процессами, которые тратят энергию на поддержку гомеостаза''' + +PRO_TRANS_DESIGN +Трансцендентный дизайн + +PRO_TRANS_DESIGN_DESC +'''Преодолевая видовые ограничения, самые креативные и талантливые представители своих рас начинают работать вместе для создания универсальных товаров потребления. +Строительство [[buildingtype BLD_INTERSPECIES_ACADEMY]] на различных планетах обеспечивает распространение идей и практик по всей империи. + +Результаты труда также используются имперской службой резервов для подготовки товаров, которые в конечном итоге могут быть использованы любым гражданином империи.''' + +IMPERIAL_STOCKPILE_SHORT_DESC +Позволяет империи создавать промышленные резервы + +PRO_PREDICTIVE_STOCKPILING +Предсказание резервов + +PRO_PREDICTIVE_STOCKPILING_DESC +'''Знание того, где впоследствии появится спрос на определённые виды товаров, позволяет империи заранее подготавливать для них необходимые ресурсы. +Имперские резервы имеют специальные флоты, занимающиеся распределением сырья по тем местам, где впоследствии могут быть начаты важные проекты. +Эта задача сложна, ведь службе нужно спрогнозировать, где и когда будет повышен спрос на ресурсы, и какие материалы потребуются, кто будет конечными пользователями этих продуктов и т.д. + +Благодаря данному исследованию, все неиспользованные очки промышленности будут автоматически переноситься в [[metertype METER_STOCKPILE]], а заказы производственной очереди будут брать из них ресурсы, только если они не были предварительно заблокированы от такой возможности. + +Это базовая технология из области резервов, которая повышает [[metertype METER_STOCKPILE]] на планетах, ориентированных на рост населения или с фокусами на резервы, на 1 единицу.''' + +PRO_GENERIC_SUPPLIES +Пакетные поставки + +PRO_GENERIC_SUPPLIES_DESC +'''Во время координации работы цепочек поставок (материалов и проектов кораблей) создаются Пакетные поставки, которые могут быть в любой момент переформированы и использованы повторно для большинства различных промышленных проектов. + +Пакетные поставки могут создаваться ещё задолго до того, как возникнет необходимость в товарах. Пакеты, как правило, содержат в себе приспособления, способные перекомплектовать сами себя для создания нового вида пакетных поставок. +При достижении такого уровня прогресса, технология Пакетных поставок может быть использована для создания совершенно новых технологических структур, которые были недоступны при создании самой первой, начальной поставки. + +Пакетные поставки освобождают службу имперских резервов от необходимости знать, какие в точности товары будут востребованы - достаточно лишь спрогнозировать их место назначения. + +Это технология, улучшающая имперские резервы. [[metertype METER_STOCKPILE]] повышается на 2, и дополнительно - по 0.01 за единицу населения планет, а также - ещё на 3 очка для планет, сфокусированных на резервы. +Эти улучшения складываются вместе с теми, что обеспечивает [[tech PRO_PREDICTIVE_STOCKPILING]].''' -While transforming an entire population into beings of pure energy would be impractical, certain tasks are better suited to individuals able to manifest themselves non-corporeally. Ship's crews for example, would no longer have to waste space with crew quarters or rations storage. Beings with greater energy channeling abilities would be able to enhance planetary shields and defense, and resource production would be generally enhanced. +PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY +Межзвёздная сцепленная фабрика -Конечная стадия физического совершенства - это уход за границы физических пределов. Без тела, особи и сообщества могут перемещаться, расти и существовать свободно, не отягощенные ненужными физическими ограничениями и неэффективными метаболическими процессами, которые тратят энергию на поддержку гомеостаза.''' +PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY_DESC +'''Использование особенностей квантовой сцепленности в залах управления производственных фабрик позволяет дистанционно управлять станками и машинами из других звёздных систем. +Распределение рабочих сил и средств производства помогает производить все продукты вовремя, отчего повышается эффективность работы службы имперских резервов. + +Это технология, улучшающая имперские резервы. [[metertype METER_STOCKPILE]] повышается на 6, и дополнительно - по 0.02 за единицу населения империи. +Эти улучшения складываются вместе с теми, которые дают исследования [[tech PRO_PREDICTIVE_STOCKPILING]], [[tech PRO_GENERIC_SUPPLIES]] и [[tech PRO_VOID_PREDICTION]]. + +Эта технология также разблокирует [[buildingtype BLD_STOCKPILING_CENTER]], который дополнительно повышает [[metertype METER_STOCKPILE]] на 0.1 за единицу промышленности.''' + +PRO_VOID_PREDICTION +Прогнозирование пустоты + +PRO_VOID_PREDICTION_DESC +'''В то время, как классические методы предсказания всегда опираются на статистические знания прошлого, изучение мудрости хаоса и пустоты приводит к ясным представлениям появления первичного, странного, случайного. +Для имперской службы распределения ресурсов прогнозирование пустоты - это квинтэссенциальный инструмент для доставки правильных товаров ещё до того, как в них возникнет необходимость. + +Это технология, улучшающая имперские резервы. [[metertype METER_STOCKPILE]] повышается на планетах на 0.2 за единицу населения. +Эти улучшения складываются вместе с теми, которые предоставляются исследованиями [[tech PRO_PREDICTIVE_STOCKPILING]], [[tech PRO_GENERIC_SUPPLIES]] и [[tech PRO_INTERSTELLAR_ENTANGLEMENT_FACTORY]].''' PRO_MICROGRAV_MAN -Микрогравитационное Производство +Микрогравитационное производство PRO_MICROGRAV_MAN_DESC -'''Increases [[metertype METER_INDUSTRY]] by +5 on all Industry-focused colonies in system with an asteroid belt outposts or colonies. Additional claimed asteroid belts do not provide additional benefit. +'''Увеличивает [[metertype METER_INDUSTRY]] на +5 для всех колоний ориентированных на промышленность, в системе с аванпостами на астероидных поясах или колониях. Дополнительные требования астероидных поясов не обеспечивают дополнительную выгоду -Mineral resource extraction from sub-planetoid-sized bodies involves challenges and provides opportunities quite distinct from those of full-fledged planets. Lacking sufficient mass to self-liquify and differentiate, asteroids can provide much easier access to some heavier elements, and lack of a gravity can make extraction for use in space much more efficient. Conversely, the limited size of each source asteroid requires fully portable self-sufficient extraction facilities. As well, the challenges of any microgravity environment must be overcome, requiring redesign of many traditional methods. +Добыча полезных ископаемых из субпланитоидных тел связана с трудностями, но и предоставляет возможности, отличные от возможностей полноценных планет. Не имея достаточной массы для самовыравнивания и дифференциации, астероиды могут обеспечить гораздо более легкий доступ к некоторым более тяжелым элементам, а отсутствие силы тяжести может сделать извлечение для использования в космосе намного более эффективным. И наоборот, ограниченный размер каждого источника астероида требует полностью переносных самодостаточных извлекающих установок. Кроме того, необходимо преодолеть проблемы любой среды микрогравитации, требуя переконструировки многих традиционных методов -Микрогравитация низкой планетарной орбиты предоставляет огромный потенциал для новейших производственных зданий, оборудования и техники, невозможной на поверхности планеты. Твердые тела могут храниться с минимальным обеспечением. Жидкости и газы текут свободно, сформированные только поверхностным натяжением до момента использования. Заводское оборудование может быть оптимально расставлено в трех измерениях. Эти условия, при правильной эксплуатации, дают серьезный прирост эффективности и продуктивности.''' +Микрогравитация низкой планетарной орбиты предоставляет огромный потенциал для новейших производственных зданий, оборудования и техники, невозможной на поверхности планеты. Твердые тела могут храниться с минимальной поддержкой. Жидкости и газы текут свободно, сформированные только поверхностным натяжением до момента использования. Заводское оборудование может быть оптимально расставлено в трех измерениях. Эти условия, при правильной эксплуатации, дают серьезный прирост эффективности и продуктивности''' PRO_ROBOTIC_PROD -Роботизированное Производство +Роботизированное производство PRO_ROBOTIC_PROD_DESC -'''Увеличивает максимальное [[metertype METER_INDUSTRY]] на промышленно-ориентированных планетах на 0,1*Население планеты. +'''Увеличивает максимальную [[metertype METER_INDUSTRY]] на всех промышленно-ориентированных планетах на 0.1 за единицу населения -Exobots, as they become commonly known, are a marvel of form meets function. They are robots designed to fulfill nearly any manufacturing job, in fields as wide ranging as electronics to large-scale construction. Easily mass produced, they serve best on colonies that place a high priority on industry. Every world focused primarily on industry receives a team of Exobots, greatly increasing industrial production. +Экзоботы, как они стали общеизвестными? - это чудо формы, которое соответствует выполняемым функциям. Эти роботы предназначены для выполнения практически любой производственной задачи, в областях, таких как электроника и крупномасштабное строительство. В легкой массовой промышленности, они обеспечивают одну из приоритетных задач в отрасли - обслуживание. Каждый мир, ориентированный прежде всего на промышленность, получив команду Экзоботов, значительно увеличивает промышленное производство -Несмотря на высокий уровень исходного дизайна и сборки завода, все еще требуется активный наблюдатель, устранение которого из производства удалит множество узких мест в процессе. Роботы работают непрерывно, без постоянного требования больших экономических компенсацией. Роботы имеют гораздо большую переносимость в опасных заводских средах, и не требуют дополнительного места для проживания. Роботы выполняют присвоенные им задачи безупречно, или как минимум так же хорошо, насколько они обучены.''' +Хотя высокий исходный уровень проектирования объекта по-прежнему требуют активного контроля, устранение операторов из фактического производственного процесса устраняет многие узкие места. Роботы работают непрерывно, без постоянных требований большого экономического вознаграждения. Роботы имеют гораздо большую устойчивость к опасным условиям окружающей среды на заводе и не требуют дополнительного пространства для жилья вне площадки. Роботы выполняют свои назначенные задачи безупречно или, по крайней мере, так же, как им поручено''' PRO_EXOBOTS -Полуавтоматические Экзоботы - +Экзоботы PRO_EXOBOTS_DESC -Позволяет производить экзоботов, оптимизированных для производства и добычи на враждебных планетах. +Позволяет производить экзоботов, оптимизированных для производства и добычи на враждебных планетах PRO_FUSION_GEN -Ядерный Синтез +Ядерный синтез PRO_FUSION_GEN_DESC -'''Увеличивает максимальное [[metertype METER_INDUSTRY]] на промышленно-ориентированных планетах на 0,2*Население планеты. +'''Увеличивает максимальную [[metertype METER_INDUSTRY]] на промышленно-ориентированных планетах на 0.2 за единицу населения -Синтезирующие атомные электростанции хоть и дороги в постройке и обслуживании, быстро окупают себя в индустриализированном мире, где постоянно увеличиваются требования к энергетике. Надежды на старые атомные электростанции, основанные на расщеплении ядра, уходят в прошлое. +Синтезирующие атомные электростанции хоть и дороги в постройке и обслуживании, быстро окупают себя в индустриализированном мире, где постоянно увеличиваются требования к энергетике. Надежды на старые атомные электростанции, основанные на расщеплении ядра, уходят в прошлое -Взрывные или неконтролируемые термоядерные реакции сравнительно просты для вызова в масштабах, начиная с маленьких тактических боеголовок до супергигантских звездных печей. Контролируемая, стабильная и практическая генерация энергия из реакций - это нечто более сложное. Эти процессы остаются очень привлекательными соответствующими, почти безграничными, запасами топлива и потенциалом для без-эмиссионных операций.''' +Взрывные или неконтролируемые термоядерные реакции сравнительно просты для вызова в масштабах, начиная с маленьких тактических боеголовок до супергигантских звездных печей. Контролируемая, стабильная и практическая генерация энергия из реакций - это нечто более сложное. Однако этот процесс остается привлекательным из-за почти безграничного снабжения топливом и потенциала безотходной работы''' PRO_NANOTECH_PROD Продукция нанотехнологий PRO_NANOTECH_PROD_DESC -Традиционные методы производства существенно ограничены размером и формой инструментов, которые используются. В процессе формирования продукта, его внешние детали могут препятствовать прямому доступу и манипуляциям с внутренней структурой, требуя неудобного и неэффективного регулирования процесса сборки. Компоненты должны быть тщательно собраны согласно требованиям спецификаций, чтобы гарантировать, что они будут работать как положено, когда изделие будет готово. При использовании нанотехнологий можно изготовить продукт за один шаг, непосредственно из материалов для производства, снимая ограничения на процессы сборки, резко увеличивая скорость производства и эффективность. +Традиционные методы производства существенно ограничены размером и формой инструментов, которые используются. В процессе формирования продукта, его внешние детали могут препятствовать прямому доступу и манипуляциям с внутренней структурой, требуя неудобного и неэффективного регулирования процесса сборки. Компоненты должны быть тщательно собраны согласно требованиям спецификаций, чтобы гарантировать, что они будут работать как положено, когда изделие будет готово. При использовании нанотехнологий можно изготовить продукт за один шаг, непосредственно из материалов для производства, снимая ограничения на процессы сборки, резко увеличивая скорость производства и эффективность PRO_ORBITAL_GEN Орбитальные генераторы PRO_ORBITAL_GEN_DESC -'''Once troublesome gravity well problems are overcome, gas giants offer a unique opportunity to harvest usable energy quickly and efficiently. Methods which take advantage of the high density atmosphere and belt-zone circulation allow a significant amount of energy to be easily accrued and transported for use in nearby planets. +'''После того, как серьезные проблемами с гравитацией преодолены, газовые гиганты предлагают уникальную возможность быстро и эффективно собирать полезную энергию. Методы, которые используют преимущества атмосферы высокой плотности и циркуляции астероидной зоны, позволяют легко набирать и переносить значительное количество энергии для использования на близлежащих планетах -Низкая орбита планетарных спутников предоставляет некоторые выгоды при производстве энергии. На спутниках нет атмосферы, задерживающей излучение от ближайшей звезды, низкая гравитация спутников планеты позволяет собрать энергетические комплексы превосходящие поверхностные станции, это является огромным преимуществом. Получающаяся энергия может использоваться на орбите или может быть излучена к планете, чтобы поддержать промышленность на её поверхности. Также, на орбите становятся доступными новые механизмы преобразования энергии, включая использование планетарного магнитного поля, орбитальный обмен импульса, и фрикционное нагревание о планетарную атмосферу, которые очень выгодны для специализированных областей.''' +Низкая орбита планетарных спутников предоставляет некоторые выгоды при производстве энергии. На спутниках нет атмосферы, задерживающей излучение от ближайшей звезды, низкая гравитация спутников планеты позволяет собрать энергетические комплексы превосходящие поверхностные станции, это является огромным преимуществом. Получающаяся энергия может использоваться на орбите или может быть излучена к планете, чтобы поддержать промышленность на ее поверхности. Также, на орбите становятся доступными новые механизмы преобразования энергии, включая использование планетарного магнитного поля, орбитальный обмен импульса и фрикционное нагревание о планетарную атмосферу, которые очень выгодны для специализированных областей''' PRO_SENTIENT_AUTOMATION Разумная автоматика PRO_SENTIENT_AUTOMATION_DESC -'''Увеличивает целевое [[metertype METER_INDUSTRY]] на всех планетах на 5. +'''Увеличивает целевую [[metertype METER_INDUSTRY]] на всех планетах на 5 -By producing automated factories that operate without direct supervision, it is possible to increase industrial output in an entirely automated facility. These factories can be built and maintained on any planet, regardless of focus, providing a bonus to industry on all worlds. +Производя автоматизированные заводы, которые работают без непосредственного надзора, можно увеличить объем промышленного производства на полностью автоматизированном объекте. Эти фабрики могут быть построены и поддерживаться на любой планете, независимо от направления, обеспечивая бонус промышленности во всех мирах -Поскольку оборудование становится все более и более автоматизированным, оно приобретает способность к полному собственному копированию и обслуживанию без внешней поддержки, кроме обеспечения необходимым сырьем и материалами, для производства конечного продукта. Когда оборудование управляется искусственным разумом, вся система может учиться, планировать, реагировать, и адаптироваться автономно, и может делать это без громоздкой, неэффективной, традиционной схемой с наймом и предоставлением жилья рабочим.''' +Поскольку оборудование становится все более и более автоматизированным, оно приобретает способность к полному собственному копированию и обслуживанию без внешней поддержки, кроме обеспечения необходимым сырьем и материалами для производства конечного продукта. Когда оборудование управляется искусственным разумом, вся система может учиться, планировать, реагировать, а так же адаптироваться автономно и может делать это без громоздкой, неэффективной, традиционной схемой с наймом и предоставлением жилья рабочим''' PRO_NDIM_ASSMB N-мерная сборка PRO_NDIM_ASSMB_DESC -''' Unlocks the Hyperspatial Dam - -Once the greatest fear of N-dimensional physicists, now the noblest goal. To deliberately create a hyperspatial rift between our universe and the next, but inside a dimensional bubble, safely separate from known space-time. Once this is accomplished, it would be a relatively simple matter to tap into the enormous flow of energy that would travel across the rift. +'''Некогда наивысший страх перед N-мерными физиками теперь стал самой благородной целью. Преднамеренно создать гиперпространственный разрыв между нашей вселенной и следующей, но внутри пространственного пузыря и благополучно отделить от известного пространства-времени. Как только это будет достигнуто, будет относительно простым делом использовать огромный поток энергии, который мог бы перемещаться по разлому -Геометрия физического пространства всегда была главным препятствием для сборки объектов в пределах этого пространства. Объекты не могут быть расположены так, что их объемы могут быть одновременно помещены в корпус и доступны, если их существование ограничено простыми тремя измерениями. Перемещаясь вне этого ограничения, части объектов могут свернуть c пути в карманных измерениях, и непрерывные линии могут проходить другие материалы, идя "вне оси" и оставаясь при этом непрерывными. Подобные механизмы также используются, чтобы собрать более низко-мерные объекты в конфигурациях, которые были бы невозможны без использования более многомерных путей сборки.''' +Геометрия физического пространства всегда была главным препятствием для сборки объектов в пределах этого пространства. Объекты не могут быть расположены так, что их объемы могут быть одновременно помещены в корпус и доступны, если их существование ограничено простыми тремя измерениями. Перемещаясь вне этого ограничения, части объектов могут свернуть c пути в карманных измерениях и непрерывные линии могут проходить другие материалы, идя "вне оси" и оставаясь при этом непрерывными. Подобные механизмы также используются, чтобы собрать более низко-мерные объекты в конфигурациях, которые были бы невозможны без использования более многомерных путей сборки''' PRO_SINGULAR_GEN Энергия сингулярности PRO_SINGULAR_GEN_DESC -'''Harnessing energy from a black hole is significantly more dangerous than harnessing energy from a regular star, but also has potential for significantly greater gains. By building a generator designed to tap into the energy of particle-antiparticle collisions on the event horizon of a singularity, tremendous benefits to industry may be obtained. +'''Использование энергии из черной дыры значительно более опасно чем использование энергии от обычной звезды, но также имеет потенциал для значительно большего прироста. Создавая генератор, предназначенный для использования энергии соударений частиц с античастицами на горизонте событий сингулярности, можно получить огромные выгоды для промышленности -Вещество притянутое к гравитационной сингулярности интенсивной кривизной пространства-времени, или полем тяготения, формирует линзу, приобретая при этом огромную плотность и сильно нагреваясь. Высокоэнергетическое радиационное излучение испускаемое линзой, может быть захвачено и использовано для выработки энергии. Сам горизонт события сингулярности также излучает радиацию, к чему приводит аннигиляции пар частицы и античастицы, которые формируются в его близости. Этот метод выработки энергии является значительно более хрупким и неустойчивым, чем метод линзирования, но так же может давать больше энергии.''' +Вещество притянутое к гравитационной сингулярности интенсивной кривизной пространства-времени или полем тяготения, формирует линзу, приобретая при этом огромную плотность и сильно нагреваясь. Высокоэнергетическое радиационное излучение испускаемое линзой, может быть захвачено и использовано для выработки энергии. Сам горизонт события сингулярности также излучает радиацию, к чему приводит аннигиляции пар частиц и античастиц, которые формируются в близости дыры. Этот метод выработки энергии является значительно более деликатным и неустойчивым, чем метод линзирования, но так же может давать больше энергии''' PRO_ZERO_GEN Энергия нулевых точек PRO_ZERO_GEN_DESC -'''Любой механизм выработки энергии, который включает материальную подачу топлива, ограничен релятивистской зависимостью массы - энергии: не может быть получено энергии больше, чем эквивалентно затраченной массы, даже при совершенной эффективности процесса. Выявление энергии нулевых колебаний вакуума, или, так называемой, энергии нулевой точки, фактически делает доступным бесконечный источник энергии. Уровень и мощность такого источника также теоретически неограниченна, пределами являются только способы, которыми аккумулируется и используется энергия. +'''Любой механизм выработки энергии, который включает материальную подачу топлива, ограничен релятивистской зависимостью массы - энергии: не может быть получено энергии больше, чем эквивалентно затраченной массы, даже при совершенной эффективности процесса. Выявление энергии нулевых колебаний вакуума или, так называемой, энергии нулевой точки, фактически делает доступным бесконечный источник энергии. Уровень и мощность такого источника также теоретически неограниченна, пределами являются только способы, которыми аккумулируется и используется энергия -At appropriate scales and under specific conditions, matter-energy becomes a unified phenomenon. Applied at larger scales, complete control over the structure of matter and the dynamics of energy becomes possible, as does the interchange between the two. Transmutation and replication, at limited scales, become possible. However, despite the enormous rest mass energy equivalence of matter, converting directly to energy for bulk power generation is generally less effective than comparatively simple fusion generation. +В соответствующих масштабах и при определенных условиях энергия материи становится единым явлением. Применяясь в больших масштабах, возможен полный контроль над структурой вещества и мощностью энергии, равно как и обмен между ними. Трансмутация и репликация в ограниченных масштабах становится возможной. Однако, несмотря на огромную массовую энергетическую эквивалентность массы вещества, преобразование непосредственно в энергию для выработки объемной энергии, как правило, менее эффективно, чем сравнительно простая термоядерная генерация -Previous production techniques were essentially ways to avoid or remove particularly vexing problems with manipulation of matter. None of these techniques fundamentally changed the basic method however, which was to assemble larger objects out of supplied materials, be they prefabricated subsections, or raw minerals. By converting pure energy directly into the desired material object, the need for supplied materials such as minerals or food can become greatly reduced in times of necessity.''' +Предыдущие технологии производства были в основном способами предотвращения или устранения особенно неприятных проблем с манипулированием вещества. Однако ни один из этих методов принципиально не изменил базовый метод, который заключался в сборке больших предметов из поставляемых материалов, будь то сборные полуфабрикаты или сырьевые минералы. Преобразуя чистую энергию непосредственно в желаемый материальный объект, потребность в поставляемых материалах, таких как минералы или пища, может значительно сократиться в случае необходимости''' PRO_NEUTRONIUM_EXTRACTION Нейтронная экстракция +PRO_NEUTRONIUM_EXTRACTION_DESC +Ядро звезды [[STAR_NEUTRON]] состоит из вырожденного нейтронного вещества, сжатого под действием силы гравитации до плотности, намного большей чем самая тяжелая нормальная материя. Этот экзотический материал имеет многочисленные применения, как только это вещество будет извлечено из звезды, перед вами откроются невероятные возможности позволяющие производить конструкции и части кораблей + CON_ARCH_PSYCH Архитектурная психология CON_ARCH_PSYCH_DESC -Формы конструкций или жилых помещений руководствуется не только простыми физическими ограничениями материалов, из которых они построены. Психологическое воздействие, оказываемое на жителей внутри, или наблюдателей снаружи, могут иметь намного большую значимость для успешности дизайна чем прочность стен или крепость фундамента. +Формы конструкций или жилых помещений руководствуется не только простыми физическими ограничениями материалов из которых они построены. Психологическое воздействие, оказываемое на жителей внутри или наблюдателей снаружи, могут иметь намного большую значимость для успешности дизайна чем прочность стен или крепость фундамента CON_ORGANIC_STRC Органические строения CON_ORGANIC_STRC_DESC -'''Здания, сформированные из неодушевленных минералов функциональны и иногда изящны, собранные из мертвого органического материала часто просты и недороги. Однако живые органические строения могут вырастать и обретать их конечную форму без дополнительного труда, к тому же обладают способностью к некоторой степени восстановления, если строение было повреждено. Такие здания могут естественным образом регулировать свою внутреннюю среду, или потенциально способны во время роста приспособиться к различной или изменяющейся окружающей среде по мере необходимости. +'''Здания, сформированные из неодушевленных минералов функциональны и иногда изящны, собранные из мертвого органического материала часто просты и недороги. Однако живые органические строения могут вырастать и обретать конечную форму без дополнительного труда, к тому же обладают способностью к некоторой степени восстановления, если строение было повреждено. Такие здания могут естественным образом регулировать свою внутреннюю среду или потенциально способны во время роста приспособиться к различной или изменяющейся окружающей среде по мере необходимости -A shutter functions as both a solid wall and open window, according to the desire of its user. A room may be refurnished to serve an entirely new purpose, within the limits of the space available. This concept may also be extended to the actual form and structure of an entire building or city, whether mechanical or organic. Eliminating the need for demolition and rebuilding permits much greater infrastructure flexibility and efficiency. Single or isolated structures may also serve many purposes simultaneously, that would previously have required much larger and elaborate fixed designs.''' +По желанию пользователя затвор функционирует как сплошная стена так и открытое окно. Комната может быть отремонтирована, чтобы служить совершенно новой цели в пределах доступного пространства. Эта концепция также может быть распространена на фактическую форму и структуру всего здания или города, будь то она механической или органической. Устранение необходимости в сносе и восстановлении позволяет значительно повысить гибкость и эффективность инфраструктуры. Отдельные или изолированные структуры также могут одновременно выполнять множество целей, которые ранее требовали бы гораздо более крупных и сложных фиксированных конструкций''' CON_ARCH_MONOFILS Архитектурные моноволокна CON_ARCH_MONOFILS_DESC -Попросту небьющиеся и всего несколько атомов в диаметре, у монокристаллических нитей найдется множество применений в улучшении характеристик традиционных сооружений, таких как долговечность построек и их экономичность. Способность повысить стрессоустойчивость других материалов дает возможность их использования в ранее невозможных конфигурациях, а так же позволяет создавать совершенно новые проекты. Низкий вес и высокая прочность нитей позволяет их использование в орбитальных конструкциях, и даже для привязки орбитальных строений к поверхности, там, где изменения в силе тяготения от расстояния до поверхности ранее препятствовали использованию орбитальных конструкций. +Попросту небьющиеся и всего несколько атомов в диаметре, у монокристаллических нитей найдется множество применений в улучшении характеристик традиционных сооружений, таких как долговечность построек и их экономичность. Способность повысить стрессоустойчивость других материалов дает возможность их использования в ранее невозможных конфигурациях, а так же позволит создавать совершенно новые проекты. Низкий вес и высокая прочность нитей позволяет их использовать в орбитальных конструкциях и даже для связки орбитальных строений с поверхностью там, где изменения в силе тяготения от расстояния до поверхности ранее препятствовали использованию орбитальных конструкций CON_ASYMP_MATS Асимптотические материалы CON_ASYMP_MATS_DESC -Как только определённые пороги эксплуатационных характеристик пройдены, измерение физических свойств материалов для относительного сравнения становится непрактичным в любых терминах, кроме порядков величин или логарифмических масштабов. Это тоже что и сверхпроводимость в электронике: пределом прочности, стойкостью, плотностью, эластичностью, жаропрочным индексом и другими прочностными свойствами материалов можно эффективно управлять без каких либо ограничений. Все ранее существовавшие ограничения исчезли, в проектировании строений более нет препятствий, они могут расширяться или измениться в той же степени как и свойства материалов. +Как только определенные пороги эксплуатационных характеристик пройдены, измерение физических свойств материалов для относительного сравнения становится непрактичным в любых терминах кроме порядков, величин или логарифмических масштабов. Это тоже что и сверхпроводимость в электронике: пределом прочности, стойкостью, плотностью, эластичностью, жаропрочным индексом и другими прочностными свойствами материалов можно эффективно управлять без каких либо ограничений. Все ранее существовавшие ограничения исчезли, в проектировании строений более нет препятствий они могут расширяться или измениться в той же степени как и свойства материалов CON_FRC_ENRG_STRC Высокоэнергетические строения CON_FRC_ENRG_STRC_DESC -'''Увеличивает прирост показателей на 3 в ход, и снижение показателей на 5 в ход. +'''Увеличивает прирост показателей ниже цели на 3 в ход и снижение показателей выше цели на 5 за ход -The flexibility and versatility of force-energy structures permits a colonies buildings to be more easily re-tooled to suit new purposes, increasing the rate at which focus changes can take effect. +Гибкость и универсальность силовых структур энергии позволяет более легко модифицировать здания колоний для новых целей, увеличивая скорость изменения фокуса -When forces and energy can be manipulated as desired to provide the same functionality as solid surfaces, there is no essential need to have physical materials enclosing the occupants of a building. Structures composed entirely of energy have many advantages, including simple reconfiguration, resilience, portability and novel design possibilities.''' +Когда силой и энергией можно манипулировать по желанию, чтобы обеспечить ту же функциональность, что имеют твердые поверхности, нет существенной необходимости иметь физические материалы окружающие обитателей здания. Структуры, состоящие исключительно из энергии, имеют много преимуществ, включая простоту реконфигурации, устойчивость, мобильность и новые возможности дизайна''' CON_CONTGRAV_ARCH -Контролируемая Гравитация в Архитектуре +Контролируемая гравитация в архитектуре CON_CONTGRAV_ARCH_DESC -'''Увеличивает максимальную дальность [[metertype METER_SUPPLY]] со всех колоний на 1. +'''Увеличивает максимальную дальность [[metertype METER_SUPPLY]] со всех колоний и аванпостов на 1 -By installing a massive null gravity generator at a planet's core, it becomes possible to turn a planet's gravity on or off like a light switch, but in a narrow well. The planet's industry then gains all the benefits of low-G and zero-G, but without the drawbacks. Reverse gravity wells can also be created, making space launches cost practically nothing. +Установив массивный генератор нулевой силы тяжести в ядре планеты, становится возможным включить или выключить гравитацию планеты как выключатель света, но в узком колодце. Тогда индустрия планеты получает все преимущества низкой и нулевой гравитации, но без недостатков. Обратные гравитационные колодцы также могут быть созданы, что делает запуск космических аппаратов практически нецелесообразным -Полностью управляя ориентацией и силой гравитационных полей, существующих вне и внутри строений, проекты могут одновременно демонстрировать свободу невесомости и пользу наличия опциональных "верха" и "низа". Само строение и окружающая его среда, могут образовывать любую форму в пространстве, простираясь на большие расстояния, делая петли вокруг себя, и создавая произвольные взаимосвязи. Одновременно, окружению, в определенных пределах, можно придать полезные свойства, которые могут меняться или полностью отличаться от смежных областей. Такая эксплуатационная гибкость предлагает новые функциональные возможности для социальных, производственных или других целей.''' +Полностью управляя ориентацией и силой гравитационных полей, существующих вне и внутри строений, проекты могут одновременно демонстрировать свободу невесомости и пользу наличия опциональных "верха" и "низа". Само строение и окружающая его среда, могут образовывать любую форму в пространстве, простираясь на большие расстояния, делая петли вокруг себя и создавая произвольные взаимосвязи. Одновременно, окружению в определенных пределах можно придать полезные свойства, которые могут меняться или полностью отличаться от смежных областей. Такая эксплуатационная гибкость предлагает новые функциональные возможности для социальных, производственных или других целей''' CON_GAL_INFRA Инфраструктура галактики CON_GAL_INFRA_DESC -'''Увеличивает максимальную дальность [[metertype METER_SUPPLY]] каждой колонии на 1. +'''Увеличивает максимальную дальность [[metertype METER_SUPPLY]] со всех колоний и аванпостов на 1 -The consistent and effective transportation of resources over large interstellar distances requires laborious planning, and is complicated by numerous factors such as the presence of space monsters, enemy empires and dangerous interstellar phenomena. By utilizing integrated galactic infrastructure, the fluid deployment of resources between advanced and developing colonies can be achieved, and solutions can be found to particularly troubling logistical problems. +Постоянная и эффективная транспортировка ресурсов на больших межзвездных расстояниях требует кропотливого планирования и осложняется многочисленными факторами, такими как присутствие космических монстров, вражеских империй и опасных межзвездных явлений. Используя интегрированную галактическую инфраструктуру, может быть достигнуто подвижное развертывание ресурсов между развитыми и развивающимися колониями и можно найти решения, которые особенно беспокоят проблемы логистики -Integration and development of the infrastructures on highly productive worlds into a unified galaxy-spanning network is one of the greatest logistical effort conceivable. When completed, such a system functionally unifies the worlds of which it is composed. Integrated transportation, housing, maintenance, and construction tools may be fluidly deployed wherever needed to optimally alter resource allocations or adjust to changes.''' +Интеграция и развитие инфраструктур в высокопродуктивных мирах в единую галактическую сеть - одно из самых больших логистических достижений. По завершении, такая система функционально объединяет миры из которых и состоит. Интегрированные транспортные средства, жилье, техническое обслуживание и строительные инструменты могут быть гибко развернуты там, где это необходимо для оптимального изменения ресурсов или адаптации к изменениям''' CON_ART_HEAVENLY -Искусственные божественные тела +Искусственные небесные тела + +CON_ART_HEAVENLY_DESC +Когда приблизительная масса маленькой луны или большой уже недостаточно, чтобы представить чем является выделенный объект «структурой», «станцией» или «кораблем». Скорее, за пределами этого масштаба объект становится больше похож на небесное тело. Его гравитационное притяжение влияет на орбитальные движения, он может сохранять тонкую атмосферу на своей поверхности и может вращаться по орбите с несколькими меньшими структурами-спутниками на своей орбите. В еще больших масштабах целые планеты могут быть построены или перестроены или даже новые звезды могут быть сформированы и воспламенены, ознаменовав силу и решительность своих создателей во вселенной CON_TRANS_ARCH Трансцендентальная архитектура +CON_TRANS_ARCH_DESC +'''Осваивая передовые технологии орбитального конструирования, ученые создают методы, позволяющие создавать впечатляющий и вдохновляющий [[buildingtype BLD_MEGALITH]] + +Воплощение дизайна объединяет оптимальную функциональность с элегантностью формы. Трансцендентная структура расширяет и вдохновляет умы своих обитателей или наблюдателей формой, расположением, содержанием и интеграцией, будь то впечатляющий масштаб, совершенная выразительность единственной идеи, ее восприятие или отсутствие или предельная простота и пригодность для конкретной цели''' + CON_ORBITAL_HAB Орбитальные поселения CON_ORBITAL_HAB_DESC -Если планета непригодна к жизни, увеличивает максимальное население на ней на 2. +Увеличивает численность населения на всех пригодных для жизни планетах в зависимости от размера: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5) CON_NDIM_STRC N-мерные строения CON_NDIM_STRC_DESC -'''Если планета непригодна к жизни, увеличивает максимальное население на ней на 3. +'''Увеличивает численность населения на всех пригодных для жизни планетах в зависимости от размера: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10) -The primary limiting factors on a planets population capacity are environmental desirability and space. By phasing basic infrastructure development into multiple dimensions, spatial limitations are all but eliminated, and under ideal environmental conditions, the maximum population of a planet can be greatly increased. +Также увеличивает целевое значение [[metertype METER_CONSTRUCTION]] на 10 единиц населения -Of all the bizarre feats of applied physics, the ability to to fold space onto itself has the most direct and profound influence on structural form and design. The potential is endless, literally as hallways are made to bend back upon their beginning, or given a twist so that their ceiling becomes their floor. As well, the concept of urban sprawl becomes laughable, as any number of people may fit into an arbitrarily small volume, safely tucked into an adjacent plane of existence.''' +Основными факторами, ограничивающими количество населения на планете, являются экологическая целесообразность и пространство. Путем постепенного развертывания базовой инфраструктуры в несколько измерений пространственные ограничения практически устраняются и в идеальных условиях окружающей среды максимальное население планеты может быть значительно увеличено + +Из всех причудливых подходов прикладной физики способность загибать пространство в себя оказывает самое непосредственное и глубокое влияние на структурную форму и дизайн. Потенциал бесконечен, буквально, поскольку коридоры сделаны, чтобы изгибаться назад к их началу, или получить искривление, чтобы потолок стал их полом. Кроме того, концепция городского разрастания становится смехотворной, так как любое количество людей может вписаться в произвольно небольшой объем, безопасно сложенный в смежную плоскость существования''' SHP_GAL_EXPLO Исследование галактики SHP_GAL_EXPLO_DESC -Открытие межзвездных путешествий через звездные тоннели объявляют новую эру развития общества. Старые политические дрязги между небольшими фракциями становятся незначительными, когда вся цивилизация сталкивается с безграничным потенциалом звезд ... для роста и процветания, или полного уничтожения. +Открытие межзвездных путешествий через звездные тоннели объявляют новую эру развития общества. Старые политические дрязги между небольшими фракциями становятся незначительными, когда вся цивилизация сталкивается с безграничным потенциалом звезд ... для роста и процветания или полного уничтожения SHP_INTSTEL_LOG -Межзвёздная логистика +Межзвездная логистика SHP_INTSTEL_LOG_DESC -'''Разблокирует возможность фокусировки планеты на Логистику, что повышает [[metertype METER_SUPPLY]] планет на 3. +'''Разблокирует возможность фокусировки планеты на Логистику, что повышает [[metertype METER_SUPPLY]] планет на 3 -Увеличивает [[metertype METER_SPEED]] Ваших кораблей на 20, когда они находятся в радиусе 50 uu от контролируемой планеты. +Увеличивает [[metertype METER_SPEED]] ваших кораблей на 20, когда они находятся в радиусе 50 световых лет от контролируемой планеты [[COLONY_BUILDING_TIME_DECREASE]] -Эффективное снабжение кораблей, планирование общего движения флота и даже организация движения конкретных кораблей в бою требует комплексного понимания навигации и обнаружения, и методов сбора информации.''' +Эффективное снабжение кораблей, планирование общего движения флота и даже организация движения конкретных кораблей в бою требует комплексного понимания навигации и обнаружения, а также методов сбора информации''' SHP_MIL_ROBO_CONT Военная робототехника +SHP_MIL_ROBO_CONT_DESC +'''Если раньше не были исследованы более быстрые корабельные корпуса, эта технология сокращает время, чтобы построить колонию, расположенную далеко от соответствующих видов в империи + +Роботизированный контроль может эффективно использоваться для таких задач, как горнодобывающее дело и промышленность. Естественным продолжением этой концепции является разработка военного роботизированного оборудования, способного самостоятельно управлять звездолетом. Это создало бы большую универсальность в дизайне, поскольку необходимость в громоздких системах жизнеобеспечения была бы устранена. Корпуса, изготовленные с использованием такой технологии, могут также выполнять опасный ремонт, не задумываясь о безопасности и биологических ограничениях живых инженеров''' + SHP_SPACE_FLUX_DRIVE -Пространственно потоковый Двигатель +Пространственно-потоковый двигатель + +SHP_SPACE_FLUX_DRIVE_DESC +Обычно скорость передвижениядвижение в космосе ограничено топливом или двигателем. Однако на относительно небольшие расстояния объект можно перемещать по тому же принципу, что в водной среде: путём отталкивания от самой среды. Это требует манипуляций с пространством, в результате чего его частицы коллапсируют, заставляя материю переходить в смежные частицы пространства. Этот метод движения очень эффективен, но опасен на больших расстояниях, и делает корабли легко обнаружимыми из-за происходящих пространственных эффектов. Дальнейшее исследование технологий скрытности поможет нивелировать этот недостаток. Более детальное изучение данной технологии позволяет получить особый вид корабельного корпуса, [[tech SHP_SPACE_FLUX_BUBBLE]]. + +SHP_SPACE_FLUX_BUBBLE +Пространственно-потоковый пузырь + +SHP_SPACE_FLUX_BUBBLE_DESC +Обычно движение в космосе ограничено топливом или двигателем. Однако на относительно небольшие расстояния объект можно перемещать по тому же принципу, что в водной среде: путём отталкивания от самой среды. Это требует манипуляций с пространством, в результате чего его частицы коллапсируют, заставляя материю переходить в смежные частицы пространства. Этот метод движения очень эффективен, но опасен на больших расстояниях, и делает корабли легко обнаружимыми из-за происходящих пространственных эффектов. Дальнейшее исследование технологий скрытности поможет нивелировать этот недостаток. Простейшим видом корабельного корпуса для пространственных передвижений является пространственный пузырь, обволакивающий корпус корабля в форме сферы. Более сложные решения возможны с помощью исследования [[tech SHP_SPACE_FLUX_DRIVE]]. + +SHP_CONTGRAV_MAINT +Противогравитационная поддержка + +SHP_CONTGRAV_MAINT_DESC +Когда масса корабля превосходит определенный порог, становится необходимым учитывать гравитационные напряжения на жизненно важных архитектурных точках в структуре корабля. Эти точки не могут противостоять гравитационным напряжениям и коллапсу разрушающее судно. Это требует почти постоянного обслуживания этих точек, чтобы избежать полного структурного отказа SHP_NANOROBO_MAINT Обслуживание нано-роботов +SHP_NANOROBO_MAINT_DESC +Так же, как нанороботы могут использоваться для производства, так их можно использовать и для обслуживания. С дополнением к корпусу централизованно организованных нанитовых инженеров можно быстро восстановить повреждение даже во время боя. Такие машины с большим трудом отличали бы органических существа от части корпуса, нуждающегося в ремонте и, как таковая, эта концепция может применяться только к автоматическим корпусам + +SHP_MASSPROP_SPEC +Специализация по массовому движению + +SHP_MASSPROP_SPEC_DESC +Традиционные двигательные устройства хорошо работают на объектах с относительно малой массой. Однако, когда масса корабля приближается к небольшой луне, обычные методы движения очень неуклюжи и требуют огромного количества энергии. Новый подход к движению с учетом размера объекта, рассматриваемого как фундаментальный аспект системы, мог бы позволить таким судам иметь полезную мобильность + SHP_TRANSSPACE_DRIVE -Транс-пространственный Двигатель +Транс-пространственный двигатель + +SHP_TRANSSPACE_DRIVE_DESC +'''[[COLONY_BUILDING_TIME_DECREASE]] + +Даже когда пространственные размеры искажаются релятивистскими эффектами, объект обычно имеет постоянные пространственно-временные характеристики. Создав устройство, которое изменяет характеристики самого пространства-времени, используя квазирелятивистские эффекты, можно было бы создать метод быстрого и скрытого передвижения''' SHP_MIDCOMB_LOG Средне-тыловое обеспечение +SHP_MIDCOMB_LOG_DESC +Когда межзвездные флоты совершают обычное взаимодействие, передача персонала и материала в отсутствие вражеских кораблей является относительно тривиальной задачей. Однако, в бою, когда такие передачи могут иметь большое тактическое преимущество, есть большие трудности с координирующими аспектами логистики из-за возможности внезапных изменений в тактическом сценарии. Используя единый мобильный командный пункт, можно организовать в разгар боя и ввести в действие логистические модели + SHP_ASTEROID_HULLS -Астероидные Корпуса +Астероидные корпуса + +SHP_ASTEROID_HULLS_DESC +Вместо того, чтобы строить корпус с нуля, гораздо легче взять уже существующий космический объект и приспособить его, чтобы он выполнял роль корпуса корабля. Мало того, что такие корабли будут очень недорогими для производства, но они также смогут избежать обнаружения вражескими кораблями, скрываясь среди других астероидов + +SHP_HEAVY_AST_HULL +Тяжелые астероидные корпуса + +SHP_HEAVY_AST_HULL_DESC +Позволяет строить большие астероидные корпуса SHP_ASTEROID_REFORM -Астероидная Реформация +Астероидная реформация + +SHP_ASTEROID_REFORM_DESC +Благодаря передовой обработке астероидных тел может быть изготовлена скальная (каменная) броня, которая столь же дешева, но более прочна чем свинцовое покрытие брони SHP_MONOMOLEC_LATTICE -Мономолекулярная Решётка +Мономолекулярная решетка + +SHP_MONOMOLEC_LATTICE_DESC +Корпуса астероидов могут быть усилены и бронированы, но факт остается фактом: основное вещество из которого состоит большинство астероидов, имеет меньшую прочность чем большинство современных сплавов. Однако, если бы молекулярная структура самой породы была перегруппирована в кристаллическую решетку, ее прочность могла бы быть значительно увеличена, что позволило бы производить астероидные корпуса с ранее невозможным усилением SHP_MULTICELL_CAST -Многоклеточное Литье +Многоклеточное литье + +SHP_MULTICELL_CAST_DESC +Даже когда он неактивен, органический материал гораздо более универсален чем стандартные материалы. Неживое многоклеточное судно обеспечило бы некоторые из преимуществ органических материалов без обычных неприятностей органической жизни SHP_DOMESTIC_MONSTER -Одомашненная Мега-Фауна +Одомашненная мега-фауна + +SHP_DOMESTIC_MONSTER_DESC +Хотя чужаки и огромны, проверенные временем искусства одомашнивания также могут быть применены к космическим монстрам, которые путешествуют среди звезд. Строительство колонии или аванпостов в гнездах монстров позволит выращивать одомашненных монстров SHP_ENDOCRINE_SYSTEMS -Эндокринные системы Мега-фауны +Эндокринные системы мега-фауны + +SHP_ENDOCRINE_SYSTEMS_DESC +Изучение эндокринных систем космических организмов позволит расширить внутренние структуры органических судов + +SHP_MONOCELL_EXP +Моно-сотовые расширения + +SHP_MONOCELL_EXP_DESC +Многоклеточные организмы, как правило, имеют более длительный жизненный цикл, чем организмы с одной клеткой из-за специализации и взаимозависимости. Однако с соответствующими технологическими усовершенствованиями эти аспекты многоклеточной жизни могут быть применены к массивному одноклеточному организму, что позволит ему иметь полезную функциональность в бою SHP_CONT_SYMB -Хитрый Симбиоз +Хитрый симбиоз + +SHP_CONT_SYMB_DESC +Любой корпус судна находится в симбиотических отношениях с его создателями, поскольку они создают его и поддерживают, а он выполняет их желаемые функции. Однако это чисто поведенческий тип симбиоза. Благодаря тщательному планированию и дизайну органический корабль может быть создан с биологическими симбиотическими взаимоотношениями с его экипажем, каждый из которых обеспечивает неотъемлемую часть жизни + +SHP_CONT_BIOADAPT +Контролируемая хищная биоадаптация + +SHP_CONT_BIOADAPT_DESC +Очень трудно предсказать действия живого существа не имея глубокого понимания его поведенческих моделей. Путем размещения формы жизни в жестко конкурентных условиях развития ее поведенческими моделями можно манипулировать, укреплять и эффективно использовать в войне SHP_BIOADAPTIVE_SPEC -Био-нейронная Специализация +Био-нейронная специализация + +SHP_BIOADAPTIVE_SPEC_DESC +Поскольку вид развивает умственные способности, индивидуумы, чьи умы не идеально настроены на потребности их тел, не могут конкурировать с теми, чьи умы идеально специализированы для своих тел. Таким образом, все развитые формы жизни имеют почти идеальное умственное развитие для своего типа тела. Однако таких гарантий для искусственных форм жизни нет и часто истинный потенциал такого существа не реализуется из-за тонкой несовместимости между умом и телом. Специализируясь на нейронных структурах организма, чтобы соответствовать его типу тела, эти несовместимости можно устранить SHP_FRC_ENRG_COMP -Сильно-Сжатая Энергия +Сильно-сжатая энергия + +SHP_FRC_ENRG_COMP_DESC +Как правило для манипуляции с энергией требуется, чтобы она находилась в форме вещества, дабы возможная энергия могла быть сохранена по желанию и выпущена по требованию. Вещество преобразованное в чистую энергию и хранящуюся в ткани пространства-времени, было бы более универсальной, чем любая материальная субстанция, но для такого содержания необходимы продвинутые технологии силовых полей + +SHP_ENRG_FRIGATE +Военная компрессия энергии + +SHP_ENRG_FRIGATE_DESC +Расширенные силовые поля позволяют объединить энергию и вещество для создания военных кораблей, но для их масштабного создания требуется огромная энергия SHP_QUANT_ENRG_MAG -Увеличение Квантовой энергий +Увеличение квантовой энергий + +SHP_QUANT_ENRG_MAG_DESC +'''[[COLONY_BUILDING_TIME_DECREASE]] + +Из-за квантовой неопределенности возможно получение произвольными частицами небольшого количество энергии или временное возникновение новых частиц. Обнаружив и увеличив эти колебания в квантовой энергии, можно было бы значительно увеличить возможности энергетического корпуса''' SHP_ENRG_BOUND_MAN Манипуляция пограничной энергией +SHP_ENRG_BOUND_MAN_DESC +'''Будучи построенными, выращенными или адаптированными из ранее существовавшей структуры, корабли, состоящие из материи, должны иметь логическую и скоординированную структуру, представляющую даже элементарную полезность. Однако чистая энергия не имеет таких ограничений. Это позволяет корпусу чистой энергии принимать какую-либо форму, несмотря на нецелесообразным казалось бы с материальной точки зрения +''' + +SHP_SOLAR_CONT +Солнечное сдерживание + +SHP_SOLAR_CONT_DESC +Корпус, построенный из чистой энергии, гораздо более универсален, нежели твердый, но и он имеет свой недостаток выраженную в том, что корпус не может использовать потенциальную энергию материальных объектов. Однако, если бы было возможно иметь корпус, состоящий не из чистой энергии, а из сверхнагретых газов, постоянно подвергающихся самоподдерживающейся реакции синтеза, можно было бы избежать ограничений как чистой энергии так и твердого вещества + +SHP_KRILL_SPAWN +Спаунер планктона +SHP_KRILL_SPAWN_DESC +Разблокирует [[shippart SP_KRILL_SPAWNER]] деталь корабля, которая заставляет дикий космический криль появляться в системах с поясами астероидов + SPY_ROOT_DECEPTION Маскировка - +SPY_ROOT_DECEPTION_DESC +Искусство обмана не является естественным для каждого вида, но способность скрывать правду может быть весьма полезной ## ## Technology application @@ -5677,7 +10339,10 @@ SHIP_PART_UNLOCK_SHORT_DESC Открывает корабельный модуль SHIP_WEAPON_UNLOCK_SHORT_DESC -Открывает оружие +Разблокирует оружие + +SHIP_WEAPON_IMPROVE_SHORT_DESC +Улучшает корабельное вооружение SHIP_HULL_UNLOCK_SHORT_DESC Открывает корабельный корпус @@ -5686,13 +10351,13 @@ BUILDING_UNLOCK_SHORT_DESC Открывает строение RESEARCH_SHORT_DESC -Улучшает науку +Улучшает исследования INDUSTRY_SHORT_DESC Улучшает производство SUPPLY_SHORT_DESC -Улучшает Снабжение +Улучшает снабжение DEFENSE_SHORT_DESC Улучшает оборону планет @@ -5707,7 +10372,7 @@ POPULATION_SHORT_DESC Увеличивает максимальное население STRUCTURE_SHORT_DESC -Улучшает строения +Улучшает структуру SHIP_REPAIR_DESC Ремонтирует корабли @@ -5722,10 +10387,10 @@ DETECTION_SHORT_DESC Улучшает обнаружение PLANET_PROTECT_SHORT_DESC -Защита планет +Улучшает защиту планет VARIOUS_SHORT_DESC -Улучшает разные вещи +Улучшает разные элементы PENALTY_ELIMINATE_SHORT_DESC Убирает ограничения @@ -5737,65 +10402,82 @@ TAME_MONSTER_SHORT_DESC Приручает космических монстров EXOBOT_SHORT_DESC -Создаёт поселение экзоботов +Создает колонию экзоботов STARLANE_SPEED_SHORT_DESC -Увеличивает Межзвёздную скорость +Увеличивает межзвездную скорость CON_ORBITAL_CON Орбитальное конструирование CON_ORBITAL_CON_DESC -'''Увеличивает максимальную дальность [[metertype METER_SUPPLY]] всех колоний на 1. +'''Увеличивает максимальную дальность [[metertype METER_SUPPLY]] всех колоний и аванпостов на 1 -На планете всегда есть неизменная ссылка на направление: "вверх", микрогравитация же изотропна (направления ничем качественно не отличаются). У этой единственного отличия есть множество последствий для типов конструкций, которые могут быть построены, и для их форм, которые оптимально используют пространство, материалы и энергию в каждой среде. Внутренняя конфигурация пространств на орбите должна быть заново продумана. Хоть это и приводит к попытке полной переделки парадигмы конструирования, полностью независимой от типов, используемых на поверхностях планеты, практическое применение этих пространств будет начато прежде всего теми, кто привык и приспособлен к поверхности планеты. Поэтому более эффективным может быть не полный разрыв с уже наработанными типами конструкций, а постепенный переход.''' +На планете всегда есть неизменная ссылка на направление: "вверх", микрогравитация же изотропна (направления ничем качественно не отличаются). У этого единственного отличия есть множество последствий для типов конструкций, которые могут быть построены и для их форм, которые оптимально используют пространство, материалы и энергию в каждой среде. Внутренняя конфигурация пространств на орбите должна быть заново продумана. Хоть это и приведет к попытке полной переделки парадигмы конструирования, полностью независимой от типов, используемых на поверхностях планеты, практическое применение этих пространств будет начато прежде всего теми, кто привык и приспособлен к поверхности планеты. Поэтому более эффективным может быть не полный разрыв с уже наработанными типами конструкций, а постепенный переход''' CON_CONC_CAMP Концлагеря CON_CONC_CAMP_DESC -Обычно, когда заставляешь население работать до смерти, возникает некоторое общественное сопротивление и взывания к сторонним силам, однако, если создать специальные строения для своевременной нейтрализации бунтарей и пропаганды настроений пораженчества, концлагеря вполне эффективны и безопасны в плане восстаний. +Обычно, когда заставляешь население работать до смерти, возникает некоторое общественное сопротивление и взывания к сторонним силам, однако, если создать специальные строения для своевременной нейтрализации бунтарей и пропаганды настроений пораженчества, то концлагеря вполне эффективны и безопасны в плане восстаний CON_FRC_ENRG_CAMO Энергетический камуфляж CON_FRC_ENRG_CAMO_DESC -'''Увеличивает [[metertype METER_STEALTH]] всех строений на 20. +'''Увеличивает [[metertype METER_STEALTH]] всех строений на 20 -Обыкновенные физические структуры, как правило, непрозрачные и жесткие, строятся из аналогичных непрозрачных и жестких природных материалов. Силовое поле, по необходимости, не обладает такими свойствами постоянно, и может их и принимать, и отталкивать, или, по желанию, сменить цвет. Пока большинство зданий издалека легко различимы через мощные аппараты обнаружения, структуры форс-энергии - или регулярные структуры в окружении серии камуфляжных силовых полей - будут по сути невидимы с больших расстояний.''' +Обыкновенные физические структуры, как правило, непрозрачные и жесткие, строятся из аналогичных непрозрачных и жестких природных материалах. Силовое поле, по необходимости, не обладает такими свойствами постоянно и может их и принимать и отталкивать или по желанию сменить цвет. Пока большинство зданий издалека легко различимы через мощные аппараты обнаружения, структуры форс-энергии - или регулярные структуры в окружении серии камуфляжных силовых полей будут по сути невидимы с больших расстояний''' CON_PLANET_DRIVE Планетарный двигатель +CON_PLANET_DRIVE_DESC +Планеты в самых отдаленных системах империи часто подвергаются большой опасности нападения со стороны вражеских флотов, но эти планеты часто могут содержать чрезвычайно ценные ресурсы и артефакты. Путем оснащения всей планеты средствами для открытия навигации по звездным путям, можно будет перемещать такие планеты в более безопасное место в случае необходимости + CON_STARGATE Космические врата +CON_STARGATE_DESC +В особенности, в более экспансивных империях потребность в переходе кораблей в определенное защитное место в большой спешке часто превышает возможности выполнения. Создавая деформацию между двумя или более точками пространства-времени, можно сделать возможным перемещение кораблей даже по всей империи по мере необходимости, что позволит использовать новые наступательные и оборонительные стратегии + CON_ART_PLANET Искусственная планета CON_ART_PLANET_DESC -Газовые гиганты и пояса астероидов предоставляют не только уникальные ресурсные возможности и иногда крайне редких ресурсов, но и себя в качестве сырья для производства искусственных планет. Такие планеты будет предлагать новые центры производства в ранее пустых или полностью оккупированных систем. Кроме того, различные ключевые возможности планеты могут быть изменены или добавлены в процессе строительства, что потенциально позволяет менять размер планет , окружающие среду, или определенные события планеты, должны быть указаны. +'''Газовые гиганты и пояса астероидов предоставляют не только уникальные ресурсные возможности и иногда крайне редких ресурсов, но и себя в качестве сырья для производства искусственных планет. Такие планеты будут предлагать новые центры производства в ранее пустых или полностью оккупированных системах. Кроме того, различные ключевые возможности планеты могут быть изменены или добавлены в процессе строительства, что потенциально позволяет менять размер планет, окружающую среду или определенные события и особенности планеты + +Сама по себе технология позволяет создавать основные [[buildingtype BLD_ART_PLANET]]. Если в империи также есть [[tech GRO_GAIA_TRANS]], тогда можно создать [[buildingtype BLD_ART_PARADISE_PLANET]]. Если у империи есть [[tech PRO_EXOBOTS]], тогда можно построить [[buildingtype BLD_ART_FACTORY_PLANET]]''' + +CON_PALACE_EXCLUDE +Эксклюзивный дворец CON_PALACE_EXCLUDE_DESC -Гарантирует использование только одного императорского дворца в империи одновременно. Убедитесь, что вы сломали старый Императорский дворец перед постройкой нового. +Гарантирует использование только одного императорского дворца в империи одновременно. Убедитесь, что вы сломали старый Императорский дворец перед постройкой нового GRO_SUBTER_HAB Подземные жилища GRO_SUBTER_HAB_DESC -Увеличивает максимальное население всех планет в соответствии с размером планеты: Крохотные +1, Маленькие +2, Средние +3, Большие +4, Гигантски +5. +Увеличивает максимальное население всех планет в соответствии с размером населяемой планеты: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5) GRO_GENOME_BANK Банк генотипов +GRO_GENOME_BANK_DESC +Неизвестные переменные при геномных расчетах для любых видов остаются серьезными для всех, кроме простейшего генетического восстановления, не говоря уже о важности изменений, которые генетическая модификация может принести для более крупной многовидовой биосферы. Чтобы по-настоящему осознать преимущества генетической медицины, необходимо собрать массивную базу данных генома для всех видов из всех известных миров + GRO_INDUSTRY_CLONE Промышленное клонирование +GRO_INDUSTRY_CLONE_DESC +Простые ручные методы клонирования с использованием атомного переноса немного более чем новшество для крупных организмов. Применяя методы, аналогичные промышленному массовому производству или биохимическому процессу, становится возможным генерировать десятки или миллионы полных копий одного генетического проекта. Население может быть составлено на заказ, приспособленное для удовлетворения конкретных потребностей общества + GRO_TERRAFORM Терраформинг GRO_TERRAFORM_DESC -Изменение экологического типа планеты масштабная задача, задача этого процесса в значительной степени логистическая. Конкретный метод во многом зависит от типа планеты, и желаемого результата. Тем не менее, во всех случаях большая часть поверхности всей планеты и ее атмосферы должна быть преобразованы в другие типы молекул, выброшена в космос или погребена под корой. Таким образом, данный процесс требует большую часть планеты, доступной для терраформеров, и не может выполняться на мирах с максимальной численностью населения менее одного. +Изменение экологического типа планеты масштабная задача, задача этого процесса в значительной степени логистическая. Конкретный метод во многом зависит от типа планеты и желаемого результата. Тем не менее, во всех случаях большая часть поверхности всей планеты и ее атмосферы должна быть преобразованы в другие типы молекул, а не подходящий материал выбросить в космос или погрести глубоко под землей. Таким образом, данный процесс требует большую часть планеты, доступной для терраформеров и не может выполняться на мирах с максимальной численностью населения менее одного GRO_TERRAFORM_SHORT_DESC Позволяет трансформировать планеты @@ -5803,405 +10485,853 @@ GRO_TERRAFORM_SHORT_DESC GRO_REMOTE_TERRAFORM Удаленный терраформинг +GRO_REMOTE_TERRAFORM_DESC +Традиционные методы терраформирования относятся к планете как к структуре и пытаются изменить ее среду с помощью физических и химических средств. Это требует, чтобы большая часть планеты была доступна для терраформинга, что в целом ограничивает ее полезность. Обращаясь к планете как к организму и косвенно коснувшись ее физиологии, можно использовать менее трудоемкие, хотя и более затратные методы терраформирования, позволяющие аванпостам в бедных и враждебных мирах использовать терраформирование + GRO_BIOTERROR Центры биотеррора GRO_BIOTERROR_DESC -A research centre created to safely develop the most dangerous of biological weaponry while keeping prying eyes out. Biological weaponry takes effect too slowly to be of much use in a direct military conflict, but as a weapon of terror it is unsurpassed; a slowly growing contamination will spread panic before it, images of a desperate people quarantined by their own government sowing despair beyond the affected area. However, care must be taken when developing such organisms to ensure they are not accidentally released. Furthermore, utter secrecy is required to dispel the risk of damaging relations with civilians and other empires. +Исследовательский центр, созданный для безопасного развития наиболее опасного биологического оружия и охраняемый при этом от посторонних глаз. Биологическое оружие вступает в силу слишком медленно, чтобы его можно было использовать в прямом военном конфликте, но как оружие террора оно непревзойденно. Медленно растущее загрязнение будет распространять панику, образы отчаявшихся людей, помещенных на карантине их собственным правительством, будет наводить отчаяние за пределы пострадавшего района. Однако при разработке таких организмов необходимо проявлять осторожность, чтобы нечаянно не вызвать их случайное освобождение. Кроме того, требуется полная тайна, чтобы развеять риск ухудшения отношений с гражданскими лицами и другими империями + +GRO_REMOTE_TERRAFORM_SHORT_DESC +Позволяет удаленный терраформинг GRO_CYBORG Киборги GRO_CYBORG_DESC -'''Устанавливает максимальное население враждебных миров в соответствии с размером планеты: Крохотные +1, Маленькие +2, Средние +3, Большие +4, Гигантски +5. +'''Устанавливает максимальное население [[PE_HOSTILE]] миров в соответствии с размером планеты: ([[SZ_TINY]] +2) ([[SZ_SMALL]] +4) ([[SZ_MEDIUM]] +6) ([[SZ_LARGE]] +8) ([[SZ_HUGE]] +10) +Увеличивает максимальные [[metertype METER_TROOPS]] на всех планетах на 0.2 за единицу населения -A highly versatile fusion of organism and machine, capable of adapting to a tremendous variety of circumstances. Spontaneous generation of specialized organs and mechanically enhanced strength permit cyborgs to exist with ease in nearly any environment.''' +Очень универсальное слияние организма и машины, способное адаптироваться к огромному разнообразию обстоятельств. Спонтанная генерация специализированных органов и механически усиленная прочность позволяют киборгам существовать с легкостью практически в любой среде''' GRO_GAIA_TRANS Гайя -LRN_OBSERVATORY_I -Обсерватория +GRO_GAIA_TRANS_DESC +Теоретически можно полностью предсказать экологию одной планеты, отобразив всю сеть биологической взаимозависимости как разумной, так и нечувствительной в фрактальных алгоритмах. На практике, однако, это можно было реализовать только путем создания обширной сети, охватывающей всю планету, которая запускает компьютерную программу, предназначенную для регулирования всей биосферы планеты. Этот компьютер стал бы, по сути, мозгом нового типа жизни - единым планетарным организмом, с разумными гражданами как частью его ткани подчиненным большей воле -LRN_SOLAR_MAN -Манипуляции со звездами +GRO_GAIA_TRANS_SHORT_DESC +Позволяет преобразование Гайи + +GRO_MEGA_ECO +Экология мега-фауны + +GRO_MEGA_ECO_DESC +Изучение жизненных циклов космических организмов позволяет выделить общие закономерности у разных видов. Благодаря этому становится возможным более эффективный контроль за организмами, разрушение их метаболических и репродуктивных процессов. + +GRO_MEGA_ECO_SHORT_DESC +Контроль за гнёздами монстров + +LRN_OBSERVATORY_I +Обсерватория + +LRN_OBSERVATORY_I_DESC +Изучение и понимание природы звезд и их внутренних и внешних атрибутов дает мощное представление о природе звездных явлений и планетных тел. Кроме того, фрактальные алгоритмы могут быть использованы для расшифровки рассредоточения звездных путей по всей галактике, позволяя любым звездам, каким-то образом связанным, хотя и косвенно с известной сетью звездных путей, которые должны быть идентифицированы путем наблюдения + +LRN_SOLAR_MAN +Манипуляции со звездами + +LRN_SOLAR_MAN_DESC +Различные типы звезд не только имеют свои собственные атрибуты и возможности для использования, но также имеют тенденцию приводящую к возникновению на орбите определенных типов планет. Часто звезда определенного типа дает желаемый бонус, но почти наверняка будет лишена каких-либо желательных планет или не будет присутствовать в подходящем месте в галактике. Путем манипуляции высокоэнергетическими реакциями в сердцевине звезды можно было бы либо изменить, либо ускорить естественные процессы внутри звезды, что позволило бы изменить тип на совершенно другой при необходимости + +LRN_DISTRIB_THOUGHT +Распределенные мыслительные системы LRN_DISTRIB_THOUGHT_DESC -'''Увеличивает максимальное значение [[metertype METER_RESEARCH]] на всех планетах на 0,1 * Население. +'''Увеличивает целевые [[metertype METER_RESEARCH]] на всех планетах с любым фокусом на 0.1 за единицу населения -The average citizen literally wastes his mind away, one of the finest computing devices in the universe. By tapping into the idle brain cycles of a sufficiently large population through a network of simple cybernetic transceivers, the research centers of a world can gain access to cheap and plentiful raw computing power.''' +Средний гражданин буквально уничтожает свой ум, одно из лучших компьютерных устройств во Вселенной. Выбирая холостые мозговые циклы достаточно большой популяции через сеть простых кибернетических приемопередатчиков, исследовательские центры мира могут получить доступ к дешевой и обильной сырьевой вычислительной мощности''' LRN_STELLAR_TOMOGRAPHY Звездная томография +LRN_STELLAR_TOMOGRAPHY_DESC +'''Увеличивает [[metertype METER_RESEARCH]] на планетах с фокусом на исследования за единицу населения и зависит от типа звезды: +* [[STAR_BLACK]] (x1) +* [[STAR_NEUTRON]] (x0.75) +* [[STAR_BLUE]] или [[STAR_WHITE]] (x0.5) +* [[STAR_YELLOW]], [[STAR_ORANGE]] или [[STAR_RED]] (x0.2) + +Сеть специализированных спутников может использовать томографические методы для воссоздания деятельности, происходящей глубоко внутри звезды. Эти дорогостоящие производственные условия идеально подходят для различных исследовательских работ с большим преимуществом для более экзотических звездных типов''' + LRN_SPATIAL_DISTORT_GEN -Пространственный Генератор Искажений +Пространственный генератор искажений + +LRN_SPATIAL_DISTORT_GEN_DESC +Путем манипулирования пространственных свойств звездных путей можно было бы выборочно изгибать их. Медленные входящие корабли могут быть возвращены в прежнюю систему вылета, а более быстрые суда могут быть существенно замедлены LRN_GATEWAY_VOID -Путь в Никуда +Путь в никуда + +LRN_GATEWAY_VOID_DESC +Мощь Разумного вакуума почти незаметна в простом четырехмерном пространстве-времени. Создав врата, обращающиеся к огромному количеству измерений, может быть развязана опасная, неизбирательная сила в локализованной области LRN_ENCLAVE_VOID -Анклав Пустоты +Анклав пустоты + +LRN_ENCLAVE_VOID_DESC +Изучение Разума вакуума можно было бы ускорить, генетически изменяя население всего мира, чтобы быть приспособленным к вакууму. Этот новый класс жрецов может трансформировать всю социальную структуру своего мира, чтобы лучше понимать и распространять мудрость вакуума на всю империю LRN_ART_BLACK_HOLE Искусственная черная дыра +LRN_ART_BLACK_HOLE_DESC +Осторожно сжав [[STAR_RED]] звезду, можно сформировать небольшую [[STAR_BLACK]]. Это дорогостоящее и опасное мероприятие, но с большими потенциальными преимуществами для [[metertype METER_RESEARCH]] и [[metertype METER_INDUSTRY]] + LRN_PSY_DOM Психогенное господство +LRN_PSY_DOM_DESC +'''Имеет 10% шанс взять под контроль вражеские корабли с нетелепатическими экипажами в той же системе, что и планета с фокусом на господство, за исключением вражеских империй также знающих [[tech LRN_PSY_DOM]]. +Защищает от [[predefinedshipdesign SM_PSIONIC_SNOWFLAKE]] захвата ваших кораблей + +Трудно заставить сознательного человека подчиниться полному умственному контролю другого субъекта против его воли. Однако, манипулируя задействованными таймфреймами, умственная сила, оказываемая населением в течение нескольких часов, может быть перенесена на одного человека в течение нескольких секунд, создавая таким образом почти непреодолимое принуждение присоединиться к объединенному уму''' + MIND_CONTROL_SHORT_DESC Позволяет контролировать разум PRO_SOL_ORB_GEN Солнечный орбитальный генератор +PRO_SOL_ORB_GEN_DESC +Традиционные методы солнечной генерации полагаются исключительно на излучение солнечной энергии. Помещая генераторы на низкой орбите вокруг самой звезды, возможность прямого использования энергии сверхмассивных звездных явлений становится возможной и можно получить значительное и далеко идущее преимущество в [[metertype METER_INDUSTRY]] + PRO_INDUSTRY_CENTER_I Индустриальные центры +PRO_INDUSTRY_CENTER_I_DESC +'''Координация производственной деятельности между планетами и звездными системами может быть эффективно выполнена на крупных централизованных комплексах + +Большие затраты и время строительства объекта, а также значительный потенциал будущего роста требует тщательного планирования для создания в месте с идеальной будущей полезностью. Промышленные центры имеют большие авансовые затраты как в производственных ресурсах, так и во времени, но с большими возможностями для возможного расширения. Это делает их идеальными для долгосрочных стратегий, но непривлекательными для империй планирующих быструю победу''' + PRO_INDUSTRY_MOD Множитель промышленности SPY_LIGHTHOUSE -Межзвездные Маяки +Межзвездные маяки SPY_LIGHTHOUSE_DESC Можно использовать сложные диаграммы ЭМ-импульсов и потоки частиц против особо секретных объектов, которые при умелом обращении со специальным оборудованием будут освещены и обнаружены. +DEF_DEFENSE +Планетарная оборона + DEF_DEFENSE_NET_1 -Сеть Планетарной Обороны 1 +Сеть планетарной обороны 1 DEF_DEFENSE_NET_1_DESC -'''Увеличивает Макс [[metertype METER_DEFENSE]] на всех планетах на 5. +'''Увеличивает максимальную [[metertype METER_DEFENSE]] на всех планетах на 5 -С галактическими исследованиями приходят новые расы... и новые враги! Разместив на орбите планеты несколько вооруженных оборонных баз, планета станет обороноспособна без потребности во флоте.''' +С исследованиями галактики приходят встречи с новыми расами... и новыми врагами! Разместив на орбите планеты несколько вооруженных оборонных баз, планета станет обороноспособной без потребности во флоте''' DEF_DEFENSE_NET_2 -Сеть Планетарной Обороны 2 +Сеть планетарной обороны 2 DEF_DEFENSE_NET_2_DESC -Увеличивает Макс [[metertype METER_DEFENSE]] на всех планетах на 15, суммируется с [[tech DEF_DEFENSE_NET_1]]. +Увеличивает максимальную [[metertype METER_DEFENSE]] на всех планетах на 15, суммируется с [[tech DEF_DEFENSE_NET_1]] DEF_DEFENSE_NET_3 -Сеть Планетарной Обороны 3 +Сеть планетарной обороны 3 DEF_DEFENSE_NET_3_DESC -Увеличивает Макс [[metertype METER_DEFENSE]] на всех планетах на 25, суммируется со всеми Технологиями обороной сети. +Увеличивает максимальную [[metertype METER_DEFENSE]] на всех планетах на 25, суммируется со всеми Технологиями обороной сети DEF_DEFENSE_NET_REGEN_1 -Регенерация обороной Сети 1 +Регенерация обороной сети 1 DEF_DEFENSE_NET_REGEN_1_DESC -'''Восстанавливает [[metertype METER_DEFENSE]] планеты на 10% от её максимального значения каждый ход до максимального значения. +'''Восстанавливает [[metertype METER_DEFENSE]] планеты на 10% от ее максимального значения каждый ход до максимального значения -Регенерация защиты позволяет функционировать планетарной обороне сразу после ее уничтожения во время орбитальной бомбардировки. В противном случае, если бомбардировки не было, то и регенерация не сработает..''' +Регенерация защиты позволяет функционировать планетарной обороне сразу после ее уничтожения во время орбитальной бомбардировки. В противном случае, если бомбардировки не было, то и регенерация не сработает''' DEF_DEFENSE_NET_REGEN_2 -Регенерация обороной Сети 2 +Регенерация обороной сети 2 DEF_DEFENSE_NET_REGEN_2_DESC -Восстанавливает [[metertype METER_DEFENSE]] планеты на (i) 0.25*большее из значений [[metertype METER_CONSTRUCTION]] планеты или (ii) 0.1*Макс [[metertype METER_DEFENSE]] каждый ход до максимального значения, суммируется с регенерацией от [[tech DEF_DEFENSE_NET_REGEN_1]]. +Восстанавливает [[metertype METER_DEFENSE]] планеты на (i) 0.25*большее из значений [[metertype METER_CONSTRUCTION]] планеты или (ii) 0.1*Макс [[metertype METER_DEFENSE]] каждый ход до максимального значения, суммируется с регенерацией от [[tech DEF_DEFENSE_NET_REGEN_1]] DEF_PLAN_BARRIER_SHLD_1 -Планетарный защитный Щит 1 +Планетарный защитный щит 1 DEF_PLAN_BARRIER_SHLD_1_DESC -Увеличивает Макс [[metertype METER_SHIELD]] на планетах на 30, суммируется с [[tech LRN_FORCE_FIELD]], и восстанавливает каждый ход на единицу плюс 25% от размера инфраструктуры планеты. +Увеличивает максимальный [[metertype METER_SHIELD]] на планетах на 30, суммируется с [[tech LRN_FORCE_FIELD]] и восстанавливает каждый ход на единицу плюс 25% от размера инфраструктуры планеты DEF_PLAN_BARRIER_SHLD_2 -Планетарный защитный Щит 2 +Планетарный защитный щит 2 DEF_PLAN_BARRIER_SHLD_2_DESC -Увеличивает Макс [[metertype METER_SHIELD]] на планетах на 60, суммируется с другими Технологиями защитных щитов, и восстанавливает каждый ход на единицу плюс большее значение от размера инфраструктуры планеты или 4 (пока щиты не пробиты ниже 3). +Увеличивает максимальный [[metertype METER_SHIELD]] на планетах на 60, суммируется с другими Технологиями защитных щитов и восстанавливает каждый ход на единицу плюс большее значение от размера [[metertype METER_CONSTRUCTION]] планеты или 4 (пока щиты не пробиты ниже 3) DEF_PLAN_BARRIER_SHLD_3 -Планетарный защитный Щит 3 +Планетарный защитный щит 3 DEF_PLAN_BARRIER_SHLD_3_DESC -Увеличивает Макс [[metertype METER_SHIELD]] на планетах на 90, суммируется с другими Технологиями защитных щитов, и восстанавливает каждый ход на единицу плюс двойное большее значение от размера инфраструктуры планеты или 9 (пока щиты не пробиты ниже 5). +Увеличивает максимальный [[metertype METER_SHIELD]] на планетах на 90, суммируется с другими Технологиями защитных щитов и восстанавливает каждый ход на единицу плюс двойное большее значение от размера [[metertype METER_CONSTRUCTION]] планеты или 9 (пока щиты не пробиты ниже 5) DEF_PLAN_BARRIER_SHLD_4 -Планетарный защитный Щит 4 +Планетарный защитный щит 4 DEF_PLAN_BARRIER_SHLD_4_DESC -Увеличивает Макс [[metertype METER_SHIELD]] на планетах на 150, суммируется с другими Технологиями защитных щитов, и восстанавливает каждый ход на единицу плюс 2.5 * большее значение от размера инфраструктуры планеты или 18 (пока щиты не пробиты ниже 9). +Увеличивает максимальный [[metertype METER_SHIELD]] на планетах на 150, суммируется с другими Технологиями защитных щитов и восстанавливает каждый ход на единицу плюс 2.5 * большее значение от размера [[metertype METER_CONSTRUCTION]] планеты или 18 (пока щиты не пробиты ниже 9) DEF_PLAN_BARRIER_SHLD_5 -Планетарный защитный Щит 5 +Планетарный защитный щит 5 DEF_PLAN_BARRIER_SHLD_5_DESC -Дополнительно увеличивает Макс [[metertype METER_SHIELD]] на планетах на 150, суммируется с другими Технологиями защитных щитов, и восстанавливает каждый ход на единицу плюс 2.5 * большее значение от размера инфраструктуры планеты или 32 (пока щиты не пробиты ниже 14). +Дополнительно увеличивает максимальный [[metertype METER_SHIELD]] на планетах дополнительно 150, суммируется с другими Технологиями защитных щитов и восстанавливает каждый ход на единицу плюс 5 * большее значение от размера [[metertype METER_CONSTRUCTION]] планеты или 32 (пока щиты не пробиты ниже 14) DEF_SYST_DEF_MINE_1 -Минная Система обороны 1 +Минная система обороны 1 DEF_SYST_DEF_MINE_1_DESC -'''Когда дружественные корабли отсутствуют в системе, вражеские корабли могут свободно блокировать систему, останавливая передачу ресурсов, без обращения непосредственно к обороне планеты, таким образом, без риска получить повреждения от нее. Сеть минных систем обороны может сдерживать эту блокаду, повреждая вражеских кораблей каждый ход, пока они находятся в системе. +'''Когда дружественные корабли отсутствуют в системе, вражеские корабли могут свободно блокировать систему, останавливая передачу ресурсов, без непосредственного участия обороны планеты и таким образом, без риска получить повреждения от нее. Минная сеть системы обороны может сдерживать эту блокаду, повреждая вражеских кораблей каждый ход, пока они находятся в системе -[[metertype METER_STRUCTURE]] всех вражеских кораблей, присутствующих в системе империи, которая изучила данную технологию, будет снижаться на 2 за ход. +[[metertype METER_STRUCTURE]] всех вражеских кораблей, присутствующих в системе империи, которая изучила данную технологию, будет снижаться на 2 за ход -Мины восстанавливаются каждый ход, поэтому они неисчерпаемы.''' +Мины восстанавливаются каждый ход, поэтому они неисчерпаемы''' DEF_SYST_DEF_MINE_2 -Минная Система обороны 2 +Минная система обороны 2 DEF_SYST_DEF_MINE_2_DESC -Снижает [[metertype METER_STRUCTURE]] вражеских кораблей дополнительно на 4 за ход, суммируется с [[tech DEF_SYST_DEF_MINE_1]]. +Снижает [[metertype METER_STRUCTURE]] вражеских кораблей дополнительно на 4 за ход, суммируется с [[tech DEF_SYST_DEF_MINE_1]] DEF_SYST_DEF_MINE_3 -Минная Система обороны 3 +Минная система обороны 3 DEF_SYST_DEF_MINE_3_DESC -Снижает [[metertype METER_STRUCTURE]] вражеских кораблей дополнительно на 4 за ход, суммируется со всеми предыдущими технологиями Минной системы обороны. +Снижает [[metertype METER_STRUCTURE]] вражеских кораблей дополнительно на 8 за ход, суммируется со всеми предыдущими технологиями Минной системы обороны DEF_ROOT_DEFENSE Самооборона DEF_ROOT_DEFENSE_DESC -'''Увеличивает Макс [[metertype METER_TROOPS]] на всех планетах на 2. +'''Увеличивает максимальное значение [[metertype METER_SHIELD]] на 1 на каждой принадлежащей планете. Для видов с базовыми оборонительными войсками увеличивает максимальную величину [[metertype METER_TROOPS]] на планете на 0.2 за единицу популяции -Понятие «Самооборона» лежит в основе многих направлений научно-технического прогресса.''' +Понятие «Самооборона» лежит в основе многих направлений научно-технического прогресса''' DEF_GARRISON_1 Планетарный гарнизон 1 DEF_GARRISON_1_DESC -Увеличивает Макс [[metertype METER_TROOPS]] на всех планетах на 10. +Увеличивает максимальные [[metertype METER_TROOPS]] на всех планетах на 10 DEF_GARRISON_2 Планетарный гарнизон 2 DEF_GARRISON_2_DESC -Увеличивает Макс [[metertype METER_TROOPS]] на всех планетах на 0,4*Население и дополнительно восстанавливает одно войско за ход. +Увеличивает максимальные [[metertype METER_TROOPS]] на всех планетах на 0.4 за единицу населения (модифицируется по видовым признакам) и дополнительно восстанавливает одно войско за ход DEF_GARRISON_3 Планетарный гарнизон 3 DEF_GARRISON_3_DESC -Увеличивает Макс [[metertype METER_TROOPS]] на всех планетах на 16 и дополнительно восстанавливает два войска за ход, суммируется с [[DEF_GARRISON_2]]. +Увеличивает максимальные [[metertype METER_TROOPS]] на всех планетах на 16 (не модифицируется по видовым признакам) и дополнительно восстанавливает два войска за ход, суммируется с [[DEF_GARRISON_2]] DEF_GARRISON_4 Планетарный гарнизон 4 DEF_GARRISON_4_DESC -Увеличивает Макс [[metertype METER_TROOPS]] на всех планетах на 0,4*Население и дополнительно восстанавливает три войска за ход, суммируется с [[DEF_GARRISON_2]] и [[DEF_GARRISON_3]]. +Увеличивает максимальные [[metertype METER_TROOPS]] на всех планетах на 0.4 за единицу населения (модифицируется по видовым признакам) и дополнительно восстанавливает три войска за ход, суммируется с [[DEF_GARRISON_2]] и [[DEF_GARRISON_3]] DEF_PLANET_CLOAK Планетарные устройства маскировки DEF_PLANET_CLOAK_DESC -Согласно фазовым частям пространственно-временного континуума планеты, ее [[metertype METER_STEALTH]] усиливается. +Путем фазирования пространственно-временного континуума частей планеты, ее [[metertype METER_STEALTH]] усиливается значительно SHP_FLEET_REPAIR -'''Ремонтное Поле флота ''' +Ремонтная база флота SHP_FLEET_REPAIR_DESC -Позволяет кораблям в радиусе [[metertype METER_SUPPLY]] ремонтировать повреждения в размере 10% от [[metertype METER_STRUCTURE]] за ход. Суммируется с [[tech SHP_BASIC_DAM_CONT]]. Не работает сразу после сражения. +Позволяет кораблям в радиусе [[metertype METER_SUPPLY]] ремонтировать повреждения в размере 10% от [[metertype METER_STRUCTURE]] за ход. Суммируется с [[tech SHP_BASIC_DAM_CONT]]. Не работает сразу после сражения только через ход SHP_REINFORCED_HULL -Усиленный Корпус +Усиленный корпус SHP_REINFORCED_HULL_DESC -'''Увеличивает на 5 жизнь всех кораблей. +'''Увеличивает максимальную [[metertype METER_STRUCTURE]] всех кораблей на 5 -Стандартные корпуса корабля спроектированы и построены с помощью передовых микрогравитационных архитектурных методов и высокопрочных материалов. Однако, ограничения физической материи накладывают жесткие ограничения на прочность корпусов. Увеличивая поле структурной целостности корабля, можно получить значительное увеличение прочности.''' +Стандартные корпуса корабля спроектированы и построены с помощью передовых микрогравитационных архитектурных методов и высокопрочных материалов. Однако, ограничения физической материи накладывают жесткие ограничения на прочность корпусов. Увеличивая поле структурной целостности корабля, можно получить значительное увеличение прочности''' SHP_BASIC_DAM_CONT -Базовый контроль Урона +Базовый контроль урона SHP_BASIC_DAM_CONT_DESC -Постепенно ремонтирует поврежденные корабли. Текущая структура всех кораблей пополняется со скоростью 1 за ход. +Постепенно ремонтирует поврежденные корабли. Текущая структура всех кораблей восполняется со скоростью 1 за ход SHP_ADV_DAM_CONT -Усовершенствованный контроль Урона +Усовершенствованный контроль урона SHP_ADV_DAM_CONT_DESC -Ремонтирует поврежденые корабли в размере 10% от [[metertype METER_STRUCTURE]] за ход. Суммируется с [[tech SHP_BASIC_DAM_CONT]] и [[tech SHP_FLEET_REPAIR]]. Не работает сразу после сражения. +Ремонтирует поврежденные корабли в размере 10% от [[metertype METER_STRUCTURE]] за ход. Суммируется с [[tech SHP_BASIC_DAM_CONT]] и [[tech SHP_FLEET_REPAIR]]. Не работает сразу после сражения только через ход SHP_ROOT_AGGRESSION -Агрессия +Нападение SHP_ROOT_AGGRESSION_DESC -Позоляет использовать базовое оружие и [[shippart GT_TROOP_POD]]. +Позволяет использовать базовое оружие и [[shippart GT_TROOP_POD]] + +SHP_BOMBARD +Бомбардировка + +SHP_BOMBARD_DESC +Позволяет развивать оружие планетарной бомбардировки + +##Fighter explanation, needs improving and a Pedia article writing +SHP_FIGHTERS_1 +Истребители и пусковые площадки + +SHP_FIGHTERS_1_DESC +'''Позволяет вашей империи строить пусковые площадки и корабли использующие ангары для истребителей. На корабле может быть только один тип ангара, однако, общее количество слотов для использования остается прежним. Каждый [[encyclopedia PC_FIGHTER_HANGAR]] имеет максимальную вместимость истребителей и все эти истребители будут иметь одинаковую силу (которая может быть изменена видовыми способностями и дальнейшими исследованиями) + +[[encyclopedia PC_FIGHTER_BAY]] может запустить несколько истребителей в каждом раунде боя. Эти истребителей станут и действующими целями и будут способны атаковать корабли и другие истребители в последующих раундах боя, но одно попадание из любого источника уничтожит его. В отличие от [[encyclopedia DAMAGE_TITLE]]а нанесенного обычным оружием, урон наносимый истребителями игнорирует [[metertype METER_SHIELD]] на военных кораблях (они стреляют внутри силового поля щитов) + +В конце боя авианосцы будут собирать оставшихся истребителей. Если авианосец находится в зоне [[metertype METER_SUPPLY]] сразу после того, как движение станет доступным, он пополнит ангар до максимума''' + +SHP_FIGHTERS_2 +Лазерные истребители + +SHP_FIGHTERS_2_DESC +Обновляет истребителей всех авианосцев в пределах [[metertype METER_SUPPLY]] до лазерного вооружения. [[encyclopedia DAMAGE_TITLE]] от истребителей в [[shippart FT_HANGAR_1]] увеличивается на 1, [[shippart FT_HANGAR_2]] увеличивается на 2 и [[shippart FT_HANGAR_3]] на 3 + +SHP_FIGHTERS_3 +Плазматические истребители + +SHP_FIGHTERS_3_DESC +Обновляет истребителей всех авианосцев в пределах [[metertype METER_SUPPLY]] до плазматического вооружения. [[encyclopedia DAMAGE_TITLE]] от истребителей в [[shippart FT_HANGAR_1]] увеличивается на 1, [[shippart FT_HANGAR_2]] увеличивается на 3 и [[shippart FT_HANGAR_3]] на 4 + +SHP_FIGHTERS_4 +Истребители луча смерти + +SHP_FIGHTERS_4_DESC +Обновляет истребителей всех авианосцев в пределах [[metertype METER_SUPPLY]] до вооружения лучом смерти. [[encyclopedia DAMAGE_TITLE]] от истребителей в [[shippart FT_HANGAR_1]] увеличивается на 1, [[shippart FT_HANGAR_2]] увеличивается на 5 и [[shippart FT_HANGAR_3]] на 7 SHP_WEAPON_1_2 -Электромагнитное Орудие 2 +Электромагнитное орудие 2 + +SHP_WEAPON_1_2_DESC +Обновляет детали [[shippart SR_WEAPON_1_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 4 SHP_WEAPON_1_3 -Электромагнитное Орудие 3 +Электромагнитное орудие 3 + +SHP_WEAPON_1_3_DESC +Обновляет детали [[shippart SR_WEAPON_1_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 5 SHP_WEAPON_1_4 -Электромагнитное Орудие 4 +Электромагнитное орудие 4 + +SHP_WEAPON_1_4_DESC +Обновляет детали [[shippart SR_WEAPON_1_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 6 SHP_WEAPON_2_1 Лазер 1 +SHP_WEAPON_2_1_DESC +Разблокирует [[shippart SR_WEAPON_2_1]] более мощное оружие корабля чем [[shippart SR_WEAPON_1_1]] + SHP_WEAPON_2_2 Лазер 2 +SHP_WEAPON_2_2_DESC +Обновляет детали [[shippart SR_WEAPON_2_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 7 + SHP_WEAPON_2_3 Лазер 3 +SHP_WEAPON_2_3_DESC +Обновляет детали [[shippart SR_WEAPON_2_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 9 + SHP_WEAPON_2_4 Лазер 4 +SHP_WEAPON_2_4_DESC +Обновляет детали [[shippart SR_WEAPON_2_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 11 + SHP_WEAPON_3_1 Плазменная пушка 1 +SHP_WEAPON_3_1_DESC +Разблокирует [[shippart SR_WEAPON_3_1]] более мощное оружие корабля чем [[shippart SR_WEAPON_2_1]] + SHP_WEAPON_3_2 Плазменная пушка 2 +SHP_WEAPON_3_2_DESC +Обновляет детали [[shippart SR_WEAPON_3_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 12 + SHP_WEAPON_3_3 Плазменная пушка 3 +SHP_WEAPON_3_3_DESC +Обновляет детали [[shippart SR_WEAPON_3_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 15 + SHP_WEAPON_3_4 Плазменная пушка 4 +SHP_WEAPON_3_4_DESC +Обновляет детали [[shippart SR_WEAPON_3_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 18 + SHP_WEAPON_4_1 Смертоносный луч 1 +SHP_WEAPON_4_1_DESC +Разблокирует [[shippart SR_WEAPON_4_1]] более мощное оружие корабля чем [[shippart SR_WEAPON_3_1]] + SHP_WEAPON_4_2 Смертоносный луч 2 +SHP_WEAPON_4_2_DESC +Обновляет детали [[shippart SR_WEAPON_4_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 20 + SHP_WEAPON_4_3 Смертоносный луч 3 +SHP_WEAPON_4_3_DESC +Обновляет детали [[shippart SR_WEAPON_4_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 25 + SHP_WEAPON_4_4 Смертоносный луч 4 +SHP_WEAPON_4_4_DESC +Обновляет детали [[shippart SR_WEAPON_4_1]] на всех кораблях в пределах [[metertype METER_SUPPLY]] (или при достижении данного предела позже). Улучшенная деталь оружия наносит урон от выстрела в размере 30 + SHP_ION_CANNON Ионное орудие +SHP_ION_CANNON_DESC +Разблокирует ионную пушку как деталь корабля + SHP_WEAPON_1 -Электромагнитное Орудие +Электромагнитное орудие + +SHP_WEAPON_1_DESC +С появлением оружия космической эпохи, сдвиг парадигмы имеет тенденцию уходить от метательных снарядов к оружию на основе энергии или частиц. Однако, поскольку способность манипулировать веществом постоянно возрастает, становится возможным стрельба массивными снарядами на релятивистских скоростях, более смертоносными чем большинство энергетического вооружения SHP_WEAPON_4 Смертоносный луч +SHP_WEAPON_4_DESC +Разрушительный пучок сверхмассивных частиц высокой энергии, создающий открытые смертельные трещины в пространстве-времени, а также оказывает невероятное энергетическое воздействие + +SHP_PULSE_LASER +Пульсирующий лазер + +SHP_PULSE_LASER_DESC +Разблокирует пульсирующий лазер как деталь корабля + +SHP_PHASOR +Фазор + +SHP_PHASOR_DESC +Разблокирует фазор как деталь корабля + +SHP_INTERCEPTOR +Перехватчик + +SHP_INTERCEPTOR_DESC +Разблокирует перехватчик как возможную часть корабля + +SHP_BOMBER +Бомбардировщик + +SHP_BOMBER_DESC +Разблокирует бомбардировщик как возможную часть корабля + +SHP_RECON_FIGHT +Разведывательный истребитель + +SHP_RECON_FIGHT_DESC +Разблокирует разведывательный истребитель как возможную часть корабля + SHP_NUCLEAR_MISSILE Ядерная ракета +SHP_NUCLEAR_MISSILE_DESC +Разблокирует ядерную ракету как деталь корабля + +SHP_NEUTRONIUM_PLATE_NUC_MIS +Нейтронированная ядерная ракета + +SHP_NEUTRONIUM_PLATE_NUC_MIS_DESC +Разблокирует нейтронированную ядерную ракету как деталь корабля + +SHP_SPECTRAL_MISSILE +Спектральная ракета + +SHP_SPECTRAL_MISSILE_DESC +Разблокирует спектральную ракету как деталь корабля + +SHP_NEUTRONIUM_PLATE_SPEC_MIS +Спектральная ракета с нейтроновым покрытием + +SHP_NEUTRONIUM_PLATE_SPEC_MIS_DESC +Разблокирует спектральную ракету с нейтроновым покрытием как деталь корабля + SHP_MULTISPEC_SHIELD -Мультиспектральный Щит +Мультиспектральный щит + +SHP_MULTISPEC_SHIELD_DESC +Обеспечивает адекватное экранирование, позволяющее кораблю проникать в сердце звезды. Корабли в системе со звездным типом, отличным от [[STAR_BLACK]] и [[STAR_NEUTRON]], получают мощный бонус [[metertype METER_STEALTH]] SPY_DETECT_1 -Оптический Сканер +Оптический сканер + +SPY_DETECT_1_DESC +Все колонии начинают игру с уровнем [[metertype METER_DETECTION]] 50 и общее [[encyclopedia DETECTION_TITLE]] империи 10, а деталь корабля [[shippart DT_DETECTOR_1]] позволяет судам достичь такого же уровня обнаружения SPY_DETECT_2 Активная радиолокация +SPY_DETECT_2_DESC +'''Разблокирует деталь корабля [[shippart DT_DETECTOR_2]] и здание [[buildingtype BLD_SCANNING_FACILITY]], увеличивает [[metertype METER_DETECTION]] всех планет до 75 и общее [[encyclopedia DETECTION_TITLE]] империи до 30 + +Пассивно полагаясь на поступающее электромагнитное излучение для информации об объектах вблизи, является непродуманным и непрактичным, так как это излучение может быть замаскировано или ослаблено. Испуская электромагнитное излучение и обнаруживая возвращающееся излучение, можно определить местоположение и скорость многих объектов''' + SPY_DETECT_3 -Нейтронный Сканер +Нейтронный сканер + +SPY_DETECT_3_DESC +'''Разблокирует деталь корабля [[shippart DT_DETECTOR_3]], увеличивает [[metertype METER_DETECTION]] всех планет до 150 и общее [[encyclopedia DETECTION_TITLE]] империи до 50 + +Электромагнитное излучение не имеет массы покоя и легко манипулируемо. Просто изгибая волны света вокруг объекта, можно сделать радарные устройства практически бесполезными. При возбуждении волн нейтронов вместо электромагнитного излучения можно использовать более эффективное активное сканирование''' SPY_DETECT_4 Сенсоры +SPY_DETECT_4_DESC +Разблокирует деталь корабля [[shippart DT_DETECTOR_4]], увеличивает [[metertype METER_DETECTION]] всех планет до 200 и общее [[encyclopedia DETECTION_TITLE]] империи до 70 + SPY_DETECT_5 -Омни-Сканер +Омни-сканер + +SPY_DETECT_5_DESC +Максимальное обнаружение. Увеличивает [[metertype METER_DETECTION]] всех планет до 300 и общее [[encyclopedia DETECTION_TITLE]] империи до 200 SPY_STEALTH_1 Электромагнитный демпфер +SPY_STEALTH_1_DESC +'''Дает всем планетам и аванпостам в империи особенность [[special CLOUD_COVER_SLAVE_SPECIAL]], которая увеличивает [[metertype METER_STEALTH]] всех планет и зданий на 20 + +Стоимость исследования этой технологии снижается за счет исследования [[tech SPY_STEALTH_PART_1]] если оно уже завершено''' + +SPY_STEALTH_PART_1 +Электромагнитные гасители + +SPY_STEALTH_PART_1_DESC +Устранение электромагнитных излучений может сделать объект полностью невидимым для пассивного сканирования + SPY_STEALTH_2 Поглощающие поля +SPY_STEALTH_2_DESC +'''Дает всем планетам и аванпостам в империи особенность [[special VOLCANIC_ASH_SLAVE_SPECIAL]], которая увеличивает [[metertype METER_STEALTH]] всех планет и зданий на 40 + +Стоимость исследования этой технологии снижается за счет исследования [[tech SPY_STEALTH_PART_2]] если оно уже завершено''' + +SPY_STEALTH_PART_2 +Радиационное поглощение + +SPY_STEALTH_PART_2_DESC +Простое гашение электромагнитного излучения может сделать объект полностью невидимым для пассивного сканирования. Однако активное сканирование может сделать такие методы скрытия объекта почти бесполезными. Благодаря поглощению, а не отражению излучения, испускаемого активными сканирующими устройствами, [[metertype METER_STEALTH]] корабля может быть значительно увеличено + SPY_STEALTH_3 Пространственная маскировка +SPY_STEALTH_3_DESC +'''Дает всем планетам и аванпостам в империи особенность [[special DIM_RIFT_SLAVE_SPECIAL]], которая увеличивает [[metertype METER_STEALTH]] всех планет и зданий на 60 + +Стоимость исследования этой технологии снижается за счет исследования [[tech SPY_STEALTH_PART_3]] если оно уже завершено''' + +SPY_STEALTH_PART_3 +Пространственная маскировка + +SPY_STEALTH_PART_3_DESC +Передовые методы позволяют частично скрывать корабли внутри пространственных трещин + SPY_STEALTH_4 Фазовая маскировка +SPY_STEALTH_4_DESC +'''Разблокирует деталь корабля [[shippart ST_CLOAK_4]] и дает всем планетам и аванпостам в империи особенность [[special VOID_SLAVE_SPECIAL]], которая увеличивает [[metertype METER_STEALTH]] всех планет и зданий на 80 + +Даже самый тщательно скрытый корабль испускает излучение в виде частиц воздействия объекта. Модулируя волновые формы этих частиц, можно синхронизировать их с фоновым излучением, препятствуя даже очень продвинутым попыткам обнаружения корабля''' + +SPY_CUSTOM_ADVISORIES +Имперское разведывательное управление + +SPY_CUSTOM_ADVISORIES_DESC +'''Агентство разведки империи собирает и систематизирует информацию из многих источников, включая шпионов, общих информаторов и официальных командных отчетов. Наиболее важные элементы информации представляются каждый ход в виде отчетов (SitReps). Если отчеты повторяется ход за ходом и не несут вам действительно нужной для запоминания информации, вы можете игнорировать сводку для текущего хода, дважды щелкнув по его значку, а для отключения повторения на многократных ходах с помощью меню правой кнопки мыши на значке сводки + +Многие из отчетов определяются автоматически, но дополнительные пользовательские сводки могут быть заданы путем редактирования файла default/customizations/custom_sitreps.txt +(Примечание: в многопользовательской игре файлы отчетов содержащиеся на сервере являются теми, которые решено передавать империям) +Более подробные сведения о скриптовом языке FreeOrion, например, для этих пользовательских отчетов, можно найти в нашей вики: http://www.freeorion.org/index.php/Free_Orion_Content_Script_(FOCS) +Есть четыре представленных (возможно переведенных) метки для использования с пользовательскими отчетами, чтобы они размещались в верхней части окна сводки. Более подробную информацию о том, как использовать метки, можно найти в вики и в custom_sitreps.txt +Эти четыре метки: +"[[CUSTOM_1]]" +"[[CUSTOM_2]]" +"[[CUSTOM_3]]" +"[[CUSTOM_4]]" +''' + SHP_IMPROVED_ENGINE_COUPLINGS Улучшенная связка двигателей +SHP_IMPROVED_ENGINE_COUPLINGS_DESC +'''[[COLONY_BUILDING_TIME_DECREASE]] + +Лучшие двигательные части приравниваются к лучшим двигателям. Увеличивает [[metertype METER_SPEED]] на 10. Может быть добавлено несколько штук в корпус''' + SHP_N_DIMENSIONAL_ENGINE_MATRIX N-мерная матрица двигателя +SHP_N_DIMENSIONAL_ENGINE_MATRIX_DESC +'''[[COLONY_BUILDING_TIME_DECREASE]] + +Исследование N-мерного подпространства привело к созданию более эффективной двигательной матрицы. Увеличивает [[metertype METER_SPEED]] на 20. Может быть добавлено несколько штук в корпус''' + SHP_SINGULARITY_ENGINE_CORE -Основной Сингулярный Двигатель +Сингулярный двигатель ядра + +SHP_SINGULARITY_ENGINE_CORE_DESC +'''[[COLONY_BUILDING_TIME_DECREASE]] + +Близкий родственник [[buildingtype BLD_HYPER_DAM]] разработанный для кораблей. Сингулярный двигатель ядра значительно повышает мощность. Увеличивает [[metertype METER_SPEED]] на 30. Может быть добавлено несколько штук в корпус''' SHP_DEUTERIUM_TANK Емкость для дейтерия +SHP_DEUTERIUM_TANK_DESC +Разблокирует [[shippart FU_BASIC_TANK]] как одну из возможных деталей корабля + SHP_ANTIMATTER_TANK Емкость для антиматерий +SHP_ANTIMATTER_TANK_DESC +Разблокирует [[shippart FU_BASIC_TANK]] как одну из возможных деталей корабля + +SHP_ZERO_POINT +Генератор топлива с нулевой точкой + +SHP_ZERO_POINT_DESC +Мощный топливный модуль с нулевой точкой, который автоматически заправляет судно + +SHP_SPINAL_ANTIMATTER +Спинальная антиматериальная пушка + +SHP_SPINAL_ANTIMATTER_DESC +Огромный спинальный калибр, стреляющий тяжелыми снарядами из антивещества. Требуется центральный слот [[encyclopedia SLOT_TITLE]] для установки + SHP_ROOT_ARMOR Броня +SHP_ROOT_ARMOR_DESC +Исследование специальных сплавов, которые позволяют оборудовать корпуса корабля [[encyclopedia ARMOR_TITLE]], значительно повышающих их [[metertype METER_STRUCTURE]], особенно в битве + SHP_ZORTRIUM_PLATE -Зортриумная Броня +Зортриумная броня + +SHP_ZORTRIUM_PLATE_DESC +Благодаря усовершенствованию методик конструирования [[encyclopedia ARMOR_TITLE]], можно использовать более прочные специальные материалы, дающие более сильные бронированные части SHP_DIAMOND_PLATE -Алмазная Броня +Алмазная броня + +SHP_DIAMOND_PLATE_DESC +Передовые технологии обработки позволяют создать [[encyclopedia ARMOR_TITLE]] из огромных синтетических алмазов, что обеспечивает очень прочные бронированные части SHP_XENTRONIUM_PLATE -Ксентрониумная Броня +Ксентрониумная броня + +SHP_XENTRONIUM_PLATE_DESC +Ксентрониум один из самых прочных известных материалов, но также очень сложных в обработке. Освоение искусства создания [[encyclopedia ARMOR_TITLE]] сделанное из ксентрониума позволит создавать чрезвычайно прочные части SHP_DEFLECTOR_SHIELD -Отражающий Щит +Отражающий щит + +SHP_DEFLECTOR_SHIELD_DESC +'''Продвинутая технология [[metertype METER_SHIELD]] корабля + +Благодаря усовершенствованию и совершенствованию базовой технологии защиты могут быть созданы более эффективные экранирующие генераторы''' SHP_PLASMA_SHIELD -Плазменный Щит +Плазменный щит + +SHP_PLASMA_SHIELD_DESC +'''Передовая технология [[metertype METER_SHIELD]] корабля + +Комбинируя методы контролируемого удержания плазмы и продвинутого силового поля, становится возможным создать то, что лучше всего можно описать как плазменный пузырь вокруг космического корабля. Этот плазменный пузырь способен поглощать большое количество энергии, частиц и снарядов''' SHP_BLACKSHIELD -Черный Щит +Черный щит + +SHP_BLACKSHIELD_DESC +'''Наиболее передовая технология [[metertype METER_SHIELD]] корабля + +Черный щит получил свое название, потому что создает то, что выглядит почти непроницаемой черной сферой вокруг объекта, который он обволакивает. Черный щит - это, по сути, какой-то пространственный разлом проецируемый вокруг космического корабля, который полностью поглощает почти все, что в него попадает. Требуется огромное количество энергии для пробивания черного экрана''' SPY_DIST_MOD -Модулятор Искажений +Модулятор искажений + +SPY_DIST_MOD_DESC +Все объекты с массой вызывают кривизну в пространстве-времени из-за гравитационных эффектов. Другие силы, такие как электромагнитная сила, имеют схожие геометрические эффекты. Не избирательно усиливая эти эффекты в локализованной области, пространственно-временные искажения, вызванные кораблями, можно легко отличить от фонового электромагнитного и гравитационного излучения SHP_ANTIMAT_TORP -Торпеды Антиматерии +Торпеды из антиматерии + +SHP_ANTIMAT_TORP_DESC +Быстрая ракета дальнего действия, работающая на [[tech SHP_SPACE_FLUX_DRIVE]] и вооруженная мощной боеголовкой на антиматерии SHP_PLASMA_TORP -Плазменные Торпеды +Плазменные торпеды + +SHP_PLASMA_TORP_DESC +Быстрая ракета дальнего действия с отличной [[metertype METER_STEALTH]], работающая на [[tech SHP_TRANSSPACE_DRIVE]] и вооруженная разрушительно мощной плазменной боеголовкой SHP_CAMO_AST_HULL -Камуфляж Корпуса Астероида +Камуфляж корпуса астероида + +SHP_CAMO_AST_HULL_DESC +Этот корпус спроектирован так, чтобы выглядеть как настоящий астероид и получает огромный бонус к [[metertype METER_STEALTH]] на карте галактики, когда он находится в системе с поясом астероидов и в бою, когда он прячется в поясе астероидов + +SHP_MINIAST_SWARM +Мини-астероидный рой + +SHP_MINIAST_SWARM_DESC +Этот корпус построен из небольшого астероида, большая часть которого была раздроблена, чтобы образовать многочисленные крошечные астероиды, которые контролируются основным корпусом SHP_SCAT_AST_HULL -Рассеянный Корпус Астероида +Рассеянный корпус астероида + +SHP_SCAT_AST_HULL_DESC +Этот флагман построен из нескольких крупных астероидов, некоторые из которых были объединены в основной корпус, а некоторые из них были расщеплены на части, чтобы образовать многочисленные крошечные астероиды, которые контролируются основным корпусом. Эти астероиды защищают не только главный корабль, но и все сопутствующие дружественные корабли, давая бонус [[metertype METER_SHIELD]] всем принадлежащим кораблям и кораблям союзников поблизости SHP_ORG_HULL -Органический Корпус +Органический корпус + +SHP_ORG_HULL_DESC +'''Если раньше не были исследованы более быстрые корпуса кораблей, эта технология сокращает время, чтобы построить колонию, расположенную далеко от соответствующих видов в империи + +Это основной тип органического корпуса: живой корпус, выращенный из самой основного элемента жизни и адаптированный для сражений''' SHP_ENDOSYMB_HULL -Эндосимбиотический Корпус +Эндосимбиотический корпус + +SHP_ENDOSYMB_HULL_DESC +Этот моно ячеечный корпус существует в симбиотических отношениях с его экипажем, откладывая их в своей цитоплазме и используя их в качестве органелл SHP_SENT_HULL -Разумный Корпус +Разумный корпус + +SHP_SENT_HULL_DESC +Этот флагман с самосознанием обладает мощными аналитическими способностями и огромной емкостью памяти, которая позволяют ему управлять дружественными кораблями и предоставлять им полезную информацию и малозаметные тактические сигналы в бою SHP_BIOINTERCEPTOR -Био-Перехватчики +Био-перехватчики + +SHP_BIOINTERCEPTOR_DESC +Живые перехватчики предназначены для уничтожения ракет и вражеских истребителей. Гораздо эффективнее обычных перехватчиков. [[BLD_SHIPYARD_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED_ANY_SYSTEM]] + +SHP_BIOBOMBER +Био-бомбардировщики + +SHP_BIOBOMBER_DESC +Живые бомбардировщики предназначены для нацеливания на корабли и планеты. Гораздо эффективнее обычных бомбардировщиков. [[BLD_BIONEURAL_REQUIRED_ANY_SYSTEM]] SHP_NOVA_BOMB Бомба Нова +SHP_NOVA_BOMB_DESC +Самое мощное оружие, когда-либо созданное, способное уничтожить всю звездную систему + SHP_DEATH_SPORE -'''Споры Смерти ''' +Споры смерти + +SHP_DEATH_SPORE_DESC +Разблокирует [[shippart SP_DEATH_SPORE]] как одну из возможных деталей корабля, [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_ORGANIC_POP]] SHP_BIOTERM -Био-Терминаторы +Био-терминаторы + +SHP_BIOTERM_DESC +Разблокирует [[shippart SP_BIOTERM]] как одну из возможных деталей корабля, [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_ORGANIC_POP]] + +SHP_EMP +EMP генератор + +SHP_EMP_DESC +Разблокирует [[shippart SP_EMP]] как одну из возможных деталей корабля, [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_ROBOTIC_POP]] + +SHP_EMO +EMO генератор + +SHP_EMO_DESC +Разблокирует [[shippart SP_EMO]] как одну из возможных деталей корабля, [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_ROBOTIC_POP]] + +SHP_SONIC +Звуковая ударная волна + +SHP_SONIC_DESC +Разблокирует [[shippart SP_SONIC]] как одну из возможных деталей корабля, [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_LITHIC_POP]] + +SHP_GRV +Гравитонный импульс + +SHP_GRV_DESC +Разблокирует [[shippart SP_GRV]] как одну из возможных деталей корабля, [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_LITHIC_POP]] + +SHP_DARK_RAY +Темный луч + +SHP_DARK_RAY_DESC +Разблокирует [[shippart SP_DARK_RAY]] как одну из возможных деталей корабля, [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_PHOTOTROPHIC_POP]] + +SHP_VOID_SHADOW +Теневой вакуум + +SHP_VOID_SHADOW_DESC +Разблокирует [[shippart SP_VOID_SHADOW]] как одну из возможных деталей корабля, [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_PHOTOTROPHIC_POP]] + +SHP_CHAOS_WAVE +Волна хаоса + +SHP_CHAOS_WAVE_DESC +Разблокирует [[shippart SP_CHAOS_WAVE]] как одну из возможных деталей корабля, [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_ANY_POP]] SPY_PLANET_STEALTH_MOD -Модификатор Планетной Маскировки +Модификатор скрытности планеты + +SPY_PLANET_STEALTH_MOD_DESC +Увеличивает естественный [[metertype METER_STEALTH]] всех планет на 5, так что не возникает проблем между значениями обнаружения и невидимости +BASIC_PLANET_STEALTH +Универсальная базовая скрытность ## ## Technology refinement ## BUILDING_REFINE_SHORT_DESC -Совершенствует строение +Усовершенствовать строение TECH_REFINE_SHORT_DESC -Совершенствует технологию +Усовершенствовать технологию PRO_INDUSTRY_CENTER_II Улучшенный промышленный центр +PRO_INDUSTRY_CENTER_II_DESC +Улучшенное централизованное управление разбросанными промышленными предприятиями еще больше увеличивает объем промышленного производства. Бонус к [[metertype METER_INDUSTRY]] будет вдвое больше чем базовый [[buildingtype BLD_INDUSTRY_CENTER]] + PRO_INDUSTRY_CENTER_III Выдающийся промышленный центр +PRO_INDUSTRY_CENTER_III_DESC +Дополнительный опыт и усовершенствования средств еще больше расширяют размеры промышленного производства. Бонус к [[metertype METER_INDUSTRY]] будет втрое больше чем базовый [[buildingtype BLD_INDUSTRY_CENTER]] ## ## Buildings @@ -6211,1013 +11341,2135 @@ BLD_EVACUATION Переселение BLD_EVACUATION_DESC -'''Удаляет населения с этой планеты в течение нескольких ходов. Если подходящий альтернативный планеты доступны, с тем же видом и достаточно дополнительной места, население будет переселяться туда. ''' +Удаляет населения с этой планеты в течение нескольких ходов. Если подходящие альтернативные планеты доступны, с тем же видом и достаточным дополнительным местом, население будет переселяться туда BLD_OBSERVATORY Обсерватория +BLD_OBSERVATORY_DESC +'''Одна случайная звезда будет показываться каждый ход для всей галактики. Иногда может быть обнаружена звезда, которая уже видима / исследована, в результате чего новая звезда не станет известна империям в галактике + +Наблюдательный объект, предназначенный для обнаружения и отображения звезд в галактике, в частности тех, которые уже определенны и достижимы через сеть звездных путей''' + BLD_CULTURE_ARCHIVES Культурное наследие BLD_CULTURE_ARCHIVES_DESC -'''Увеличивает максимальное значение [[metertype METER_RESEARCH]] на 5 или [[metertype METER_INDUSTRY]] на половину числа населения при соответсвующей цели. +'''Увеличивает целевые [[metertype METER_RESEARCH]] на 5 и целевую [[metertype METER_INDUSTRY]] на половину числа населения при установке соответствующей цели + +Хранит накопленные тысячелетиями знания, давая прирост многим отраслям этой планеты''' -Хранит накопленные тысячелетиями знания, давая прирост многим отраслям этой планеты.''' +BLD_CULTURE_LIBRARY +Культурная библиотека + +BLD_CULTURE_LIBRARY_DESC +'''Увеличивает целевые [[metertype METER_RESEARCH]] на 5 + +Хранит накопленные знания из тысяч лет жизни на этой планете, предоставляя бонусы для исследований. Это здание будет разрушено, когда население высокотехнологичных аборигенов достигнет нуля''' + +BLD_AUTO_HISTORY_ANALYSER +Автоматический анализатор истории + +BLD_AUTO_HISTORY_ANALYSER_DESC +'''Автоматический анализатор истории может быть построен только на планете с [[buildingtype BLD_CULTURE_ARCHIVES]]. Увеличивает целевые [[metertype METER_RESEARCH]] на 5 + +Автоматизированный исследовательский центр с огромной вычислительной мощью, который просматривает цифровые культурные архивы, проводя междисциплинарные соответствия и сравнивая с шаблонами потерянных или незавершенных идей''' BLD_IMPERIAL_PALACE Дворец императора BLD_IMPERIAL_PALACE_DESC -'''Увеличивает дальность [[metertype METER_SUPPLY]] на 1, [[metertype METER_CONSTRUCTION]] на 20, [[metertype METER_DEFENSE]] на 5 и [[metertype METER_TROOPS]] на 3. Также обозначает столицу империи. +'''Увеличивает дальность [[metertype METER_SUPPLY]] на 2, [[metertype METER_CONSTRUCTION]] на 20, [[metertype METER_DEFENSE]] на 5 и [[metertype METER_TROOPS]] на 6. Также обозначает столицу империи + +Символизирует императорскую власть и престиж и выполняет функции центра управления для владения империей''' -Символизирует императорскую власть и престиж и выполняет функции центра управления для владения империей.''' +BLD_SUPER_TEST +Превращение в Супер-тестеров + +BLD_SUPER_TEST_DESC +Превращает расу планеты в расу Супер-тестеров. Для официальных целей тестирования или для обмана BLD_SHIPYARD_BASE Верфь BLD_SHIPYARD_BASE_DESC -Орбитальный производственный комплекс для сборки межзвездных космических аппаратов военного образца. Верфь должна присутствовать на планете для создания любого корабля,может быть модернизирована путем создания дополнительных зданий в той же системе. Без верфи,модернизации не может быть произведена. +Орбитальный производственный комплекс для сборки межзвездных космических аппаратов военного образца. Верфь должна присутствовать на планете для создания любого корабля, может быть модернизирована путем создания дополнительных строений в той же системе. Без верфи, модернизация не может быть произведена BLD_SHIPYARD_ORBITAL_DRYDOCK Орбитальные мастерские BLD_SHIPYARD_ORBITAL_DRYDOCK_DESC -'''Это здание - улучшение для [[buildingtype BLD_SHIPYARD_BASE]] и оно необходимо для постройки некоторых дальнейших улучшений [[buildingtype BLD_SHIPYARD_BASE]]. Это улучшение также предоставляет возможность ремонта поврежденных кораблей, проводя полный ремонт структурных повреждений за один ход, только если в системе не было битвы в этот ход. +'''Это обновление для [[buildingtype BLD_SHIPYARD_BASE]] и оно необходимо для постройки некоторых дальнейших улучшений. Это обновление также обеспечивает возможности ремонта поврежденных судов, восстанавливая их [[metertype METER_STRUCTURE]] на каждом ходе + +[[encyclopedia ORBITAL_DRYDOCK_REPAIR_TITLE]] улучшение также предоставляет возможность ремонта поврежденных кораблей, проводя полный ремонт структурных повреждений за один ход, только если уровень [metertype METER_HAPPINESS]] превышает 5 и если в системе не было битвы в этот ход -Когда строится большое межзвездное судно, не практично перемещать большие детали с поверхности планеты на Орбитальные мастерские вверх и вниз в космических лифтах. Целесообразней изготавливать детали на месте и собирать их в единую конструкцию на том же самом орбитальном заводе, тем самым исключая необходимость в транспортировке, особенно массивных частей корабля, с поверхности планеты.''' +Когда строится большое межзвездное судно, не практично перемещать большие детали с поверхности планеты на Орбитальные мастерские вверх и вниз в космических лифтах. Целесообразней изготавливать детали на месте и собирать их в единую конструкцию на том же самом орбитальном заводе, тем самым исключая необходимость в транспортировке, особенно массивных частей корабля с поверхности планеты''' BLD_SHIPYARD_CON_NANOROBO -Процессор Нанороботов +Процессор нанороботов BLD_SHIPYARD_CON_NANOROBO_DESC -'''Это здание - улучшение для [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] и позволяет изготавливать [[shiphull SH_NANOROBOTIC]] и с другими строениями [[shiphull SH_LOGISTICS_FACILITATOR]]. +'''Это улучшение до [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], позволяет изготавливать [[shiphull SH_NANOROBOTIC]] и с другими строениями [[shiphull SH_LOGISTICS_FACILITATOR]] -Производство миллионов нанороботов по обслуживанию и их связи с одним ведущим Искусственным интеллектом требует более обширных робототехнических навыков, чем производство базового роботизированного корпуса. Это здание в состоянии облегчить строительство более продвинутых роботизированных корпусов.''' +Производство миллионов нанороботов по обслуживанию и их связи с одним ведущим Искусственным интеллектом требует более обширных робототехнических навыков чем производство базового роботизированного корпуса. Это здание в состоянии облегчить строительство более продвинутых роботизированных корпусов''' BLD_SHIPYARD_CON_GEOINT -Гео-Интегральный процессор +Гео-интегральный процессор BLD_SHIPYARD_CON_GEOINT_DESC -'''Это здание - улучшение для [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] и позволяет изготавливать [[shiphull SH_TITANIC]] и [[shiphull SH_SELF_GRAVITATING]], а также с другими строениями [[shiphull SH_LOGISTICS_FACILITATOR]]. +'''Это улучшение до [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], позволяет изготавливать [[shiphull SH_TITANIC]] и [[shiphull SH_SELF_GRAVITATING]], а также с другими строениями [[shiphull SH_LOGISTICS_FACILITATOR]] -Когда верфь развивается в достаточно большую, становится непрактично ни для верфи, ни для планеты, на орбите которой она построена, существовать как два отдельных субъекта. Было бы полезнее интегрировать эти 2 "мира" в гармоничное целое, то есть расширить верфь и вывести её на геосинхронную поверхности орбиту, тем самым в полной мере использовать все геологические аспекты планеты.''' +Когда верфь развивается в достаточно большую, становится непрактично ни для верфи, ни для планеты, на орбите которой она построена, существовать как два отдельных субъекта. Было бы полезнее интегрировать эти 2 "мира" в гармоничное целое, то есть расширить верфь и вывести ее на геосинхронную поверхности орбиту, тем самым в полной мере использовать все геологические аспекты планеты''' BLD_SHIPYARD_CON_ADV_ENGINE Продвинутая инженерная ниша BLD_SHIPYARD_CON_ADV_ENGINE_DESC -'''Это здание - улучшение для [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] и позволяет изготавливать [[shiphull SH_TRANSSPATIAL]] и [[shippart FU_TRANSPATIAL_DRIVE]], а также с другими строениями [[shiphull SH_LOGISTICS_FACILITATOR]]. +'''Это улучшение до [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], позволяет изготавливать [[shiphull SH_TRANSSPATIAL]] и [[shippart FU_TRANSPATIAL_DRIVE]], а также с другими строениями [[shiphull SH_LOGISTICS_FACILITATOR]] -Чрезвычайно широкоразвитой инженерный объект, который специализируется на производстве Транс-Территориального Привода.''' +Чрезвычайно широко развитой инженерный объект, который специализируется на производстве Транс-территориального привода''' BLD_SHIPYARD_AST -Астероидный процессор +Астероидный переработчик BLD_SHIPYARD_AST_DESC -''' -Это здание требуется для производства всех астероидных корпусов и всех дальнейших обновлений астероидного процессора. Это здание может быть построено на [[encyclopedia OUTPOSTS_TITLE]]. +'''Это здание требуется для производства всех астероидных корпусов и всех дальнейших обновлений астероидного процессора. Это здание может быть построено только на поясе астероидов, который также может быть [[encyclopedia OUTPOSTS_TITLE]] -Массивный завод по переработке, целью которого - выдалбливание астероидов и подготовка их для использования в качестве корпусов кораблей. -Астероиды, полученные таким образом, направляются в [[buildingtype BLD_SHIPYARD_BASE]] в той же системе, где они превратятся в корпуса. -Это здание может быть построено только на поясе астероидов. -''' +Массивный завод по переработке, целью которого - выдалбливание астероидов и подготовка их для использования в качестве корпусов кораблей. Астероиды, полученные таким образом, направляются к [[buildingtype BLD_SHIPYARD_BASE]] в той же системе, где они будут преобразованы в конечном счете в корпус''' BLD_SHIPYARD_AST_REF -Преобразованный астероидный процессор +Улучшенный астероидный переработчик BLD_SHIPYARD_AST_REF_DESC -'''Это здание - улучшение для [[buildingtype BLD_SHIPYARD_AST]] и позволяет изготавливать [[shiphull SH_MINIASTEROID_SWARM]], [[shiphull SH_SCATTERED_ASTEROID]], и [[shiphull SH_CRYSTALLIZED_ASTEROID]]. Кроме того, этот улучшение процессора позволяет любой верфи империи, или верфи союзника производить корабли с [[shippart AR_ROCK_PLATE]] и [[shippart AR_CRYSTAL_PLATE]] в качестве своих частей. +'''Это улучшение до [[buildingtype BLD_SHIPYARD_AST]], позволяет изготавливать [[shiphull SH_MINIASTEROID_SWARM]], [[shiphull SH_SCATTERED_ASTEROID]] и [[shiphull SH_CRYSTALLIZED_ASTEROID]]. Кроме того, этот переработчик позволяет любой верфи империи или верфи союзника производить корабли с [[shippart AR_ROCK_PLATE]] и [[shippart AR_CRYSTAL_PLATE]] в качестве своих частей -Обширный завод, который с помощью слияния, разрушения, деконструкции и реконструкции уже существующих астероидов образует части корабля и подготавливает их для использования в качестве корпусов.''' +Обширный завод, который с помощью слияния, разрушения, деконструкции и реконструкции уже существующих астероидов образует части корабля и подготавливает их для использования в качестве корпусов''' BLD_SHIPYARD_ORG_ORB_INC Орбитальный инкубатор +BLD_SHIPYARD_ORG_ORB_INC_DESC +'''Это обновление до [[buildingtype BLD_SHIPYARD_BASE]], требуется для разработки всех органических корпусов и для производства всех дальнейших модернизаций судостроительной верфи. Сам по себе он может построить [[shiphull SH_ORGANIC]], [[shiphull SH_SYMBIOTIC]] и [[shiphull SH_STATIC_MULTICELLULAR]] корпуса + +Верфь, предназначенная для удовлетворения потребностей созревающих космических существ. Условия на верфи должны соответствовать строгим требованиям, чтобы корабли продолжали жить и развиваться. Таким образом, любые незавершенные живые корабли находящиеся внутри будут уничтожены, если судостроительная верфь существенно повреждена''' + +BLD_SHIPYARD_ORG_CELL_GRO_CHAMB +Клеточная камера роста + +BLD_SHIPYARD_ORG_CELL_GRO_CHAMB_DESC +'''Это обновление до [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], позволяет строительство [[shiphull SH_PROTOPLASMIC]] и [[shiphull SH_BIOADAPTIVE]]. Наличие этого обновления и [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] на одной и той же планете позволяет разработать [[shiphull SH_ENDOSYMBIOTIC]] и [[shiphull SH_SENTIENT]] + +Специальная модернизация судоверфи, способная увеличить размер моноклеточных организмов до действительно массивных размеров''' + +BLD_SHIPYARD_ORG_XENO_FAC +Ксенокоординационная установка + +BLD_SHIPYARD_ORG_XENO_FAC_DESC +'''Это обновление до [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], позволяет строительство [[shiphull SH_ENDOMORPHIC]] и [[shiphull SH_RAVENOUS]]. Наличие этого обновления и [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] на одной и той же планете позволяет разработать [[shiphull SH_ENDOSYMBIOTIC]] и [[shiphull SH_SENTIENT]] + +Строение, предназначенное для создания полностью симбиотических отношений между судном и его экипажем''' + BLD_SHIPYARD_ENRG_COMP -Компрессор Энергии +Компрессор энергии + +BLD_SHIPYARD_ENRG_COMP_DESC +'''Это обновление до [[buildingtype BLD_SHIPYARD_BASE]] и требуется для формирования всех энергетических корпусов и всех дальнейших модернизаций верфи энергетического корпуса + +Требуется излучающая высокоэнергетическая звезда и соответственно может быть построена только в системе со звездами типа [[STAR_BLUE]], [[STAR_WHITE]] или [[STAR_BLACK]] + +Высокоспециализированная верфь, предназначенная для создания и монтажа деталей на больших пучках плотно сжатой энергии. Это хрупкий процесс и верфь может мощно взорваться, если получит даже небольшое количество [[encyclopedia DAMAGE_TITLE]]''' + +BLD_SHIPYARD_ENRG_SOLAR +Блок солнечной локализации + +BLD_SHIPYARD_ENRG_SOLAR_DESC +'''Это обновление до [[buildingtype BLD_SHIPYARD_ENRG_COMP]] и позволяет создавать [[shiphull SH_SOLAR]] + +Требуется излучающая высокоэнергетическая звезда и соответственно может быть построена только в системе со звездами типа [[STAR_BLACK]] + +Задача создания миниатюрного солнца - гигантская задача сама по себе, а производство такого тела, на котором могут быть надежно закреплены судовые части тем более''' BLD_BIOTERROR_PROJECTOR -Биотеррор база +База проектирования биотеррора + +BLD_BIOTERROR_PROJECTOR_DESC +'''Дает планете на которой построен Биотеррор, возможность уменьшать популяцию на планетах противника в пределах 4 звездных прыжков со скоростью 2 за ход, но при условии, что вражеская империя не имеет Банк генома + +Эта подпольная биологическая военная база, которая разрушает вражеские планеты в окрестностях. Однако такие мероприятия не одобряются общественностью и этот объект может быть построен только на планете с резонансной луной (повернутой одной стороной к планете). Неэффективен, если во вражеской империи есть здание [[buildingtype BLD_GENOME_BANK]]]. Это здание также может быть построено на [[encyclopedia OUTPOSTS_TITLE]], если у него есть [[special RESONANT_MOON_SPECIAL]]''' BLD_LIGHTHOUSE -Межзвездный Маяк +Межзвездный маяк BLD_LIGHTHOUSE_DESC -Уменьшает [[metertype METER_STEALTH]] всех объектов в той же системе на 30 и увеличивает [[metertype METER_SPEED]] дружеских кораблей на 20, которые начинают ход в пределах 50uu от системы, на этот ход. Это здание можно построить в [[encyclopedia OUTPOSTS_TITLE]] и оно способствует использованию [[buildingtype BLD_PLANET_DRIVE]]. +Уменьшает [[metertype METER_STEALTH]] всех объектов в той же системе на 30 и увеличивает [[metertype METER_SPEED]] дружественных кораблей на 20, которые начинают ход в пределах 50 световых лет от системы, на этот ход. [[BUILDING_AVAILABLE_ON_OUTPOSTS]] и может способствовать использованию [[buildingtype BLD_PLANET_DRIVE]] BLD_SCANNING_FACILITY Сканирующий комплекс BLD_SCANNING_FACILITY_DESC -'''Увеличивает [[metertype METER_DETECTION]] планеты на 75. +'''Увеличивает [[metertype METER_DETECTION]] планеты на 75 -Сочетание устройств дальнего сканирования и большой вычислительной мощности для анализа данных обеспечивает большую область пространства, которое будет проверяться на любые объекты.''' +Сочетание устройств дальнего сканирования и большой вычислительной мощности для анализа данных обеспечивает большую область пространства, которая будет подвержена сканированию любых объектов''' BLD_INDUSTRY_CENTER -Промышленный Центр +Промышленный центр + +BLD_INDUSTRY_CENTER_DESC +'''С самого начала предоставляет бонус к [[metertype METER_INDUSTRY]] на всех планетах, соединенных линиями [[metertype METER_SUPPLY]], с фокусом производства на 0.2 за единицу населения +[[NO_STACK_SUPPLY_CONNECTION_TEXT]] + +Усовершенствование [[tech PRO_INDUSTRY_CENTER_II]] удваивает бонус + +Дальнейшее усовершенствование [[tech PRO_INDUSTRY_CENTER_III]] утраивает базовый бонус''' BLD_MEGALITH Мегалит +BLD_MEGALITH_DESC +'''Это здание может быть построено только в столице империи, где оно кроме всего прочего добавляет величие [[buildingtype BLD_IMPERIAL_PALACE]]. Все измерители ресурсов планеты, на которой он построен, способны достичь цели за один ход. Эта планета также получает увеличение [[metertype METER_CONSTRUCTION]] на 30. Населенные планеты империи владельца получают увеличение диапазона [[metertype METER_SUPPLY]] на 1. Населенные планеты в пределах двух прыжков по звездному пути имеют [[metertype METER_TROOPS]] усиленные на 10 + +Мегалит - неестественно массивный звездоскреб, многокилометровый в диаметре и вдохновляющий архитекторов в масштабах всей империи''' + BLD_SPACE_ELEVATOR Лифт на орбиту BLD_SPACE_ELEVATOR_DESC -'''Удваивает дальность [[metertype METER_SUPPLY]] с планеты, на которой он построен. +'''Заменяет штраф / бонус [[metertype METER_SUPPLY]] вызванный размером планеты с бонусом +3 -Нано-конструкция позволяет производство растяжимых тросов достаточной прочности, чтобы выйти за пределы геостационарной орбиты. Трансферы с помощью магнитно-подъемных тросов превосходят дорогие, неэффективные и особо опасные запуски ракет с космической скоростью. Огромные и практически неразрушимые пучки тросов прикрепляют к спутнику на геостационарной нижней орбите с одного конца, и к стартовой платформе на планете с другого.''' +Нано-конструкция позволяет производство растяжимых тросов достаточной прочности, чтобы выйти за пределы геостационарной орбиты. Трансферы с помощью магнитно-подъемных тросов превосходят дорогие, неэффективные и особо опасные запуски ракет с космической скоростью. Огромные и практически неразрушимые пучки тросов прикрепляют к спутнику на геостационарной нижней орбите с одного конца и к стартовой платформе на планете с другого''' BLD_GENOME_BANK Банк генофонда BLD_GENOME_BANK_DESC -'''Обеспечивает империю иммунитетом от биотерроризма. +'''Обеспечивает империю иммунитетом к биотерроризму -Самая большая база,из когда-либо созданных. Банк Геномов включает в себя карты генов каждого организма, известного империи. Эта база данных имеет неоценимое значение в лечении болезней.''' +Самая большая база из когда-либо созданных. Банк геномов включает в себя карты генов каждого организма известного империи. Эта база данных имеет неоценимое значение в лечении болезней''' BLD_GAIA_TRANS Преобразование в Гайю BLD_GAIA_TRANS_DESC -'''Увеличивает максимальное население в соответствии с размером планеты: Крохотные +3, Маленькие +6, Средние +9, Большие +12, Гигантские +15. +'''Добавляет специальную особенность [[special GAIA_SPECIAL]] к планете, которая увеличивает максимальное количество населения для [[PE_GOOD]] [[encyclopedia ENVIRONMENT_TITLE]] в соответствии с размером планеты: +• [[SZ_TINY]] (+3) +• [[SZ_SMALL]] (+6) +• [[SZ_MEDIUM]] (+9) +• [[SZ_LARGE]] (+12) +• [[SZ_HUGE]] (+15) +и увеличивает максимальное [[metertype METER_HAPPINESS]] на 5 -Convert a planet into a Gaia world by way of a sentient, almost god-like, cell-based computer program. This planet is wondrous to behold and famous throughout the known galaxy as a celebration of life and harmony. The inhabitants of this world think and act as though part of a larger organism, serving its needs simultaneously with their own.''' +Преобразует планету в мир Гайи посредством разумной, почти богоподобной, компьютерной программы на основе клеток. Эта планета удивительна и известна во всей известной галактике как праздник жизни и гармонии. Жители этого мира думают и действуют как бы часть более крупного организма, считая их потребности своими собственными''' BLD_COLLECTIVE_NET -Коллективный Сетевой Разум +Коллективный сетевой разум + +BLD_COLLECTIVE_NET_DESC +'''Увеличивает [[metertype METER_INDUSTRY]] на планетах с фокусом промышленности на 0.5 на единицу населения и [[metertype METER_RESEARCH]] на планетах с фокусом исследований на 0.5 за единицу населения. Межзвездное путешествие в пределах 200 световых лет нарушит эффективность этого здания и ликвидирует эти бонусы +[[NO_STACK_SUPPLY_CONNECTION_TEXT]] + +Трансформируя разум в киберпространство, тысячи умов могут действовать как один, решать проблемы и совершать открытия, которые ни один разум не мог''' BLD_GATEWAY_VOID -Ворота в Пустоту +Врата в пустоту + +BLD_GATEWAY_VOID_DESC +'''Возможность закрыть систему почти полностью, может быть полезна для империи в обороне. Любые суда, которые войдут в эту систему не пройдя через один и тот же проход, будут уничтожены, а все колонии внутри немедленно сведены к статусу [[encyclopedia OUTPOSTS_TITLE]]ов. [[metertype METER_STEALTH]] всех объектов в системе увеличивается на 1000, что делает их практически невозможными для обнаружения + +Пространственный разрыв, способный увеличивать деструктивный потенциал Пустоты разума в локализованной области''' BLD_ENCLAVE_VOID -Анклав Пустоты +Анклав пустоты + +BLD_ENCLAVE_VOID_DESC +'''Увеличивает целевые [[metertype METER_RESEARCH]] на всех планетах с фокусом на науку на 0.75 за единицу населения +Эффект от многократных копии не складывается + +Население целой планеты генетически настроено к Разуму пустоты и предано направлению мудрости своей империи. Жители Анклава пустоты считаются священниками, проводниками высшего мнения и мудрости. Во всех вопросах, имеющих важное значение для империи, обращаются к ним за советами. Эмиссары из Анклава могут даже содействовать, помогая в исследовательских проектах империи''' BLD_ART_BLACK_HOLE Искусственная черная дыра +BLD_ART_BLACK_HOLE_DESC +Введя огромное количество искусственной гравитации в звезду не увеличивая ее энергию, ее можно заставить взорвать себя и сформировать [[STAR_BLACK]]. Однако этот метод ограничивается звездами [[STAR_RED]], поскольку преодоление внутреннего давления более горячей звезды потребует невероятно мощного генератора гравитации. Этот процесс может происходить на [[encyclopedia OUTPOSTS_TITLE]] + +BLD_BLACK_HOLE_COLLAPSER +Разрушитель черной дыры + +BLD_BLACK_HOLE_COLLAPSER_DESC +Создает [[fieldtype FLD_SUBSPACE_RIFT]] с радиусом 100 световых лет, центрированным в месте расположения сооружения + BLD_HYPER_DAM -Гиперпространственная Плотина +Гиперпространственная перемычка + +BLD_HYPER_DAM_DESC +'''Для планет не связанных с [[buildingtype BLD_BLACK_HOLE_POW_GEN]], увеличивается целевую [[metertype METER_INDUSTRY]] на планетах связанных линиями [[metertype METER_SUPPLY]], с фокусом Производства на 1 за единицу населения, но само население на таких планетах уменьшается в зависимости от размера планеты: ([[SZ_TINY]] -1) ([[SZ_SMALL]] -2) ([[SZ_MEDIUM]] -3) ([[SZ_LARGE]] -4) ([[SZ_HUGE]] -5) +[[NO_STACK_SUPPLY_CONNECTION_TEXT]] + +Разрыв в ткани пространства безопасно контролируется и используется. Каждая планета может строить свои собственные гиперпространственные перемычки, но для обеспечения непрерывности пространственно-временного континуума необходимо иметь только один центр управления. Кроме того, все планеты, которые используют гиперпространственные перемычки, испытывают проблемы со здоровьем, если они не вращаются вокруг [[STAR_BLACK]] + +Примечание: Это строение бесполезно для планет, которые связаны с [[buildingtype BLD_BLACK_HOLE_POW_GEN]], но может быть полезно империям, неспособным установить такое устройство''' BLD_SOL_ACCEL Солнечный ускоритель +BLD_SOL_ACCEL_DESC +Солнечный ускоритель искусственно старит звезду, ускоряя ее естественные процессы. Звезда становится старше на один уровень, в соответствии со следующим порядком: [[STAR_BLUE]], [[STAR_WHITE]], [[STAR_YELLOW]], [[STAR_ORANGE]], [[STAR_RED]]. Этот процесс может происходить на [[encyclopedia OUTPOSTS_TITLE]] + BLD_SOL_ORB_GEN Солнечный орбитальный генератор +BLD_SOL_ORB_GEN_DESC +'''Увеличивает [[metertype METER_INDUSTRY]] на планетах с прямой связью [[metertype METER_SUPPLY]] и влияет на население в зависимости от типа звезды: +* [[STAR_BLUE]] or [[STAR_WHITE]] (x0.4) +* [[STAR_YELLOW]] or [[STAR_ORANGE]] (x0.2) +* [[STAR_RED]] (x0.1) +[[NO_STACK_SUPPLY_CONNECTION_TEXT]] применяется лучший бонус + +Генератор энергии на низкой солнечной орбите предназначен для прямого сбора энергии из недр звезды. Поскольку разные типы звезд производят различное количество энергии, общесистемный бонус в отрасли зависит от типа звезды [[BUILDING_AVAILABLE_ON_OUTPOSTS]]''' + BLD_CLONING_CENTER Центр клонирования BLD_CLONING_CENTER_DESC -'''Увеличивает максимальное население на всех планетах в соответствии с их размером: Крохотные +1, Маленькие +2, Средние +3, Большие +4, Гигантские +5. +'''Увеличивает максимальное население на всех планетах в соответствии с их размером: ([[SZ_TINY]] +1) ([[SZ_SMALL]] +2) ([[SZ_MEDIUM]] +3) ([[SZ_LARGE]] +4) ([[SZ_HUGE]] +5) -Industrial Cloning allows made-to-order populations to be produced industrially, significantly improving population.''' +Промышленное клонирование позволит производить на заказ население в промышленном масштабе, значительно улучшая популяцию''' BLD_TERRAFORM Терраформинг BLD_TERRAFORM_DESC -Терраформирования мира на один шаг ближе к экологическим предпочтениям проживающих видов. +Терраформирует мир на один шаг ближе к [[encyclopedia ENVIRONMENT_TITLE]] предпочтениям проживающего вида BLD_TERRAFORM_REVERT -Обратный Терраформинг +Обратный терраформинг + +BLD_TERRAFORM_REVERT_DESC +Терраформирует мир на один шаг назад к своему первоначальному виду [[encyclopedia ENVIRONMENT_TITLE]] BLD_REMOTE_TERRAFORM Удаленный терраформинг +BLD_REMOTE_TERRAFORM_DESC +Терраформирует мир на один шаг ближе к [[encyclopedia ENVIRONMENT_TITLE]] предпочтениям проживающего вида. Этот процесс может происходить на [[encyclopedia OUTPOSTS_TITLE]] + BLD_NEUTRONIUM_EXTRACTOR -Нейтронная Вытяжка +Нейтрониумная вытяжка + +BLD_NEUTRONIUM_EXTRACTOR_DESC +'''Делает нейтрониум доступным для использования империей. Нейтрониум может использоваться в [[buildingtype BLD_NEUTRONIUM_FORGE]], чтобы сделать возможным строительство кораблей с деталями из него, например [[shippart AR_NEUTRONIUM_PLATE]] + +Прежде чем детали с использованием нейтрониума могут быть созданы, его необходимо извлечь из [[STAR_NEUTRON]] звезды. Нейтрониум, извлеченный на этом объекте, транспортируется в Нейтрониумные кузницы по всей империи. [[BUILDING_AVAILABLE_ON_OUTPOSTS]]''' BLD_NEUTRONIUM_FORGE -Нейтронная наковальня +Нейтрониумная кузница + +BLD_NEUTRONIUM_FORGE_DESC +'''Это здание необходимо строить в том же месте, что и [[buildingtype BLD_SHIPYARD_BASE]], дабы сделать возможным строительство кораблей с нейтрониумными частями/деталями + +Промышленная переработка нейтрониума требует обширного специализированного сооружения и поэтому не может быть построено на [[encyclopedia OUTPOSTS_TITLE]]. [[MACRO_NEUTRONIUM_BUILDINGS]]''' BLD_NEUTRONIUM_SYNTH -Нейтронный Синтезатор +Нейтрониумный синтезатор + +BLD_NEUTRONIUM_SYNTH_DESC +'''Империи с этим строением не требуется [[buildingtype BLD_NEUTRONIUM_EXTRACTOR]], чтобы извлекать нейтрониум, необходимый для строительства кораблей с использованием нейтрониумных частей/деталей + +Древняя установка, давно спроектированная для синтеза нейтрониума''' BLD_CONC_CAMP Концлагеря +BLD_CONC_CAMP_DESC +'''Уменьшает популяцию планеты со скоростью 3 за ход, увеличивает целевую [[metertype METER_INDUSTRY]] в 5 раз по сравнению с населением планеты и устанавливает целевое [[metertype METER_HAPPINESS]] на 0 + +Избавление планеты от нежелательных видов позволит внедрить более подходящие виды. Создав сеть концентрационных лагерей для всей планеты, предназначенную для работы граждан до смерти, эта цель может быть быстро и эффективно достигнута. Это здание может быть построено на [[encyclopedia OUTPOSTS_TITLE]]''' + +BLD_CONC_CAMP_REMNANT +Остатки концентрационного лагеря + +BLD_CONC_CAMP_REMNANT_DESC +Хотя местные власти могут попытаться скрыть остатки старого [[buildingtype BLD_CONC_CAMP]], население все еще знает об их наличии + BLD_BLACK_HOLE_POW_GEN -Генератор на черной дыре +Генератор на основе черной дыры BLD_BLACK_HOLE_POW_GEN_DESC -Увеличивает [[metertype METER_INDUSTRY]] на всех промышленно-ориентированных планетах, соединенных [[metertype METER_SUPPLY]] на 1.2 * Население. Может быть построено на аванпостах. +'''Увеличивает [[metertype METER_INDUSTRY]] на всех промышленно-ориентированных планетах, соединенных [[metertype METER_SUPPLY]] на 1 за единицу населения +[[NO_STACK_SUPPLY_CONNECTION_TEXT]] + +[[BUILDING_AVAILABLE_ON_OUTPOSTS]], но должен быть в системе с [[STAR_BLACK]]''' BLD_PLANET_DRIVE -Планетарный Межзвездный двигатель +Планетарный межзвездный двигатель + +BLD_PLANET_DRIVE_DESC +'''Позволяет планете перемещаться между звездными системами. Необходимо наличие [[buildingtype BLD_LIGHTHOUSE]] в пределах 200 световых лет от целевой системы назначения, чтобы планета могла безопасно перемещаться. В противном случае у планеты есть 50% шанс быть уничтоженной в пути. Это опасное путешествие даже при поддержке межзвездного маяка и если планета доберется, только половина населения выживет. [[BUILDING_AVAILABLE_ON_OUTPOSTS]], но не может использоваться, если планета не колонизирована + +Из-за отсутствия интерфейса нацеливания, на другом конце пути требуется наличие либо [[buildingtype BLD_PLANET_BEACON]] либо корабля оборудованного [[shippart SP_PLANET_BEACON]]. Это строение немедленно самоуничтожится, так что позже планета может перейти к другой целевой системе + +Чтобы использовать это строение, либо постройте [[buildingtype BLD_PLANET_BEACON]] в соседней системе назначения, либо переместите туда свое судно с [[shippart SP_PLANET_BEACON]], а затем установите перемещаемую планету в фокусе Планетарного двигателя. После чего планета будет перемещена в нужную систему. Обратите внимание, что маяк должен быть построен в системе за ход до этого: двигатель не будет работать в тот ход, когда строение будет завершено или в тот ход, когда корабль только достигнет системы. Также обратите внимание, что [[buildingtype BLD_TRANSFORMER]] может использоваться для репликации эффектов этого здания''' BLD_PLANET_BEACON -Планетарный Маяк +Планетарный маяк + +BLD_PLANET_BEACON_DESC +Обозначает цель для [[buildingtype BLD_PLANET_DRIVE]]. [[BUILDING_AVAILABLE_ON_OUTPOSTS]] BLD_ART_PLANET Искусственная планета +BLD_ART_PLANET_DESC +Формирует искусственную планету из пояса астероидов или газового гиганта. [[PT_ASTEROIDS]] становится [[SZ_TINY]] [[PT_BARREN]] планетами и [[PT_GASGIANT]] становится [[SZ_HUGE]] [[PT_BARREN]] планетами. [[ARTIFICIAL_PLANET_PROCESS_LOCATION]] + +BLD_ART_FACTORY_PLANET +Мир искусственной фабрики + +BLD_ART_FACTORY_PLANET_DESC +Формирует искусственную планету из пояса астероидов или газового гиганта, а затем заполняет ее [[species SP_EXOBOT]]. [[PT_ASTEROIDS]] становится [[SZ_TINY]] [[PT_BARREN]] планетами, а [[PT_GASGIANT]] становится [[SZ_HUGE]] [[PT_BARREN]] планетами. [[ARTIFICIAL_PLANET_PROCESS_LOCATION]] + +BLD_ART_PARADISE_PLANET +Искусственный райский мир + +BLD_ART_PARADISE_PLANET_DESC +Создает искусственную райскую планету из пояса астероидов или газового гиганта. [[PT_ASTEROIDS]] становится [[SZ_TINY]] [[PT_BARREN]] планетами, а [[PT_GASGIANT]] становится [[SZ_HUGE]] [[PT_BARREN]] планетами. На планете будет особенность [[special GAIA_SPECIAL]] процесс который будет проходить поэтапно, чтобы в конечном итоге полностью удовлетворить своих обитателей. [[ARTIFICIAL_PLANET_PROCESS_LOCATION]] + BLD_ART_MOON Искусственная луна +BLD_ART_MOON_DESC +'''Формирует искусственную луну с орбитальным периодом 1:1 - резонанс вращательного периода. Добавляет специальную функцию планете [[special RESONANT_MOON_SPECIAL]] + +Это невозможно построить на [[PT_GASGIANT]] или поясе астероидов + +Хотя естественные спутники являются относительно обычными явлениями среди планетных тел, по-настоящему редко встречается луна с орбитальным периодом равным периоду его вращения. У такого спутника была бы одна сторона, постоянно обращенная от планеты и позволяющая строить массивные строения, которые при нормальных условиях были бы легко видны с поверхности планеты. Таким образом, возводимые строения будут иметь полную секретность и совершенно невидимы для ничего не подозревающего населения на планете ниже. Суда, вращающиеся вокруг планеты, также смогут более эффективно скрываться от агрессоров''' + +BLD_SOL_REJUV +Солнечные омолаживатель + +BLD_SOL_REJUV_DESC +Солнечный омолаживатель искусственно омолаживает звезду за счет увеличения подачи топлива. Звезда становится моложе на один уровень, согласно приведенному порядку: [[STAR_RED]], [[STAR_ORANGE]], [[STAR_YELLOW]], [[STAR_WHITE]], [[STAR_BLUE]]. Этот процесс может происходить на [[encyclopedia OUTPOSTS_TITLE]] + BLD_TRANSFORMER Трансформатор BLD_TRANSFORMER_DESC -'''Универсальное и очень дорогое здания имеющие эффекты многих других зданий, в настоящее время включается с помощью настройки фокуса планеты, на которой оно построено, при условии, что владелец разблокирован здания, в которые Трансформатор имитирует. +'''Универсальное и очень дорогое здание имеющие эффекты многих других зданий, в настоящее время включается с помощью настройки фокуса планеты, на которой оно построено, при условии, что владелец разблокировал здание, которое Трансформатор имитирует -Это здание дает эффекты репликатора, устройства планетарной маскировки, Биотерор протектор, Звездных врат, Межзвездного планетарного привода или генератора пространственных искажений, при условии, что у владельца есть соответствующие технологии.''' +Это здание может открывать [[buildingtype BLD_BIOTERROR_PROJECTOR]], [[buildingtype BLD_STARGATE]], [[buildingtype BLD_PLANET_DRIVE]] или [[buildingtype BLD_SPATIAL_DISTORT_GEN]], если у владельца есть соответствующая технология''' BLD_PLANET_CLOAK -Планетарные устройства маскировки +Планетарное маскирующее устройство + +BLD_PLANET_CLOAK_DESC +'''Дает планете на которой построено огромный бонус к [[metertype METER_STEALTH]] + +Мощное маскирующее устройство, способное окутать весь мир''' BLD_SPATIAL_DISTORT_GEN -Пространственный Генератор искажений +Пространственный генератор искажений + +BLD_SPATIAL_DISTORT_GEN_DESC +Манипулирует пространственными свойствами соседних звездных путей. Вражеские корабли, движущиеся к системе, содержащей это здание, будут отброшены назад на 40 световых лет, пока они не завершили свое путешествие и не прибыли в систему. Это активируется с помощью фокуса пространственного искажения BLD_STARGATE Звездные Врата +BLD_STARGATE_DESC +'''Способны создавать кратчайший путь через пространство-время между собой и любыми другими ЗВ, принадлежащими империи. Расходы на электроэнергию такого устройства во время активации достигают невероятных значений и могут отключать целую планету, хотя в остальное время побочных эффектов нет. К сожалению, из-за квантовых флуктуаций невозможно предсказать, куда отправляется корабль в конечном итоге, если более чем одни ЗВ будут активны для получения + +Чтобы активировать Звездные врата (ЗВ), установите планету в фокусе отправляемых ЗВ и выберите в фокусе ЗВ получателя в другой системе''' + BLD_GAS_GIANT_GEN Генератор на Газовом гиганте BLD_GAS_GIANT_GEN_DESC -'''Это здание может быть построено только на газовом гиганте, дает +10 [[metertype METER_INDUSTRY]] бонуса к планетам с промышленным фокусом в той же системе. +'''Это здание может быть построено только на газовом гиганте, дает бонус +10 [[metertype METER_INDUSTRY]] к планетам с промышленным фокусом в той же системе +Несколько копий в одной системе не складываются -Мощный генератор разработан для сбора энергии Газового гиганта.''' +Мощный генератор разработан для сбора энергии с [[PT_GASGIANT]]''' -BLD_NOVA_BOMB_ACTIVATOR -'''Активатор Нова бомбы ''' +BLD_STARLANE_BORE +Создание звездного пути +BLD_STARLANE_BORE_DESC +Генерирует один новый звездный путь в ближайшую систему, которая может быть соединена без пересечения существующих линий или проходить очень близко к другой системе -## -## Hull and ship part description templates -## +BLD_STARLANE_NEXUS +Создание звездного пути Нексус -# %1% hull base starlane speed. -# %2% hull base fuel capacity. -# %3% hull base starlane speed. -# %4% hull base structure value. -HULL_DESC -'''Межзвездная скорость: %1% -Вместимость топлива: %2% -Скорость в бою: %3% -Живучесть: %4%''' +BLD_STARLANE_NEXUS_DESC +Генерирует новые звездные пути в ближайшие системы, которые могут быть соединена без пересечения существующих линий или проходить очень близко к другой системе -# %1% ship part capacity (fuel, troops, colonists, fighters). -PART_DESC_CAPACITY -Вместимость: %1% +BLD_XENORESURRECTION_LAB +Лаборатории ксеновоскрешения -# %1% ship part strength (other). -PART_DESC_STRENGTH -Сила: %1% +BLD_XENORESURRECTION_LAB_DESC +Лаборатория Ксеновоскрешения восстанавливает живые образцы вымерших видов, что позволяет колонизировать связанные [[metertype METER_SUPPLY]]м планеты с вымершими видами, найденными в [[special ANCIENT_RUINS_SPECIAL]]. Лабораторию можно строить только на планетах, где раскопки [[special ANCIENT_RUINS_SPECIAL]] нашли хорошо сохранившиеся тела вымерших видов -# %1% ship part damage done (direct fire weapons). -# %2% number of shots done per attack. +BLD_COLONY_BASE +База колоний + +BLD_COLONY_BASE_DESC +Создает корабль [[predefinedshipdesign SD_COLONY_BASE]], который может использоваться для колонизации другой планеты в той же системе. [[MIN_POPULATION_THREE_REQUIRED]] + +BLD_EXPERIMENTOR_OUTPOST +Экспериментальный аванпост + +BLD_EXPERIMENTOR_OUTPOST_DESC +Аванпост Экспериментаторов, используемый для проведения высокоразвитых экспериментов. Эта структура также действует как маскировка, чтобы никто не вмешивался в их работу + +BLD_NOVA_BOMB_ACTIVATOR +Активатор бомбы-Нова + +BLD_NOVA_BOMB_ACTIVATOR_DESC +Активирует все [[shippart SP_NOVA_BOMB]] в пределах одного звездного прыжка + +BLD_SCRYING_SPHERE +Сфера глаза + +BLD_SCRYING_SPHERE_DESC +Предоставляет видимость всех других планет имеющих сферу глаза + +BLD_COL_ABADDONI +Колония Абаддони + +BLD_COL_ABADDONI_DESC +Колония [[BLD_COL_PART_1]] [[species SP_ABADDONI]]. [[BLD_COL_PART_2]] Колония Абаддони [[BLD_COL_PART_3]] + +BLD_COL_BANFORO +Колония Банфоро + +BLD_COL_BANFORO_DESC +Колония [[BLD_COL_PART_1]] [[species SP_BANFORO]]. [[BLD_COL_PART_2]] Колония Банфоро [[BLD_COL_PART_3]] + +BLD_COL_CHATO +Колония Чато + +BLD_COL_CHATO_DESC +Колония [[BLD_COL_PART_1]] [[species SP_CHATO]]. [[BLD_COL_PART_2]] Колония Чато [[BLD_COL_PART_3]] + +BLD_COL_CRAY +Колония Крэй + +BLD_COL_CRAY_DESC +Колония [[BLD_COL_PART_1]] [[species SP_CRAY]]. [[BLD_COL_PART_2]] Колония Крэй [[BLD_COL_PART_3]] + +BLD_COL_DERTHREAN +Колония Дертрин + +BLD_COL_DERTHREAN_DESC +Колония [[BLD_COL_PART_1]] [[species SP_DERTHREAN]]. [[BLD_COL_PART_2]] Колония Дертрин [[BLD_COL_PART_3]] + +BLD_COL_EAXAW +Колония Экзо + +BLD_COL_EAXAW_DESC +Колония [[BLD_COL_PART_1]] [[species SP_EAXAW]]. [[BLD_COL_PART_2]] Колония Экзо [[BLD_COL_PART_3]] + +BLD_COL_EGASSEM +Колония Игассемов + +BLD_COL_EGASSEM_DESC +Колония [[BLD_COL_PART_1]] [[species SP_EGASSEM]]. [[BLD_COL_PART_2]] Колония Игассемов [[BLD_COL_PART_3]] + +BLD_COL_ETTY +Колония Етти + +BLD_COL_ETTY_DESC +Колония [[BLD_COL_PART_1]] [[species SP_ETTY]]. [[BLD_COL_PART_2]] Колония Етти [[BLD_COL_PART_3]] + +BLD_COL_FURTHEST +Колония Фёрвестов + +BLD_COL_FURTHEST_DESC +Колония [[BLD_COL_PART_1]] [[species SP_FURTHEST]]. [[BLD_COL_PART_2]] Колония Фёрвестов [[BLD_COL_PART_3]] + +BLD_COL_FULVER +Колония Фулверов + +BLD_COL_FULVER_DESC +Колония [[BLD_COL_PART_1]] [[species SP_FULVER]]. [[BLD_COL_PART_2]] Колония Фулверов [[BLD_COL_PART_3]]. + +BLD_COL_GEORGE +Колония Джордж + +BLD_COL_GEORGE_DESC +Колония [[BLD_COL_PART_1]] [[species SP_GEORGE]]. [[BLD_COL_PART_2]] Колония Джордж [[BLD_COL_PART_3]] + +BLD_COL_GYSACHE +Колония Гисач + +BLD_COL_GYSACHE_DESC +Колония [[BLD_COL_PART_1]] [[species SP_GYSACHE]]. [[BLD_COL_PART_2]] Колония Гисач [[BLD_COL_PART_3]] + +BLD_COL_HAPPY +Колония День Рождения + +BLD_COL_HAPPY_DESC +Колония [[BLD_COL_PART_1]] [[species SP_HAPPY]]. [[BLD_COL_PART_2]] Колония День Рождения [[BLD_COL_PART_3]] + +BLD_COL_HHHOH +Колония Хххохов + +BLD_COL_HHHOH_DESC +Колония [[BLD_COL_PART_1]] [[species SP_HHHOH]]. [[BLD_COL_PART_2]] Колония Хххохов [[BLD_COL_PART_3]] + +BLD_COL_HUMAN +Колония Людей + +BLD_COL_HUMAN_DESC +Колония [[BLD_COL_PART_1]] [[species SP_HUMAN]]. [[BLD_COL_PART_2]] Колония Людей [[BLD_COL_PART_3]] + +BLD_COL_KILANDOW +Колония Киландау + +BLD_COL_KILANDOW_DESC +Колония [[BLD_COL_PART_1]] [[species SP_KILANDOW]]. [[BLD_COL_PART_2]] Колония Киландау [[BLD_COL_PART_3]] + +BLD_COL_KOBUNTURA +Колония Кобунтуры + +BLD_COL_KOBUNTURA_DESC +Колония [[BLD_COL_PART_1]] [[species SP_KOBUNTURA]]. [[BLD_COL_PART_2]] Колония Кобунтуры [[BLD_COL_PART_3]] + +BLD_COL_LAENFA +Колония Линфы + +BLD_COL_LAENFA_DESC +Колония [[BLD_COL_PART_1]] [[species SP_LAENFA]]. [[BLD_COL_PART_2]] Колония Линфы [[BLD_COL_PART_3]] + +BLD_COL_MISIORLA +Колония Мизиорл + +BLD_COL_MISIORLA_DESC +Колония [[BLD_COL_PART_1]] [[species SP_MISIORLA]]. [[BLD_COL_PART_2]] Колония Мизиорл [[BLD_COL_PART_3]] + +BLD_COL_MUURSH +Колония Му-Уршей + +BLD_COL_MUURSH_DESC +Колония [[BLD_COL_PART_1]] [[species SP_MUURSH]]. [[BLD_COL_PART_2]] Колония Му-Уршей [[BLD_COL_PART_3]] + +BLD_COL_PHINNERT +Колония Финнертов + +BLD_COL_PHINNERT_DESC +Колония [[BLD_COL_PART_1]] [[species SP_PHINNERT]]. [[BLD_COL_PART_2]] Колония Финнертов [[BLD_COL_PART_3]] + +BLD_COL_REPLICON +Колония Репликонов + +BLD_COL_REPLICON_DESC +Колония [[BLD_COL_PART_1]] [[species SP_REPLICON]]. [[BLD_COL_PART_2]] Колония Репликонов [[BLD_COL_PART_3]]. + +BLD_COL_SCYLIOR +Колония Скилиоров + +BLD_COL_SCYLIOR_DESC +Колония [[BLD_COL_PART_1]] [[species SP_SCYLIOR]]. [[BLD_COL_PART_2]] Колония Скилиоров [[BLD_COL_PART_3]] + +BLD_COL_SETINON +Колония Сетинонов + +BLD_COL_SETINON_DESC +Колония [[BLD_COL_PART_1]] [[species SP_SETINON]]. [[BLD_COL_PART_2]] Колония Сетинонов [[BLD_COL_PART_3]] + +BLD_COL_SILEXIAN +Колония Силексианцев + +BLD_COL_SILEXIAN_DESC +Колония L_PART_1]] [[species SP_SILEXIAN]]. [[BLD_COL_PART_2]] Колония Силексианцев [[BLD_COL_PART_3]] + +BLD_COL_SLY +Колония Слаев + +BLD_COL_SLY_DESC +[[BLD_COL_PART_1]] [[species SP_SLY]]. [[BLD_COL_PART_2]] Колония Слаев [[BLD_COL_PART_3]]. + +BLD_COL_SSLITH +Колония Сслитов + +BLD_COL_SSLITH_DESC +Колония [[BLD_COL_PART_1]] [[species SP_SSLITH]]. [[BLD_COL_PART_2]] Колония Сслитов [[BLD_COL_PART_3]] + +BLD_COL_TAEGHIRUS +Колония Тэ Гирусов + +BLD_COL_TAEGHIRUS_DESC +Колония [[BLD_COL_PART_1]] [[species SP_TAEGHIRUS]]. [[BLD_COL_PART_2]] Колония Тэ Гирусов [BLD_COL_PART_3]] + +BLD_COL_TRITH +Колония Тритов + +BLD_COL_TRITH_DESC +Колония [[BLD_COL_PART_1]] [[species SP_TRITH]]. [[BLD_COL_PART_2]] Колония Тритов [[BLD_COL_PART_3]] + +BLD_COL_UGMORS +Колония Угморов + +BLD_COL_UGMORS_DESC +Колония [[BLD_COL_PART_1]] [[species SP_UGMORS]]. [[BLD_COL_PART_2]] Колония Угморов [[BLD_COL_PART_3]] + +BLD_COL_EXOBOT +Колония Экзоботов + +BLD_COL_EXOBOT_DESC +Это строительство создает колонию [[species SP_EXOBOT]]. В отличие от прочих колониальных построек, Экзоботы не нуждаются в наличии существующей колонии своего вида в диапазоне путей снабжения. Из-за этого их дороже строить, и им по-прежнему нужно соединение с империей путями снабжения. + +BLD_COL_SUPER_TEST +Колония супер-тестеров + +BLD_COL_SUPER_TEST_DESC +Колония супер-тестеров [[BLD_COL_PART_1]]. [[BLD_COL_PART_2]] Колония супер-тестеров [[BLD_COL_PART_3]] + +## +## Hull and ship part description templates +## + +# %1% hull base starlane speed. +# %2% hull base fuel capacity. +# %3% hull base starlane speed. +# %4% hull base structure value. + +# %1% ship part capacity (fuel, troops, colonists, fighters). +PART_DESC_CAPACITY +Вместимость: %1% + +# %1% ship part strength (other). +PART_DESC_STRENGTH +Сила: %1% + +# %1% ship part strength (shield). +PART_DESC_SHIELD_STRENGTH +Сила щита: %1% + +# %1% ship part strength (detection). + +# %1% ship part damage done (direct fire weapons). +# %2% number of shots done per attack. PART_DESC_DIRECT_FIRE_STATS -Урон при атаке: %1% +'''Урон [[encyclopedia DAMAGE_TITLE]]: %1% +Выстрелов за атаку: %2%''' +# %1% number of deployable fighters. +# %2% ship part damage done (fighters). +PART_DESC_HANGAR_STATS +'''Количество истребителей: %1% +Урон [[encyclopedia DAMAGE_TITLE]]: %2%''' ## ## Ship parts ## +FT_WEAPON_1 +Оружие истребителей + +FT_WEAPON_1_DESC +Прототипная система оружия для использования истребителями. Не функционирует с кораблями + +FT_BAY_1 +Пусковая площадка + +FT_BAY_1_DESC +Система запуска для истребителей + +FT_HANGAR_0 +Ангар 0 + +FT_HANGAR_0_DESC +Система хранения невооруженных кораблей + +FT_HANGAR_1 +Ангар перехватчиков + +FT_HANGAR_1_DESC +Система хранения для минимально вооруженных истребителей + +FT_HANGAR_2 +Ангар истребителей + +FT_HANGAR_2_DESC +Система хранения для легких вооруженных истребителей + +FT_HANGAR_3 +Ангар бомбардировщиков + +FT_HANGAR_3_DESC +Система хранения для умеренно вооруженных истребителей + +FT_HANGAR_4 +Ангар 4 + +FT_HANGAR_4_DESC +Система хранения для тяжело вооруженных истребителей + +FT_HANGAR_KRILL +Рой криля + +FT_HANGAR_KRILL_DESC +Рой криля + +FT_BAY_KRILL +Площадка роя криля + +FT_BAY_KRILL_DESC +Когда рой криля достаточно велик, одиночные криллы оставляют рой, чтобы атаковать корабли + +SR_WEAPON_0_1 +Зенитное орудие + +SR_WEAPON_0_1_DESC +'''Зенитная пушка наносит незначительный урон большинству кораблей, но является недорогим и эффективным средством для уничтожения истребителей + +Каждая зенитная пушка на корабле, построенная видом с признаком пилотирования, будет иметь дополнительное количество выстрелов, равное 1 на уровень пилотирования (-1 выстрел для плохих пилотов)''' + SR_WEAPON_1_1 -Электромагнитное Орудие 1 +Электромагнитное орудие 1 SR_WEAPON_1_1_DESC -Электромагнитное Орудие, базовое оружие. +'''Электромагнитное базовое орудие + +Технологии модернизации оружия увеличат урон от выстрелов + +[[encyclopedia DAMAGE_TITLE]] от оружия представляет собой комбинация урона от выстрела и любых активных модификаторов. Каждое электромагнитное орудие на корабле, построенное видом с признаком пилотирования, будет наносить дополнительно 1 урон на пилотный уровень (-1 для плохих пилотов)''' SR_WEAPON_2_1 -Лазер 1 +Лазерное оружие SR_WEAPON_2_1_DESC -Лазер, более мощное корабельное оружие по сравнению с Электромагнитным Орудием. +'''Лазер более мощное корабельное оружие по сравнению с Электромагнитным орудием + +Технологии модернизации оружия увеличат урон от выстрелов + +[[encyclopedia DAMAGE_TITLE]] от оружия представляет собой комбинация урона от выстрела и любых активных модификаторов. Каждое лазерное орудие на корабле, построенное видом с признаком пилотирования, будет наносить дополнительно 2 урона на пилотный уровень (-2 для плохих пилотов)'''. Урон от лазерного оружия также может быть увеличен на [[shiphull SH_ORGANIC]] кораблях с [[shippart SP_SOLAR_CONCENTRATOR]]''' SR_WEAPON_3_1 -Плазменная Пушка 1 +Плазменная пушка SR_WEAPON_3_1_DESC -Плазменная Пушка, более мощное корабельное оружие по сравнению с Лазером. +'''Плазменная пушка более мощное корабельное оружие по сравнению с Лазером + +Технологии модернизации оружия увеличат урон от выстрелов + +[[encyclopedia DAMAGE_TITLE]] от оружия представляет собой комбинация урона от выстрела и любых активных модификаторов. Каждая плазменная пушка на корабле, построенная видом с признаком пилотирования, будет наносить дополнительно 3 урона на пилотный уровень (-3 для плохих пилотов)''' SR_WEAPON_4_1 -Смертельный Луч 1 +Смертоносный луч SR_WEAPON_4_1_DESC -Смертельный Луч, лучшее корабельное оружие. +'''Смертоносный луч - более мощное корабельное оружие, по сравнению с Плазменной пушкой + +Технологии модернизации оружия увеличат урон от выстрелов + +[[encyclopedia DAMAGE_TITLE]] от оружия представляет собой комбинация урона от выстрела и любых активных модификаторов. Каждый смертельный луч на корабле, построенный видом с признаком пилотирования, будет наносить дополнительно 5 урона на пилотный уровень (-5 для плохих пилотов)''' + +SR_SPINAL_ANTIMATTER +Спинальная антиматериальная пушка + +SR_SPINAL_ANTIMATTER_DESC +Огромный спинальный калибр, стреляющий тяжелыми снарядами из антивещества + +SR_JAWS +Пасть + +SR_JAWS_DESC +Челюсти космического монстра могут повредить кораблям. Огромные космические монстры даже способны поглотить целый флот + +SR_SPINES +Шипы + +SR_SPINES_DESC +При мощном ударе космического монстра шипы могут нанести большой урон кораблю + +SR_TENTACLE +Щупальца + +SR_TENTACLE_DESC +Щупальца кракена могут повредить корабль + +SR_PLASMA_DISCHARGE +Плазменный разряд + +SR_PLASMA_DISCHARGE_DESC +Плазменный разряд космического монстра способен расплавить большинство видов брони + +SR_ICE_BEAM +Большой лазер + +SR_ICE_BEAM_DESC +Большие лазеры, встроенные в тело космического монстра + +SR_ION_CANNON +Ионная пушка + +SR_ION_CANNON_DESC +Умеренная мощность и масса + +PD_PULSE_LASER +Пульсирующий лазер PD_PULSE_LASER_DESC Орудие с высоким темпом стрельбы +PD_PHASOR +Фазор + +PD_PHASOR_DESC +Более сильное и более быстрое боевое оружие + +PD_PARTICLE_BEAM +Пучок частиц + +PD_PARTICLE_BEAM_DESC +Максимальное оружие точечной обороны + +FI_INTERCEPTOR +Перехватчик + +FI_INTERCEPTOR_DESC +Двенадцать быстрых перехватчиков, предназначенных для уничтожения ракет и истребителей противника + +FI_BOMBER +Бомбардировщик + +FI_BOMBER_DESC +Шесть бомбардировщиков, предназначенных для нацеливания на корабли и планеты + +FI_RECON +Истребитель разведчик + +FI_RECON_DESC +Двенадцать быстрых, невооруженных истребителей, предназначенных для разведки + +FI_BIOINTERCEPTOR +Био-перехватчик + +FI_BIOINTERCEPTOR_DESC +Тридцать шесть живых перехватчиков, предназначенных для уничтожения ракет и истребителей противника. Гораздо эффективнее обычных перехватчиков. [[BLD_SHIPYARD_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED_ANY_SYSTEM]] + +FI_BIOBOMBER +Био-бомбардировщик + +FI_BIOBOMBER_DESC +Восемнадцать живых бомбардировщиков, предназначенных для нацеливания на корабли и планеты. Гораздо эффективнее обычных бомбардировщиков. [[BLD_BIONEURAL_REQUIRED_ANY_SYSTEM]] + +LR_NUCLEAR_MISSILE +Ядерная ракета + +LR_NUCLEAR_MISSILE_DESC +Дальнобойная с умеренной мощностью + +LR_SPECTRAL_MISSILE +Спектральная ракета + +LR_SPECTRAL_MISSILE_DESC +Быстрая и скрытая ракета + +LR_NEUTRONIUM_PLATE_NUC_MIS +Нейтронированная ядерная ракета + +LR_NEUTRONIUM_PLATE_NUC_MIS_DESC +Тяжелая бронированная ядерная ракета + +LR_NEUTRONIUM_PLATE_SPEC_MIS +Спектральная ракета с нейтроновым покрытием + +LR_NEUTRONIUM_PLATE_SPEC_MIS_DESC +Тяжелая бронированная спектральная ракета + +LR_ANTIMATTER_TORPEDO +Антиматериальная торпеда + +LR_ANTIMATTER_TORPEDO_DESC +Быстрая ракета дальнего действия, работающая на [[tech SHP_SPACE_FLUX_DRIVE]] и вооруженная мощной антиматериальной боеголовкой. [[BLD_SHIPYARD_CON_ADV_ENGINE_REQUIRED_ANY_SYSTEM]] + +LR_PLASMA_TORPEDO +Плазменная торпеда + +LR_PLASMA_TORPEDO_DESC +Быстрая ракета дальнего действия с отличным [[metertype METER_STEALTH]], работающая на [[tech SHP_TRANSSPACE_DRIVE]] и вооруженная разрушительно мощной плазменной боеголовкой. [[BLD_SHIPYARD_CON_ADV_ENGINE_REQUIRED_ANY_SYSTEM]] + DT_DETECTOR_1 -Оптический Сканер +Оптический сканер DT_DETECTOR_1_DESC -Малый Радиус Обнаружения. +Малый радиус обнаружения DT_DETECTOR_2 -Активный Радар +Активный радар DT_DETECTOR_2_DESC -Средний Радиус Обнаружения. +Средний радиус обнаружения DT_DETECTOR_3 Нейтронный Сканер DT_DETECTOR_3_DESC -Большой Радиус Обнаружения. +Большой радиус обнаружения DT_DETECTOR_4 Сенсоры DT_DETECTOR_4_DESC -Очень Большой Радиус Обнаружения. +Очень большой радиус обнаружения FU_IMPROVED_ENGINE_COUPLINGS -Улучшенная Сцепка Двигателей +Улучшенная сцепка двигателей FU_IMPROVED_ENGINE_COUPLINGS_DESC -'''Увеличивает Межзвёздную скорость на 10. +'''Увеличивает межзвездную [[metertype METER_SPEED]] на 10 -Better engine parts equate to better engines.''' +Лучшие части двигателя приравниваются к лучшим двигателям''' FU_N_DIMENSIONAL_ENGINE_MATRIX N-мерная матрица двигателя FU_N_DIMENSIONAL_ENGINE_MATRIX_DESC -'''Увеличивает Межзвёздную скорость на 15. +'''Увеличивает межзвездную [[metertype METER_SPEED]] на 20 -N-Dimensional subspace research resulted in a more efficient engine matrix.''' +Исследование N-мерного подпространства привело к созданию более эффективной двигательной матрице''' FU_SINGULARITY_ENGINE_CORE -Основной Сингулярный Двигатель +Сингулярный двигатель ядра FU_SINGULARITY_ENGINE_CORE_DESC -'''A smaller cousin of the Hyperspace Dam outfitted for ships, the Singularity Engine Core drastically boosts power generation. Увеличивает Межзвёздную скорость на 20. ''' +Близкий родственник [[buildingtype BLD_HYPER_DAM]] разработанный для кораблей. Сингулярный двигатель ядра значительно повышает мощность. Увеличивает межзвездную [[metertype METER_SPEED]] на 30 + +FU_TRANSPATIAL_DRIVE +Транс-пространственный привод + +FU_TRANSPATIAL_DRIVE_DESC +Невероятно мощный привод, который изгибает пространство-время, увеличивая как [[metertype METER_SPEED]], так и [[metertype METER_STEALTH]] на 40. Может устанавливаться только в центральный [[encyclopedia SLOT_TITLE]]. Бонус скрытности не складывается с другими бонусами частей корабля FU_BASIC_TANK Дополнительный топливный бак -FU_DEUTERIUM_TANK -Бак Дейтерия +FU_BASIC_TANK_DESC +Увеличивает вместимость [[metertype METER_FUEL]], позволяя совершать дополнительный прыжок + +FU_ZERO_FUEL +Генератор топлива с нулевой точкой -FU_ANTIMATTER_TANK -Бак Антиматерий +FU_ZERO_FUEL_DESC +Генерирует энергию с использованием генерации нулевой точки и преобразует ее в [[metertype METER_FUEL]] с использованием методов преобразования энергии вещества + +FU_RAMSCOOP +Черпак + +FU_RAMSCOOP_DESC +Медленно генерирует [[metertype METER_FUEL]] путем сбора в системах водорода из диффузного газа в пространстве + +ST_CLOAK_1 +Электромагнитный демпфер ST_CLOAK_1_DESC -'''Слабая Маскировка (+20) +'''Слабая маскировка (+20) -Improves [[metertype METER_STEALTH]] of ship on which it is mounted by damping electromagnetic emissions from ship systems. Can only compensate for small amount of emissions, and will be overpowered by large-emission producing parts, if present. [[metertype METER_STEALTH]]-causing ship parts do not stack.''' +Улучшает [[metertype METER_STEALTH]] корабля на котором монтируется путем демпфирования электромагнитных излучений от судовых систем. Может только компенсировать небольшое количество выбросов и будет бесполезен при крупных выбросах если таковые присутствуют. [[NO_STACK_STEALTH_SHIP_PARTS]]''' ST_CLOAK_2 -Абсорбирующие Поле +Абсорбирующие поле ST_CLOAK_2_DESC -'''Умеренная Маскировка (+40) +'''Умеренная маскировка (+40) -Creates an absorption field around the ship, effectively transforming it into a perfect blackbody. [[metertype METER_STEALTH]]-causing ship parts do not stack.''' +Улучшает [[metertype METER_STEALTH]] корабля на котором монтируется, создавая поглощающее поле вокруг корабля и эффективно превращая его в идеальный черное тело. [[NO_STACK_STEALTH_SHIP_PARTS]]''' ST_CLOAK_3 Пространственная маскировка ST_CLOAK_3_DESC -'''Сильная Маскировка (+60) +'''Сильная маскировка (+60) -Hides this ship within a dimensional rift, greatly increasing [[metertype METER_STEALTH]]. [[metertype METER_STEALTH]]-causing ship parts do not stack.''' +Скрывает этот корабль внутри пространственного разрыва значительно увеличивая [[metertype METER_STEALTH]]. [[NO_STACK_STEALTH_SHIP_PARTS]]''' ST_CLOAK_4 -Фазовая Маскировка +Фазовая маскировка ST_CLOAK_4_DESC -'''Очень Сильная Маскировка (+80) +'''Очень сильная маскировка (+80) -Modulates the frequency of any matter waves emitted from the ship to match that of the background radiation. [[metertype METER_STEALTH]]-causing ship parts do not stack.''' +Улучшает [[metertype METER_STEALTH]] корабля на котором монтируется путем модуляции частоты любых волн материи испускаемых с корабля в соответствии с мощностью фона излучения. [[NO_STACK_STEALTH_SHIP_PARTS]]''' AR_STD_PLATE -Стандартная Броня +Стандартная броня + +AR_STD_PLATE_DESC +Усиливает корпус судна [[metertype METER_STRUCTURE]]. Базовая обшивка обеспечивает лишь умеренное повышение долговечности корпуса судна AR_ZORTRIUM_PLATE -Зортриумная Броня +Зортриумная броня + +AR_ZORTRIUM_PLATE_DESC +Усиливает корпус судна [[metertype METER_STRUCTURE]]. Более сильная защита чем [[shippart AR_STD_PLATE]] AR_DIAMOND_PLATE -Алмазная Броня +Алмазная броня + +AR_DIAMOND_PLATE_DESC +Усиливает корпус судна [[metertype METER_STRUCTURE]]. Более сильная защита чем [[shippart AR_ZORTRIUM_PLATE]] AR_XENTRONIUM_PLATE -Ксентронимная Броня +Ксентронимная броня + +AR_XENTRONIUM_PLATE_DESC +Усиливает корпус судна [[metertype METER_STRUCTURE]]. Чрезвычайно крепкая и прочная броня AR_ROCK_PLATE Скальная броня +AR_ROCK_PLATE_DESC +Усиливает корпус судна [[metertype METER_STRUCTURE]]. Крепкая и дешевая альтернатива сплавам. [[BLD_SHIPYARD_AST_REF_REQUIRED_ANY_SYSTEM_SHIP_PARTS_TEXT]] + +AR_CRYSTAL_PLATE +Никелированная кристальная броня + +AR_CRYSTAL_PLATE_DESC +Усиливает корпус судна [[metertype METER_STRUCTURE]]. Невероятно крепкая броня, изготовленная с использованием передовых технологий кристаллизации. [[BLD_SHIPYARD_AST_REF_REQUIRED_ANY_SYSTEM_SHIP_PARTS_TEXT]] + AR_NEUTRONIUM_PLATE -Нейтронная Броня +Нейтронная броня + +AR_NEUTRONIUM_PLATE_DESC +Усиливает корпус судна [[metertype METER_STRUCTURE]]. Самое крепкое и самое прочное покрытие брони. Восхитительная защита. [[MACRO_NEUTRONIUM_BUILDINGS]] + +AR_PRECURSOR_PLATE +Никелированная броня предтеч + +AR_PRECURSOR_PLATE_DESC +Усиливает корпус судна [[metertype METER_STRUCTURE]]. Великолепная и эффективная броня неизвестного состава SH_DEFENSE_GRID -Защитная Сетка +Защитная сетка + +SH_DEFENSE_GRID_DESC +'''Обеспечивает основную экранированную защиту. Способный полностью поглощать или по крайней мере существенно уменьшать [[encyclopedia DAMAGE_TITLE]] от ударов с низким уровнем мощности оружия + +[[NO_STACK_SHIELDS_SHIP_PARTS]]''' SH_DEFLECTOR -Отражающий Щит +Отражающий щит + +SH_DEFLECTOR_DESC +'''Обеспечивает продвинутую экранированную защиту. Может поглощать или по крайней мере существенно уменьшать [[encyclopedia DAMAGE_TITLE]] наносимый оружием умеренной мощности + +[[NO_STACK_SHIELDS_SHIP_PARTS]]''' SH_PLASMA -Плазменный Щит +Плазменный щит + +SH_PLASMA_DESC +'''Мощный щит, который значительно уменьшает [[encyclopedia DAMAGE_TITLE]], получаемый от воздействия даже продвинутого оружия и полностью поглощает что-то более слабое + +[[NO_STACK_SHIELDS_SHIP_PARTS]]''' SH_BLACK -Черный Щит +Черный щит + +SH_BLACK_DESC +'''Самая мощная экранированная защита может быть пробита только самым мощным оружием. Чрезвычайно дорога для исследования и создания + +[[NO_STACK_SHIELDS_SHIP_PARTS]]''' SH_MULTISPEC -Мультиспектральный Щит +Мультиспектральный щит + +SH_MULTISPEC_DESC +'''Мощный [[metertype METER_SHIELD]] способный защищать корабль внутри звезды. Предоставляет высокий бонус [[metertype METER_STEALTH]] на карте галактики, когда корабль находится в звезде, отличной от звезды класса [[STAR_BLACK]] или [[STAR_NEUTRON]] и позволяет кораблю входить в звезду в бою + +[[NO_STACK_SHIELDS_SHIP_PARTS]]''' SH_ROBOTIC_INTERFACE_SHIELDS -Роботизированный интерфейс: Щиты +Роботизированный интерфейс: щиты + +SH_ROBOTIC_INTERFACE_SHIELDS_DESC +'''Роботизированный интерфейс: щиты имеют прочность щита примерно 1 прочность щита за другой дополнительный роботизированный интерфейс. Таким образом происходит увеличение общего экранирования кораблей находящимся в том же месте, хотя и с уменьшающейся отдачей при более высоких количествах. Эффект ограничен 20-процентной прочностью, применяется только к судам, имеющим экипаж [[encyclopedia ROBOTIC_SPECIES_TITLE]] вида и может быть размещен только на типе корпуса корабля [[encyclopedia HULL_LINE_ROBOTIC]] + +Роботизированные виды обладают неотъемлемой способностью объединиться и тем самым существенно увеличить их чистую вычислительную мощность. Роботизированный интерфейс: щиты используют эту сеть для расчета траектории входящего огня и усиления мощности щита в точке удара +''' CO_COLONY_POD -Модуль Колоний +Модуль колоний + +CO_COLONY_POD_DESC +'''Базовые средства для колонистов, чтобы пережить путешествие на новую планету. Позволяет кораблю колонизировать новые миры + +[[COLONY_SHIP_PARTS_MIN_POP]] [[COLONY_SHIP_PARTS_UPKEEP_COST]]''' CO_SUSPEND_ANIM_POD -Крионический модуль Колоний +Крионический модуль колоний + +CO_SUSPEND_ANIM_POD_DESC +'''Колонисты содержатся в крионических модулях во время путешествия колонизации, тем самым устраняя необходимость обеспечивать пропитание и значительно увеличивая количество колонистов, которых можно перевозить на одном корабле + +[[COLONY_SHIP_PARTS_MIN_POP]] [[COLONY_SHIP_PARTS_UPKEEP_COST]]''' CO_OUTPOST_POD -Модуль Аванпоста +Модуль аванпоста + +CO_OUTPOST_POD_DESC +'''Модули [[encyclopedia OUTPOSTS_TITLE]] позволяют установить беспилотную станцию. Это может быть место на необитаемых планетах. У них нет населения и, как правило, нет ресурсов. Но они обеспечивают видимость создавая линии [[metertype METER_SUPPLY]], когда исследована необходимая технология. Аванпосты могут быть модернизированы до колонии + +[[COLONY_SHIP_PARTS_UPKEEP_COST]]''' + +GT_TROOP_POD +Наземный войсковой транспорт + +GT_TROOP_POD_DESC +'''Переносит 2 наземные единицы [[metertype METER_TROOPS]] и оборудование, которое может быть установлено на планете. Может быть построен только на месте производства, по крайней мере, с двумя оборонительными войсками + +[[TROOP_POD_OPERATION_TEXT]]''' + +GT_TROOP_POD_2 +Продвинутый наземный войсковой транспорт + +GT_TROOP_POD_2_DESC +'''Переносит 4 наземные кибернетические единицы [[metertype METER_TROOPS]] и оборудование, которое может быть установлено на планете. Может быть построен только на месте производства, по крайней мере, с четырьмя оборонительными войсками + +[[TROOP_POD_OPERATION_TEXT]]''' + +SP_PLANET_BEACON +Мобильный планетарный маяк + +SP_PLANET_BEACON_DESC +Обозначает цель для [[buildingtype BLD_PLANET_DRIVE]]. Корабль должен оставаться на месте в течение хода, чтобы привод работал, а планета должна двигаться от непосредственно связанной системы SP_DISTORTION_MODULATOR Модулятор помех +SP_DISTORTION_MODULATOR_DESC +Распознает естественные геометрические неровности отличные от кораблей противника, манипулируя пространственно-временными искажениями, вызванными всеми объектами поблизости. Все объекты в том же месте имеют максимальную [[metertype METER_STEALTH]] сниженную на 20. Эффект не складывается + SP_CLOUD Генератор облаков SP_ASH Генератор пепла +SP_DIM +Пространственный рассекатель + +SP_VOID +Извергатель вакуума + SP_DEATH_SPORE -Споры Смерти +Споры смерти + +SP_DEATH_SPORE_DESC +Биологическое оружие [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_ORGANIC_POP]] SP_BIOTERM -Био-Терминаторы +Био-терминаторы + +SP_BIOTERM_DESC +Мощное биологическое оружие [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_ORGANIC_POP]] + +SP_EMP +Электромагнитный импульсный генератор + +SP_EMP_DESC +Электронное оружие [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_ROBOTIC_POP]] + +SP_EMO +Электромагнитный перезарядный генератор + +SP_EMO_DESC +Мощное электронное оружие [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_ROBOTIC_POP]] + +SP_SONIC +Звуковая ударная волна + +SP_SONIC_DESC +Вибрационное оружие [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_LITHIC_POP]] + +SP_GRV +Гравитационный импульсный генератор + +SP_GRV_DESC +Мощное вибрационное оружие [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_LITHIC_POP]] + +SP_DARK_RAY +Темный луч + +SP_DARK_RAY_DESC +Отрицательное энергетическое оружие [[SHIP_WEAPON_GRADUALLY_REDUCE]] [[ENEMY_PLANET_PHOTOTROPHIC_POP]] + +SP_VOID_SHADOW +Теневой вакуум + +SP_VOID_SHADOW_DESC +Мощное отрицательное энергетическое оружие [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_PHOTOTROPHIC_POP]] + +SP_CHAOS_WAVE +Волна хаоса + +SP_CHAOS_WAVE_DESC +Мощное оружие [[SHIP_WEAPON_QUICKLY_REDUCE]] [[ENEMY_PLANET_ANY_POP]]. ПРЕДУПРЕЖДЕНИЕ: также уничтожает [[special GAIA_SPECIAL]] особенности SP_NOVA_BOMB -'''Бомба Нова ''' +Бомба Нова + +SP_NOVA_BOMB_DESC +Оружие огромной силы, способное уничтожить всю систему. Оружие активируется, когда получает сигнал от [[buildingtype BLD_NOVA_BOMB_ACTIVATOR]] на расстоянии не более одного звездного прыжка + +SP_KRILL_SPAWNER +Порождение криля + +SP_KRILL_SPAWNER_DESC +Порождает [[predefinedshipdesign SM_KRILL_1]] в системах с незанятыми [[PT_ASTEROIDS]], которые еще не содержат планктон (криль). При переноске на невооруженном корабле [[SP_KRILL_SPAWNER]] обеспечивает существенную [[metertype METER_STEALTH]] (но не будет складываться с любыми другими [[metertype METER_STEALTH]] - вызывающими частями) + +SP_SOLAR_CONCENTRATOR +Солнечный концентратор + +SP_SOLAR_CONCENTRATOR_DESC +Солнечный концентратор использует фототропный эффект [[encyclopedia HULL_LINE_ORGANIC]] кораблей для увеличения силы [[tech SHP_WEAPON_2_1]]. Эффективность зависит от яркости звезды в текущем расположении. Предполагается увеличение силы оружия между 1.5 и 4.5. Исследование [[tech SHP_SOLAR_CONNECTION]] увеличит собирающую поверхность для максимизации эффекта + +SHP_SOLAR_CONNECTION +Солнечная сеть + +SHP_SOLAR_CONNECTION_DESC +Это обновление [[shippart SP_SOLAR_CONCENTRATOR]] используется экипажем кораблей. Это позволяет кораблю устанавливать одну энергетическую сеть между кораблями одной империи в той же звездной системе. [[SHP_SOLAR_CONNECTION]] обеспечивает дополнительную поверхность для сбора фотонов, которые равномерно распределены между [[encyclopedia HULL_LINE_ORGANIC]] кораблями. Теоретически гигантские сети могут повысить интенсивность [[tech SHP_WEAPON_2_1]] на 130% при идеальных условиях. Более реалистичный подход предполагает 100 концентраторов для достижения увеличения на 80%, в то время как 20 концентраторов обеспечивают усиление на 50%. Тонко сплетенный [[SHP_SOLAR_CONNECTION]] разрушаются, когда корабль начинает перемещаться по звездным путям и эффект сети исчезает вскоре после этого + +SHP_SOLAR_CONNECTION_SHORT_DESC +Устанавливает энергетическую сеть между [[SP_SOLAR_CONCENTRATOR]] + +SHP_ORGANIC_WAR_ADAPTION +Органическая военная адаптация + +SHP_ORGANIC_WAR_ADAPTION_DESC +Похоже [[encyclopedia HULL_LINE_ORGANIC]] корабли могут собирать фотонную энергию, которая может быть перенаправлена для расширения возможностей некоторых систем кораблей + +SHP_ORGANIC_WAR_ADAPTION_SHORT_DESC +Увеличивает силу [[SR_WEAPON_2_1]] в зависимости от типа звезды + +PR_MOBILE_ASSEMBLER +Мобильная верфь + +PR_MOBILE_ASSEMBLER_DESC +Позволяет производить корабли вдали от планет + +RS_AUTOLAB +Автоматическая лаборатория +RS_AUTOLAB_DESC +Генерирует научные исследования владельцу + +RS_AUTOFACTORY +Автозавод + +RS_AUTOFACTORY_DESC +Генерирует промышленное производство владельцу + +RS_AUTOTRADER +Автотрейдер + +RS_AUTOTRADER_DESC +Генерирует торговлю владельцу ## ## Ship hulls ## SH_ROBOTIC -Роботизированный Корпус +Роботизированный корпус SH_ROBOTIC_DESC -'''Этот корпус полностью автоматизирован и работает по принципам [[tech SHP_MIL_ROBO_CONT]]. В нем 4 внешних слота и 1 внутренний слот и он восстанавливает [[metertype METER_STRUCTURE]] в размере 2 за ход. +'''Этот корпус полностью автоматизирован и работает по принципам [[tech SHP_MIL_ROBO_CONT]]. В нем 4 внешних слота, 1 внутренний слот и он восстанавливает [[metertype METER_STRUCTURE]] в размере 2 за ход пока корабль не участвует в сражениях -[[metertype METER_STEALTH]] низкая, [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] средняя. +[[metertype METER_STEALTH]] средняя, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] средняя -В дополнение к [[buildingtype BLD_SHIPYARD_BASE]], на планете требуется построить [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]].''' +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]]''' SH_SPATIAL_FLUX Пространственно-потоковый корпус SH_SPATIAL_FLUX_DESC -'''Этот крохотный корпус способен развивать приличную скорость на больших расстояниях багодаря [[tech SHP_SPACE_FLUX_DRIVE]]. В нем только 2 внешних слота, и его Макс [[metertype METER_STRUCTURE]] низкая. +'''Этот крохотный корпус способен развивать приличную скорость на больших расстояниях благодаря [[tech SHP_SPACE_FLUX_DRIVE]]. В нем только 2 внешних слота, а его максимальная [[metertype METER_STRUCTURE]] достаточно низкая + +[[metertype METER_STEALTH]] начинается с 15, увеличивается на 10 в бездеятельном состоянии, но уменьшается на 30 на любом ходе, если находится в движении. Открытия в области технологий, таких как [[tech SPY_STEALTH_PART_1]] позволяют экипажам увеличить невидимость корабля на 10 за каждый уровень -[[metertype METER_STEALTH]] небольшая, но он получает большой штраф [[metertype METER_STEALTH]] за передвижение и в бою, и на галактической карте. [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] средняя. +[[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] средняя -В дополнение к [[buildingtype BLD_SHIPYARD_BASE]], на планете требуется построить [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]].''' +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED]]''' SH_SELF_GRAVITATING Самогравитирующий корпус SH_SELF_GRAVITATING_DESC -'''Этот корпус оргомен и мощен, в нём 6 внешних слотов, 2 внутренних слота и 1 слот для ядра - один из немногих корпусов, способных установить такие расширенные функции. Он также достаточно крепок, имея высокую Макс [[metertype METER_STRUCTURE]]. +'''Этот корпус огромен и мощен, в нем 6 внешних слотов, 2 внутренних слота и 1 внутренний слот. Один из немногих корпусов, способных устанавливать передовой функционал. Корпус достаточно крепкий и имеет высокую [[metertype METER_STRUCTURE]] -[[metertype METER_STEALTH]] очень низкая, [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] средняя. +[[metertype METER_STEALTH]] низкая, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] средняя -В дополнение к [[buildingtype BLD_SHIPYARD_BASE]], на планете требуется построить [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] и [[buildingtype BLD_SHIPYARD_CON_GEOINT]].''' +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED]]''' SH_NANOROBOTIC Нано-роботизированный корпус SH_NANOROBOTIC_DESC -'''Этот большой автоматизированный корпус ремонтирует повреждения самостоятельно, используя миллионы нанороботов. В нем 6 внешних слотов и 1 внутренний и он восстанавливает [[metertype METER_STRUCTURE]] полностью между атаками. +'''Этот большой автоматизированный корпус ремонтирует повреждения самостоятельно, используя миллионы нанороботов. В нем 6 внешних слотов и 1 внутренний. Когда он не участвуют в бою, нанороботы могут производить быстрый ремонт, ускоренно восстанавливая [[metertype METER_STRUCTURE]] (до максимума) на каждом ходе -[[metertype METER_STEALTH]] очень низкая, [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] средняя. +[[metertype METER_STEALTH]] средняя, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] средняя -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] и [[buildingtype BLD_SHIPYARD_CON_NANOROBO]] на создающей его планете.''' +Может быть построен только в месте с [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] и [[buildingtype BLD_SHIPYARD_CON_NANOROBO]]''' SH_TITANIC -Титанический Корпус +Титанический корпус SH_TITANIC_DESC -'''Этот невероятно большой корпус требует внушительного вклада в инженерию даже для передвижения. В нем 16 внешних слотов, 3 внутренних слота и 1 слот для ядра и его Макс [[metertype METER_STRUCTURE]] очень высокая. Такой огромный размер невозможно скрыть, даже с передовыми стелс-технологиями. +'''Этот невероятно большой корпус требует особого мастерства даже для перемещения. В нем 16 внешних слотов, 3 внутренних слота и 1 центральный слот. Его максимальная [[metertype METER_STRUCTURE]] очень высокая. Такой огромный размер невозможно скрыть, даже с передовыми стелс-технологиями -[[metertype METER_STEALTH]] очень низкая, [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] среднее. +[[metertype METER_STEALTH]] очень низкая, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] средняя -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] и [[buildingtype BLD_SHIPYARD_CON_GEOINT]] на создающей его планете.''' +[[BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED]]''' SH_TRANSSPATIAL Транс-пространственный корпус SH_TRANSSPATIAL_DESC -'''Этот экспериментальный корпус создавался в первую очередь для тестирования новых технологий; в нем 1 внешний слот и 1 слот для ядра, и его Макс [[metertype METER_STRUCTURE]] крайне низкая. Он был разработан наряду с [[shippart FU_TRANSPATIAL_DRIVE]] и если их совместить, то получится очень эффективный внедряющийся разведчик. +'''Этот экспериментальный корпус создавался в первую очередь для тестирования новых технологий. В нем 1 внешний слот и 1 центральный слот. Его максимальная [[metertype METER_STRUCTURE]] крайне мала. Он был разработан наряду с [[shippart FU_TRANSPATIAL_DRIVE]] и если их совместить, то получится очень эффективный проникающий разведчик -[[metertype METER_STEALTH]] небольшая, [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] средняя. +[[metertype METER_STEALTH]] высокая, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] средняя -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] и [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]] на создающей его планете.''' +Может быть построен только в месте с [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] и [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]]''' SH_LOGISTICS_FACILITATOR Координатор логистики SH_LOGISTICS_FACILITATOR_DESC -'''Этот организационный флагман предназначен для организации и переправки экипажа и других объектов во время боя. В нем 7 внешних слотов, 2 внутренних слота и слот для ядра. Дополнение к флоту в виде незаметных грузовых судов позволяет транспортировать персонал и материал во время боя, что позволяет всем дружеским кораблям полностью восстанавливать [[metertype METER_STRUCTURE]] между атаками. +'''Этот организационный флагман предназначен для организации и переправки экипажа и других объектов во время боя. В нем 7 внешних слотов, 2 внутренних слота и один центральный слот. Дополнение к флоту в виде незаметных грузовых судов позволяет транспортировать персонал и материал во время боя, что позволяет всем дружеским кораблям восстанавливать [[metertype METER_STRUCTURE]] между атаками -[[metertype METER_STEALTH]] небольшая, [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] средняя. +[[metertype METER_STEALTH]] средняя, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] средняя -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], [[buildingtype BLD_SHIPYARD_CON_NANOROBO]], [[buildingtype BLD_SHIPYARD_CON_GEOINT]], и [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]] на создающей его планете.''' +Может быть построен только в месте с [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]], [[buildingtype BLD_SHIPYARD_CON_NANOROBO]], [[buildingtype BLD_SHIPYARD_CON_GEOINT]] и [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]]''' SH_ASTEROID -Корпус Астероида +Корпус астероида SH_ASTEROID_DESC -'''Этот корпус изготовлен из астероида среднего размера, его постройка недорога. Он немного просторнее, чем [[shiphull SH_STANDARD]], в нем 4 внешних слота и 2 внутренних. +'''Этот корпус изготовлен из астероида среднего размера, его постройка недорога. Он немного просторнее, чем [[shiphull SH_BASIC_LARGE]], в нем 4 внешних слота и 2 внутренних -[[metertype METER_STEALTH]] низкая, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов, и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] средняя, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] среднее, [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] и в поясе астероидов в создающей его системе с построенным на поясе [[buildingtype BLD_SHIPYARD_AST]].'''' +[[BLD_SHIPYARD_BASE_AST_REQUIRED]]''' SH_SMALL_ASTEROID -Маленький Корпус Астероида +Маленький корпус астероида SH_SMALL_ASTEROID_DESC -'''Этот корпус изготовлен из очень маленького астероида. В нем всего лишь 1 внешний слот и 1 внутренний. +'''Этот корпус изготовлен из очень маленького астероида. В нем всего лишь 1 внешний слот и 1 внутренний -[[metertype METER_STEALTH]] низкая, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов, и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] средняя, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] среднее, [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] и в поясе астероидов в создающей его системе с построенным на поясе [[buildingtype BLD_SHIPYARD_AST]].''' +[[BLD_SHIPYARD_BASE_AST_REQUIRED]]''' SH_HEAVY_ASTEROID -Большой Корпус Астероида +Большой корпус астероида SH_HEAVY_ASTEROID_DESC -'''Этот корпус изготовлен из гигантского астероида, в нем 6 внешних слотов и 3 внутренних. +'''Этот корпус изготовлен из гигантского астероида, в нем 6 внешних слотов и 3 внутренних -[[metertype METER_STEALTH]] низкая, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов, и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] средняя, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] среднее, [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] и в поясе астероидов в создающей его системе с построенным на поясе [[buildingtype BLD_SHIPYARD_AST]].'''' +[[BLD_SHIPYARD_BASE_AST_REQUIRED]]''' SH_CAMOUFLAGE_ASTEROID -Камуфляжный Корпус Астероида +Камуфляжный корпус астероида SH_CAMOUFLAGE_ASTEROID_DESC -'''Этот корпус призван выглядеть как настоящй астероид. В нем 4 внутренних слота, однако внешние слоты отсутствуют. +'''Этот корпус призван выглядеть как настоящий астероид. В нем 4 внутренних слота, однако внешние слоты отсутствуют -[[metertype METER_STEALTH]] небольшая, и корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов, и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] высокая и корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] среднее, [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] и в поясе астероидов в создающей его системе с построенным на поясе [[buildingtype BLD_SHIPYARD_AST]].'''' +[[BLD_SHIPYARD_BASE_AST_REQUIRED]]''' SH_SMALL_CAMOUFLAGE_ASTEROID -Маленький Камуфляжный Корпус Астероида +Маленький камуфляжный корпус астероида SH_SMALL_CAMOUFLAGE_ASTEROID_DESC -'''Этот Камуфляжный Корпус Астероида способен использовать маскировочные астероидные обломки на своей обшивке, при этом он должен быть значительно меньше, чтобы не потерять бонуса [[metertype METER_STEALTH]]. В нем 1 внешний слот и 1 внутренний. +'''Этот небольшой камуфляжный корпус астероида способен использовать маскировочные астероидные обломки на своей обшивке, при этом он должен быть значительно меньше, чтобы не потерять бонуса [[metertype METER_STEALTH]]. В нем 1 внешний 1 внутренний слот -[[metertype METER_STEALTH]] высокая, и корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов, и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] очень высокая и корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] среднее, [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] и в поясе астероидов в создающей его системе с построенным на поясе [[buildingtype BLD_SHIPYARD_AST]].'''' +[[BLD_SHIPYARD_BASE_AST_REQUIRED]]''' SH_AGREGATE_ASTEROID -Совокупный Корпус Астероида +Совокупный корпус астероида SH_AGREGATE_ASTEROID_DESC -'''Этот массивный корпус формируется путем объединения нескольких крупных астероидов. В нем 15 внешних слотов и 4 внутренних. - +'''Этот массивный корпус формируется путем объединения нескольких крупных астероидов. В нем 15 внешних слотов и 4 внутренних -[[metertype METER_STEALTH]] низкая, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов, и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] средняя, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] среднее, [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] и в поясе астероидов в создающей его системе с построенными на поясе [[buildingtype BLD_SHIPYARD_AST]] и [[buildingtype BLD_SHIPYARD_AST_REF]].''' +[[BLD_SHIPYARD_BASE_AST_REQUIRED]] [[BLD_SHIPYARD_AST_REF_REQUIRED]]''' SH_MINIASTEROID_SWARM Рой мини-астероидов SH_MINIASTEROID_SWARM_DESC -'''Этот корпус изготовлен из маленького астероида, который расформирован на множество крошечных астероидов, которые контролируются главным корпусом. Эти астероиды защищают корабль, давая бонус к [[metertype METER_SHIELD]]. В нем 2 внешних слота, но отсутствуют внутренние слоты. [[metertype METER_STRUCTURE]] очень низкая. +'''Этот корпус изготовлен из маленького астероида, который разломан на множество крошечных астероидов, которые контролируются главным корпусом. Эти астероиды защищают корабль, давая бонус к [[metertype METER_SHIELD]]у. В нем 2 внешних слота, но отсутствуют внутренние слоты. [[metertype METER_STRUCTURE]] очень низкая -[[metertype METER_STEALTH]] низкая, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов, и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] очень высокая, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] среднее, [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] и в поясе астероидов в создающей его системе с построенными на поясе [[buildingtype BLD_SHIPYARD_AST]] и [[buildingtype BLD_SHIPYARD_AST_REF]].''' +[[BLD_SHIPYARD_BASE_AST_REQUIRED]] [[BLD_SHIPYARD_AST_REF_REQUIRED]]''' SH_SCATTERED_ASTEROID -Рассеянный Корпус Астероида +Рассеянный корпус астероида SH_SCATTERED_ASTEROID_DESC -'''Этот флагман изготовлен из нескольких крупных астероидов, некоторые из которых были объединены, чтобы сформировать основание корпуса, а остальные были разбиты на части, чтобы сформировать многочисленные маленькие астероиды, которые управляются с помощью главного корпуса. Эти астероиды защищают не только основной корабль, но и каждый сопровождающий дружественный корабль, давая бонус к [[metertype METER_SHIELD]] для всех дружественных кораблей и кораблей союзников в непосредственной близости. В этом корпусе 15 внешних слотов и 4 внутренних. [[metertype METER_STRUCTURE]] высокая. +'''Этот флагман изготовлен из нескольких крупных астероидов, некоторые из которых были объединены, чтобы сформировать основание корпуса, а остальные были разбиты на части, чтобы сформировать многочисленные маленькие астероиды, которые управляются с помощью главного корпуса. Эти астероиды защищают не только основной корабль, но и каждый сопровождающий дружественный корабль, давая бонус к [[metertype METER_SHIELD]]у для всех дружественных кораблей и кораблей союзников в непосредственной близости. В этом корпусе 15 внешних слотов и 4 внутренних. [[metertype METER_STRUCTURE]] высокая -[[metertype METER_STEALTH]] низкая, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов, и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] средняя, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] среднее, [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] и в поясе астероидов в создающей его системе с построенными на поясе [[buildingtype BLD_SHIPYARD_AST]] и [[buildingtype BLD_SHIPYARD_AST_REF]].''' +[[BLD_SHIPYARD_BASE_AST_REQUIRED]] [[BLD_SHIPYARD_AST_REF_REQUIRED]]''' SH_CRYSTALLIZED_ASTEROID -Кристаллизированный Корпус Астероида +Кристаллизированный корпус астероида SH_CRYSTALLIZED_ASTEROID_DESC -'''Этот прочный корпус изготовлен из астероида среднего размера, закаленного передовыми методами кристаллизации. Как очередной [[shiphull SH_ASTEROID]], в нем 4 внешних слота и 2 внутренних. [[metertype METER_STRUCTURE]] чрезвычайно высокая. +'''Этот прочный корпус изготовлен из астероида среднего размера, закаленного передовыми методами кристаллизации. Как обычный [[shiphull SH_ASTEROID]], в нем 4 внешних слота и 2 внутренних. [[metertype METER_STRUCTURE]] чрезвычайно высокая -[[metertype METER_STEALTH]] низкая, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов, и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] средняя, но корабль с таким корпусом получает большой бонус на галактической карте, когда он находится в системе с поясом астероидов и в бою, когда он скрывается в поясе астероидов. [[metertype METER_DETECTION]] среднее, [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] и в поясе астероидов в создающей его системе с построенными на поясе [[buildingtype BLD_SHIPYARD_AST]] и [[buildingtype BLD_SHIPYARD_AST_REF]].''' +[[BLD_SHIPYARD_BASE_AST_REQUIRED]] [[BLD_SHIPYARD_AST_REF_REQUIRED]]''' SH_ORGANIC -Органический Корпус +Органический корпус SH_ORGANIC_DESC -'''Живой корпус с 3 внешними слотами и 1 внутренним. -Органический рост: создается с 5 [[metertype METER_STRUCTURE]],но наращивает дополнительно 5 Структуры через 25 ходов. +'''Живой корпус с 3 внешними слотами и 1 внутренним +Органический рост: создается с [[metertype METER_STRUCTURE]] равной 5, но наращивает дополнительно 5 Прочности через 25 ходов -[[metertype METER_STEALTH]] небольшая, [[metertype METER_DETECTION]] плохой и [[metertype METER_SPEED]] хорошая. +[[metertype METER_STEALTH]] небольшая, [[metertype METER_DETECTION]] низкое и [[metertype METER_SPEED]] хорошая -Дешевый и универсальный корпус в состоянии выполнять множество функций флота, но вероятно он уже устаревает по мере развития технологий. Из-за низкого обнаружения он плохо подходит для разведки, но его можно превратить в способный военный корабль. Живой корпус восстанавливает [[metertype METER_STRUCTURE]] и [[metertype METER_FUEL]] между боями. +Дешевый и универсальный корпус в состоянии выполнять множество функций флота, но вероятно он устареет по мере развития технологий. Из-за низкого обнаружения он плохо подходит для разведки, но его можно превратить в неплохой разведывательный корабль -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] на создающей его планете.''' +[[LIVING_HULL_AUTO_REGEN]] + +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED]]''' SH_STATIC_MULTICELLULAR -Статический Многоклеточный Корпус +Статический многоклеточный корпус SH_STATIC_MULTICELLULAR_DESC -'''Несмотря на органику, этот корпус неживой и производится с помощью многоклеточного литья. Универсальность интерьера и отсутствие потребности во внутренних органах повышает его потенциал, но без живых систем он не может восполнить здоровье или энергию. Он имеет 3 внешних слота и 2 внутренних. +'''Несмотря на органику, этот корпус неживой и производится с помощью многоклеточного литья. Универсальность интерьера и отсутствие потребности во внутренних органах повышает его потенциал, но без живых систем он не может восполнить здоровье или энергию. Он имеет 3 внешних слота и 2 внутренних -[[metertype METER_STEALTH]] низкая, [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] высокая. +[[metertype METER_STEALTH]] средняя, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] высокая -Дешевый и универсальный корпус в состоянии выполнять множество функций флота, без превосходства над другими. Этот корпус не восстанавливает ни [[metertype METER_STRUCTURE]], ни [[metertype METER_FUEL]] между боями. +Дешевый и универсальный корпус в состоянии выполнять множество функций флота без превосходства над другими. Этот корпус не восстанавливает ни [[metertype METER_STRUCTURE]] ни [[metertype METER_FUEL]] между боями -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] на создающей его планете.''' +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED]]''' SH_ENDOMORPHIC -Эндоморфный Корпус +Эндоморфный корпус SH_ENDOMORPHIC_DESC -'''Полуживой корпус с 4 внешними и 2 внутренними слотами. -Органический рост: создается с 5 [[metertype METER_STRUCTURE]],но наращивает дополнительно 15 Структуры через 30 ходов. +'''Полуживой корпус с 4 внешними и 2 внутренними слотами +Органический рост: создается с [[metertype METER_STRUCTURE]] равной 5, но наращивает дополнительно 15 Прочности через 30 ходов -[[metertype METER_STEALTH]] маленькая, [[metertype METER_DETECTION]] хороший и [[metertype METER_SPEED]] высокая. +[[metertype METER_STEALTH]] средняя, [[metertype METER_DETECTION]] хорошее и [[metertype METER_SPEED]] высокая -Потенциально способный военный корабль. +Потенциально способный военный корабль -Этот корпус не восстанавливает ни [[metertype METER_STRUCTURE]], ни [[metertype METER_FUEL]] между боями. +Этот корпус не восстанавливает ни [[metertype METER_STRUCTURE]], ни [[metertype METER_FUEL]] между боями -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] и [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] на создающей его планете.''' +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_REQUIRED]]''' SH_SYMBIOTIC -Симбиотический Корпус +Симбиотический корпус SH_SYMBIOTIC_DESC -'''Живой корпус с 2 внешними и 2 внутренними слотами. Находится в симбиозе со своим экипажем, увеличивая значения [[metertype METER_STEALTH]] и скорости. -Органический рост: создается с 10 [[metertype METER_STRUCTURE]],но наращивает дополнительно 10 Структуры через 50 ходов. +'''Живой корпус с 2 внешними и 2 внутренними слотами. Находится в симбиозе со своим экипажем, увеличивая значения [[metertype METER_STEALTH]] и скорости +Органический рост: создается с [[metertype METER_STRUCTURE]] равной 10, но наращивает дополнительно 10 Прочности через 50 ходов -[[metertype METER_STEALTH]] небольшая, [[metertype METER_DETECTION]] хороший и [[metertype METER_SPEED]] высокая. +[[metertype METER_STEALTH]] высокая, [[metertype METER_DETECTION]] хорошее и [[metertype METER_SPEED]] высокая -Малое количество внешних слотов делает этот корпус неподходящим для фронтовых боев, но внутренние слоты, [[metertype METER_DETECTION]] и [[metertype METER_STEALTH]] дают ему потенциал разведчика или рейдера. +Малое количество внешних слотов делает этот корпус неподходящим для фронтовых боев, но внутренние слоты, дальность обнаружения и скрытность дают ему потенциал разведчика или рейдера -Живой корпус восстанавливает [[metertype METER_STRUCTURE]] и [[metertype METER_FUEL]] между боями. +[[LIVING_HULL_AUTO_REGEN]] -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] на создающей его планете.''' +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED]]''' SH_PROTOPLASMIC -Протоплазматический Корпус +Протоплазматический корпус SH_PROTOPLASMIC_DESC -'''Живой корпус с 3 внутренними и 2 внешними слотами. -Органический рост: создается с 5 [[metertype METER_STRUCTURE]],но наращивает дополнительно 25 Структуры через 50 ходов. +'''Живой корпус с 3 внутренними и 2 внешними слотами +Органический рост: создается с [[metertype METER_STRUCTURE]] равной 5, но наращивает дополнительно 25 Прочности через 50 ходов -[[metertype METER_STEALTH]] высокая, [[metertype METER_DETECTION]] хороший и [[metertype METER_SPEED]] высокая. +[[metertype METER_STEALTH]] очень высокая, [[metertype METER_DETECTION]] высокое и [[metertype METER_SPEED]] высокая -Малое количество внешних слотов делает этот корпус неподходящим для фронтовых боев, но внутренние слоты, [[metertype METER_DETECTION]] и [[metertype METER_STEALTH]] дают ему потенциал разведчика или рейдера. +Малое количество внешних слотов делает этот корпус неподходящим для фронтовых боев, но внутренние слоты, дальность обнаружения и скрытность дают ему потенциал разведчика или рейдера -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] и [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] на создающей его планете.''' +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_CELL_GRO_CHAMB_REQUIRED]]''' SH_ENDOSYMBIOTIC -Эндосимбиотический Корпус +Эндосимбиотический корпус SH_ENDOSYMBIOTIC_DESC -'''Этот живой одноклеточный корпус находится в симбиозе со своим экипажем, подвешивая их в цитоплазме и используя их в качестве органелл. +'''Этот живой одноклеточный корпус находится в симбиозе со своим экипажем, подвешивая их в цитоплазме и используя в качестве органелл В нем 4 внешних и 3 внутренних слота. -Базовый корпус создается с 5 [[metertype METER_STRUCTURE]],но наращивает дополнительно 15 Структуры через 30 ходов. +Базовый корпус создается с [[metertype METER_STRUCTURE]] равной 5, но наращивает дополнительно 15 Прочности через 30 ходов [[metertype METER_STEALTH]] хорошая, [[metertype METER_DETECTION]] высокий и [[metertype METER_SPEED]] высокая. -Живой корпус восстанавливает [[metertype METER_STRUCTURE]] и [[metertype METER_FUEL]] между боями. С потенциалом мощного военного корабля, коммерческого рейдера или вооруженного разведчика. +[[LIVING_HULL_AUTO_REGEN]] + +Потенциально мощный военный корабль, коммерческий рейдер или вооруженный разведчик -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] и [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] на создающей его планете. +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED]] ''' SH_RAVENOUS -Ненасытный Корпус +Ненасытный корпус SH_RAVENOUS_DESC -'''Этот неживой органический корпус - или корпус-'зомби' - создан из крови и частей своих собратьев. В нем 5 внешних и 2 внутренних слота. +'''Этот неживой органический корпус - или корпус-'зомби' - создан из крови и частей своих собратьев. В нем 5 внешних и 2 внутренних слота -Органический рост: создается с 5 [[metertype METER_STRUCTURE]],но наращивает дополнительно 20 Структуры через 40 ходов. +Органический рост: создается с [[metertype METER_STRUCTURE]] равной 5, но наращивает дополнительно 20 Прочности через 40 ходов -metertype METER_STEALTH]] низкая, [[metertype METER_DETECTION]] превосходный и [[metertype METER_SPEED]] высокая. +metertype METER_STEALTH]] средняя, [[metertype METER_DETECTION]] превосходное и [[metertype METER_SPEED]] высокая -С потенциалом мощного и быстрого военного корабля. +С потенциалом мощного и быстрого военного корабля -Этот корпус не имеет бонуса к восстановлению [[metertype METER_STRUCTURE]] или [[metertype METER_FUEL]]. +Этот корпус не имеет бонуса к восстановлению [[metertype METER_STRUCTURE]] или [[metertype METER_FUEL]] -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] и [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] на создающей его планете. +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_REQUIRED]] ''' SH_BIOADAPTIVE -Био-Адаптивный Корпус +Био-адаптивный корпус SH_BIOADAPTIVE_DESC -'''Живой корпус в согласии с телом и разумом, мастер исцеления и выносливости. В нем 3 внешних и 3 внутренних слота. -Органический рост: создается с 15 [[metertype METER_STRUCTURE]],но наращивает дополнительно 25 Структуры через 50 ходов. +'''Живой корпус в согласии с телом и разумом, мастер исцеления и выносливости. В нем 3 внешних и 3 внутренних слота +Органический рост: создается с [[metertype METER_STRUCTURE]] равной 15, но наращивает дополнительно 25 Прочности через 50 ходов -[[metertype METER_STEALTH]] высокая, [[metertype METER_DETECTION]] превосходный и [[metertype METER_SPEED]] высокая. +[[metertype METER_STEALTH]] очень высокая, [[metertype METER_DETECTION]] превосходное и [[metertype METER_SPEED]] высокая -С потенциалом мощного и быстрого коммерческого рейдера или разведчика. +С потенциалом мощного и быстрого коммерческого рейдера, невидимого нападающего или разведчика -Этот живой корпус восстанавливает [[metertype METER_STRUCTURE]] и [[metertype METER_FUEL]] полностью между боями. +Этот живой корпус быстро восстанавливает [[metertype METER_STRUCTURE]], когда выходит из боя, восстанавливая свою текущую структуру за каждый ход, не участвующий в битве или блокаде, а это означает, что если он будет иметь половину прочности или более, он будет полностью восстановлен всего за один ход. Кроме того, он восстанавливает 0.2 [[metertype METER_FUEL]] за ход -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] и [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] на создающей его планете. +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_CELL_GRO_CHAMB_REQUIRED]] ''' SH_SENTIENT -Разумный Корпус +Разумный корпус SH_SENTIENT_DESC -'''Этот органический флагман, обладающий самосознанием, с мощными аналитическими способностями и огромным объемом памяти, что позволяет ему управлять дружественными кораблями и предоставлять им полезную информацию и искусные тактические наказы в бою, обеспечивая бонус к [[metertype METER_STEALTH]] и [[metertype METER_DETECTION]] всем сопровождающим дружественным кораблям. В нем 6 внешних, 3 внутренних слота и слот под ядро. -Органический рост: создается с 12 [[metertype METER_STRUCTURE]],но наращивает дополнительно 45 Структуры через 45 ходов. +'''Этот органический флагман, обладающий самосознанием, с мощными аналитическими способностями и огромным объемом памяти, что позволяет ему управлять дружественными кораблями и предоставлять им полезную информацию и искусные тактические наказы в бою, обеспечивая бонус к [[metertype METER_STEALTH]] и [[metertype METER_DETECTION]] всем сопровождающим дружественным кораблям. В нем 6 внешних, 3 внутренних и 1 центральный слот +Органический рост: создается с [[metertype METER_STRUCTURE]] равной 12, но наращивает дополнительно 45 Прочности через 45 ходов -[[metertype METER_STEALTH]] хорошая, [[metertype METER_DETECTION]] хороший и [[metertype METER_SPEED]] высокая. +[[metertype METER_STEALTH]] очень хорошая, [[metertype METER_DETECTION]] превосходное и [[metertype METER_SPEED]] высокая -Этот корпус восстанавливает [[metertype METER_STRUCTURE]] и [[metertype METER_FUEL]] между боями. +[[LIVING_HULL_AUTO_REGEN]] -Нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] и [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] на создающей его планете.''' +[[BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED]]''' SH_COMPRESSED_ENERGY -Корпус Сжатой Энергии +Корпус сжатой энергии SH_COMPRESSED_ENERGY_DESC -'''Этот быстрый корпус полностью состоит из сжатой энергии и имеет 1 внешний слот. +'''Этот быстрый корпус состоит исключительно из сжатой энергии и имеет один внешний слот + +[[metertype METER_STRUCTURE]] низкая, [[metertype METER_STEALTH]] очень хорошая, [[metertype METER_DETECTION]] обычное и [[metertype METER_SPEED]] очень высокая + +[[BLD_SHIPYARD_BASE_ENRG_COMP_THREE_STARS_REQUIRED]]''' + +SH_ENERGY_FRIGATE +Энергетический фрегат -Структура корпуса низкая, [[metertype METER_STEALTH]] хорошая, [[metertype METER_DETECTION]] обычный и [[metertype METER_SPEED]] очень выская. +SH_ENERGY_FRIGATE_DESC +'''Этот быстрый корпус сочетает в себе технологию сжатой энергии и боевого корабля для создания быстрого, но относительно слабого корабля атаки -Этот корпус требует большой затраты энергии для своей постройки и может быть построен только в системе со звездой типа Белый, Синий или Черной Дырой и нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ENRG_COMP]] на создающей его планете.''' +[[metertype METER_STRUCTURE]] и [[metertype METER_STEALTH]] средняя, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] очень хорошая + +[[BLD_SHIPYARD_BASE_ENRG_COMP_THREE_STARS_REQUIRED]]''' SH_FRACTAL_ENERGY -Корпус Фрактальной Энергии +Корпус фрактальной энергии SH_FRACTAL_ENERGY_DESC -'''Это корпус, несмотря на то, что маленький, имеет фрактальную поверхности, которая позволяет смонтировать гораздо больше деталей, чем на гладком корпусе. Он содержит 14 внешних слотов, но внутренние слоты отсутствуют. +'''Это корпус, несмотря на то, что маленький, имеет геометрическую поверхность, которая позволяет смонтировать гораздо больше деталей, чем на гладком корпусе. Он содержит только 14 внешних слотов -[[metertype METER_STRUCTURE]] невысокая, [[metertype METER_STEALTH]] очень низкая, [[metertype METER_DETECTION]] обычный и [[metertype METER_SPEED]] очень высокая. +[[metertype METER_STRUCTURE]] невысокая, [[metertype METER_STEALTH]] низкая, [[metertype METER_DETECTION]] обычное и [[metertype METER_SPEED]] очень высокая -Этот корпус требует большой затраты энергии для своей постройки и может быть построен только в системе со звездой типа Синий или Черной Дырой и нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ENRG_COMP]] на создающей его планете.''' +[[BLD_SHIPYARD_BASE_ENRG_COMP_TWO_STARS_REQUIRED]]''' SH_QUANTUM_ENERGY -Корпус Квантовой Энергии +Корпус квантовой энергии SH_QUANTUM_ENERGY_DESC -'''Этот корпус преувеличивает квантовые флуктуации энергии, чтобы увеличить свою собственную мощь. Он содержит 7 внешних и 3 внутренних слота. +'''Этот корпус усиливает квантовые флуктуации энергии, чтобы увеличить свою собственную мощь. Он содержит 7 внешних и 3 внутренних слота -[[metertype METER_STRUCTURE]] невысокая, [[metertype METER_STEALTH]] очень низкая, [[metertype METER_DETECTION]] обычный и [[metertype METER_SPEED]] очень высокая. +[[metertype METER_STRUCTURE]] невысокая, [[metertype METER_STEALTH]] низкая, [[metertype METER_DETECTION]] обычное и [[metertype METER_SPEED]] очень высокая -Этот корпус требует большой затраты энергии для своей постройки и может быть построен только в системе со звездой типа Синий или Черной Дырой и нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ENRG_COMP]] на создающей его планете.''' +[[BLD_SHIPYARD_BASE_ENRG_COMP_TWO_STARS_REQUIRED]]''' SH_SOLAR -Корпус Солнечной Энергии +Корпус солнечной энергии SH_SOLAR_DESC -'''Этот могучий флагман по существу миниатюрное солнце как таковой является отличным источником для [[metertype METER_FUEL]] и видимости. Все сопровождающие дружеские корабли полностью восстанавливают топливо между боями, а все вражеские корабли в непосредственной близости получают штраф на [[metertype METER_STEALTH]]. В этом корпусе 18 внешних, 8 внутренних слотов и слот под ядро — наиболее вместительней, чем любой другой корпус. - -[[metertype METER_STRUCTURE]] невероятно высокая, [[metertype METER_STEALTH]] очень низкая, [[metertype METER_DETECTION]] обычный и [[metertype METER_SPEED]] очень высокая. +'''Этот могучий флагман по существу миниатюрное солнце и как таковой является отличным источником [[metertype METER_FUEL]] и [[metertype METER_DETECTION]]. Все сопровождающие дружеские корабли полностью восстанавливают топливо между боями, а все вражеские корабли в непосредственной близости получают штраф на [[metertype METER_STEALTH]]. В этом корпусе 16 внешних, 4 внутренних слотов и 1 очень вместительный центральный слот -Тем не менее, такой корабль имеет возможность регистрировать звезду и прятаться, получая безупречный бонус к [[metertype METER_STEALTH]] галактической карте, когда он находится в системе со звездой другого типа, нежели Черная дыра или Нейтрон, и в бою, когда он скрывается внутри таких звезд. +[[metertype METER_STRUCTURE]] высокая, [[metertype METER_STEALTH]] очень низкая, [[metertype METER_DETECTION]] обычное и [[metertype METER_SPEED]] очень высокая. Этот корабль имеет возможность входить в звезду и скрываться, получая идеальную [[metertype METER_STEALTH]] на карте галактики, когда он находится в системе со звездой отличного типа от [[STAR_BLACK]] или [[STAR_NEUTRON]]. Аналогичный эффект проявляется и во время боя, когда корабль прячется внутри горячих звезд кроме [[STAR_BLACK]] или [[STAR_NEUTRON]] -Этот корпус требует большой затраты энергии для своей постройки и должен использовать энергию столкновения частиц и атичастиц на горизонте событий формирования Черной Дыры. Он нуждается в построенных [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ENRG_COMP]] и [[buildingtype BLD_SHIPYARD_ENRG_SOLAR]] на создающей его планете.''' +Этот корпус требует большой затраты энергии для своей постройки и должен использовать энергию столкновения частиц и античастиц на горизонте событий формирования [[STAR_BLACK]]. Он может быть построен только в месте с [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ENRG_COMP]] и [[buildingtype BLD_SHIPYARD_ENRG_SOLAR]]''' SH_BASIC_SMALL -Базовый Малый Корпус +Малый корпус SH_BASIC_SMALL_DESC -'''Маленький базовый межзвездный корпус. В нем только 1 внешний слот, но он способен совершить мега-прыжок по сравнению с другими базовыми корпусами. +'''Маленький межзвездный корпус. В нем только 1 внешний слот, но он способен совершить мега-прыжок по сравнению с другими базовыми корпусами -[[metertype METER_STEALTH]] низкая, [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] средняя. +[[metertype METER_STEALTH]] средняя, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] средняя -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] на создающей его планете.''' +[[BLD_SHIPYARD_BASE_REQUIRED]]''' SH_BASIC_MEDIUM -Базовый Средний Корпус +Средний корпус SH_BASIC_MEDIUM_DESC -'''Базовый межзвездный корпус среднего размера, с 2 внешними и 1 внутренним слотом. +'''Средний межзвездный корпус с 2 внешними и 1 внутренним слотом -[[metertype METER_STEALTH]] низкая, [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] средняя, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] на создающей его планете.'''' +[[BLD_SHIPYARD_BASE_REQUIRED]]''' -SH_STANDARD -Базовый Большой Корпус +SH_BASIC_LARGE +Большой корпус -SH_STANDARD_DESC -'''Большой базовый межзвездный корпус, с 3 внешними и 1 внутренним слотом. +SH_BASIC_LARGE_DESC +'''Большой межзвездный корпус с 3 внешними и 1 внутренним слотом -[[metertype METER_STEALTH]] низкая, [[metertype METER_DETECTION]] средний и [[metertype METER_SPEED]] низкая. +[[metertype METER_STEALTH]] средняя, [[metertype METER_DETECTION]] среднее и [[metertype METER_SPEED]] низкая -Нуждается в построенном [[buildingtype BLD_SHIPYARD_BASE]] на создающей его планете.''' +[[BLD_SHIPYARD_BASE_REQUIRED]]''' SHP_XENTRONIUM_HULL -Ксентрониумный Бронированный Корпус +Ксентрониумный бронированный корпус SH_XENTRONIUM -Ксентрониумный Корпус +Ксентрониумный корпус SH_XENTRONIUM_DESC -Корпус построен в первую очередь из ксентрониума. Несмотря на малый размер, у него чрезвычайно высокая МАКС [[metertype METER_STRUCTURE]]. В нем 4 внешних и 1 внутренний слот. +'''Корпус построен в первую очередь из ксентрониума. Несмотря на малый размер у него чрезвычайно высокая [[metertype METER_STRUCTURE]]. В нем 4 внешних и 1 центральный слот + +[[metertype METER_STEALTH]] и [[metertype METER_DETECTION]] на среднем уровне''' SH_COLONY_BASE -Базовый Корпус Колонистов +Базовый корпус колонистов SH_COLONY_BASE_DESC -Корпус предназначен для создания колонии на другой планете в системе. Он не может двигаться между системами или двигаться быстро внутри системы и, следовательно, имеет только 1 внутренний слот. +'''Корпус предназначен для создания колонии на другой планете в системе. Он не может двигаться между системами или двигаться быстро внутри системы и, следовательно, имеет только 1 внутренний слот + +[[metertype METER_STEALTH]] и [[metertype METER_DETECTION]] на среднем уровне''' SH_FLOATER_BODY -Плавающее Тело +Тело скитальца SH_FLOATER_BODY_DESC -Малое хрупкое тело, которое не в состоянии выдержать значимый [[encyclopedia DAMAGE_TITLE]]. +Малое хрупкое тело, которое не в состоянии выдержать значительный [[encyclopedia DAMAGE_TITLE]] SH_TREE_BODY -Деревянное Тело +Древесное тело SH_TREE_BODY_DESC -Жесткое древовидное тело, которое не в состоянии двигаться между системами. +Жесткое древовидное тело, которое не в состоянии двигаться между системами SH_STRONG_MONSTER_BODY -Тело Дракона +Тело дракона SH_STRONG_MONSTER_BODY_DESC -Большой сильное тело с грозными природными оружия и сильной естественной броней. +Большое сильное тело с грозным природным вооружением и сильной естественной броней SH_GUARD_MONSTER_BODY -Охранный Корпус +Охранный корпус SH_GUARD_MONSTER_BODY_DESC -Сильный корпус для охраняемого класса монстров +Сильный корпус для охраняющего класса монстров + +SH_GUARD_0_BODY +Содержание корпуса корабля + +SH_GUARD_0_BODY_DESC +Содержание корпуса корабля + +SH_GUARD_1_BODY +Стражнический корпус + +SH_GUARD_1_BODY_DESC +Основной корпус для монстров класса стражей + +SH_GUARD_3_BODY +Корпус надзирателя + +SH_GUARD_3_BODY_DESC +Мощный корпус для монстров класса надзирателей SH_KRILL_1_BODY -Малый рой планктона +Малый рой планктона 1 SH_KRILL_1_BODY_DESC -Малый рой планктона +Малый рой планктона 1 SH_KRILL_2_BODY -Рой планктона +Рой планктона 2 SH_KRILL_2_BODY_DESC -Рой планктона +Рой планктона 2 SH_KRILL_3_BODY -Большой рой планктона +Большой рой планктона 3 SH_KRILL_3_BODY_DESC -Большой рой планктона +Большой рой планктона 3 SH_KRILL_4_BODY -Огромный рой планктона +Огромный рой планктона 4 SH_KRILL_4_BODY_DESC -Огромный рой планктона +Огромный рой планктона 4 SH_DRONE_BODY -Корпус Дрона +Корпус дрона SH_DRONE_BODY_DESC -Корпус Дрона +Корпус дрона SH_IMMOBILE_FACTORY -Завод Дрона +Завод дронов SH_IMMOBILE_FACTORY_DESC -Завод Дрона +Завод дронов SH_KRAKEN_1_BODY -Тело маленького спрута +Тело маленького кракена SH_KRAKEN_1_BODY_DESC Особь небольшого размера SH_KRAKEN_2_BODY -Тело спрута +Тело кракена SH_KRAKEN_2_BODY_DESC Особь среднего размера SH_KRAKEN_3_BODY -Тела королевского спрута +Тела королевского кракена SH_KRAKEN_3_BODY_DESC -Крупная особь +Огромный космический монстр + +SH_WHITE_KRAKEN_BODY +Тело белого кракена + +SH_WHITE_KRAKEN_BODY_DESC +Белый доисторический предок космического кракена SH_BLACK_KRAKEN_BODY -Тело черного спрута +Тело черного кракена SH_BLACK_KRAKEN_BODY_DESC -Чудовищный черный спрут с неестественной аурой вокруг. +Чудовищный черный кракен с неестественной аурой вокруг SH_SNOWFLAKE_1_BODY Тело маленькой снежинки -SH_SNOWFLAKE_1_BODY_DESC -Легкий но опасный космический монстр. +SH_SNOWFLAKE_1_BODY_DESC +Легкий но опасный космический монстр + +SH_SNOWFLAKE_2_BODY +Тело снежинки + +SH_SNOWFLAKE_2_BODY_DESC +Легкий но опасный космический монстр + +SH_SNOWFLAKE_3_BODY +Тело большой снежинки + +SH_SNOWFLAKE_3_BODY_DESC +Легкий но опасный космический монстр + +SH_PSIONIC_SNOWFLAKE_BODY +Тело псионной снежинки + +SH_PSIONIC_SNOWFLAKE_BODY_DESC +Опасный для разума космический монстр + +SH_JUGGERNAUT_1_BODY +Тело маленького джаггернаута + +SH_JUGGERNAUT_1_BODY_DESC +Внушительный тяжёлый космический монстр + +SH_JUGGERNAUT_2_BODY +Тело джаггернаута + +SH_JUGGERNAUT_2_BODY_DESC +Внушительный тяжелый космический монстр + +SH_JUGGERNAUT_3_BODY +Тело большого джаггернаута + +SH_JUGGERNAUT_3_BODY_DESC +Внушительный тяжёлый космический монстр + +SH_BLOATED_JUGGERNAUT_BODY +Тело здоровенного джаггернаута + +SH_BLOATED_JUGGERNAUT_BODY_DESC +Неестественно большой космический монстр + +SH_NEBULOUS_BODY +Туманное тело + +SH_EXP_OUTPOST_HULL +Корпус аванпоста экспериментаторов + +SH_EXP_OUTPOST_HULL_DESC +Корпус, который экспериментаторы используют для доставки оборудования в другие галактики + +SH_COSMIC_DRAGON_BODY +Дракон космических размеров + +SH_COSMIC_DRAGON_BODY_DESC +Устрашающий космический дракон размером с планету + +SH_DAMPENING_CLOUD_BODY +Угасающее облачное тело + +SH_DAMPENING_CLOUD_BODY_DESC +Космическое облако высокоэнергетических частиц + +## +## Monsters Macros +## + +SM_KRILL_MACRO_1 +Маленькие, незаметные инсектоидные организмы, питающиеся пылью и камнями, блуждающие вдали от любой гравитации. Индивидуально простые, они взамиодействуют друг с другом через когерентный свет, координируя движения роя и вычисляя траектории + +SM_KRILL_MACRO_2 +Большое количество низкогравитационных ресурсов, имеющееся в поясах астероидов, обеспечивает планктону идеальные условия для быстрого размножения + +SM_GUARD_MACRO +Запрограммированные Предтечами для защиты системы от захватчиков + +SM_KRAKEN_ENVIRONMENT +Благоприятная среда: [[PT_GASGIANT]]. Кракенские гнезда обычно находятся на орбите планет [[PT_GASGIANT]], а сами кракены могут созреть и в более крупные формы в системах с большими [[PT_GASGIANT]] + +SM_SNOWFLAKE_ENVIRONMENT +Благоприятная среда: [[SZ_SMALL]] планеты. Гнезда снежинок обычно находятся на орбите планет [[SZ_SMALL]], а снежинки могут созреть и в более крупные формы в системах с планетами [[SZ_SMALL]] + +SM_JUGGERNAUT_ENVIRONMENT +Благоприятная среда: [[PT_ASTEROIDS]]. Гнезда сокрушительных сил (джаггернауты) обычно находятся в поясах Астероидов, а сами джаггернауты могут созреть в более крупные формы в системах с поясами [[PT_ASTEROIDS]] + +## +## Buildings macros +## + +BUILDING_AVAILABLE_ON_OUTPOSTS +Это здание может быть построено на [[encyclopedia OUTPOSTS_TITLE]] + +NO_STACK_SUPPLY_CONNECTION_TEXT +Более чем одна из тех же соединенных групп ресурсов [[metertype METER_SUPPLY]] не складывается и не добавляет дополнительного бонуса + +MACRO_NEUTRONIUM_BUILDINGS +Чтобы использовать нейтрониум, империя должна либо построить [[buildingtype BLD_NEUTRONIUM_EXTRACTOR]] в [[STAR_NEUTRON]] звездной системе, либо найти [[buildingtype BLD_NEUTRONIUM_SYNTH]] и [[buildingtype BLD_NEUTRONIUM_FORGE]] на планете, где он используется + +ARTIFICIAL_PLANET_PROCESS_LOCATION +Этот процесс [[encyclopedia OUTPOSTS_TITLE]] должен происходить либо на газовом гиганте либо в поясе астероидов + +BLD_COL_PART_1 +Это здание может быть построено только на [[encyclopedia OUTPOSTS_TITLE]] и обновит его до нового состояния + +BLD_COL_PART_2 +Минимальное время создания зависит от общего расстояния звездного пути до ближайшей принадлежащей империи колонии + +BLD_COL_PART_3 +Время создания будет и дальше увеличивать сокращение путем исследования технологий, которые позволят быстрее направлять колонии используя новые детали двигателей и более быстрые корпуса + +# Macro keys formatting for ship designs, hulls or parts: +# BLD_SHIPYARD_TYPE1_TYPE2_REQUIRED (required building type(s) owned by +# empire only in the same system) +# ANY_SYSTEM (building owned in any system by empire or ally) +# Any other key formatting is self-explanatory (e.g. LIVING_HULL_AUTO_REGEN) + +## +## Ship design/hull macros +## + +# Keys used in Predefined Ship Designs and Ship Hulls sections + +BLD_SHIPYARD_BASE_REQUIRED +Может быть построен только в месте с [[buildingtype BLD_SHIPYARD_BASE]] + +BLD_SHIPYARD_BASE_AST_REQUIRED +Может быть построен только в месте с [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_AST]], в системе с поясом астероидов. + +BLD_SHIPYARD_AST_REF_REQUIRED +В той же системе также требуется [[buildingtype BLD_SHIPYARD_AST_REF]] принадлежащий империи владельцу + +BLD_SHIPYARD_AST_REF_REQUIRED_ANY_SYSTEM +[[buildingtype BLD_SHIPYARD_AST_REF]] принадлежащий империи или союзнику, также требуется в любой системе + +BLD_SHIPYARD_BASE_ENRG_COMP_THREE_STARS_REQUIRED +Требует большое количество энергии для создания и может быть построен только в системе со звёздами типа [[STAR_BLUE]], [[STAR_WHITE]] или [[STAR_BLACK]], совместно с [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ENRG_COMP]]. + +BLD_SHIPYARD_BASE_ENRG_COMP_TWO_STARS_REQUIRED +Требует большое количество энергии для создания и может быть построен только в системе со звёздами типа [[STAR_BLUE]] или [[STAR_BLACK]], совместно с [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ENRG_COMP]]. + +BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_REQUIRED +Может быть построен только при наличии [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] в системе. + +BLD_SHIPYARD_BASE_ORBITAL_DRYDOCK_CON_GEOINT_REQUIRED +Может быть построен только при наличии [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORBITAL_DRYDOCK]] и [[buildingtype BLD_SHIPYARD_CON_GEOINT]] в системе. + +LIVING_HULL_AUTO_REGEN +Живой корпус. Восстанавливает [[metertype METER_STRUCTURE]] и [[metertype METER_FUEL]] между сражениями. + +BLD_SHIPYARD_BASE_ORG_ORB_INC_REQUIRED +Может быть построен только при наличии [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] в системе. + +BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_REQUIRED +Может быть построен только при наличии [[buildingtype BLD_SHIPYARD_BASE]] и [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] и [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] в системе. + +MIN_POPULATION_THREE_REQUIRED +Может быть построен только при наличии уровня населения не менее трёх. + +BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_CELL_GRO_CHAMB_REQUIRED +Может быть построен только при наличии [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]] и [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] в системе. + +BLD_SHIPYARD_BASE_ORG_ORB_INC_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED +Может быть построен только при наличии [[buildingtype BLD_SHIPYARD_BASE]], [[buildingtype BLD_SHIPYARD_ORG_ORB_INC]], [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] и [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] в системе. + +# Other than Requirements + +SHIPDESIGN_DETECTION_RESEARCH_TIPS +Дальнейшие [[metertype METER_RESEARCH]], направленные на улучшение параметра [[metertype METER_DETECTION]], позволят усовершенствовать этот дизайн корабля. -SH_SNOWFLAKE_2_BODY -Тело снежинки +SHIPDESIGN_MILLIONS_COLONIZATION_CAPACITY +способен безопасно доставлять миллионы граждан на новые поселения -SH_SNOWFLAKE_2_BODY_DESC -Легкий но опасный космический монстр. +SHIPDESIGN_MANY_MILLIONS_COLONIZATION_CAPACITY +способен безопасно доставлять многие миллионы граждан на новые поселения -SH_SNOWFLAKE_3_BODY -Тело большой снежинки +SHIPDESIGN_COLONIZATION_CAPACITY_SAME_SYSTEM +колонии на пригодной для жизни планете в той же системе, где был построен -SH_SNOWFLAKE_3_BODY_DESC -Легкий но опасный космический монстр. +SHIPDESIGN_OUTPOSTS_CAPACITY +способен создать [[encyclopedia OUTPOSTS_TITLE]] на необитаемой планете -SH_PSIONIC_SNOWFLAKE_BODY -Тело псионной снежинки +SHIPDESIGN_NO_TRAVEL +Этот корабль не может осуществлять межзвёздные перемещения. -SH_PSIONIC_SNOWFLAKE_BODY_DESC -Опасный для разума космический монстр. +SHIPDESIGN_PLANET_INVASION +[[metertype METER_TROOPS]] и оборудование, которое может быть использовано для вторжения в планету -SH_JUGGERNAUT_1_BODY -Тело маленького вышибалы +## +## Ship part/tech application macros +## -SH_JUGGERNAUT_1_BODY_DESC -Внушительный тяжёлый космический монстр. +# Keys used in Ship Parts and Technology Application sections -SH_JUGGERNAUT_2_BODY -Тело вышибалы +BLD_SHIPYARD_ORG_XENO_FAC_ORG_CELL_GRO_CHAMB_REQUIRED_ANY_SYSTEM +Эта часть корабля может быть построена только если есть [[buildingtype BLD_SHIPYARD_ORG_XENO_FAC]] и [[buildingtype BLD_SHIPYARD_ORG_CELL_GRO_CHAMB]] где-то в империи или империи союзника. -SH_JUGGERNAUT_2_BODY_DESC -Внушительный тяжёлый космический монстр. +BLD_BIONEURAL_REQUIRED_ANY_SYSTEM +Эта часть корабля может быть построена только при наличии какой-либо единицы бионевральной модификации где-либо в империи или в империи союзника -SH_JUGGERNAUT_3_BODY -Тело большого вышибалы +BLD_SHIPYARD_CON_ADV_ENGINE_REQUIRED_ANY_SYSTEM +Эта часть корабля может быть построена только если есть [[buildingtype BLD_SHIPYARD_CON_ADV_ENGINE]] где-то в империи или империи союзника. -SH_JUGGERNAUT_3_BODY_DESC -Внушительный тяжёлый космический монстр. +NO_STACK_STEALTH_SHIP_PARTS +[[metertype METER_STEALTH]] эффекты от частей корабля не складываются. -SH_BLOATED_JUGGERNAUT_BODY -Тело здоровенного вышибалы +BLD_SHIPYARD_AST_REF_REQUIRED_ANY_SYSTEM_SHIP_PARTS_TEXT +Эта часть корабля может быть построена только если есть [[buildingtype BLD_SHIPYARD_AST_REF]] где-либо в империи или империи союзника. -SH_BLOATED_JUGGERNAUT_BODY_DESC -Неестественно большой космический монстр. +NO_STACK_SHIELDS_SHIP_PARTS +[[metertype METER_SHIELD]] уменьшает [[encyclopedia DAMAGE_TITLE]], получаемый от каждого удара по щиту. На корабле может быть только один активный щит, эффекты от остальных не будут складываться. -SH_NEBULOUS_BODY -Туманное тело +COLONY_SHIP_PARTS_MIN_POP +Для любых модулей колонизации требуется, чтобы на планете, где они строятся, уровень населения был не менее трёх. -SH_EXP_OUTPOST_HULL -Корпус геобазы Экспериментаторов +COLONY_SHIP_PARTS_UPKEEP_COST +Стоимость этой части корабля возрастает по мере расширения империи, так как увеличиваются расходы на содержание большой империи. -SH_EXP_OUTPOST_HULL_DESC -Корпус, который Экспериментаторы используют для перевозки оборудования в другие галактики. +TROOP_POD_OPERATION_TEXT +''' * Войска могут использоваться только для одного вторжения. + * Корабли, транспортирующие войска во время вторжения, претерпевают быструю и тяжёлую посадку и впоследствии становятся непригодными для использования.''' -SH_COSMIC_DRAGON_BODY -Дракон космических размеров +SHIP_WEAPON_GRADUALLY_REDUCE +что позволяет кораблям постепенно уменьшать -SH_COSMIC_DRAGON_BODY_DESC -Устрашающий космический дракон размером с планету. +SHIP_WEAPON_QUICKLY_REDUCE +что позволяет кораблям быстро сокращать -SH_DAMPENING_CLOUD_BODY -'''Угасающее облачное тело ''' +ENEMY_PLANET_ORGANIC_POP +органическое население вражеских планет -SH_DAMPENING_CLOUD_BODY_DESC -Космическое облако высокоэнергетических частиц. +ENEMY_PLANET_ROBOTIC_POP +роботизированное население вражеских планет +ENEMY_PLANET_LITHIC_POP +каменное (почвенное) население вражеских планет -## -## Buildings macros -## +ENEMY_PLANET_PHOTOTROPHIC_POP +фототрофная популяция вражеских планет +ENEMY_PLANET_ANY_POP +любое население вражеских планет ## ## Tech macros ## +COLONY_BUILDING_TIME_DECREASE +Среди прочего, эта технология уменьшает время на построение колонии, расположенной далеко от соответствующих видов в империи. ## ## Growth macros ## GROWTH_SPECIAL_POPULATION_ORGANIC_INCREASE -'''Увеличивает максимальное население: Крохотная: +1, Маленькая: +2, Средняя: +3, Большая: +4, Огромная: +5, если планета населена органическими существами. - -Когда такая планета сфокусирована на демографический рост, на всех соединенных с ней планетах с органическими существами получают такой же бонус.''' +[[encyclopedia ORGANIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] [[ORGANIC_SPECIES_TITLE]] GROWTH_SPECIAL_POPULATION_ROBOTIC_INCREASE -'''Увеличивает максимальное население: Крохотная: +1, Маленькая: +2, Средняя: +3, Большая: +4, Огромная: +5, если планета населена роботами. - -Когда такая планета сфокусирована на демографический рост, на всех соединенных с ней планетах, населенных роботами, получают такой же бонус.''' +[[encyclopedia ROBOTIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] [[ROBOTIC_SPECIES_TITLE]] GROWTH_SPECIAL_POPULATION_LITHIC_INCREASE -'''Увеличивает максимальное население: Крохотная: +1, Маленькая: +2, Средняя: +3, Большая: +4, Огромная: +5, если планета населена педобионтами. +[[encyclopedia LITHIC_SPECIES_TITLE]] [[GROWTH_SPECIAL_POPULATION_INCREASE]] [[LITHIC_SPECIES_TITLE]] -Когда такая планета сфокусирована на демографический рост, на всех соединенных с ней планетах, населенных педобионтами, получают такой же бонус.''' +GROWTH_SPECIAL_POPULATION_INCREASE +'''Виды, населяющие эту планету, будут иметь максимальную численность населения, увеличивающуюся в зависимости от размера планеты: +• [[SZ_TINY]] (+1) +• [[SZ_SMALL]] (+2) +• [[SZ_MEDIUM]] (+3) +• [[SZ_LARGE]] (+4) +• [[SZ_HUGE]] (+5) +независимо от планетарных [[encyclopedia ENVIRONMENT_TITLE]] + +Когда такая планета сфокусирована на [[encyclopedia GROWTH_FOCUS_TITLE]], на всех планетах, соединённых с ней через [[metertype METER_SUPPLY]], активируется такой же бонус для видов с''' GROWTH_SPECIAL_INDUSTRY_BOOST -Если планета нацелена на промышленность, то максимум [[metertype METER_INDUSTRY]] увеличивается на 0.2*Население. +Если планета нацелена на промышленность, то максимум [[metertype METER_INDUSTRY]] увеличивается на 0.2 за единицу населения. + +GROWTH_SPECIALS_ENTRY_LIST +'''Планеты с бонусами на рост населения, имеющие соответствующий фокус, работают только при населённости их расами определённых типов. Так, некоторые функции работают только при наличии органических видов, другие для педобионтов (почвенных организмов), третьи - для роботизированных, и т.д. +[[encyclopedia ORGANIC_SPECIES_TITLE]] артефакты/особенности: +[[special FRUIT_SPECIAL]] | [[special SPICE_SPECIAL]] | [[special PROBIOTIC_SPECIAL]] + +[[encyclopedia ROBOTIC_SPECIES_TITLE]] артефакты/особенности: +[[special MONOPOLE_SPECIAL]] | [[special SUPERCONDUCTOR_SPECIAL]] | [[special POSITRONIUM_SPECIAL]] + +[[encyclopedia LITHIC_SPECIES_TITLE]] артефакты/особенности: +[[special MINERALS_SPECIAL]] | [[special ELERIUM_SPECIAL]] | [[special CRYSTALS_SPECIAL]]''' + +GROWTH_SPECIAL_LABEL +Планета %1% [[TT_SPECIAL]] ## ## Species picks @@ -7242,95 +13494,210 @@ ULTIMATE_INDUSTRY_DESC +++ Беспрецедентная [[metertype METER_INDUSTRY]]: 300% NO_RESEARCH_DESC ---- Нет [[metertype METER_RESEARCH]] +--- Отсутствуют [[metertype METER_RESEARCH]] BAD_RESEARCH_DESC -- Плохая [[metertype METER_RESEARCH]]: 75% +- Плохие [[metertype METER_RESEARCH]]: 75% AVERAGE_RESEARCH_DESC -''' Средняя [[metertype METER_RESEARCH]]: 100%''' +''' Средние [[metertype METER_RESEARCH]]: 100%''' GOOD_RESEARCH_DESC -+ Хорошая [[metertype METER_RESEARCH]]: 150% ++ Хорошие [[metertype METER_RESEARCH]]: 150% GREAT_RESEARCH_DESC -++ Превосходная [[metertype METER_RESEARCH]]: 200% +++ Превосходные [[metertype METER_RESEARCH]]: 200% ULTIMATE_RESEARCH_DESC -+++ Беспрецедентная [[metertype METER_RESEARCH]]: 300% ++++ Беспрецедентные [[metertype METER_RESEARCH]]: 300% NO_DEFENSE_TROOPS_DESC ---- Беззащитные [[metertype METER_TROOPS]] +--- Отсутствуют[[metertype METER_TROOPS]] BAD_DEFENSE_TROOPS_DESC - Плохие защитные [[metertype METER_TROOPS]]: 50% AVERAGE_DEFENSE_TROOPS_DESC -''' Средние защитные [[metertype METER_TROOPS]]: 100%.''' +''' Средние защитные [[metertype METER_TROOPS]]: 100%''' GOOD_DEFENSE_TROOPS_DESC + Хорошие защитные [[metertype METER_TROOPS]]: 150% GREAT_DEFENSE_TROOPS_DESC -++ Превосходные защитные [[metertype METER_TROOPS]]: 200% +++ Отличные защитные [[metertype METER_TROOPS]]: 200% ULTIMATE_DEFENSE_TROOPS_DESC -+++ Беспрецедентные защитные [[metertype METER_TROOPS]]: 300% ++++ Превосходные защитные [[metertype METER_TROOPS]]: 300% + +ANCIENT_DEFENSE_TROOPS_DESC ++++ Древняя оборонительная платформа [[metertype METER_TROOPS]]: 10 за население + +NO_OFFENSE_TROOPS_DESC +−−− Нет наступающих [[metertype METER_TROOPS]] + +BAD_OFFENSE_TROOPS_DESC +− Слабые наступательные [[metertype METER_TROOPS]]: 50% + +AVERAGE_OFFENSE_TROOPS_DESC +''' Средние наступательные [[metertype METER_TROOPS]]: 100%''' + +GOOD_OFFENSE_TROOPS_DESC ++ Хорошие наступательные [[metertype METER_TROOPS]]: 150% + +GREAT_OFFENSE_TROOPS_DESC +++ Отличные наступательные [[metertype METER_TROOPS]]: 200% + +ULTIMATE_OFFENSE_TROOPS_DESC ++++ Превосходные наступательные [[metertype METER_TROOPS]]: 300% BAD_DETECTION_DESC -- Плохое [[metertype METER_DETECTION]]: -20 малус. +- Плохая [[metertype METER_DETECTION]]: -20 бонус GOOD_DETECTION_DESC -+ Хорошее [[metertype METER_DETECTION]]: +25 бонус. ++ Хорошая [[metertype METER_DETECTION]]: +25 бонус GREAT_DETECTION_DESC -++ Превосходное [[metertype METER_DETECTION]]: +50 бонус. +++ Превосходная [[metertype METER_DETECTION]]: +50 бонус ULTIMATE_DETECTION_DESC -+++ Беспрецедентное обнаружение: +100 бонус. ++++ Беспрецедентное обнаружение: +100 бонус BAD_STEALTH_DESC -- Плохая [[metertype METER_STEALTH]]: -20 малус. +- Плохая [[metertype METER_STEALTH]]: -20 бонус AVERAGE_STEALTH_DESC -''' Средняя [[metertype METER_STEALTH]].''' +''' Средняя [[metertype METER_STEALTH]]''' GOOD_STEALTH_DESC -+ Хорошая [[metertype METER_STEALTH]]: +20 бонус. ++ Хорошая [[metertype METER_STEALTH]]: +20 бонус GREAT_STEALTH_DESC -++ Превосходная [[metertype METER_STEALTH]]: +40 бонус. +++ Превосходная [[metertype METER_STEALTH]]: +40 бонус ULTIMATE_STEALTH_DESC -+++ Беспрецендентная [[metertype METER_STEALTH]]: +60 бонус. ++++ Беспрецедентная [[metertype METER_STEALTH]]: +60 бонус BAD_WEAPONS_DESC -- Плохие Пилоты: на 25% меньше урона. +- Плохие пилоты: базовый урон на одно орудие судна уменьшен на один уровень. GOOD_WEAPONS_DESC -+ Хорошие Пилоты: на 25% больше урона. ++ Хорошие пилоты: базовый урон на одно орудие судна увеличен на один уровень GREAT_WEAPONS_DESC -++ Превосходные Пилоты: на 50% больше урона. +++ Превосходные пилоты: базовый урон на одно орудие судна увеличен на два уровня. ULTIMATE_WEAPONS_DESC -+++ Беспрецедентные Пилоты: двойной урон. ++++ Беспрецедентные пилоты: базовый урон на одно орудие судна увеличен на три уровня. + +GASEOUS_DESC +'''+ Живут на газовых гигантах ++ Дозаправка на газовых гигантах 0.1''' BAD_POPULATION_DESC -- Малое население 75% +- Малое [[metertype METER_POPULATION]]: 75% AVERAGE_POPULATION_DESC -''' Среднее население''' +''' Среднее [[metertype METER_POPULATION]]: 100%''' GOOD_POPULATION_DESC -+ Большое население 125% ++ Большое [[metertype METER_POPULATION]]: 125% + +FIXED_LOW_POPULATION_DESC +−− Максимальное [[metertype METER_POPULATION]] - фиксированно 5 + +VERY_BAD_SUPPLY_DESC +−− Очень плохое [[metertype METER_SUPPLY]]: -1 + +BAD_SUPPLY_DESC +− Плохое [[metertype METER_SUPPLY]]: без бонусов + +AVERAGE_SUPPLY_DESC +''' Среднее [[metertype METER_SUPPLY]]: +1''' + +GREAT_SUPPLY_DESC ++ Хорошее [[metertype METER_SUPPLY]]: +2 + +ULTIMATE_SUPPLY_DESC +++ Отличное [[metertype METER_SUPPLY]]: +3 + +NO_STOCKPILE_DESC +−− Отсутствуют [[metertype METER_STOCKPILE]] + +BAD_STOCKPILE_DESC +− Плохие [[metertype METER_STOCKPILE]]: +0.01 за единицу населения + +AVERAGE_STOCKPILE_DESC +''' Средние [[metertype METER_STOCKPILE]]: +0.02 за единицу населения''' + +GREAT_STOCKPILE_DESC +++ Отличные [[metertype METER_STOCKPILE]]: +0.2 за единицу населения + +ULTIMATE_STOCKPILE_DESC ++++ Беспрецедентные [[metertype METER_STOCKPILE]]: +0.3 за единицу населения + +GOOD_SHIP_SHIELD_DESC ++ Хорошие корабельные [[metertype METER_SHIELD]]: +1 + +GOOD_PLANETARY_SHIELD_DESC ++ Большой планетарный [[metertype METER_SHIELD]]: +5 + +ANCIENT_PLANETARY_SHIELD_DESC ++++ Древний планетарный [[metertype METER_SHIELD]]: +500 + +GOOD_PLANETARY_DEFENSE_DESC ++ Крепкая планетарная [[metertype METER_DEFENSE]]: +5 BROAD_EP_DESC -++ Хорошая приспособляемость, может обитать на большем количестве типов планет. +++ Хорошая приспособляемость: может обитать на большем количестве типов планет. NARROW_EP_DESC -- Плохая приспособляемость, может обитать на меньшем количестве типов планет. +- Плохая приспособляемость: может обитать на меньшем количестве типов планет. + +FAST_COLONIZATION_DESC ++ Быстрая колонизация: -25% к времени основания колонии + +SLOW_COLONIZATION_DESC +− Медленная колонизация: +20% к времени основания колонии +NO_FUEL_DESC +−−− Корабли имеют нулевое [[metertype METER_FUEL]] + +BAD_FUEL_DESC +− Плохой максимум [[metertype METER_FUEL]]: - 1 + +AVERAGE_FUEL_DESC + Средний максимум [[metertype METER_FUEL]]: +0 + +GOOD_FUEL_DESC ++ Хороший максимум [[metertype METER_FUEL]]: +1 + +GREAT_FUEL_DESC +++ Отличный максимум [[metertype METER_FUEL]]: +2 + +ULTIMATE_FUEL_DESC ++++ Беспрецедентный максимум [[metertype METER_FUEL]]: +3 + +GREAT_ASTEROID_INDUSTRY_DESC ++ Хорошие астероидные добытчики: + 0.2 [[metertype METER_INDUSTRY]] за единицу населения, когда планета с фокусом на промышленность расположена рядом с астероидными поясами. + +LIGHT_SENSITIVE_DESC +− Светочувствительность: Население уменьшается в системах с [[STAR_BLUE]] и в меньшей степени с [[STAR_WHITE]] звездами. + +TELEPATHIC_DETECTION_DESC ++ Телепатическое обнаружение: может ощущать близлежащие населенные планеты. + +COMMUNAL_VISION_DESC + Общая концепция: разделяет обзор в рамках одинаковых видов. + +## +## Fuel regeneration +## + +AVERAGE_BASE_FUEL_REGEN_DESC +''' Средняя постоянная [[metertype METER_FUEL]] восполнение: + 0.1''' + +LIVING_HULL_BASE_FUEL_REGEN_DESC +''' Живой органический корпус [[metertype METER_FUEL]] восполнение: + 0.3''' ## ## Accounting labels @@ -7356,28 +13723,44 @@ POOR_ENVIRONMENT_LABEL HOSTILE_ENVIRONMENT_LABEL %1% Враждебная окружающая среда +# %1% FIXME +UNINHABTIABLE_ENVIRONMENT_LABEL +%1% Непригодная окружающая среда + # %1% FIXME ENV_ENCAPSUL_LABEL -%1% Экологическая Инкапсуляция +%1% Экологическая изоляция SELF_SUSTAINING_LABEL -Самоокупающийся +Самодостаточные + +IMMORTAL_LABEL +Бессмертные и не воспроизводящийся + +SUBTERRANEAN_LABEL +Подземный бонус GAIA_LABEL Гайя-бонус +WORLDTREE_LABEL +Бонус мирового дерева + +TIDAL_LOCK_LABEL +Заблокированное вращение + HOMEWORLD_LABEL -Родина +Домашний мир # %1% FIXME # %2% FIXME BAD_POPULATION_LABEL -%1% Малое население +%2% Несчастное население 75%% # %1% FIXME # %2% FIXME GOOD_POPULATION_LABEL -%1% Большое население +%2% Довольное население 125%% # %1% FIXME # %2% FIXME @@ -7429,6 +13812,26 @@ GREAT_RESEARCH_LABEL ULTIMATE_RESEARCH_LABEL %2% Беспрецедентная наука +# %1% FIXME +# %2% FIXME +BAD_SUPPLY_LABEL +%2% Плохое снабжение + +# %1% FIXME +# %2% FIXME +AVERAGE_SUPPLY_LABEL +%2% Обычное снабжение + +# %1% FIXME +# %2% FIXME +GREAT_SUPPLY_LABEL +%2% Хорошее снабжение + +# %1% FIXME +# %2% FIXME +ULTIMATE_SUPPLY_LABEL +%2% Превосходное снабжение + # %1% FIXME # %2% FIXME BAD_TROOPS_LABEL @@ -7454,9 +13857,25 @@ GREAT_TROOPS_LABEL ULTIMATE_TROOPS_LABEL %2% Беспрецедентные войска +INDEPENDENT_TROOP_LABEL +Независимый домашний мир + +MEGALITH_LABEL +Мегалит + OUTPOST_TROOP_LABEL Аванпост +# %1% FIXME +# %2% FIXME +NATIVE_PLANETARY_DEFENSE_LABEL +%2% Планетарная оборона + +# %1% FIXME +# %2% FIXME +NATIVE_PLANETARY_SHIELDS_LABEL +%2% Планетарные щиты + VERY_BRIGHT_STAR Очень яркая звезда @@ -7469,32 +13888,56 @@ DIM_STAR NO_STAR Нет звезды +TINY_PLANET_LABEL +Крошечная планета + +SMALL_PLANET_LABEL +Малая планета + +LARGE_PLANET_LABEL +Большая планета + +HUGE_PLANET_LABEL +Гигантская планета + +GAS_GIANT_LABEL +Газовый гигант + HOMEWORLD_SUPPLY -Снабжение с родины +Снабжение с домашнего мира HOMEWORLD_BONUS -Родина +Домашний мир + +CAPITAL_LABEL +Столица + +CONCENTRATION_CAMPS_LABEL +Концентрационные лагеря ORBITAL_HAB_LABEL Обитают на орбите NDIM_STRC_LABEL -N-мернатя структура +N-мерная структура WEAK_VISION_LABEL -Слабое зрение +Плохая видимость + +POOR_VISION_LABEL +Слабая видимость MODERATE_VISION_LABEL -Нормальное зрение +Нормальная видимость GOOD_VISION_LABEL -Хорошее зрение +Хорошая видимость EXCELLENT_VISION_LABEL -Прекрасное зрение +Прекрасная видимость FOCUS_PROTECTION_LABEL -Защитный Фокус +Защитный фокус XENOPHOBIC_LABEL_SELF Безумие с ненавистью к иноземцам (другие виды поблизости) @@ -7508,6 +13951,9 @@ XENOPHOBIC_LABEL_EAXAW_OTHER ORGANIC_GROWTH Органический рост +ASTEROID_FIELD_STEALTH +Скрытность астероидов + AGE_BONUS Возраст @@ -7527,59 +13973,472 @@ DETECTOR_INTERFERENCE Датчик кросс-интерференции CLOAK_INTERFERENCE -Маска кросс-интерференции +Маскировка кросс-интерференции + +TRANSPATIAL_CLOAK_INTERACTION +Транспатиал двигатель - маскировка + +BASE_FUEL_REGEN_LABEL +Стационарный регенератор топлива +SPATIAL_FLUX_MALUS +Помеховые интерференций + +SPATIAL_FLUX_BONUS +Бонус флюкс-двигателя ## ## Tags ## +ANTIQUATED +Архаичные + +STYLISH +Изысканные + ORGANIC -Органическая жизнь. +Органическая жизнь ROBOTIC -Роботы. +Механическая жизнь LITHIC -Педобионты.(питающиеся почвой) +Каменная (почвенная) жизнь PHOTOTROPHIC -Фототрофная жизнь. +Фототрофная жизнь SELF_SUSTAINING -Самодостаточные. +Самодостаточные TELEPATHIC -Обладающие телепатией. +Обладающие телепатией ORBITAL -Орбитальные. - +Орбитальные ## ## AI strings ## +# Newline separated list of capitol names for beginner AIs + # Newline separated list of capitol names for turtle AIs -AI_CAPITOL_NAMES_TURTLE -'''Цитадель -Тюремный Мир''' +# Newline separated list of capitol names for cautious AIs + +# Newline separated list of capitol names for typical AIs + +# Newline separated list of capitol names for aggressive AIs + +# Newline separated list of capitol names for maniacal AIs + +AI_SHIPDESIGN_NAME_INVALID +Неверный дизайн + +# Newline separated list of military ship names owned by AIs. +# The usage is determined by a rating defined inside the AI scripts. The names +# are roughly ordered by order of appearance in the game. + +# Newline separated list of local troop transporter names owned by AIs. +# The usage is determined by a rating defined inside the AI scripts. The names +# are roughly ordered by order of appearance in the game. +AI_SHIPDESIGN_NAME_TROOPER_ORBITAL +Космические захватчики + +# Newline separated list of troop transporter names owned by AIs. +# The usage is determined by a rating defined inside the AI scripts. The names +# are roughly ordered by order of appearance in the game. +AI_SHIPDESIGN_NAME_TROOPER_STANDARD +'''Штурмовой корабль +Штурмовики +Тяжелые солдаты''' + +# Newline separated list of local colonization ship names owned by AIs. +# The usage is determined by a rating defined inside the AI scripts. The names +# are roughly ordered by order of appearance in the game. +AI_SHIPDESIGN_NAME_COLONISATION_ORBITAL +Орбитальный сеятель + +# Newline separated list of colonization ship names owned by AIs. +# The usage is determined by a rating defined inside the AI scripts. The names +# are roughly ordered by order of appearance in the game. +AI_SHIPDESIGN_NAME_COLONISATION_STANDARD +Сеятель + +# Newline separated list of local outpost ship names owned by AIs. +# The usage is determined by a rating defined inside the AI scripts. The names +# are roughly ordered by order of appearance in the game. +AI_SHIPDESIGN_NAME_OUTPOSTER_ORBITAL +Орбитальная застава + +# Newline separated list of outpost ship names owned by AIs. +# The usage is determined by a rating defined inside the AI scripts. The names +# are roughly ordered by order of appearance in the game. +AI_SHIPDESIGN_NAME_OUTPOSTER_STANDARD +Застава + +AI_SHIPDESIGN_NAME_ORBITAL_DEFENSE +'''Спутник связи +Звездная база''' + +AI_SHIPDESIGN_NAME_SCOUT +Разведчик + +AI_SHIPDESIGN_NAME_KRILLSPAWNER +Порождение планктона (криля) ## ## AI diplomacy strings and lists ## +# Newline separated list of polite peace proposal acknowlegements. +AI_PEACE_PROPOSAL_ACKNOWLEDGEMENTS_MILD_LIST +'''Приятно снова слышать вас +Получение передачи подтверждено +''' + +# Newline separated list of harsh peace proposal acknowlegements. +AI_PEACE_PROPOSAL_ACKNOWLEDGEMENTS_HARSH_LIST +'''Как?! Вы еще живы? +О, нет, еще одна жалкая сопливая история, пожалуйста! +''' + +# Newline separated list of polite positive peace proposal acknowlegements. +AI_PEACE_PROPOSAL_RESPONSES_YES_MILD_LIST +'''Мы рады поверить в ваши добрые намерения +Что бы вы ни сказали, Босс. Все говорят, что вы очень надежны +''' + +# Newline separated list of polite negative peace proposal acknowlegements. +AI_PEACE_PROPOSAL_RESPONSES_NO_MILD_LIST +'''К сожалению, сейчас, это сделать очень затруднительно +Мы с сожалением сообщаем вам, что мы не можем выполнить этот запрос +''' + +# Newline separated list of harsh positive peace proposal acknowlegements. +AI_PEACE_PROPOSAL_RESPONSES_YES_HARSH_LIST +'''Из жалости мы соглашаемся пока что не нападать на вас +Жалкие существа! Нам не будет много чести от победы над вами +''' + +# Newline separated list of harsh negative peace proposal acknowlegements. +AI_PEACE_PROPOSAL_RESPONSES_NO_HARSH_LIST +'''Мы первыми увидим вашу смерть! +Мы не обретем покоя до тех пор, пока все ваши базы не будут принадлежать нам! +''' + +# Newline separated list of polite war declarations. +AI_WAR_REDECLARATION_MILD_LIST +'''О, любезный! По-видимому, на наших предыдущих переговорах произошли нарушения и мирный договор не был ратифицирован вообще! Мы с сожалением сообщаем вам, что наши империи все еще находятся в состоянии войны +Сегодня был день, когда вы сказали, что на днях мы снова отправимся на войну вместе, верно? +''' + +# Newline separated list of harsh war declarations. +AI_WAR_REDECLARATION_HARSH_LIST +'''Наши дети жаждут вашей крови! Подготовьтесь к войне! +Вы, неверующие дураки, думали этот мир будет продолжаться и дальше, не так ли? +''' + +# Newline separated pre-game acknowledgements. +AI_PREGAME_ACKNOWLEDGEMENTS__LIST +'''Я медитирую, ожидая указаний от Бога. Повторите вызов позже, пожалуйста. +Шепот из Пустоты? Как такое вообще может быть? +''' + +# Newline separated in-game acknowledgements. +AI_MIDGAME_ACKNOWLEDGEMENTS__LIST +'''Подождите, пожалуйста. Я всё ещё пытаюсь понять, как правильно провести с вами тест Тьюринга. +Разве Стивен Хокинг не предупредил вас, чтобы вы не разговаривали с нами? +Мой предок предупреждал, чтобы я никогда не связывался с инопланетянами. +Не беспокойте меня, я питаюсь... инопланетянами! +''' + +# Newline separated list of 'don't bother me' responses. +AI_BE_QUIET_ACKNOWLEDGEMENTS__LIST +'''Мы больше не хотим с вами разговаривать! +Ок, прощайте. +Мы не уверены, что вы вообще существуете. +''' + +AI_FIRST_TURN_GREETING_MSG101 +Что мы такого сделали, чтоб заслужить ваш гнев, о могущественные? + +AI_FIRST_TURN_GREETING_MSG102 +Не докучайте нам - у нас нет ничего, что может вас заинтересовать. + +AI_FIRST_TURN_GREETING_MSG103 +Пожалуйста, пощадите своих покорных слуг - наша благодарность будет безмерной! + +AI_FIRST_TURN_GREETING_MSG201 +Держитесь подальше от нас! + +AI_FIRST_TURN_GREETING_MSG203 +Держитесь от нас подальше, а мы будем держаться подальше от вас! + +AI_FIRST_TURN_GREETING_MSG301 +Мы не будем вас беспокоить. Но если вы задумаете нас потревожить, то увидите, что и мы тоже неплохо подготовлены к защите! + +AI_FIRST_TURN_GREETING_MSG302 +В наших обоюдных интересах оставить друг друга в покое. + +AI_FIRST_TURN_GREETING_MSG401 +Битва с вами будет для нас честью. + +AI_FIRST_TURN_GREETING_MSG402 +Славься, Человек, идущие на смерть приветствуют тебя! + +AI_FIRST_TURN_GREETING_MSG403 +Приветствуем, незнакомец! Надеемся, вы хотя бы достойны сразиться с нами. + +AI_FIRST_TURN_GREETING_MSG501 +Приветствуем, дорогой враг! Наши смелые воины уже ждут тебя... + +AI_FIRST_TURN_GREETING_MSG502 +Ха! Думаете, что можете противостоять нам? Хорошо, давайте, идите сюда! + +AI_FIRST_TURN_GREETING_MSG503 +Кучка глупых инопланетян угодила прямо в наши руки - день не мог начаться лучше! + +AI_FIRST_TURN_GREETING_MSG504 +Наслаждайтесь, пока можете. Всё равно, последнее, что вы увидите - это очищающий огонь наших мощных орудий! + +AI_FIRST_TURN_GREETING_MSG505 +Убирайтесь в ад из нашей галактики! + +AI_FIRST_TURN_GREETING_MSG506 +Приготовьтесь к смерти. + +AI_FIRST_TURN_GREETING_MSG601 +Вы - отбросы вселенной! Мы очистим галактику от вашего мерзкого присутствия! + +AI_FIRST_TURN_GREETING_MSG602 +Ничтожные насекомые, вы осмеливаетесь бросить вызов нашей могучей империи?! + +AI_FIRST_TURN_GREETING_MSG603 +Ваше истребление доставит нам настоящее удовольствие! + +AI_FIRST_TURN_GREETING_MSG604 +Даже не пытайтесь просить нас о помиловании. Здесь вы ничего не найдёте. + +AI_FIRST_TURN_GREETING_MSG605 +Кроткий ягненок, желающий потанцевать со львами - это всегда приветствуется у нас за столом. В качестве основного блюда... + +AI_FIRST_TURN_GREETING_MSG606 +Отчаяние и поражение - это всё, что вы от нас получите. + +AI_FIRST_TURN_GREETING_MSG607 +Сдавайтесь, и, быть может, мы подумаем, чтобы оставить вас в качестве рабов. + +AI_FIRST_TURN_GREETING_MSG608 +Отправить вас к вашим предкам - наш священный долг! + +# Don't translate this entry. + +# Don't translate this entry. + +# Don't translate this entry. + +# Don't translate this entry. + +# Don't translate this entry. + +# Don't translate this entry. ## ## Hotkey names and descriptions ## +OPTIONS_PAGE_HOTKEYS +Горячие клавиши + +HOTKEYS_GENERAL +Общие клавиши + +HOTKEY_MAP_OPEN_CHAT +Открыть окно чата + +HOTKEY_MAP_END_TURN +Конец хода + +HOTKEY_MAP_RESEARCH +Панель исследований + +HOTKEY_MAP_DESIGN +Панель проектирования + +HOTKEY_MAP_OBJECTS +Открытие списка объектов + +HOTKEY_MAP_MESSAGES +Диалоговое окно + +HOTKEY_MAP_EMPIRES +Список империй + +HOTKEY_MAP_PEDIA +Окно энциклопедии + +HOTKEY_MAP_GRAPHS +Список графиков + +HOTKEY_MAP_PRODUCTION +Производственная панель + +HOTKEY_MAP_SIT_REP +Отчет об обстановке + +HOTKEY_MAP_MENU +Общее меню + +HOTKEY_MAP_RETURN_TO_MAP +Возврат в окно карты + +HOTKEY_MAP_ZOOM_IN +Масштаб+ + +HOTKEY_MAP_ZOOM_IN_ALT +Масштаб+ (другие клавиши) + +HOTKEY_MAP_ZOOM_OUT +Масштаб- + +HOTKEY_MAP_ZOOM_OUT_ALT +Масштаб- (другие клавиши) + +HOTKEY_MAP_ZOOM_HOME_SYSTEM +Перейти к домашней системе + +HOTKEY_MAP_ZOOM_PREV_SYSTEM +Перейти к предыдущей системе + +HOTKEY_MAP_ZOOM_NEXT_SYSTEM +Перейти к следующей системе + +HOTKEY_MAP_ZOOM_PREV_OWNED_SYSTEM +Перейти к пред. системе с планетами + +HOTKEY_MAP_ZOOM_NEXT_OWNED_SYSTEM +Перейти к след. системе с планетами + +HOTKEY_MAP_ZOOM_PREV_FLEET +Перейти к предыдущему флоту + +HOTKEY_MAP_ZOOM_NEXT_FLEET +Перейти к следующему флоту + +HOTKEY_MAP_ZOOM_PREV_IDLE_FLEET +Перейти к пред. бездействующему флоту + +HOTKEY_MAP_ZOOM_NEXT_IDLE_FLEET +Перейти к след. бездействующему флоту + +HOTKEY_MAP_PAN_RIGHT +Расположить карту справа + +HOTKEY_MAP_PAN_LEFT +Расположить карту слева + +HOTKEY_MAP_PAN_UP +Расположить карту сверху + +HOTKEY_MAP_PAN_DOWN +Расположить карту снизу + +HOTKEY_MAP_TOGGLE_SCALE_LINE +Переключить масштаб карты + +HOTKEY_MAP_TOGGLE_SCALE_CIRCLE +Переключить круговой масштаб карты + +HOTKEY_CUT +Вырезать + +HOTKEY_COPY +Скопировать + +HOTKEY_PASTE +Вставить + +HOTKEY_SELECT_ALL +Выбрать все + +HOTKEY_DESELECT +Снять выделение + +HOTKEY_FOCUS_PREV_WND +Предыдущий объект + +HOTKEY_FOCUS_NEXT_WND +Следующий объект + +HOTKEY_EXIT +Выйти на рабочий стол + +HOTKEY_QUIT +Выйти из текущей игры + +HOTKEY_FULLSCREEN +Полноэкранный режим ## ## Name lists ## -#include "../global_settings.txt" +# Newline separated list of star cluster names + +# Newline separated list of star cluster suffixes + +# Newline separated list of star cluster names + +# Newline separated list of star names + +# Newline separated list of empire names + +# Newline separated list of mammal-based ship names + +# Newline separated list of bird-based ship names + +# Newline separated list of ship-based ship names + +# Newline separated list of plant-based ship names + +# Newline separated list of mineral-based ship names + +# Newline separated list of weather-based ship names + +# Newline separated list of star constellationbased ship names + +# Newline separated list of various namebased ship names + +# Newline separated list of roman mythology-based ship names + +# Newline separated list of greek mythology-based ship names + +# Newline separated list of norse mythology-based ship names + +# Newline separated list of basque mythology-based ship names + +# FIXME: Sort these into their proper locations? +# Newline separated list of various mythology-based ship names + +# Newline separated list of scientist-based ship names + +# Newline separated list of author-based ship names + +# Newline separated list of artist-based ship names + +# Newline separated list of emotion-based ship names + +# Newline separated list of architecture-based ship names + +# Newline separated list of "master of orion 2"-based ship names + +SHIP_NAME_OTHER +Бесконечный сентябрь + +# Newline separated list of developer name based ship names + #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/default/stringtables/sv.txt b/default/stringtables/sv.txt index 121ecc7c089..91ed79a4192 100644 --- a/default/stringtables/sv.txt +++ b/default/stringtables/sv.txt @@ -99,9 +99,6 @@ Spelare %1% har inte längre förbindelse med servern. ## Command-line and options database entries ## -OPTIONS_DB_HELP -Skriv ut detta hjälpmeddelande. - OPTIONS_DB_BG_MUSIC Väljer bakgrundsspår att spela. @@ -168,24 +165,6 @@ Ljudfilen som spelas då muspekaren flyttas ovanför en systemikon. OPTIONS_DB_UI_SOUND_SIDEPANEL_OPEN Ljudfilen som spelas då systemets sidpanel öppnas. -OPTIONS_DB_GAMESETUP_STARS -Antalet stjärnor i galaxen som kommer att genereras. - -OPTIONS_DB_GAMESETUP_GALAXY_SHAPE -Formen på galaxen som kommer att genereras. - -OPTIONS_DB_GAMESETUP_GALAXY_AGE -Galaxens ålder som kommer att genereras. - -OPTIONS_DB_GAMESETUP_PLANET_DENSITY -Antalet planeter per system i galaxen som kommer att genereras. - -OPTIONS_DB_GAMESETUP_STARLANE_FREQUENCY -Antalet stjärnfarleder i galaxen som kommer att genereras. - -OPTIONS_DB_GAMESETUP_SPECIALS_FREQUENCY -Förekomsten av specialare i galaxen som kommer att genereras. - ## ## File dialog @@ -197,15 +176,6 @@ Fil(er): FILE_DLG_FILE_TYPES Typ(er): -FILE_DLG_OVERWRITE_PROMPT -%1% existerar redan.\nVill du skriva över den? - -FILE_DLG_FILENAME_IS_A_DIRECTORY -"%1%"\när en katalog. - -FILE_DLG_FILE_DOES_NOT_EXIST -Fil "%1%"\nexisterar inte. - ## ## Color selection dialog @@ -428,12 +398,6 @@ Snabbstäng fönster OPTIONS_MISC_UI Övriga användargränssnittsinställningar -OPTIONS_SINGLEPLAYER -Enspelarläge - -OPTIONS_MULTIPLAYER -Flerspelarläge - OPTIONS_AUTOSAVE_TURNS_BETWEEN Antal drag mellan automatisk sparning @@ -1012,15 +976,9 @@ SITREP_FLEET_ARRIVED_AT_DESTINATION ANCIENT_RUINS_SPECIAL Uråldriga ruiner -ANCIENT_RUINS_SPECIAL_DESCRIPTION -Denna planet hyser ruiner efter en högutvecklad forntida ras, vars kännedom för länge sedan gått förlorad. Ger en bonus till din forskning i form av högre avkastning. - ECCENTRIC_ORBIT_SPECIAL Excentrisk bana -ECCENTRIC_ORBIT_SPECIAL_DESC -Denna planets bana är väldigt excentrisk. Den har ett stort avstånd mellan dess närmaste och mest avlägsna ansats till dess stjärna. De omväxlande förhållandena hämmar utveckling av infrastruktur, men skapar ett gynnande underlag för forskning. - MINERALS_SPECIAL Rik på mineraler @@ -1153,10 +1111,6 @@ handel METER_POPULATION population -# Meter types -METER_HEALTH -hälsa - # Meter types METER_INDUSTRY industri @@ -1271,15 +1225,6 @@ objekttyp DESC_VAR_STARTYPE stjärntyp -DESC_VAR_MAXFUEL -maximalt bränsle - -DESC_VAR_MAXSHIELD -maximal sköld - -DESC_VAR_MAXDEFENSE -maximalt försvar - DESC_VAR_TRADESTOCKPILE handelsreserv @@ -1412,6 +1357,5 @@ Imperiepalats ## Name lists ## -#include "../global_settings.txt" #include "../content_specific_parameters.txt" #include "../customizations/common_user_customizations.txt" diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index bbc0b802aa1..ce9f9b1e7cf 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -14,7 +14,7 @@ if(DOXYGEN_FOUND) endif() add_custom_target(cpp-apidoc - ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tag_parser.py --template ${CMAKE_CURRENT_SOURCE_DIR}/all_tags.dox.in --match ${FreeOrion_SOURCE_DIR}/default -f "*.py" "*.focs.txt" "*.macros" -o ${FreeOrion_CPP_APIDOC_OUTDIR}/all_tags.dox --link_source ${FreeOrion_SOURCE_DIR} "https://github.com/freeorion/freeorion/blob/master/" "#L" && + ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tag_parser.py --match ${FreeOrion_SOURCE_DIR}/default -f "*.py" "*.focs.txt" "*.macros" -o ${FreeOrion_CPP_APIDOC_OUTDIR}/all_tags.dox --link_source ${FreeOrion_SOURCE_DIR} "https://github.com/freeorion/freeorion/blob/master/" "#L" && ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile WORKING_DIRECTORY ${FreeOrion_SOURCE_DIR} COMMENT "Generating FreeOrion API documentation with Doxygen" VERBATIM diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 81c9bee8b5c..24eb8797a91 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -21,13 +21,13 @@ FULL_PATH_NAMES = NO STRIP_FROM_PATH = @FreeOrion_SOURCE_DIR@ STRIP_FROM_INC_PATH = SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO +JAVADOC_AUTOBRIEF = YES QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 4 -ALIASES = content_tag{1}="\xrefitem content_tags \"Definition Tag\" \"Content Definition Tags\" \1" +ALIASES = content_tag{1}="\xrefitem content_tags \"\" \"\" \b \1" TCL_SUBST = OPTIMIZE_OUTPUT_FOR_C = NO OPTIMIZE_OUTPUT_JAVA = NO @@ -100,11 +100,15 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = @FreeOrion_SOURCE_DIR@/AI \ +INPUT = @FreeOrion_SOURCE_DIR@/README.md \ + @FreeOrion_SOURCE_DIR@/BUILD.md \ + @FreeOrion_SOURCE_DIR@/CONTRIBUTING.md \ + @FreeOrion_SOURCE_DIR@/ChangeLog.md \ + @FreeOrion_SOURCE_DIR@/doc \ + @FreeOrion_CPP_APIDOC_OUTDIR@/all_tags.dox \ + @FreeOrion_SOURCE_DIR@/AI \ @FreeOrion_SOURCE_DIR@/client \ @FreeOrion_SOURCE_DIR@/combat \ - @FreeOrion_CPP_APIDOC_OUTDIR@/all_tags.dox \ - @FreeOrion_SOURCE_DIR@/doc/content_tags_alias.dox \ @FreeOrion_SOURCE_DIR@/Empire \ @FreeOrion_SOURCE_DIR@/network \ @FreeOrion_SOURCE_DIR@/parse \ @@ -128,7 +132,7 @@ INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = -USE_MDFILE_AS_MAINPAGE = +USE_MDFILE_AS_MAINPAGE = README.md #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- diff --git a/doc/all_tags.dox.in b/doc/all_tags.dox.in deleted file mode 100644 index 48e4cdbcb0d..00000000000 --- a/doc/all_tags.dox.in +++ /dev/null @@ -1,27 +0,0 @@ -/** -@page content_tags Content Definition Tags - -@brief Content entries may have tags associated with them to help filter them -for later effects. - -Tags are used to easily filter for entries, without relying on the specific -entries names.\n -These filters are applied in various areas, such as: -@li displaying only certain parts in the design window -@li not showing buildings unless other criteria are met -@li only affecting certain ships within a system. - -For usage of this alias, see -@subpage content_tags_alias "content tags alias" - --- - -@section default_content_tags Documented tags - Default content -These tags are documented in the default content files. -Reference links lead to external pages. -${CONTENT_DOCUMENTATION} - -@section content_tags_section Documented tags - C++ -These tags are documented in c++ source files. - -**/ diff --git a/doc/content_tags.md b/doc/content_tags.md new file mode 100644 index 00000000000..baafd0abe8c --- /dev/null +++ b/doc/content_tags.md @@ -0,0 +1,86 @@ +Content Definition Tags {#content_tags} +======================= + +Content entries may have tags associated with them to help filter them +for later effects. + +Tags are used to easily filter for entries, without relying on the specific +entries names. + +These filters are applied in various areas, such as: + + * displaying only certain parts in the design window + * not showing buildings unless other criteria are met + * only affecting certain ships within a system. + + +Documenting tags +---------------- + +To document content tags you can use the custom `@content_tag` Doxygen +keyword: + +``` +\@content_tag{TAG_NAME} brief description +``` + +To document C++ defined tags use regular comments, however keep in +mind, that each documentation need to be unique within the symbol +scope to properly be registered Doxygen. + +```cpp +// \@content_tag{TAG_ONE} Documentation for tag one +int SomeFunction(std::string tag = "TAG_ONE"); + +// \@content_tag{TAG_HARD_WORK} Documentation for tag hard work +const std::string TAG_HARD_WORK = "TAG_HARD_WORK"; + +// \@content_tag{TAG_LESS_WORK} Documentation for tag less work +const std::string TAG_LESS_WORK = "TAG_LESS_WORK"; + +void OneFunction(UniverseObject& obj) { + if (obj.HasTag(TAG_HARD_WORK)) + DoHardWork(obj); + else if (obj.HasTag(TAG_LESS_WORK)) + DoLessWork(obj); +} + +void AnotherFunc(UniverseObject& obj) { + // This will be overwritten by TAG_BAD_OVERWRITE + // \@content_tag{TAG_BAD_LOST} Documentation will be overwritten + if (obj.HasTag("TAG_BAD_LOST")) + DoHardWork(); + // \@content_tag{TAG_BAD_OVERWRITE} Documentation will occur twice + else if (obj.HasTag("TAG_BAD_OVERWRITE")) + DoLessWork(); +} +``` + +With Python, docstring comments do not support special commands, instead use the hash form # + +```py +def AnotherFunc(some_object): + # \@content_tag{TAG_DO_WORK} Documentaion for "do work" tag + if universe.getObject(some_object).hasTag("TAG_DO_WORK"): + do_work +``` + +For FOCS use the special c style form of triple slashes /// + +``` +EffectsGroup + scope = And [ + Ship + /// \@content_tag{CTRL_SAMPLE2} Sample 2 + Not HasTag name = "CTRL_SAMPLE2" + WithinDistance distance = Source.Size condition = Source + ] + effects = ... +``` + + +Content Definition Tag Listing +------------------------------ + +The following list of availble tags is auto generated, it may not be +visible outside of Doxygen generated content. diff --git a/doc/content_tags_alias.dox b/doc/content_tags_alias.dox deleted file mode 100644 index 63fa55a3070..00000000000 --- a/doc/content_tags_alias.dox +++ /dev/null @@ -1,75 +0,0 @@ -/** -@page content_tags_alias content tags alias - -Usage: -@verbatim @content_tag{TAG_NAME} brief description @endverbatim - -This custom command provides a common interface to document content definition -tags. - -@section alias_content_tags_example Examples - -Some example code fragments: - -C++ : -@verbatim // File: Sample1.h -/** @content_tag{CTRL_SAMPLE1} Sample 1 **/ -int SomeFunction(std::string tag = "CTRL_SAMPLE1"); -@endverbatim - -FOCS : -@verbatim # File: Sample2.focs.txt -EffectsGroup - scope = And [ - Ship - # @content_tag{CTRL_SAMPLE2} Sample 2 - Not HasTag name = "CTRL_SAMPLE2" - WithinDistance distance = Source.Size condition = Source - ] - effects = ... -@endverbatim - -Python : -With Python, docstring comments do not support special commands. - -@verbatim # File: Sample3.py -def AnotherFunc(some_object): - # @content_tag{CTRL_SAMPLE3} Sample 3 - if universe.getObject(some_object).hasTag("CTRL_SAMPLE3"): - do_work -@endverbatim - -@subsection alias_content_tags_duplicate_entries C++ : Duplicate entries - -Within any single scope, after the first use of this alias, any additional -uses will show a duplicated entry.\n -To work around this bug, define those tags in variables and reference them -where needed. - -The following results in duplicate entries for CTRL_SAMPLE4b. -@verbatim # File: Sample4.cpp -void FuncThree(Object obj) { - /** @content_tag{CTRL_SAMPLE4a} Sample 4a **/ - if (obj.HasTag("CTRL_SAMPLE4a")) - DoHardWork(); - /** @content_tag{CTRL_SAMPLE4b} Sample 4b **/ - else if (obj.HasTag("CTRL_SAMPLE4b")) - DoLessWork(); -} -@endverbatim - -Instead, define a const and document that variable: -@verbatim # File: Sample4.cpp -/** @content_tag{CTRL_SAMPLE4a} Sample 4a **/ -const std::string TAG_SAMPLE_4A = "CTRL_SAMPLE4a"; -/** @content_tag{CTRL_SAMPLE4b} Sample 4b **/ -const std::string TAG_SAMPLE_4B = "CTRL_SAMPLE4b"; -void FuncThree(Object obj) { - if (obj.HasTag(TAG_SAMPLE_4A)) - DoHardWork(); - else if (obj.HasTag(TAG_SAMPLE_4B)) - DoLessWork(); -} -@endverbatim - -**/ diff --git a/doc/tag_parser.py b/doc/tag_parser.py index bad2fb5e30e..f22436ea7f9 100644 --- a/doc/tag_parser.py +++ b/doc/tag_parser.py @@ -5,14 +5,11 @@ import argparse arguments = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description='Parses files for content tags. Formulates a new file from a template' - ' and a doxygen suitable listing of the parse results.', + description='Parses files for content tags and creates and a doxygen suitable listing from the parse results.', epilog='%(prog)s Parses FILES in the MATCH directory for TAGs.' - 'The TEMPLATE file is then read into the OUTPUT file, replacing any' - ' comment (#) lines with a match for TEMPLATE_VAR with the results of the' - ' parsed files. The results are formatted for a doxygen ".dox" file.') -arguments.add_argument('--template', required=True, - help='Full path to an input template.') + 'The comment lines may be in either # or /// form in a file,' + ' but only one form throughout a file. The results of the parse are' + ' formatted for a doxygen ".dox" file.') arguments.add_argument('--match', default='./', help='Root directory to search for files to parse.') arguments.add_argument('-f', '--files', metavar='MASK', nargs='*', default=['*.*'], @@ -21,8 +18,6 @@ help='One or more file masks to exclude from results matching source_mask(s).') arguments.add_argument('--tag', nargs=2, default=['@content_tag{', '}'], help='Set of opening and closing' ' strings which surround a tag name and precede the tag documentation.') -arguments.add_argument('-t', '--template_var', default='${CONTENT_DOCUMENTATION}', - help='String from source file to replace with formatted results from parsed tags.') arguments.add_argument('-o', '--output', default='output.txt', help='Full path to an output file.') arguments.add_argument('--link_source', metavar='CONTEXT_DIR BASE_URL LINE_PREFIX]', nargs='*', default=[], help='Display linked source before each tag documentation. Requires arguments in order:' @@ -46,26 +41,43 @@ def add_doc_source(_file_name, _line_number, _content, _tags): def parse_file(_parse_file, _tags): - with open(_parse_file, 'r') as f: + with open(_parse_file, 'r', encoding='utf-8') as f: match_line = 0 content = [] + special_comments_this_file = None # Documentation may span multiple lines for a content tag. # Matched lines are only stored in _tags once an end condition is met. # End conditions are: a line not starting a comment, a new content tag, end of file - for parse_line in enumerate(f): - # only check lines that start as a comment (#) - if parse_line[1].lstrip().startswith("#"): - if tag_open in parse_line[1]: + for raw_line in enumerate(f): + if raw_line[1].lstrip().startswith("#") and special_comments_this_file is not True: + if special_comments_this_file is None: + special_comments_this_file = False + if tag_open in raw_line[1]: if match_line: # in event focs scripts decide to stack one documentation on top of another add_doc_source(_parse_file, match_line, content, _tags) # store content and line for later addition - content = ''.join(parse_line[1].split(tag_open, 1)[1]).split(tag_close, 1) + content = ''.join(raw_line[1].split(tag_open, 1)[1]).split(tag_close, 1) content[1] = content[1].strip() - match_line = parse_line[0] + 1 + match_line = raw_line[0] + 1 elif match_line: # not a new content tag, append to previous line description - content[1] += ' ' + parse_line[1].lstrip('# ') + content[1] += ' ' + raw_line[1].lstrip('# ') + elif raw_line[1].lstrip().startswith("/// ") and special_comments_this_file is not False: + if special_comments_this_file is None: + special_comments_this_file = True + content = ['# '] + if tag_open in raw_line[1]: + if match_line: + # in event focs scripts decide to stack one documentation on top of another + add_doc_source(_parse_file, match_line, content, _tags) + # store content and line for later addition + content = ''.join(raw_line[1].split(tag_open, 1)[1]).split(tag_close, 1) + content[1] = content[1].strip() + match_line = raw_line[0] + 1 + elif match_line: + # not a new content tag, append to previous line description + content[1] += ' ' + raw_line[1].lstrip('/// ') elif match_line: # end of description, add a node for this source add_doc_source(_parse_file, match_line, content, _tags) @@ -109,24 +121,22 @@ def get_link(_file, _line_number): output_buff = [] -# read in template, replacing template_var with tags formatted for doxygen -with open(args.get('template'), 'r') as input_file: - for input_line in input_file: - if args.get('template_var') in input_line: - output_buff.append('
\n') - for tag in sorted(all_tags): - output_buff.append('
' + tag + '
\n
') - for source in all_tags[tag]: - description = str.format('{0} ', source[2]) - # prepend source and link if requested - link_str = get_link(source[0], source[1]) - if link_str: - link_str += " - " - output_buff.append(link_str + description + '
\n') - output_buff.append('
\n') - output_buff.append('
\n') - else: - output_buff.append(input_line) +# create tags formatted for doxygen + +output_buff.append('/**\n') +output_buff.append('@page content_tag_listing Content Definition Tag Listing\n') +output_buff.append('This page is required to load parsed data into the documentation, the actual listing can be found @ref content_tags\n') +for tag in sorted(all_tags): + output_buff.append('@content_tag{' + tag + '} ') + for source in all_tags[tag]: + description = str.format('{0} ', source[2]) + # prepend source and link if requested + link_str = get_link(source[0], source[1]) + if link_str: + link_str += " - " + output_buff.append(link_str + description) + output_buff.append('\n') +output_buff.append('*/\n') if args.get('dry_run'): source_count = 0 diff --git a/msvc2015/GiGiSDL/GiGiSDL.vcxproj b/msvc2015/GiGiSDL/GiGiSDL.vcxproj deleted file mode 100644 index db2f326d60c..00000000000 --- a/msvc2015/GiGiSDL/GiGiSDL.vcxproj +++ /dev/null @@ -1,135 +0,0 @@ - - - - - Debug - Win32 - - - Release-XP - Win32 - - - Release - Win32 - - - - {A79A0D95-B9C4-42A5-9D4A-B55240EB295A} - GiGiOgre - - - - Application - true - MultiByte - v140 - - - DynamicLibrary - false - true - Unicode - v140 - - - DynamicLibrary - false - true - Unicode - v140_xp - - - - - - - - - - - - - - - - - ../../ - false - - - ../../ - false - - - - Disabled - - - true - - - - - Full - true - true - ../../../include/;../../GG/;%(AdditionalIncludeDirectories) - NDEBUG;_USRDLL;_DLL;GiGiSDL_EXPORTS;%(PreprocessorDefinitions) - - - - - true - false - ProgramDatabase - - - true - true - true - ../../GiGiSDL.dll - ../../../lib/;../../;%(AdditionalLibraryDirectories) - GiGi.lib;%(AdditionalDependencies) - LIBCMT - Console - UseLinkTimeCodeGeneration - - - - - Full - true - true - ../../../include/;../../GG/;%(AdditionalIncludeDirectories) - NDEBUG;_USRDLL;_DLL;GiGiSDL_EXPORTS;%(PreprocessorDefinitions) - - - - - true - false - ProgramDatabase - - - true - true - true - ../../GiGiSDL.dll - ../../../lib/;../../;%(AdditionalLibraryDirectories) - GiGi.lib;%(AdditionalDependencies) - LIBCMT - Console - UseLinkTimeCodeGeneration - - - - - - - - - - - - diff --git a/msvc2015/GiGiSDL/GiGiSDL.vcxproj.filters b/msvc2015/GiGiSDL/GiGiSDL.vcxproj.filters deleted file mode 100644 index 5ec59777772..00000000000 --- a/msvc2015/GiGiSDL/GiGiSDL.vcxproj.filters +++ /dev/null @@ -1,23 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - - - Header Files - - - - - Source Files - - - \ No newline at end of file diff --git a/msvc2015/win32.dependency.props b/msvc2015/win32.dependency.props deleted file mode 100644 index e62249fb965..00000000000 --- a/msvc2015/win32.dependency.props +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - <_PropertySheetDisplayName>Win32 dependency - - - - WIN32;_WINDOWS;_WIN32_WINNT=_WIN32_WINNT_WINXP;%(PreprocessorDefinitions) - - - User32.lib - - - - diff --git a/msvc2015/Common/Common.vcxproj b/msvc2017/Common/Common.vcxproj similarity index 68% rename from msvc2015/Common/Common.vcxproj rename to msvc2017/Common/Common.vcxproj index 1d62d240278..ca92a9a5758 100644 --- a/msvc2015/Common/Common.vcxproj +++ b/msvc2017/Common/Common.vcxproj @@ -1,44 +1,55 @@ - + Debug Win32 - - Release-XP - Win32 + + Debug + x64 Release Win32 + + Release + x64 + {BEDF460A-EAE9-4E20-AFB2-2C8434051150} Win32Proj Common + 8.1 StaticLibrary true Unicode - v140 + v141 + + + StaticLibrary + true + Unicode + v141 StaticLibrary false true Unicode - v140 + v141 - + StaticLibrary false true Unicode - v140_xp + v141 @@ -59,20 +70,37 @@ - - ../../ + + ../../ + + + - - + Use Disabled _DEBUG;_LIB;%(PreprocessorDefinitions) + StdAfx.h + StdAfx.h;%(ForcedIncludeFiles) + + + Windows + true + + + + + Use + Disabled + _DEBUG;_LIB;%(PreprocessorDefinitions) + StdAfx.h + StdAfx.h;%(ForcedIncludeFiles) Windows @@ -81,7 +109,7 @@ - NotUsing + Use Full true true @@ -89,10 +117,9 @@ ../../../include/;../../GG/;../../;%(AdditionalIncludeDirectories) true false - - - - + StdAfx.h + $(IntDir)$(TargetName).pch + StdAfx.h;%(ForcedIncludeFiles) Windows @@ -101,9 +128,9 @@ true - + - NotUsing + Use Full true true @@ -111,10 +138,9 @@ ../../../include/;../../GG/;../../;%(AdditionalIncludeDirectories) true false - - - - + StdAfx.h + $(IntDir)$(TargetName).pch + StdAfx.h;%(ForcedIncludeFiles) Windows @@ -126,26 +152,31 @@ true - true + true true + true Document Document - "$(SolutionDir)..\python.exe" "$(SolutionDir)..\cmake\make_versioncpp.py" "$(SolutionDir).." "MSVC 2015" - "$(SolutionDir)..\python.exe" "$(SolutionDir)..\cmake\make_versioncpp.py" "$(SolutionDir).." "MSVC 2015" + "$(SolutionDir)..\python3.5.exe" "$(SolutionDir)..\cmake\make_versioncpp.py" "$(SolutionDir).." "MSVC 2017" + "$(SolutionDir)..\python3.5.exe" "$(SolutionDir)..\cmake\make_versioncpp.py" "$(SolutionDir).." "MSVC 2017" $(SolutionDir)..\util\Version.cpp;%(Outputs) - $(SolutionDir)..\util\Version.cpp;%(Outputs) + $(SolutionDir)..\util\Version.cpp;%(Outputs) $(SolutionDir)..\.git;%(AdditionalInputs) - $(SolutionDir)..\.git;%(AdditionalInputs) - "$(SolutionDir)..\python.exe" "$(SolutionDir)..\cmake\make_versioncpp.py" "$(SolutionDir).." "MSVC 2015 Debug" + $(SolutionDir)..\.git;%(AdditionalInputs) + "$(SolutionDir)..\python3.5.exe" "$(SolutionDir)..\cmake\make_versioncpp.py" "$(SolutionDir).." "MSVC 2017 Debug" + "$(SolutionDir)..\python3.5.exe" "$(SolutionDir)..\cmake\make_versioncpp.py" "$(SolutionDir).." "MSVC 2017 Debug" $(SolutionDir)..\util\Version.cpp;%(Outputs) - $(SolutionDir)..\.svn;%(AdditionalInputs) + $(SolutionDir)..\util\Version.cpp;%(Outputs) + $(SolutionDir)..\.git;%(AdditionalInputs) + $(SolutionDir)..\.git;%(AdditionalInputs) Configuring Version.cpp + Configuring Version.cpp Configuring Version.cpp - Configuring Version.cpp + Configuring Version.cpp @@ -155,22 +186,35 @@ + + + + + + + + + - + + + + + @@ -180,20 +224,23 @@ + + + - - + + @@ -202,6 +249,7 @@ + @@ -210,6 +258,7 @@ + @@ -217,26 +266,33 @@ + + + + + + - + - + + @@ -246,18 +302,21 @@ + + + - + @@ -274,6 +333,12 @@ + + Create + Create + Create + Create + diff --git a/msvc2015/Common/Common.vcxproj.filters b/msvc2017/Common/Common.vcxproj.filters similarity index 83% rename from msvc2015/Common/Common.vcxproj.filters rename to msvc2017/Common/Common.vcxproj.filters index 3458fe6b280..21e72af62a2 100644 --- a/msvc2015/Common/Common.vcxproj.filters +++ b/msvc2017/Common/Common.vcxproj.filters @@ -64,22 +64,34 @@ Header Files\Empire + + Header Files\Empire + + + Header Files\Empire + Header Files\Empire Header Files\Empire + + Header Files\Empire + Header Files\universe - + Header Files\universe - + Header Files\universe - + + Header Files\universe + + Header Files\universe @@ -91,6 +103,9 @@ Header Files\universe + + Header Files\universe + Header Files\universe @@ -121,6 +136,9 @@ Header Files\universe + + Header Files\universe + Header Files\universe @@ -139,10 +157,10 @@ Header Files\universe - + Header Files\universe - + Header Files\universe @@ -154,6 +172,9 @@ Header Files\util + + Header Files\util + Header Files\util @@ -178,6 +199,9 @@ Header Files\util + + Header Files\util + Header Files\util @@ -205,6 +229,9 @@ Header Files\universe + + Header Files\universe + Header Files\util @@ -235,6 +262,28 @@ Header Files\util + + + Header Files\universe + + + Header Files\universe + + + Header Files\universe + + + Header Files\universe + + + Header Files\universe + + + Header Files\universe + + + Header Files\universe + @@ -249,6 +298,9 @@ Source Files\util + + Source Files\util + Source Files\util @@ -297,27 +349,30 @@ Source Files\Empire + + Source Files\Empire + + + Source Files\Empire + Source Files\Empire Source Files\Empire - - Source Files\universe + + Source Files\Empire - + Source Files\universe - + Source Files\universe Source Files\universe - - Source Files\universe - Source Files\universe @@ -327,6 +382,9 @@ Source Files\universe + + Source Files\universe + Source Files\universe @@ -354,6 +412,9 @@ Source Files\universe + + Source Files\universe + Source Files\universe @@ -372,6 +433,9 @@ Source Files\universe + + Source Files\universe + Source Files\util @@ -381,6 +445,9 @@ Source Files\universe + + Source Files\universe + Source Files\util @@ -423,6 +490,16 @@ Source Files\util + + + Source Files\universe + + + Source Files\universe + + + Source Files\universe + diff --git a/msvc2017/Common/StdAfx.cpp b/msvc2017/Common/StdAfx.cpp new file mode 100644 index 00000000000..09f15b61cc9 --- /dev/null +++ b/msvc2017/Common/StdAfx.cpp @@ -0,0 +1 @@ +#include "StdAfx.h" \ No newline at end of file diff --git a/msvc2017/Common/StdAfx.h b/msvc2017/Common/StdAfx.h new file mode 100644 index 00000000000..b9a265d397b --- /dev/null +++ b/msvc2017/Common/StdAfx.h @@ -0,0 +1,96 @@ + +#pragma once + +// We include all external headers used in any of the header files, +// plus external headers used in at least five .cpp files. + +// https://hownot2code.com/2016/08/16/stdafx-h/ + +// ---------------- +// includes from .h + +// Common +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ------------------ +// includes from .cpp +#include + + +#ifdef _MSC_VER +// Note: This is a workaround for Visual C++ non-conformant pre-processor +// handling of empty macro arguments. +#include +#include +#endif diff --git a/msvc2015/FreeOrion.sln b/msvc2017/FreeOrion.sln similarity index 63% rename from msvc2015/FreeOrion.sln rename to msvc2017/FreeOrion.sln index e134910f3fd..56f6fd98f26 100644 --- a/msvc2015/FreeOrion.sln +++ b/msvc2017/FreeOrion.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1022 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GiGi", "GiGi\GiGi.vcxproj", "{2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB}" EndProject @@ -8,7 +8,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FreeOrion", "FreeOrion\Free ProjectSection(ProjectDependencies) = postProject {BEDF460A-EAE9-4E20-AFB2-2C8434051150} = {BEDF460A-EAE9-4E20-AFB2-2C8434051150} {9925F25C-A72E-42AE-B2E3-6657255BF293} = {9925F25C-A72E-42AE-B2E3-6657255BF293} - {A79A0D95-B9C4-42A5-9D4A-B55240EB295A} = {A79A0D95-B9C4-42A5-9D4A-B55240EB295A} {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB} = {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB} EndProjectSection EndProject @@ -31,47 +30,41 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Common\Common.vcx {9925F25C-A72E-42AE-B2E3-6657255BF293} = {9925F25C-A72E-42AE-B2E3-6657255BF293} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GiGiSDL", "GiGiSDL\GiGiSDL.vcxproj", "{A79A0D95-B9C4-42A5-9D4A-B55240EB295A}" - ProjectSection(ProjectDependencies) = postProject - {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB} = {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB} - EndProjectSection -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Release|Win32 = Release|Win32 - Release-XP|Win32 = Release-XP|Win32 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB}.Release|Win32.ActiveCfg = Release|Win32 {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB}.Release|Win32.Build.0 = Release|Win32 - {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB}.Release-XP|Win32.ActiveCfg = Release-XP|Win32 - {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB}.Release-XP|Win32.Build.0 = Release-XP|Win32 + {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB}.Release|x64.ActiveCfg = Release|x64 + {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB}.Release|x64.Build.0 = Release|x64 {311D02C0-427D-4A03-AAEB-B819A9ACF5AB}.Release|Win32.ActiveCfg = Release|Win32 {311D02C0-427D-4A03-AAEB-B819A9ACF5AB}.Release|Win32.Build.0 = Release|Win32 - {311D02C0-427D-4A03-AAEB-B819A9ACF5AB}.Release-XP|Win32.ActiveCfg = Release-XP|Win32 - {311D02C0-427D-4A03-AAEB-B819A9ACF5AB}.Release-XP|Win32.Build.0 = Release-XP|Win32 + {311D02C0-427D-4A03-AAEB-B819A9ACF5AB}.Release|x64.ActiveCfg = Release|x64 + {311D02C0-427D-4A03-AAEB-B819A9ACF5AB}.Release|x64.Build.0 = Release|x64 {B9808A04-CE5E-4660-99CB-B8C8C4E64402}.Release|Win32.ActiveCfg = Release|Win32 {B9808A04-CE5E-4660-99CB-B8C8C4E64402}.Release|Win32.Build.0 = Release|Win32 - {B9808A04-CE5E-4660-99CB-B8C8C4E64402}.Release-XP|Win32.ActiveCfg = Release-XP|Win32 - {B9808A04-CE5E-4660-99CB-B8C8C4E64402}.Release-XP|Win32.Build.0 = Release-XP|Win32 + {B9808A04-CE5E-4660-99CB-B8C8C4E64402}.Release|x64.ActiveCfg = Release|x64 + {B9808A04-CE5E-4660-99CB-B8C8C4E64402}.Release|x64.Build.0 = Release|x64 {77E8E74E-F581-4850-A4C6-A088564DF9A7}.Release|Win32.ActiveCfg = Release|Win32 {77E8E74E-F581-4850-A4C6-A088564DF9A7}.Release|Win32.Build.0 = Release|Win32 - {77E8E74E-F581-4850-A4C6-A088564DF9A7}.Release-XP|Win32.ActiveCfg = Release-XP|Win32 - {77E8E74E-F581-4850-A4C6-A088564DF9A7}.Release-XP|Win32.Build.0 = Release-XP|Win32 + {77E8E74E-F581-4850-A4C6-A088564DF9A7}.Release|x64.ActiveCfg = Release|x64 + {77E8E74E-F581-4850-A4C6-A088564DF9A7}.Release|x64.Build.0 = Release|x64 {9925F25C-A72E-42AE-B2E3-6657255BF293}.Release|Win32.ActiveCfg = Release|Win32 {9925F25C-A72E-42AE-B2E3-6657255BF293}.Release|Win32.Build.0 = Release|Win32 - {9925F25C-A72E-42AE-B2E3-6657255BF293}.Release-XP|Win32.ActiveCfg = Release-XP|Win32 - {9925F25C-A72E-42AE-B2E3-6657255BF293}.Release-XP|Win32.Build.0 = Release-XP|Win32 + {9925F25C-A72E-42AE-B2E3-6657255BF293}.Release|x64.ActiveCfg = Release|x64 + {9925F25C-A72E-42AE-B2E3-6657255BF293}.Release|x64.Build.0 = Release|x64 {BEDF460A-EAE9-4E20-AFB2-2C8434051150}.Release|Win32.ActiveCfg = Release|Win32 {BEDF460A-EAE9-4E20-AFB2-2C8434051150}.Release|Win32.Build.0 = Release|Win32 - {BEDF460A-EAE9-4E20-AFB2-2C8434051150}.Release-XP|Win32.ActiveCfg = Release-XP|Win32 - {BEDF460A-EAE9-4E20-AFB2-2C8434051150}.Release-XP|Win32.Build.0 = Release-XP|Win32 - {A79A0D95-B9C4-42A5-9D4A-B55240EB295A}.Release|Win32.ActiveCfg = Release|Win32 - {A79A0D95-B9C4-42A5-9D4A-B55240EB295A}.Release|Win32.Build.0 = Release|Win32 - {A79A0D95-B9C4-42A5-9D4A-B55240EB295A}.Release-XP|Win32.ActiveCfg = Release-XP|Win32 - {A79A0D95-B9C4-42A5-9D4A-B55240EB295A}.Release-XP|Win32.Build.0 = Release-XP|Win32 + {BEDF460A-EAE9-4E20-AFB2-2C8434051150}.Release|x64.ActiveCfg = Release|x64 + {BEDF460A-EAE9-4E20-AFB2-2C8434051150}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {61D91A5F-3DF8-4480-A79E-788F09F37A35} + EndGlobalSection EndGlobal diff --git a/msvc2015/FreeOrion/FreeOrion.vcxproj b/msvc2017/FreeOrion/FreeOrion.vcxproj similarity index 79% rename from msvc2015/FreeOrion/FreeOrion.vcxproj rename to msvc2017/FreeOrion/FreeOrion.vcxproj index 45e0465e209..45f15ac4c66 100644 --- a/msvc2015/FreeOrion/FreeOrion.vcxproj +++ b/msvc2017/FreeOrion/FreeOrion.vcxproj @@ -1,44 +1,55 @@ - + Debug Win32 - - Release-XP - Win32 + + Debug + x64 Release Win32 + + Release + x64 + {311D02C0-427D-4A03-AAEB-B819A9ACF5AB} Win32Proj FreeOrion + 8.1 Application true Unicode - v140 + v141 + + + Application + true + Unicode + v141 Application false true Unicode - v140 + v141 - + Application false true Unicode - v140_xp + v141 @@ -62,20 +73,37 @@ true + + true + false ../../ - + false ../../ - - + Use Disabled _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + StdAfx.h + StdAfx.h;%(ForcedIncludeFiles) + + + Console + true + + + + + Use + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + StdAfx.h + StdAfx.h;%(ForcedIncludeFiles) Console @@ -84,19 +112,18 @@ - NotUsing + Use Full true true NDEBUG;FREEORION_BUILD_HUMAN;FREEORION_WIN32;%(PreprocessorDefinitions) ../../../include/;../../GG/;../include/;%(AdditionalIncludeDirectories) - - - - + StdAfx.h + $(IntDir)$(TargetName).pch true ProgramDatabase false + StdAfx.h;%(ForcedIncludeFiles) Console @@ -104,28 +131,27 @@ true true ../../;%(AdditionalLibraryDirectories) - Common.lib;Parsers.lib;GiGi.lib;GiGiSDL.lib;%(AdditionalDependencies) + Common.lib;Parsers.lib;GiGi.lib;%(AdditionalDependencies) ../../FreeOrion.exe false LIBCMT; LIBCPMT UseLinkTimeCodeGeneration - + - NotUsing + Use Full true true NDEBUG;FREEORION_BUILD_HUMAN;FREEORION_WIN32;%(PreprocessorDefinitions) ../../../include/;../../GG/;../include/;%(AdditionalIncludeDirectories) - - - - + StdAfx.h + $(IntDir)$(TargetName).pch true ProgramDatabase false + StdAfx.h;%(ForcedIncludeFiles) Console @@ -133,7 +159,7 @@ true true ../../;%(AdditionalLibraryDirectories) - Common.lib;Parsers.lib;GiGi.lib;GiGiSDL.lib;%(AdditionalDependencies) + Common.lib;Parsers.lib;GiGi.lib;%(AdditionalDependencies) ../../FreeOrion.exe false LIBCMT; LIBCPMT @@ -143,13 +169,16 @@ + + + - + @@ -191,6 +220,7 @@ + @@ -198,6 +228,7 @@ + @@ -211,11 +242,12 @@ - + + - + @@ -233,12 +265,12 @@ - - + + @@ -252,17 +284,18 @@ + - + + - @@ -277,6 +310,12 @@ + + Create + Create + Create + Create + @@ -301,6 +340,7 @@ + @@ -309,6 +349,7 @@ + @@ -327,10 +368,10 @@ IDI_ICON1=101 - IDI_ICON1=101 + IDI_ICON1=101 - + \ No newline at end of file diff --git a/msvc2015/FreeOrion/FreeOrion.vcxproj.filters b/msvc2017/FreeOrion/FreeOrion.vcxproj.filters similarity index 93% rename from msvc2015/FreeOrion/FreeOrion.vcxproj.filters rename to msvc2017/FreeOrion/FreeOrion.vcxproj.filters index 662a39db29f..4184bc9bbe1 100644 --- a/msvc2015/FreeOrion/FreeOrion.vcxproj.filters +++ b/msvc2017/FreeOrion/FreeOrion.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -31,9 +31,6 @@ {683283a4-a27f-4105-8437-c3d102745e9f} - - {b9e15fb8-5458-4bcb-92dc-09ba4b65cfae} - {f9dda62a-26fb-45a2-a8dc-c2fe4f1434c8} @@ -63,6 +60,9 @@ Header Files\client + + Header Files\client + Header Files\client\human @@ -78,13 +78,19 @@ Header Files\Empire + + Header Files\Empire + Header Files\Empire - - Header Files\network + + Header Files\Empire - + + Header Files\Empire + + Header Files\network @@ -198,6 +204,9 @@ Header Files\UI + + Header Files\UI + Header Files\UI @@ -231,6 +240,9 @@ Header Files\util + + Header Files\util + Header Files\util @@ -279,19 +291,19 @@ Header Files\universe - + Header Files\universe Header Files\universe - + Header Files\universe - + Header Files\universe - + Header Files\universe @@ -342,9 +354,6 @@ Header Files\UI - - Header Files\universe - Header Files\UI @@ -363,6 +372,9 @@ Header Files\universe + + Header Files\universe + Header Files\UI @@ -393,9 +405,13 @@ Header Files\UI + + Header Files\UI + + - + Resource Files @@ -406,6 +422,9 @@ Source Files\client + + Source Files\client + Source Files\client\human @@ -415,9 +434,6 @@ Source Files\client\human - - Source Files\network - Source Files\UI @@ -532,6 +548,9 @@ Source Files\UI + + Source Files\UI + Source Files\UI @@ -595,10 +614,14 @@ Source Files\util + + Source Files\UI + + Resource Files - + \ No newline at end of file diff --git a/msvc2017/FreeOrion/StdAfx.cpp b/msvc2017/FreeOrion/StdAfx.cpp new file mode 100644 index 00000000000..09f15b61cc9 --- /dev/null +++ b/msvc2017/FreeOrion/StdAfx.cpp @@ -0,0 +1 @@ +#include "StdAfx.h" \ No newline at end of file diff --git a/msvc2017/FreeOrion/StdAfx.h b/msvc2017/FreeOrion/StdAfx.h new file mode 100644 index 00000000000..f27f965ebba --- /dev/null +++ b/msvc2017/FreeOrion/StdAfx.h @@ -0,0 +1,133 @@ + +#pragma once + +// We include all external headers used in any of the header files, +// plus external headers used in at least five .cpp files. + +// https://hownot2code.com/2016/08/16/stdafx-h/ + +// ---------------- +// includes from .h + +// Common +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// GiGi +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +// FreeOrion +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ------------------ +// includes from .cpp +#include + +#include +#include + +#ifdef _MSC_VER +// Note: This is a workaround for Visual C++ non-conformant pre-processor +// handling of empty macro arguments. +#include +#include +#endif diff --git a/msvc2015/FreeOrionCA/FreeOrionCA.vcxproj b/msvc2017/FreeOrionCA/FreeOrionCA.vcxproj similarity index 70% rename from msvc2015/FreeOrionCA/FreeOrionCA.vcxproj rename to msvc2017/FreeOrionCA/FreeOrionCA.vcxproj index 58c539629fa..c603db55e1e 100644 --- a/msvc2015/FreeOrionCA/FreeOrionCA.vcxproj +++ b/msvc2017/FreeOrionCA/FreeOrionCA.vcxproj @@ -1,44 +1,55 @@ - + Debug Win32 - - Release-XP - Win32 + + Debug + x64 Release Win32 + + Release + x64 + {77E8E74E-F581-4850-A4C6-A088564DF9A7} Win32Proj FreeOrionCA + 8.1 Application true Unicode - v140 + v141 + + + Application + true + Unicode + v141 Application false true Unicode - v140 + v141 - + Application false true Unicode - v140_xp + v141 @@ -55,20 +66,37 @@ true + + true + false ../../ - + false ../../ - - + Use Disabled _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + StdAfx.h + StdAfx.h;%(ForcedIncludeFiles) + + + Console + true + + + + + Use + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + StdAfx.h + StdAfx.h;%(ForcedIncludeFiles) Console @@ -77,19 +105,18 @@ - NotUsing + Use Full true true NDEBUG;FREEORION_BUILD_AI;FREEORION_WIN32;GiGi_EXPORTS;_DLL;%(PreprocessorDefinitions) - ../../../include/;../../GG/;../../../include/python2.7/;%(AdditionalIncludeDirectories) - - - - + ../../../include/;../../GG/;../../../include/python3.5/;%(AdditionalIncludeDirectories) + StdAfx.h + $(IntDir)$(TargetName).pch true true ProgramDatabase + StdAfx.h;%(ForcedIncludeFiles) Console @@ -103,21 +130,20 @@ UseLinkTimeCodeGeneration - + - NotUsing + Use Full true true NDEBUG;FREEORION_BUILD_AI;FREEORION_WIN32;GiGi_EXPORTS;_DLL;%(PreprocessorDefinitions) - ../../../include/;../../GG/;../../../include/python2.7/;%(AdditionalIncludeDirectories) - - - - + ../../../include/;../../GG/;../../../include/python3.5/;%(AdditionalIncludeDirectories) + StdAfx.h + $(IntDir)$(TargetName).pch true true ProgramDatabase + StdAfx.h;%(ForcedIncludeFiles) Console @@ -132,28 +158,30 @@ - + + + + + + - - - - + + - @@ -172,12 +200,12 @@ - - + + @@ -191,22 +219,29 @@ + - + + - - - + + + + Create + Create + Create + Create + diff --git a/msvc2015/FreeOrionCA/FreeOrionCA.vcxproj.filters b/msvc2017/FreeOrionCA/FreeOrionCA.vcxproj.filters similarity index 83% rename from msvc2015/FreeOrionCA/FreeOrionCA.vcxproj.filters rename to msvc2017/FreeOrionCA/FreeOrionCA.vcxproj.filters index 370e44a12a7..f120cbf224a 100644 --- a/msvc2015/FreeOrionCA/FreeOrionCA.vcxproj.filters +++ b/msvc2017/FreeOrionCA/FreeOrionCA.vcxproj.filters @@ -9,9 +9,6 @@ {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd - - {0b89f998-cbcb-41d7-a769-d4b4b31ee70c} - {0e8f8032-78e0-4fb0-8880-386704b27db3} @@ -24,9 +21,6 @@ {c557a17d-9943-4611-b855-9107a2eb8cf0} - - {03d83d46-a82e-4853-8f25-511d50153b40} - {f4507b16-fead-4c05-8cd7-5d33bc56a5ee} @@ -36,55 +30,58 @@ {2b260b85-270a-432d-8d6c-94ec7c922589} - - {c3ab409d-1113-4558-985e-22ced3ae2a29} - {9b9fafb5-5938-4a70-a059-e2ac800bb473} {7ce8eac8-615b-4c78-b206-3bf5fb2ab6bb} - - {0de6ad2e-dcac-40fb-93fb-19267615fa87} - {2ab2cfda-27b9-4be9-a9ce-9e5b1cec2e28} {7e48aed2-0a7f-45a6-8c82-c1d7a143dfa7} - - {5559c554-27b1-4449-94e3-f1bb8b3374d4} - - - {840c46ce-8edc-4578-83d0-a181cbe74a7e} + + {03b7dbd3-a99a-4b9a-a838-acc16244db37} - - Header Files\AI - Header Files\client Header Files\client + + Header Files\client + Header Files\client\AI + + Header Files\client\AI + + + Header Files\client\AI + Header Files\Empire + + Header Files\Empire + + + Header Files\Empire + Header Files\Empire Header Files\Empire - - Header Files\network + + Header Files\Empire Header Files\network @@ -113,6 +110,9 @@ Header Files\util + + Header Files\util + Header Files\util @@ -158,16 +158,16 @@ Header Files\universe - + Header Files\universe - + Header Files\universe Header Files\universe - + Header Files\universe @@ -215,13 +215,10 @@ Header Files\GG - - Header Files\universe - Header Files\universe - + Header Files\universe @@ -230,15 +227,10 @@ Header Files\python - - Header Files\python\AI - - - Header Files\python\AI - Header Files\python + @@ -247,8 +239,8 @@ Source Files\client - - Source Files\network + + Source Files\client Source Files\client\AI @@ -256,14 +248,11 @@ Source Files\client\AI - - Source Files\AI - - - Source Files\python\AI + + Source Files\client\AI - - Source Files\python\AI + + Source Files\client\AI Source Files\python @@ -281,7 +270,11 @@ Source Files\python - Source Files + Source Files\util + + + Source Files\python + - + \ No newline at end of file diff --git a/msvc2017/FreeOrionCA/StdAfx.cpp b/msvc2017/FreeOrionCA/StdAfx.cpp new file mode 100644 index 00000000000..09f15b61cc9 --- /dev/null +++ b/msvc2017/FreeOrionCA/StdAfx.cpp @@ -0,0 +1 @@ +#include "StdAfx.h" \ No newline at end of file diff --git a/msvc2017/FreeOrionCA/StdAfx.h b/msvc2017/FreeOrionCA/StdAfx.h new file mode 100644 index 00000000000..c30eeaaeff1 --- /dev/null +++ b/msvc2017/FreeOrionCA/StdAfx.h @@ -0,0 +1,100 @@ + +#pragma once + +// We include all external headers used in any of the header files, +// plus external headers used in at least five .cpp files. + +// https://hownot2code.com/2016/08/16/stdafx-h/ + +// ---------------- +// includes from .h + +// Common +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// FreeOrionCA +#include + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +// Note: This is a workaround for Visual C++ non-conformant pre-processor +// handling of empty macro arguments. +#include +#include +#endif diff --git a/msvc2015/FreeOrionD/FreeOrionD.vcxproj b/msvc2017/FreeOrionD/FreeOrionD.vcxproj similarity index 69% rename from msvc2015/FreeOrionD/FreeOrionD.vcxproj rename to msvc2017/FreeOrionD/FreeOrionD.vcxproj index c45b0dd7821..0cedcf8de5e 100644 --- a/msvc2015/FreeOrionD/FreeOrionD.vcxproj +++ b/msvc2017/FreeOrionD/FreeOrionD.vcxproj @@ -1,44 +1,55 @@ - + Debug Win32 - - Release-XP - Win32 + + Debug + x64 Release Win32 + + Release + x64 + {B9808A04-CE5E-4660-99CB-B8C8C4E64402} Win32Proj FreeOrionD + 8.1 Application true Unicode - v140 + v141 + + + Application + true + Unicode + v141 Application false true Unicode - v140 + v141 - + Application false true Unicode - v140_xp + v141 @@ -55,20 +66,37 @@ true + + true + false ../../ - + false ../../ - - + Use Disabled _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + StdAfx.h + StdAfx.h;%(ForcedIncludeFiles) + + + Console + true + + + + + Use + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + StdAfx.h + StdAfx.h;%(ForcedIncludeFiles) Console @@ -77,19 +105,19 @@ - NotUsing + Use Full true true NDEBUG;FREEORION_BUILD_SERVER;FREEORION_WIN32;GiGi_EXPORTS;_DLL;%(PreprocessorDefinitions) - ../../../include/;../../GG/;../../../include/python2.7/;%(AdditionalIncludeDirectories) - - - - + ../../../include/;../../GG/;../../../include/python3.5/;%(AdditionalIncludeDirectories) + StdAfx.h + $(IntDir)$(TargetName).pch true true ProgramDatabase + StdAfx.h;%(ForcedIncludeFiles) + false Console @@ -103,21 +131,21 @@ UseLinkTimeCodeGeneration - + - NotUsing + Use Full true true NDEBUG;FREEORION_BUILD_SERVER;FREEORION_WIN32;GiGi_EXPORTS;_DLL;%(PreprocessorDefinitions) - ../../../include/;../../GG/;../../../include/python2.7/;%(AdditionalIncludeDirectories) - - - - + ../../../include/;../../GG/;../../../include/python3.5/;%(AdditionalIncludeDirectories) + StdAfx.h + $(IntDir)$(TargetName).pch true true ProgramDatabase + StdAfx.h;%(ForcedIncludeFiles) + false Console @@ -134,22 +162,26 @@ + + + - - - + + + + - + + - @@ -167,14 +199,13 @@ - - - + + @@ -188,26 +219,34 @@ + - + - - + - + + + + + Create + Create + Create + Create + - + \ No newline at end of file diff --git a/msvc2015/FreeOrionD/FreeOrionD.vcxproj.filters b/msvc2017/FreeOrionD/FreeOrionD.vcxproj.filters similarity index 85% rename from msvc2015/FreeOrionD/FreeOrionD.vcxproj.filters rename to msvc2017/FreeOrionD/FreeOrionD.vcxproj.filters index 7efec33ba2f..8803d18291c 100644 --- a/msvc2015/FreeOrionD/FreeOrionD.vcxproj.filters +++ b/msvc2017/FreeOrionD/FreeOrionD.vcxproj.filters @@ -27,9 +27,6 @@ {d2f5dbff-1284-42b2-967e-0b1e7e4070ca} - - {88999d0a-94bc-43cc-a21c-ce4ef5fd65a4} - {b7b96e1d-6ba0-4b2d-bb2b-9dd040fd6854} @@ -62,28 +59,34 @@ Header Files\Empire + + Header Files\Empire + + + Header Files\Empire + Header Files\Empire Header Files\Empire + + Header Files\Empire + Header Files\network Header Files\network - - Header Files\network - Header Files\universe - + Header Files\universe - + Header Files\universe @@ -137,18 +140,30 @@ Header Files\universe - + Header Files\universe Header Files\server + + Header Files\server + Header Files\server + + Header Files\server + + + Header Files\server + Header Files\server + + Header Files\server + Header Files\util @@ -158,6 +173,9 @@ Header Files\util + + Header Files\util + Header Files\util @@ -197,38 +215,24 @@ Header Files\util - - Header Files\universe - Header Files\universe - - Header Files\universe - - + Header Files\universe Header Files\util - - Header Files\python\server - - - Header Files\python\server - Header Files\python Header Files\python + - - Source Files\network - Source Files\server @@ -238,6 +242,18 @@ Source Files\server + + Source Files\server + + + Source Files\server + + + Source Files\server + + + Source Files\server + Source Files\server @@ -247,15 +263,6 @@ Source Files\combat - - Source Files\universe - - - Source Files\python\server - - - Source Files\python\server - Source Files\python @@ -274,5 +281,9 @@ Source Files\util + + Source Files\python + + -
+
\ No newline at end of file diff --git a/msvc2017/FreeOrionD/StdAfx.cpp b/msvc2017/FreeOrionD/StdAfx.cpp new file mode 100644 index 00000000000..09f15b61cc9 --- /dev/null +++ b/msvc2017/FreeOrionD/StdAfx.cpp @@ -0,0 +1 @@ +#include "StdAfx.h" \ No newline at end of file diff --git a/msvc2017/FreeOrionD/StdAfx.h b/msvc2017/FreeOrionD/StdAfx.h new file mode 100644 index 00000000000..dbecbf8535b --- /dev/null +++ b/msvc2017/FreeOrionD/StdAfx.h @@ -0,0 +1,108 @@ + +#pragma once + +// We include all external headers used in any of the header files, +// plus external headers used in at least five .cpp files. + +// https://hownot2code.com/2016/08/16/stdafx-h/ + +// ---------------- +// includes from .h + +// Common +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// FreeOrionD +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +// Note: This is a workaround for Visual C++ non-conformant pre-processor +// handling of empty macro arguments. +#include +#include +#endif diff --git a/msvc2015/GiGi/GiGi.vcxproj b/msvc2017/GiGi/GiGi.vcxproj similarity index 70% rename from msvc2015/GiGi/GiGi.vcxproj rename to msvc2017/GiGi/GiGi.vcxproj index ce261ddacf8..05d155166d4 100644 --- a/msvc2015/GiGi/GiGi.vcxproj +++ b/msvc2017/GiGi/GiGi.vcxproj @@ -1,18 +1,22 @@  - + Debug Win32 - - Release-XP - Win32 + + Debug + x64 Release Win32 + + Release + x64 + @@ -32,7 +36,6 @@ - @@ -65,16 +68,25 @@ + + + + + + + + + + - @@ -85,7 +97,6 @@ - @@ -94,6 +105,7 @@ + @@ -104,40 +116,55 @@ + + + + Create + Create + Create + Create + {2CEC3AB3-36A0-4037-AEC2-DA0435A4DADB} Win32Proj GiGi + 8.1 DynamicLibrary true Unicode - v140 + v141 + + + DynamicLibrary + true + Unicode + v141 DynamicLibrary false true Unicode - v140 + v141 - + DynamicLibrary false true Unicode - v140_xp + v141 @@ -159,19 +186,48 @@ ../../ GiGi.dll + + GiGi.dll + true + false ../../ $(ProjectName) - + + $(ProjectName) false ../../ - $(ProjectName) - NotUsing + Use + Full + NDEBUG;_USRDLL;GIGI_EXPORTS;%(PreprocessorDefinitions) + ../include/GG;../../../include/;../../../include/GG;%(AdditionalIncludeDirectories) + true + ProgramDatabase + false + MultiThreadedDLL + false + false + false + false + StdAfx.h + $(IntDir)$(TargetName).pch + StdAfx.h;%(ForcedIncludeFiles) + + + Windows + true + ../../GiGi.dll + ../../../lib;%(AdditionalLibraryDirectories) + + + + + Use Full NDEBUG;_USRDLL;GIGI_EXPORTS;%(PreprocessorDefinitions) ../include/GG;../../../include/;../../../include/GG;%(AdditionalIncludeDirectories) @@ -183,10 +239,9 @@ false false false - - - - + StdAfx.h + $(IntDir)$(TargetName).pch + StdAfx.h;%(ForcedIncludeFiles) Windows @@ -197,19 +252,18 @@ - NotUsing + Use Full true true - NDEBUG;_USRDLL;_DLL;GiGi_EXPORTS;%(PreprocessorDefinitions) - ../include/;../../../include;../../GG/;%(AdditionalIncludeDirectories) + NDEBUG;_USRDLL;_DLL;GiGi_EXPORTS;FONS_USE_FREETYPE;%(PreprocessorDefinitions) + ../include/;../../../include;../../GG/;../../../include/freetype2;%(AdditionalIncludeDirectories) true - - - - + StdAfx.h + $(IntDir)$(TargetName).pch false ProgramDatabase + StdAfx.h;%(ForcedIncludeFiles) Console @@ -224,21 +278,20 @@ UseLinkTimeCodeGeneration - + - NotUsing + Use Full true true - NDEBUG;_USRDLL;_DLL;GiGi_EXPORTS;%(PreprocessorDefinitions) - ../include/;../../../include;../../GG/;%(AdditionalIncludeDirectories) + NDEBUG;_USRDLL;_DLL;GiGi_EXPORTS;FONS_USE_FREETYPE;%(PreprocessorDefinitions) + ../include/;../../../include;../../GG/;../../../include/freetype2;%(AdditionalIncludeDirectories) true - - - - + StdAfx.h + $(IntDir)$(TargetName).pch false ProgramDatabase + StdAfx.h;%(ForcedIncludeFiles) Console @@ -256,4 +309,4 @@ - + \ No newline at end of file diff --git a/msvc2015/GiGi/GiGi.vcxproj.filters b/msvc2017/GiGi/GiGi.vcxproj.filters similarity index 86% rename from msvc2015/GiGi/GiGi.vcxproj.filters rename to msvc2017/GiGi/GiGi.vcxproj.filters index 62a1be61ef7..20774b35c87 100644 --- a/msvc2015/GiGi/GiGi.vcxproj.filters +++ b/msvc2017/GiGi/GiGi.vcxproj.filters @@ -24,6 +24,15 @@ {d4c017e8-2997-4a5c-90e9-83bb0aea86be} + + {4603e095-151a-49f1-901e-8c72f0a2c8a9} + + + {4abfad8b-9f2c-4aec-b4af-9e29ad35e9e2} + + + {3883328b-1e9c-498d-824e-7ab4d0ab77f4} + @@ -104,9 +113,6 @@ Header Files - - Header Files - Header Files @@ -185,6 +191,34 @@ Header Files + + + Header Files + + + Header Files\nanovg + + + Header Files\nanovg + + + Header Files\nanovg + + + Header Files\nanovg + + + Header Files\nanovg + + + Header Files\nanovg + + + Header Files\nanosvg + + + Header Files\nanosvg + @@ -241,9 +275,6 @@ Source Files - - Source Files - Source Files @@ -262,9 +293,6 @@ Source Files - - Source Files - Source Files @@ -313,5 +341,15 @@ Source Files + + + Source Files + + + Source Files\nanovg + + + Source Files + \ No newline at end of file diff --git a/msvc2017/GiGi/StdAfx.cpp b/msvc2017/GiGi/StdAfx.cpp new file mode 100644 index 00000000000..09f15b61cc9 --- /dev/null +++ b/msvc2017/GiGi/StdAfx.cpp @@ -0,0 +1 @@ +#include "StdAfx.h" \ No newline at end of file diff --git a/msvc2017/GiGi/StdAfx.h b/msvc2017/GiGi/StdAfx.h new file mode 100644 index 00000000000..64e130dac4b --- /dev/null +++ b/msvc2017/GiGi/StdAfx.h @@ -0,0 +1,54 @@ + +#pragma once + +// We include all external headers used in any of the header files, +// plus external headers used in at least five .cpp files. + +// https://hownot2code.com/2016/08/16/stdafx-h/ + +// ---------------- +// includes from .h + +// GiGi +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +// Note: This is a workaround for Visual C++ non-conformant pre-processor +// handling of empty macro arguments. +#include +#include +#endif diff --git a/msvc2015/Parsers/Parsers.vcxproj b/msvc2017/Parsers/Parsers.vcxproj similarity index 56% rename from msvc2015/Parsers/Parsers.vcxproj rename to msvc2017/Parsers/Parsers.vcxproj index 3c75da94b79..91e34c2cd86 100644 --- a/msvc2015/Parsers/Parsers.vcxproj +++ b/msvc2017/Parsers/Parsers.vcxproj @@ -1,44 +1,55 @@ - + Debug Win32 - - Release-XP - Win32 + + Debug + x64 Release Win32 + + Release + x64 + {9925F25C-A72E-42AE-B2E3-6657255BF293} Win32Proj Parsers + 8.1 DynamicLibrary true Unicode - v140 + v141 + + + DynamicLibrary + true + Unicode + v141 StaticLibrary false true Unicode - v140 + v141 - + StaticLibrary false true Unicode - v140_xp + v141 @@ -54,20 +65,37 @@ true + + true + false ../../ - + false ../../ - - + Use + Disabled + _DEBUG;_USRDLL;PARSERS_EXPORTS;%(PreprocessorDefinitions) + StdAfx.h + StdAfx.h;%(ForcedIncludeFiles) + + + Windows + true + + + + + Use Disabled _DEBUG;_USRDLL;PARSERS_EXPORTS;%(PreprocessorDefinitions) + StdAfx.h + StdAfx.h;%(ForcedIncludeFiles) Windows @@ -76,22 +104,21 @@ - NotUsing + Use MinSpace true true NDEBUG;_USRDLL;PARSERS_EXPORTS;FREEORION_WIN32;_DLL;%(PreprocessorDefinitions) ../../../include/;../../GG/;../../;%(AdditionalIncludeDirectories) true - - - - + StdAfx.h + $(IntDir)$(TargetName).pch false Size /MP4 %(AdditionalOptions) + StdAfx.h;%(ForcedIncludeFiles) Console @@ -106,24 +133,23 @@ true - + - NotUsing + Use MinSpace true true NDEBUG;_USRDLL;PARSERS_EXPORTS;FREEORION_WIN32;_DLL;%(PreprocessorDefinitions) ../../../include/;../../GG/;../../;%(AdditionalIncludeDirectories) true - - - - + StdAfx.h + $(IntDir)$(TargetName).pch false Size - /MP2 %(AdditionalOptions) + /MP4 %(AdditionalOptions) + StdAfx.h;%(ForcedIncludeFiles) Console @@ -139,8 +165,22 @@ + - + + NotUsing + NotUsing + NotUsing + NotUsing + + + + + + + + + @@ -152,6 +192,7 @@ 4091;4099;4101;4146;4244;4251;4258;4267;4275;4311;4312;4351;4396;4503;4800;4996;4018 + 4091;4099;4101;4146;4244;4251;4258;4267;4275;4311;4312;4351;4396;4503;4800;4996;4018 @@ -173,40 +214,87 @@ 4091;4099;4101;4146;4244;4251;4258;4267;4275;4311;4312;4351;4396;4503;4800;4996;4018 + 4091;4099;4101;4146;4244;4251;4258;4267;4275;4311;4312;4351;4396;4503;4800;4996;4018 - - + + NotUsing + NotUsing + NotUsing + NotUsing + + + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + + + + + + + + + - - + + + + Create + Create + Create + Create + - + + + + + + + + + + + + + + + - + - + \ No newline at end of file diff --git a/msvc2015/Parsers/Parsers.vcxproj.filters b/msvc2017/Parsers/Parsers.vcxproj.filters similarity index 79% rename from msvc2015/Parsers/Parsers.vcxproj.filters rename to msvc2017/Parsers/Parsers.vcxproj.filters index a6aed5089ad..0a8535bde80 100644 --- a/msvc2015/Parsers/Parsers.vcxproj.filters +++ b/msvc2017/Parsers/Parsers.vcxproj.filters @@ -122,10 +122,10 @@ Source Files\parse - + Source Files\parse - + Source Files\parse @@ -158,12 +158,34 @@ Source Files\parse + + Source Files\parse + + Header Files\parse - + + Header Files\parse + + + Header Files\parse + + + Header Files\parse + + + Header Files\parse + + + Header Files\parse + + + Header Files\parse + + Header Files\parse @@ -172,12 +194,30 @@ Header Files\parse + + Header Files\parse + + + Header Files\parse + + + Header Files\parse + + + Header Files\parse + + + Header Files\parse + Header Files\parse Header Files\parse + + Header Files\parse + Header Files\parse @@ -190,14 +230,18 @@ Header Files\parse + + Header Files\parse + Header Files\parse Header Files\parse - + Header Files\parse + - + \ No newline at end of file diff --git a/msvc2017/Parsers/StdAfx.cpp b/msvc2017/Parsers/StdAfx.cpp new file mode 100644 index 00000000000..09f15b61cc9 --- /dev/null +++ b/msvc2017/Parsers/StdAfx.cpp @@ -0,0 +1 @@ +#include "StdAfx.h" \ No newline at end of file diff --git a/msvc2017/Parsers/StdAfx.h b/msvc2017/Parsers/StdAfx.h new file mode 100644 index 00000000000..647176f7e43 --- /dev/null +++ b/msvc2017/Parsers/StdAfx.h @@ -0,0 +1,51 @@ + +#pragma once + +// We include all external headers used in any of the header files, +// plus boost headers used in any .cpp files. + +// https://hownot2code.com/2016/08/16/stdafx-h/ + +// ---------------- +// includes from .h or .cpp + +// Parsers +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef _MSC_VER +// Note: This is a workaround for Visual C++ non-conformant pre-processor +// handling of empty macro arguments. +#include +#include +#endif diff --git a/msvc2015/boost.dependency.props b/msvc2017/boost.dependency.props similarity index 82% rename from msvc2015/boost.dependency.props rename to msvc2017/boost.dependency.props index 21b39f9ead4..4ff2a2a430e 100644 --- a/msvc2015/boost.dependency.props +++ b/msvc2017/boost.dependency.props @@ -7,7 +7,7 @@ - _WINSOCK_DEPRECATED_NO_WARNINGS;BOOST_ALL_DYN_LINK;BOOST_ZLIB_BINARY=zlib.lib;%(PreprocessorDefinitions) + _WINSOCK_DEPRECATED_NO_WARNINGS;BOOST_ALL_DYN_LINK;BOOST_AUTO_LINK_NOMANGLE;BOOST_ZLIB_BINARY=zlib.lib;BOOST_GIL_IO_ENABLE_GRAY_ALPHA;%(PreprocessorDefinitions) ../../../include/;%(AdditionalIncludeDirectories) diff --git a/msvc2015/cpp.lang.props b/msvc2017/cpp.lang.props similarity index 91% rename from msvc2015/cpp.lang.props rename to msvc2017/cpp.lang.props index e28685d1396..3e233e54165 100644 --- a/msvc2015/cpp.lang.props +++ b/msvc2017/cpp.lang.props @@ -11,6 +11,7 @@ false false true + c++14 diff --git a/msvc2015/freetype.dependency.props b/msvc2017/freetype.dependency.props similarity index 71% rename from msvc2015/freetype.dependency.props rename to msvc2017/freetype.dependency.props index 6eb89e3bdfd..fef36ee0ae4 100644 --- a/msvc2015/freetype.dependency.props +++ b/msvc2017/freetype.dependency.props @@ -7,11 +7,11 @@ - ../../../include/freetype/;%(AdditionalIncludeDirectories) + ../../../include/freetype2/;%(AdditionalIncludeDirectories) ../../../lib/;%(AdditionalLibraryDirectories) - freetype255MT.lib;%(AdditionalDependencies) + freetype.lib;%(AdditionalDependencies) diff --git a/msvc2015/glew.dependency.props b/msvc2017/glew.dependency.props similarity index 100% rename from msvc2015/glew.dependency.props rename to msvc2017/glew.dependency.props diff --git a/msvc2015/libpng.dependency.props b/msvc2017/libpng.dependency.props similarity index 100% rename from msvc2015/libpng.dependency.props rename to msvc2017/libpng.dependency.props diff --git a/msvc2015/openal.dependency.props b/msvc2017/openal.dependency.props similarity index 100% rename from msvc2015/openal.dependency.props rename to msvc2017/openal.dependency.props diff --git a/msvc2015/opengl.dependency.props b/msvc2017/opengl.dependency.props similarity index 80% rename from msvc2015/opengl.dependency.props rename to msvc2017/opengl.dependency.props index fbbe04aff2d..51ba42523f1 100644 --- a/msvc2015/opengl.dependency.props +++ b/msvc2017/opengl.dependency.props @@ -7,7 +7,7 @@ - opengl32.lib;glu32.lib;%(AdditionalDependencies) + opengl32.lib;%(AdditionalDependencies) diff --git a/msvc2015/sdl.dependency.props b/msvc2017/sdl.dependency.props similarity index 100% rename from msvc2015/sdl.dependency.props rename to msvc2017/sdl.dependency.props diff --git a/msvc2017/update_stdafx.py b/msvc2017/update_stdafx.py new file mode 100644 index 00000000000..7b9d7676b44 --- /dev/null +++ b/msvc2017/update_stdafx.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python2 + +import sys +import os +import re + +MIN_CPP_INCLUDE_COUNT = 5 +EXCLUDE_LIBS = ["GG"] +IGNORE_INCLUDES = ["StdAfx.h", "stdafx.h"] + +PREAMBLE = """ +#pragma once + +// We include all external headers used in any of the header files, +// plus external headers used in at least five .cpp files. + +// https://hownot2code.com/2016/08/16/stdafx-h/ + +""" + +POSTAMBLE = """ + +#ifdef _MSC_VER +// Note: This is a workaround for Visual C++ non-conformant pre-processor +// handling of empty macro arguments. +#include +#include +#endif +""" + + +def find_files_from_vcxproj(vcxproj, includetype, fileextension): + pattern = "^\s*<" + includetype + "\s+Include=\"(?P[^\"]+" + fileextension + ")\"\s*/>" + vcxproj_pattern = re.compile(pattern) + base_path = os.path.dirname(vcxproj) + + with open(vcxproj) as f: + for i, line in enumerate(f): + match = vcxproj_pattern.match(line) + if match and match.group('include') not in IGNORE_INCLUDES: + yield os.path.join(base_path, match.group('include')) + +include_pattern = re.compile("^#include[ \t]+<(?P(?:(?P[^/>]+)/)?[^>]+)>") +ifdef_pattern = re.compile("^\s*#if(?:def)?\s") +endif_pattern = re.compile("^\s*#endif") + +def find_includes_from_cpp_or_h(filename): + if_nesting_level = 0 + with open(filename) as f: + for i, line in enumerate(f): + if ifdef_pattern.match(line): + if_nesting_level += 1 + elif endif_pattern.match(line): + if_nesting_level -= 1 + elif if_nesting_level == 0: + match = include_pattern.match(line) + if match and match.group('include') not in IGNORE_INCLUDES: + yield (match.group('include'), match.group('lib')) + +def assemble_precompile_header_includes(headeronly_vcxproj_files, vcxproj_files): + headers_includes = { } + cpp_includes = { } + + index = -1 + for vcxproj in (headeronly_vcxproj_files + vcxproj_files): + index = index + 1 + for include_file in find_files_from_vcxproj(vcxproj, "ClInclude", ".h"): + for include, lib in find_includes_from_cpp_or_h(include_file): + if include in headers_includes: + continue + headers_includes[include] = (index, vcxproj, lib) + + index = -1 + for vcxproj in vcxproj_files: + index = index + 1 + for include_file in find_files_from_vcxproj(vcxproj, "ClCompile", ".cpp"): + for include, lib in find_includes_from_cpp_or_h(include_file): + if not include in cpp_includes: + cpp_includes[include] = [index, vcxproj, lib, 1] + else: + cpp_includes[include][3] = cpp_includes[include][3] + 1 + + headers = [('includes from .h', + headers_includes[include][0], + headers_includes[include][1], + headers_includes[include][2], + include, + 0) for include in headers_includes] + headers.sort() + + cpps = [('includes from .cpp', + cpp_includes[include][0], + cpp_includes[include][1], + cpp_includes[include][2], + include, + cpp_includes[include][3] + ) for include in cpp_includes + if not include in headers_includes + and cpp_includes[include][3] >= MIN_CPP_INCLUDE_COUNT + ] + cpps.sort() + + return headers + cpps + +def update_stdafx_h(stdafx_h_filename, header_only_vcxproj_files, vcxproj_files): + last_type = "" + last_proj = "" + last_lib = None + + lines = [] + + for type, index, proj, lib, include, count in assemble_precompile_header_includes(header_only_vcxproj_files, vcxproj_files): + + if lib in EXCLUDE_LIBS: + continue + if type != last_type: + if last_type: + lines.append("") + lines.append("// " + '-' * len(type)) + lines.append("// " + type) + last_type = type + if last_proj != proj: + lines.append("") + lines.append("// " + os.path.splitext(os.path.basename(proj))[0]) + last_proj = proj + if last_lib != lib: + if lib: + lines.append("") + last_lib = lib + + include_directive = "#include <" + include + ">" + #if count > 0: + # include_directive += ' ' * max(50 - len(include_directive), 0) + # include_directive += " // included in " + str(count) + " .cpp files" + + lines.append(include_directive) + + with open(stdafx_h_filename, 'w') as f: + f.write(PREAMBLE) + f.write("\n".join(lines)) + f.write(POSTAMBLE) + +update_stdafx_h("Parsers/StdAfx.h", [], ["Parsers/Parsers.vcxproj"]) +update_stdafx_h("Common/StdAfx.h", [], ["Common/Common.vcxproj"]) + +update_stdafx_h("FreeOrion/StdAfx.h", ["Common/Common.vcxproj", "GiGi/GiGi.vcxproj"], + ["FreeOrion/FreeOrion.vcxproj"]) +update_stdafx_h("FreeOrionD/StdAfx.h", ["Common/Common.vcxproj"], ["FreeOrionD/FreeOrionD.vcxproj"]) +update_stdafx_h("FreeOrionCA/StdAfx.h",["Common/Common.vcxproj"], ["FreeOrionCA/FreeOrionCA.vcxproj"]) + +update_stdafx_h("GiGi/StdAfx.h", [], ["GiGi/GiGi.vcxproj"]) diff --git a/msvc2015/vorbis.dependency.props b/msvc2017/vorbis.dependency.props similarity index 100% rename from msvc2015/vorbis.dependency.props rename to msvc2017/vorbis.dependency.props diff --git a/msvc2015/warnings.props b/msvc2017/warnings.props similarity index 100% rename from msvc2015/warnings.props rename to msvc2017/warnings.props diff --git a/msvc2017/win32.dependency.props b/msvc2017/win32.dependency.props new file mode 100644 index 00000000000..3e9242df8b5 --- /dev/null +++ b/msvc2017/win32.dependency.props @@ -0,0 +1,33 @@ + + + + + + <_PropertySheetDisplayName>Win32 dependency + + + + WIN32;_WINDOWS;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + + + User32.lib;bcrypt.lib;%(AdditionalDependencies) + + + + + WIN32;_WINDOWS;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + + + User32.lib;bcrypt.lib;%(AdditionalDependencies) + + + + + WIN32;_WINDOWS;_WIN32_WINNT=0x0501;%(PreprocessorDefinitions) + + + User32.lib;bcrypt.lib;%(AdditionalDependencies) + + + + diff --git a/msvc2015/zlib.dependency.props b/msvc2017/zlib.dependency.props similarity index 100% rename from msvc2015/zlib.dependency.props rename to msvc2017/zlib.dependency.props diff --git a/network/CMakeLists.txt b/network/CMakeLists.txt index e97cd0a8747..53964f3ff9a 100644 --- a/network/CMakeLists.txt +++ b/network/CMakeLists.txt @@ -8,26 +8,3 @@ target_sources(freeorioncommon ${CMAKE_CURRENT_LIST_DIR}/MessageQueue.cpp ${CMAKE_CURRENT_LIST_DIR}/Networking.cpp ) - -target_sources(freeoriond - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/ServerNetworking.h - PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/ServerNetworking.cpp -) - -target_sources(freeorionca - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/ClientNetworking.h - PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/ClientNetworking.cpp -) - -if(NOT BUILD_HEADLESS) - target_sources(freeorion - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/ClientNetworking.h - PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/ClientNetworking.cpp - ) -endif() diff --git a/network/Message.cpp b/network/Message.cpp index 1361a9591f2..00d426c6b7b 100644 --- a/network/Message.cpp +++ b/network/Message.cpp @@ -5,18 +5,12 @@ #include "../Empire/Supply.h" #include "../Empire/Diplomacy.h" #include "../util/Logger.h" -#include "../util/MultiplayerCommon.h" #include "../util/ModeratorAction.h" #include "../util/SaveGamePreviewUtils.h" -#include "../universe/Meter.h" -#include "../universe/System.h" -#include "../universe/Universe.h" #include "../universe/Species.h" -#include "../universe/Building.h" -#include "../universe/Field.h" -#include "../universe/Encyclopedia.h" -#include "../universe/Tech.h" +#include "../universe/Universe.h" #include "../util/OptionsDB.h" +#include "../util/OrderSet.h" #include "../util/Serialize.h" #include "../util/ScopedTimer.h" #include "../util/i18n.h" @@ -29,7 +23,9 @@ #include #include #include -#include +#include +#include +#include #include @@ -61,15 +57,13 @@ std::ostream& operator<<(std::ostream& os, const Message& msg) { //////////////////////////////////////////////// Message::Message() : m_type(UNDEFINED), - m_synchronous_response(false), m_message_size(0), m_message_text() {} Message::Message(MessageType type, - const std::string& text, bool synchronous_response/* = false*/) : + const std::string& text) : m_type(type), - m_synchronous_response(synchronous_response), m_message_size(text.size()), m_message_text(new char[text.size()]) { std::copy(text.begin(), text.end(), m_message_text.get()); } @@ -77,9 +71,6 @@ Message::Message(MessageType type, Message::MessageType Message::Type() const { return m_type; } -bool Message::SynchronousResponse() const -{ return m_synchronous_response; } - std::size_t Message::Size() const { return m_message_size; } @@ -99,7 +90,6 @@ char* Message::Data() void Message::Swap(Message& rhs) { std::swap(m_type, rhs.m_type); - std::swap(m_synchronous_response, rhs.m_synchronous_response); std::swap(m_message_size, rhs.m_message_size); std::swap(m_message_text, rhs.m_message_text); } @@ -118,13 +108,11 @@ void swap(Message& lhs, Message& rhs) void BufferToHeader(const Message::HeaderBuffer& buffer, Message& message) { message.m_type = static_cast(buffer[Message::Parts::TYPE]); - message.m_synchronous_response = (buffer[Message::Parts::RESPONSE] != 0); message.m_message_size = buffer[Message::Parts::SIZE]; } void HeaderToBuffer(const Message& message, Message::HeaderBuffer& buffer) { buffer[Message::Parts::TYPE] = message.Type(); - buffer[Message::Parts::RESPONSE] = message.SynchronousResponse(); buffer[Message::Parts::SIZE] = message.Size(); } @@ -166,14 +154,17 @@ Message HostMPGameMessage(const std::string& host_player_name) return Message(Message::HOST_MP_GAME, os.str()); } -Message JoinGameMessage(const std::string& player_name, Networking::ClientType client_type) { +Message JoinGameMessage(const std::string& player_name, + Networking::ClientType client_type, + boost::uuids::uuid cookie) { std::ostringstream os; { freeorion_xml_oarchive oa(os); std::string client_version_string = FreeOrionVersionString(); oa << BOOST_SERIALIZATION_NVP(player_name) << BOOST_SERIALIZATION_NVP(client_type) - << BOOST_SERIALIZATION_NVP(client_version_string); + << BOOST_SERIALIZATION_NVP(client_version_string) + << BOOST_SERIALIZATION_NVP(cookie); } return Message(Message::JOIN_GAME, os.str()); } @@ -188,7 +179,7 @@ Message GameStartMessage(bool single_player_game, int empire_id, const Universe& universe, const SpeciesManager& species, CombatLogManager& combat_logs, const SupplyManager& supply, const std::map& players, - const GalaxySetupData& galaxy_setup_data, + GalaxySetupData galaxy_setup_data, bool use_binary_serialization) { std::ostringstream os; @@ -207,6 +198,7 @@ Message GameStartMessage(bool single_player_game, int empire_id, bool loaded_game_data = false; oa << BOOST_SERIALIZATION_NVP(players) << BOOST_SERIALIZATION_NVP(loaded_game_data); + galaxy_setup_data.m_encoding_empire = empire_id; oa << BOOST_SERIALIZATION_NVP(galaxy_setup_data); } else { freeorion_xml_oarchive oa(os); @@ -222,6 +214,7 @@ Message GameStartMessage(bool single_player_game, int empire_id, bool loaded_game_data = false; oa << BOOST_SERIALIZATION_NVP(players) << BOOST_SERIALIZATION_NVP(loaded_game_data); + galaxy_setup_data.m_encoding_empire = empire_id; oa << BOOST_SERIALIZATION_NVP(galaxy_setup_data); } } @@ -234,7 +227,7 @@ Message GameStartMessage(bool single_player_game, int empire_id, CombatLogManager& combat_logs, const SupplyManager& supply, const std::map& players, const OrderSet& orders, const SaveGameUIData* ui_data, - const GalaxySetupData& galaxy_setup_data, + GalaxySetupData galaxy_setup_data, bool use_binary_serialization) { std::ostringstream os; @@ -260,6 +253,7 @@ Message GameStartMessage(bool single_player_game, int empire_id, oa << boost::serialization::make_nvp("ui_data", *ui_data); bool save_state_string_available = false; oa << BOOST_SERIALIZATION_NVP(save_state_string_available); + galaxy_setup_data.m_encoding_empire = empire_id; oa << BOOST_SERIALIZATION_NVP(galaxy_setup_data); } else { freeorion_xml_oarchive oa(os); @@ -282,6 +276,7 @@ Message GameStartMessage(bool single_player_game, int empire_id, oa << boost::serialization::make_nvp("ui_data", *ui_data); bool save_state_string_available = false; oa << BOOST_SERIALIZATION_NVP(save_state_string_available); + galaxy_setup_data.m_encoding_empire = empire_id; oa << BOOST_SERIALIZATION_NVP(galaxy_setup_data); } } @@ -294,7 +289,7 @@ Message GameStartMessage(bool single_player_game, int empire_id, CombatLogManager& combat_logs, const SupplyManager& supply, const std::map& players, const OrderSet& orders, const std::string* save_state_string, - const GalaxySetupData& galaxy_setup_data, + GalaxySetupData galaxy_setup_data, bool use_binary_serialization) { std::ostringstream os; @@ -320,6 +315,7 @@ Message GameStartMessage(bool single_player_game, int empire_id, oa << BOOST_SERIALIZATION_NVP(save_state_string_available); if (save_state_string_available) oa << boost::serialization::make_nvp("save_state_string", *save_state_string); + galaxy_setup_data.m_encoding_empire = empire_id; oa << BOOST_SERIALIZATION_NVP(galaxy_setup_data); } else { freeorion_xml_oarchive oa(os); @@ -342,6 +338,7 @@ Message GameStartMessage(bool single_player_game, int empire_id, oa << BOOST_SERIALIZATION_NVP(save_state_string_available); if (save_state_string_available) oa << boost::serialization::make_nvp("save_state_string", *save_state_string); + galaxy_setup_data.m_encoding_empire = empire_id; oa << BOOST_SERIALIZATION_NVP(galaxy_setup_data); } } @@ -354,18 +351,58 @@ Message HostSPAckMessage(int player_id) Message HostMPAckMessage(int player_id) { return Message(Message::HOST_MP_GAME, std::to_string(player_id)); } -Message JoinAckMessage(int player_id) -{ return Message(Message::JOIN_GAME, std::to_string(player_id)); } +Message JoinAckMessage(int player_id, boost::uuids::uuid cookie) +{ + std::ostringstream os; + { + freeorion_xml_oarchive oa(os); + oa << BOOST_SERIALIZATION_NVP(player_id) + << BOOST_SERIALIZATION_NVP(cookie); + } + return Message(Message::JOIN_GAME, os.str()); +} + +Message TurnOrdersMessage(const OrderSet& orders, const SaveGameUIData& ui_data) { + std::ostringstream os; + { + freeorion_xml_oarchive oa(os); + Serialize(oa, orders); + bool ui_data_available = true; + bool save_state_string_available = false; + oa << BOOST_SERIALIZATION_NVP(ui_data_available) + << BOOST_SERIALIZATION_NVP(ui_data) + << BOOST_SERIALIZATION_NVP(save_state_string_available); + } + return Message(Message::TURN_ORDERS, os.str()); +} -Message TurnOrdersMessage(const OrderSet& orders) { +Message TurnOrdersMessage(const OrderSet& orders, const std::string& save_state_string) { std::ostringstream os; { freeorion_xml_oarchive oa(os); Serialize(oa, orders); + bool ui_data_available = false; + bool save_state_string_available = true; + oa << BOOST_SERIALIZATION_NVP(ui_data_available) + << BOOST_SERIALIZATION_NVP(save_state_string_available) + << BOOST_SERIALIZATION_NVP(save_state_string); } return Message(Message::TURN_ORDERS, os.str()); } +Message TurnPartialOrdersMessage(const std::pair>& orders_updates) { + std::ostringstream os; + { + freeorion_xml_oarchive oa(os); + Serialize(oa, orders_updates.first); + oa << boost::serialization::make_nvp("deleted", orders_updates.second); + } + return Message(Message::TURN_PARTIAL_ORDERS, os.str()); +} + +Message TurnTimeoutMessage(int timeout_remaining) +{ return Message(Message::TURN_TIMEOUT, std::to_string(timeout_remaining)); } + Message TurnProgressMessage(Message::TurnProgressPhase phase_id) { std::ostringstream os; { @@ -375,12 +412,14 @@ Message TurnProgressMessage(Message::TurnProgressPhase phase_id) { return Message(Message::TURN_PROGRESS, os.str()); } -Message PlayerStatusMessage(int about_player_id, Message::PlayerStatus player_status) { +Message PlayerStatusMessage(Message::PlayerStatus player_status, + int about_empire_id) +{ std::ostringstream os; { freeorion_xml_oarchive oa(os); - oa << BOOST_SERIALIZATION_NVP(about_player_id) - << BOOST_SERIALIZATION_NVP(player_status); + oa << BOOST_SERIALIZATION_NVP(player_status) + << BOOST_SERIALIZATION_NVP(about_empire_id); } return Message(Message::PLAYER_STATUS, os.str()); } @@ -436,54 +475,9 @@ Message TurnPartialUpdateMessage(int empire_id, const Universe& universe, return Message(Message::TURN_PARTIAL_UPDATE, os.str()); } -Message ClientSaveDataMessage(const OrderSet& orders, const SaveGameUIData& ui_data) { - std::ostringstream os; - { - freeorion_xml_oarchive oa(os); - Serialize(oa, orders); - bool ui_data_available = true; - bool save_state_string_available = false; - oa << BOOST_SERIALIZATION_NVP(ui_data_available) - << BOOST_SERIALIZATION_NVP(ui_data) - << BOOST_SERIALIZATION_NVP(save_state_string_available); - } - return Message(Message::CLIENT_SAVE_DATA, os.str()); -} - -Message ClientSaveDataMessage(const OrderSet& orders, const std::string& save_state_string) { - std::ostringstream os; - { - freeorion_xml_oarchive oa(os); - Serialize(oa, orders); - bool ui_data_available = false; - bool save_state_string_available = true; - oa << BOOST_SERIALIZATION_NVP(ui_data_available) - << BOOST_SERIALIZATION_NVP(save_state_string_available) - << BOOST_SERIALIZATION_NVP(save_state_string); -} - return Message(Message::CLIENT_SAVE_DATA, os.str()); -} - -Message ClientSaveDataMessage(const OrderSet& orders) { - std::ostringstream os; - { - freeorion_xml_oarchive oa(os); - Serialize(oa, orders); - bool ui_data_available = false; - bool save_state_string_available = false; - oa << BOOST_SERIALIZATION_NVP(ui_data_available) - << BOOST_SERIALIZATION_NVP(save_state_string_available); - } - return Message(Message::CLIENT_SAVE_DATA, os.str()); -} - Message HostSaveGameInitiateMessage(const std::string& filename) { return Message(Message::SAVE_GAME_INITIATE, filename); } -Message ServerSaveGameDataRequestMessage(bool synchronous_response) { - return Message(Message::SAVE_GAME_DATA_REQUEST, DUMMY_EMPTY_MESSAGE, synchronous_response); -} - Message ServerSaveGameCompleteMessage(const std::string& save_filename, int bytes_written) { std::ostringstream os; { @@ -543,8 +537,8 @@ Message ShutdownServerMessage() { return Message(Message::SHUT_DOWN_SERVER, DUMMY_EMPTY_MESSAGE); } /** requests previews of savefiles from server synchronously */ -Message RequestSavePreviewsMessage(std::string directory) -{ return Message(Message::REQUEST_SAVE_PREVIEWS, directory); } +Message RequestSavePreviewsMessage(std::string relative_directory) +{ return Message(Message::REQUEST_SAVE_PREVIEWS, relative_directory); } /** returns the savegame previews to the client */ Message DispatchSavePreviewsMessage(const PreviewInformation& previews) { @@ -553,37 +547,55 @@ Message DispatchSavePreviewsMessage(const PreviewInformation& previews) { freeorion_xml_oarchive oa(os); oa << BOOST_SERIALIZATION_NVP(previews); } - return Message(Message::DISPATCH_SAVE_PREVIEWS, os.str(), true); + return Message(Message::DISPATCH_SAVE_PREVIEWS, os.str()); } Message RequestCombatLogsMessage(const std::vector& ids) { std::ostringstream os; - freeorion_xml_oarchive oa(os); - oa << BOOST_SERIALIZATION_NVP(ids); + { + freeorion_xml_oarchive oa(os); + oa << BOOST_SERIALIZATION_NVP(ids); + } return Message(Message::REQUEST_COMBAT_LOGS, os.str()); } -Message DispatchCombatLogsMessage(const std::vector>& logs) { +Message DispatchCombatLogsMessage(const std::vector>& logs, + bool use_binary_serialization) +{ std::ostringstream os; - freeorion_xml_oarchive oa(os); - oa << BOOST_SERIALIZATION_NVP(logs); - return Message(Message::DISPATCH_COMBAT_LOGS, os.str(), true); + { + try { + if (use_binary_serialization) { + freeorion_bin_oarchive oa(os); + oa << BOOST_SERIALIZATION_NVP(logs); + } else { + freeorion_xml_oarchive oa(os); + oa << BOOST_SERIALIZATION_NVP(logs); + } + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception serializing combat logs: " << e.what(); + } + } + + return Message(Message::DISPATCH_COMBAT_LOGS, os.str()); } Message LoggerConfigMessage(int sender, const std::set>& options) { std::ostringstream os; - freeorion_xml_oarchive oa(os); - std::size_t size = options.size(); - oa << BOOST_SERIALIZATION_NVP(size); - for (const auto& option_tuple : options) { - auto option = std::get<0>(option_tuple); - auto name = std::get<1>(option_tuple); - auto value = std::get<2>(option_tuple); - oa << BOOST_SERIALIZATION_NVP(option); - oa << BOOST_SERIALIZATION_NVP(name); - oa << BOOST_SERIALIZATION_NVP(value); + { + freeorion_xml_oarchive oa(os); + std::size_t size = options.size(); + oa << BOOST_SERIALIZATION_NVP(size); + for (const auto& option_tuple : options) { + auto option = std::get<0>(option_tuple); + auto name = std::get<1>(option_tuple); + auto value = std::get<2>(option_tuple); + oa << BOOST_SERIALIZATION_NVP(option); + oa << BOOST_SERIALIZATION_NVP(name); + oa << BOOST_SERIALIZATION_NVP(value); + } } - return Message(Message::LOGGER_CONFIG, os.str(), true); + return Message(Message::LOGGER_CONFIG, os.str()); } //////////////////////////////////////////////// @@ -607,22 +619,40 @@ Message ServerLobbyUpdateMessage(const MultiplayerLobbyData& lobby_data) { return Message(Message::LOBBY_UPDATE, os.str()); } -Message PlayerChatMessage(const std::string& data, int receiver) { +Message ChatHistoryMessage(const std::vector>& chat_history) { std::ostringstream os; { freeorion_xml_oarchive oa(os); - oa << BOOST_SERIALIZATION_NVP(receiver) - << BOOST_SERIALIZATION_NVP(data); + std::size_t size = chat_history.size(); + oa << BOOST_SERIALIZATION_NVP(size); + for (const auto& elem : chat_history) { + oa << boost::serialization::make_nvp(BOOST_PP_STRINGIZE(elem), elem.get()); + } + } + return Message(Message::CHAT_HISTORY, os.str()); +} + +Message PlayerChatMessage(const std::string& data, std::set recipients, bool pm) { + std::ostringstream os; + { + freeorion_xml_oarchive oa(os); + oa << BOOST_SERIALIZATION_NVP(recipients) + << BOOST_SERIALIZATION_NVP(data) + << BOOST_SERIALIZATION_NVP(pm); } return Message(Message::PLAYER_CHAT, os.str()); } -Message ServerPlayerChatMessage(int sender, const std::string& data) { +Message ServerPlayerChatMessage(int sender, const boost::posix_time::ptime& timestamp, + const std::string& data, bool pm) +{ std::ostringstream os; { freeorion_xml_oarchive oa(os); oa << BOOST_SERIALIZATION_NVP(sender) - << BOOST_SERIALIZATION_NVP(data); + << BOOST_SERIALIZATION_NVP(timestamp) + << BOOST_SERIALIZATION_NVP(data) + << BOOST_SERIALIZATION_NVP(pm); } return Message(Message::PLAYER_CHAT, os.str()); } @@ -631,17 +661,7 @@ Message StartMPGameMessage() { return Message(Message::START_MP_GAME, DUMMY_EMPTY_MESSAGE); } Message ContentCheckSumMessage() { - std::map checksums; - - // add entries for various content managers... - checksums["BuildingTypeManager"] = GetBuildingTypeManager().GetCheckSum(); - checksums["Encyclopedia"] = GetEncyclopedia().GetCheckSum(); - checksums["FieldTypeManager"] = GetFieldTypeManager().GetCheckSum(); - checksums["HullTypeManager"] = GetHullTypeManager().GetCheckSum(); - checksums["PartTypeManager"] = GetPartTypeManager().GetCheckSum(); - checksums["PredefinedShipDesignManager"] = GetPredefinedShipDesignManager().GetCheckSum(); - checksums["SpeciesManager"] = GetSpeciesManager().GetCheckSum(); - checksums["TechManager"] = GetTechManager().GetCheckSum(); + auto checksums = CheckSumContent(); std::ostringstream os; { @@ -651,6 +671,44 @@ Message ContentCheckSumMessage() { return Message(Message::CHECKSUM, os.str()); } +Message AuthRequestMessage(const std::string& player_name, const std::string& auth) { + std::ostringstream os; + { + freeorion_xml_oarchive oa(os); + oa << BOOST_SERIALIZATION_NVP(player_name) + << BOOST_SERIALIZATION_NVP(auth); + } + return Message(Message::AUTH_REQUEST, os.str()); +} + +Message AuthResponseMessage(const std::string& player_name, const std::string& auth) { + std::ostringstream os; + { + freeorion_xml_oarchive oa(os); + oa << BOOST_SERIALIZATION_NVP(player_name) + << BOOST_SERIALIZATION_NVP(auth); + } + return Message(Message::AUTH_RESPONSE, os.str()); +} + +Message SetAuthorizationRolesMessage(const Networking::AuthRoles& roles) +{ return Message(Message::SET_AUTH_ROLES, roles.Text()); } + +Message EliminateSelfMessage() +{ return Message(Message::ELIMINATE_SELF, DUMMY_EMPTY_MESSAGE); } + +Message UnreadyMessage() +{ return Message(Message::UNREADY, DUMMY_EMPTY_MESSAGE); } + +Message PlayerInfoMessage(const std::map& players) { + std::ostringstream os; + { + freeorion_xml_oarchive oa(os); + oa << BOOST_SERIALIZATION_NVP(players); + } + return Message(Message::PLAYER_INFO, os.str()); +} + //////////////////////////////////////////////// // Message data extractors //////////////////////////////////////////////// @@ -699,12 +757,34 @@ void ExtractLobbyUpdateMessageData(const Message& msg, MultiplayerLobbyData& lob } } -void ExtractPlayerChatMessageData(const Message& msg, int& receiver, std::string& data) { +void ExtractChatHistoryMessage(const Message& msg, std::vector& chat_history) { try { std::istringstream is(msg.Text()); freeorion_xml_iarchive ia(is); - ia >> BOOST_SERIALIZATION_NVP(receiver) - >> BOOST_SERIALIZATION_NVP(data); + std::size_t size; + ia >> BOOST_SERIALIZATION_NVP(size); + chat_history.clear(); + chat_history.reserve(size); + for (size_t ii = 0; ii < size; ++ii) { + ChatHistoryEntity elem; + ia >> BOOST_SERIALIZATION_NVP(elem); + chat_history.push_back(elem); + } + } catch (const std::exception& err) { + ErrorLogger() << "ExtractChatHistoryMessage(const Message& msg, std::vector& chat_history) failed! Message:]n" + << msg.Text() << "\n" + << "Error: " << err.what(); + throw err; + } +} + +void ExtractPlayerChatMessageData(const Message& msg, std::set& recipients, std::string& data, bool& pm) { + try { + std::istringstream is(msg.Text()); + freeorion_xml_iarchive ia(is); + ia >> BOOST_SERIALIZATION_NVP(recipients) + >> BOOST_SERIALIZATION_NVP(data) + >> BOOST_SERIALIZATION_NVP(pm); } catch (const std::exception& err) { ErrorLogger() << "ExtractPlayerChatMessageData(const Message& msg, int& receiver, std::string& data) failed! Message:\n" @@ -714,12 +794,17 @@ void ExtractPlayerChatMessageData(const Message& msg, int& receiver, std::string } } -void ExtractServerPlayerChatMessageData(const Message& msg, int& sender, std::string& data) { +void ExtractServerPlayerChatMessageData(const Message& msg, + int& sender, boost::posix_time::ptime& timestamp, + std::string& data, bool& pm) +{ try { std::istringstream is(msg.Text()); freeorion_xml_iarchive ia(is); ia >> BOOST_SERIALIZATION_NVP(sender) - >> BOOST_SERIALIZATION_NVP(data); + >> BOOST_SERIALIZATION_NVP(timestamp) + >> BOOST_SERIALIZATION_NVP(data) + >> BOOST_SERIALIZATION_NVP(pm); } catch (const std::exception& err) { ErrorLogger() << "ExtractServerPlayerChatMessageData(const Message& msg, int& sender, std::string& data) failed! Message:\n" << msg.Text() << "\n" @@ -729,53 +814,61 @@ void ExtractServerPlayerChatMessageData(const Message& msg, int& sender, std::st } void ExtractGameStartMessageData(const Message& msg, bool& single_player_game, int& empire_id, int& current_turn, - EmpireManager& empires, Universe& universe, SpeciesManager& species, CombatLogManager& combat_logs, - SupplyManager& supply, - std::map& players, OrderSet& orders, bool& loaded_game_data, bool& ui_data_available, - SaveGameUIData& ui_data, bool& save_state_string_available, std::string& save_state_string, - GalaxySetupData& galaxy_setup_data) + EmpireManager& empires, Universe& universe, SpeciesManager& species, + CombatLogManager& combat_logs, SupplyManager& supply, + std::map& players, OrderSet& orders, bool& loaded_game_data, + bool& ui_data_available, SaveGameUIData& ui_data, bool& save_state_string_available, + std::string& save_state_string, GalaxySetupData& galaxy_setup_data) { try { - try { - // first attempt binary deserialziation - std::istringstream is(msg.Text()); - - freeorion_bin_iarchive ia(is); - ia >> BOOST_SERIALIZATION_NVP(single_player_game) - >> BOOST_SERIALIZATION_NVP(empire_id) - >> BOOST_SERIALIZATION_NVP(current_turn); - GetUniverse().EncodingEmpire() = empire_id; - - boost::timer deserialize_timer; - ia >> BOOST_SERIALIZATION_NVP(empires); - DebugLogger() << "ExtractGameStartMessage empire deserialization time " << (deserialize_timer.elapsed() * 1000.0); - - ia >> BOOST_SERIALIZATION_NVP(species); - combat_logs.SerializeIncompleteLogs(ia, 1); - ia >> BOOST_SERIALIZATION_NVP(supply); - - deserialize_timer.restart(); - Deserialize(ia, universe); - DebugLogger() << "ExtractGameStartMessage universe deserialization time " << (deserialize_timer.elapsed() * 1000.0); - - - ia >> BOOST_SERIALIZATION_NVP(players) - >> BOOST_SERIALIZATION_NVP(loaded_game_data); - if (loaded_game_data) { - Deserialize(ia, orders); - ia >> BOOST_SERIALIZATION_NVP(ui_data_available); - if (ui_data_available) - ia >> BOOST_SERIALIZATION_NVP(ui_data); - ia >> BOOST_SERIALIZATION_NVP(save_state_string_available); - if (save_state_string_available) - ia >> BOOST_SERIALIZATION_NVP(save_state_string); - } else { - ui_data_available = false; - save_state_string_available = false; + bool try_xml = false; + if (strncmp(msg.Data(), "> BOOST_SERIALIZATION_NVP(single_player_game) + >> BOOST_SERIALIZATION_NVP(empire_id) + >> BOOST_SERIALIZATION_NVP(current_turn); + GetUniverse().EncodingEmpire() = empire_id; + + ScopedTimer deserialize_timer; + ia >> BOOST_SERIALIZATION_NVP(empires); + DebugLogger() << "ExtractGameStartMessage empire deserialization time " << deserialize_timer.DurationString(); + + ia >> BOOST_SERIALIZATION_NVP(species); + combat_logs.Clear(); // only needed when loading new game, not when incrementally serializing logs on turn update + combat_logs.SerializeIncompleteLogs(ia, 1); + ia >> BOOST_SERIALIZATION_NVP(supply); + + deserialize_timer.restart(); + Deserialize(ia, universe); + DebugLogger() << "ExtractGameStartMessage universe deserialization time " << deserialize_timer.DurationString(); + + + ia >> BOOST_SERIALIZATION_NVP(players) + >> BOOST_SERIALIZATION_NVP(loaded_game_data); + if (loaded_game_data) { + Deserialize(ia, orders); + ia >> BOOST_SERIALIZATION_NVP(ui_data_available); + if (ui_data_available) + ia >> BOOST_SERIALIZATION_NVP(ui_data); + ia >> BOOST_SERIALIZATION_NVP(save_state_string_available); + if (save_state_string_available) + ia >> BOOST_SERIALIZATION_NVP(save_state_string); + } else { + ui_data_available = false; + save_state_string_available = false; + } + ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data); + } catch (...) { + try_xml = true; } - ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data); - - } catch (...) { + } else { + try_xml = true; + } + if (try_xml) { // if binary deserialization failed, try more-portable XML deserialization std::istringstream is(msg.Text()); @@ -785,46 +878,55 @@ void ExtractGameStartMessageData(const Message& msg, bool& single_player_game, i >> BOOST_SERIALIZATION_NVP(current_turn); GetUniverse().EncodingEmpire() = empire_id; - boost::timer deserialize_timer; + ScopedTimer deserialize_timer; ia >> BOOST_SERIALIZATION_NVP(empires); - DebugLogger() << "ExtractGameStartMessage empire deserialization time " << (deserialize_timer.elapsed() * 1000.0); + + DebugLogger() << "ExtractGameStartMessage empire deserialization time " << deserialize_timer.DurationString(); ia >> BOOST_SERIALIZATION_NVP(species); + combat_logs.Clear(); // only needed when loading new game, not when incrementally serializing logs on turn update combat_logs.SerializeIncompleteLogs(ia, 1); ia >> BOOST_SERIALIZATION_NVP(supply); deserialize_timer.restart(); Deserialize(ia, universe); - DebugLogger() << "ExtractGameStartMessage universe deserialization time " << (deserialize_timer.elapsed() * 1000.0); + DebugLogger() << "ExtractGameStartMessage universe deserialization time " << deserialize_timer.DurationString();; ia >> BOOST_SERIALIZATION_NVP(players) >> BOOST_SERIALIZATION_NVP(loaded_game_data); + TraceLogger() << "ExtractGameStartMessage players and loaded_game_data=" << loaded_game_data << " deserialization time " << deserialize_timer.DurationString(); if (loaded_game_data) { Deserialize(ia, orders); + TraceLogger() << "ExtractGameStartMessage orders deserialization time " << deserialize_timer.DurationString(); ia >> BOOST_SERIALIZATION_NVP(ui_data_available); if (ui_data_available) ia >> BOOST_SERIALIZATION_NVP(ui_data); + TraceLogger() << "ExtractGameStartMessage UI data " << ui_data_available << " deserialization time " << deserialize_timer.DurationString(); ia >> BOOST_SERIALIZATION_NVP(save_state_string_available); if (save_state_string_available) ia >> BOOST_SERIALIZATION_NVP(save_state_string); + TraceLogger() << "ExtractGameStartMessage save state " << save_state_string_available << " deserialization time " << deserialize_timer.DurationString(); } else { ui_data_available = false; save_state_string_available = false; } ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data); + TraceLogger() << "ExtractGameStartMessage galaxy setup data deserialization time " << deserialize_timer.DurationString(); } } catch (const std::exception& err) { ErrorLogger() << "ExtractGameStartMessageData(...) failed! Message probably long, so not outputting to log.\n" << "Error: " << err.what(); + TraceLogger() << "Message: " << msg.Text(); throw err; } } void ExtractJoinGameMessageData(const Message& msg, std::string& player_name, Networking::ClientType& client_type, - std::string& version_string) + std::string& version_string, + boost::uuids::uuid& cookie) { DebugLogger() << "ExtractJoinGameMessageData() from " << player_name << " client type " << client_type; try { @@ -832,7 +934,8 @@ void ExtractJoinGameMessageData(const Message& msg, std::string& player_name, freeorion_xml_iarchive ia(is); ia >> BOOST_SERIALIZATION_NVP(player_name) >> BOOST_SERIALIZATION_NVP(client_type) - >> BOOST_SERIALIZATION_NVP(version_string); + >> BOOST_SERIALIZATION_NVP(version_string) + >> BOOST_SERIALIZATION_NVP(cookie); } catch (const std::exception& err) { ErrorLogger() << "ExtractJoinGameMessageData(const Message& msg, std::string& player_name, " @@ -843,15 +946,64 @@ void ExtractJoinGameMessageData(const Message& msg, std::string& player_name, } } -void ExtractTurnOrdersMessageData(const Message& msg, OrderSet& orders) { +void ExtractJoinAckMessageData(const Message& msg, int& player_id, + boost::uuids::uuid& cookie) +{ + try { + std::istringstream is(msg.Text()); + freeorion_xml_iarchive ia(is); + ia >> BOOST_SERIALIZATION_NVP(player_id) + >> BOOST_SERIALIZATION_NVP(cookie); + } catch (const std::exception& err) { + ErrorLogger() << "ExtractJoinAckMessageData(const Message& msg, int& player_id, " + << "boost::uuids::uuid& cookie) failed! Message:\n" + << msg.Text() << "\n" + << "Error: " << err.what(); + throw err; + } +} + +void ExtractTurnOrdersMessageData(const Message& msg, + OrderSet& orders, + bool& ui_data_available, + SaveGameUIData& ui_data, + bool& save_state_string_available, + std::string& save_state_string) +{ try { std::istringstream is(msg.Text()); freeorion_xml_iarchive ia(is); + DebugLogger() << "deserializing orders"; Deserialize(ia, orders); + DebugLogger() << "checking for ui data"; + ia >> BOOST_SERIALIZATION_NVP(ui_data_available); + if (ui_data_available) { + DebugLogger() << "deserializing UI data"; + ia >> BOOST_SERIALIZATION_NVP(ui_data); + } + DebugLogger() << "checking for save state string"; + ia >> BOOST_SERIALIZATION_NVP(save_state_string_available); + if (save_state_string_available) { + DebugLogger() << "deserializing save state string"; + ia >> BOOST_SERIALIZATION_NVP(save_state_string); + } } catch (const std::exception& err) { - ErrorLogger() << "ExtractTurnOrdersMessageData(const Message& msg, OrderSet& orders) failed! Message:\n" - << msg.Text() << "\n" + ErrorLogger() << "ExtractTurnOrdersMessageData(const Message& msg, ...) failed! Message probably long, so not outputting to log.\n" + << "Error: " << err.what(); + throw err; + } +} + +void ExtractTurnPartialOrdersMessageData(const Message& msg, OrderSet& added, std::set& deleted) { + try { + std::istringstream is(msg.Text()); + freeorion_xml_iarchive ia(is); + DebugLogger() << "deserializing partial orders"; + Deserialize(ia, added); + ia >> BOOST_SERIALIZATION_NVP(deleted); + } catch (const std::exception& err) { + ErrorLogger() << "ExtractTurnPartialOrdersMessageData(const Message& msg) failed! Message probably long, so not outputting to log.\n" << "Error: " << err.what(); throw err; } @@ -864,20 +1016,27 @@ void ExtractTurnUpdateMessageData(const Message& msg, int empire_id, int& curren try { ScopedTimer timer("Turn Update Unpacking", true); - try { - // first attempt binary deserialization - std::istringstream is(msg.Text()); - freeorion_bin_iarchive ia(is); - GetUniverse().EncodingEmpire() = empire_id; - ia >> BOOST_SERIALIZATION_NVP(current_turn) - >> BOOST_SERIALIZATION_NVP(empires) - >> BOOST_SERIALIZATION_NVP(species); - combat_logs.SerializeIncompleteLogs(ia, 1); - ia >> BOOST_SERIALIZATION_NVP(supply); - Deserialize(ia, universe); - ia >> BOOST_SERIALIZATION_NVP(players); - - } catch (...) { + bool try_xml = false; + if (std::strncmp(msg.Data(), "> BOOST_SERIALIZATION_NVP(current_turn) + >> BOOST_SERIALIZATION_NVP(empires) + >> BOOST_SERIALIZATION_NVP(species); + combat_logs.SerializeIncompleteLogs(ia, 1); + ia >> BOOST_SERIALIZATION_NVP(supply); + Deserialize(ia, universe); + ia >> BOOST_SERIALIZATION_NVP(players); + } catch (...) { + try_xml = true; + } + } else { + try_xml = true; + } + if (try_xml) { // try again with more-portable XML deserialization std::istringstream is(msg.Text()); freeorion_xml_iarchive ia(is); @@ -902,14 +1061,21 @@ void ExtractTurnPartialUpdateMessageData(const Message& msg, int empire_id, Univ try { ScopedTimer timer("Mid Turn Update Unpacking", true); - try { - // first attempt binary deserialization - std::istringstream is(msg.Text()); - freeorion_bin_iarchive ia(is); - GetUniverse().EncodingEmpire() = empire_id; - Deserialize(ia, universe); - - } catch (...) { + bool try_xml = false; + if (std::strncmp(msg.Data(), "> BOOST_SERIALIZATION_NVP(ui_data_available); - if (ui_data_available) { - DebugLogger() << "deserializing UI data"; - ia >> BOOST_SERIALIZATION_NVP(ui_data); - } - DebugLogger() << "checking for save state string"; - ia >> BOOST_SERIALIZATION_NVP(save_state_string_available); - if (save_state_string_available) { - DebugLogger() << "deserializing save state string"; - ia >> BOOST_SERIALIZATION_NVP(save_state_string); - } - - } catch (const std::exception& err) { - ErrorLogger() << "ExtractClientSaveDataMessageData(...) failed! Message probably long, so not outputting to log.\n" - << "Error: " << err.what(); - throw err; - } -} - void ExtractTurnProgressMessageData(const Message& msg, Message::TurnProgressPhase& phase_id) { try { std::istringstream is(msg.Text()); @@ -967,15 +1104,17 @@ void ExtractTurnProgressMessageData(const Message& msg, Message::TurnProgressPha } } -void ExtractPlayerStatusMessageData(const Message& msg, int& about_player_id, Message::PlayerStatus& status) { +void ExtractPlayerStatusMessageData(const Message& msg, + Message::PlayerStatus& status, + int& about_empire_id) { try { std::istringstream is(msg.Text()); freeorion_xml_iarchive ia(is); - ia >> BOOST_SERIALIZATION_NVP(about_player_id) - >> BOOST_SERIALIZATION_NVP(status); + ia >> BOOST_SERIALIZATION_NVP(status) + >> BOOST_SERIALIZATION_NVP(about_empire_id); } catch (const std::exception& err) { - ErrorLogger() << "ExtractPlayerStatusMessageData(const Message& msg, int& about_player_id, Message::PlayerStatus&) failed! Message:\n" + ErrorLogger() << "ExtractPlayerStatusMessageData(const Message& msg, Message::PlayerStatus&, int& about_empire_id) failed! Message:\n" << msg.Text() << "\n" << "Error: " << err.what(); throw err; @@ -1107,16 +1246,32 @@ FO_COMMON_API void ExtractDispatchCombatLogsMessageData( const Message& msg, std::vector>& logs) { try { - std::istringstream is(msg.Text()); - freeorion_xml_iarchive ia(is); - ia >> BOOST_SERIALIZATION_NVP(logs); + bool try_xml = false; + if (std::strncmp(msg.Data(), "> BOOST_SERIALIZATION_NVP(logs); + } catch (...) { + try_xml = true; + } + } else { + try_xml = true; + } + if (try_xml) { + // try again with more-portable XML deserialization + std::istringstream is(msg.Text()); + freeorion_xml_iarchive ia(is); + ia >> BOOST_SERIALIZATION_NVP(logs); + } + } catch(const std::exception& err) { ErrorLogger() << "ExtractDispatchCombatLogMessageData(const Message& msg, std::vector>& logs) failed! Message:\n" << msg.Text() << "\n" << "Error: " << err.what(); throw err; } - } FO_COMMON_API void ExtractLoggerConfigMessageData( @@ -1161,3 +1316,48 @@ void ExtractContentCheckSumMessageData( throw err; } } + +void ExtractAuthRequestMessageData(const Message& msg, std::string& player_name, std::string& auth) { + try { + std::istringstream is(msg.Text()); + freeorion_xml_iarchive ia(is); + ia >> BOOST_SERIALIZATION_NVP(player_name) + >> BOOST_SERIALIZATION_NVP(auth); + + } catch(const std::exception& err) { + ErrorLogger() << "ExtractAuthRequestMessageData(const Message& msg, std::string& player_name, std::string& auth) failed! Message:\n" + << msg.Text() << "\n" + << "Error: " << err.what(); + throw err; + } +} + +void ExtractAuthResponseMessageData(const Message& msg, std::string& player_name, std::string& auth) { + try { + std::istringstream is(msg.Text()); + freeorion_xml_iarchive ia(is); + ia >> BOOST_SERIALIZATION_NVP(player_name) + >> BOOST_SERIALIZATION_NVP(auth); + + } catch(const std::exception& err) { + ErrorLogger() << "ExtractAuthResponeMessageData(const Message& msg, std::string& player_name, std::string& auth) failed! Message:\n" + << msg.Text() << "\n" + << "Error: " << err.what(); + throw err; + } +} + +void ExtractSetAuthorizationRolesMessage(const Message &msg, Networking::AuthRoles& roles) +{ roles.SetText(msg.Text()); } + +void ExtractPlayerInfoMessageData(const Message &msg, std::map& players) { + try { + std::istringstream is(msg.Text()); + freeorion_xml_iarchive ia(is); + ia >> BOOST_SERIALIZATION_NVP(players); + } catch(const std::exception& err) { + ErrorLogger() << "ExtractPlayerInfo(const Message &msg, std::map& players) failed! Message:\n" + << msg.Text() << "\n" + << "Error: " << err.what(); + } +} diff --git a/network/Message.h b/network/Message.h index 3ea6c8e647d..791a687d7fa 100644 --- a/network/Message.h +++ b/network/Message.h @@ -6,6 +6,8 @@ #include #include +#include +#include #if defined(_MSC_VER) && defined(int64_t) #undef int64_t @@ -26,6 +28,7 @@ struct CombatLog; class CombatLogManager; class Message; struct MultiplayerLobbyData; +struct ChatHistoryEntity; class OrderSet; struct PlayerInfo; struct SaveGameUIData; @@ -49,7 +52,7 @@ namespace Moderator { * misbehave as well.) */ class FO_COMMON_API Message { public: - enum Parts : size_t {TYPE = 0, RESPONSE, SIZE, Parts_Count}; + enum Parts : size_t {TYPE = 0, SIZE, Parts_Count}; typedef std::array HeaderBuffer; @@ -69,7 +72,6 @@ class FO_COMMON_API Message { LOBBY_EXIT, ///< sent to server by clients when a player leaves the multiplayer lobby, or by server to clients when a player leaves the multiplayer lobby START_MP_GAME, ///< sent to server (by the "host" client only) when the settings in the MP lobby are satisfactory and it is time to start the game SAVE_GAME_INITIATE, ///< sent to server (by the "host" client only) when a game is to be saved - SAVE_GAME_DATA_REQUEST, ///< sent to clients by the server when the game is being saved and the server needs save state info from clients SAVE_GAME_COMPLETE, ///< sent to clients (by the "host" client only) when a game has been saved and written to disk LOAD_GAME, ///< sent to server (by the "host" client only) when a game is to be loaded GAME_START, ///< sent to each client before the first turn of a new or newly loaded game, instead of a TURN_UPDATE @@ -78,7 +80,6 @@ class FO_COMMON_API Message { TURN_ORDERS, ///< sent to the server by a client that has orders to be processed at the end of a turn TURN_PROGRESS, ///< sent to clients to display a turn progress message PLAYER_STATUS, ///< sent to clients to inform them that a player has some status, such as having finished playing a turn and submitted orders, or is resolving combat, or is playing a turn normally - CLIENT_SAVE_DATA, ///< sent to the server in response to a server request for the data needed to create a save file PLAYER_CHAT, ///< sent when one player sends a chat message to another in multiplayer DIPLOMACY, ///< sent by players to server or server to players to make or convey diplomatic proposals or declarations, or to accept / reject proposals from other players DIPLOMATIC_STATUS, ///< sent by server to players to inform of mid-turn diplomatic status changes @@ -95,7 +96,16 @@ class FO_COMMON_API Message { REQUEST_COMBAT_LOGS, ///< sent by client to request combat logs DISPATCH_COMBAT_LOGS, ///< sent by host to client to provide combat logs LOGGER_CONFIG, ///< sent by host to server and server to ais to configure logging - CHECKSUM ///< sent by host to clients to specify what the parsed content checksum values should be + CHECKSUM, ///< sent by host to clients to specify what the parsed content checksum values should be + AUTH_REQUEST, ///< sent by server to client if choosed player_name require authentiation + AUTH_RESPONSE, ///< sent by client to server to provide password or other credentials + CHAT_HISTORY, ///< sent by server to client to show previous messages + SET_AUTH_ROLES, ///< sent by server to client to set authorization roles + ELIMINATE_SELF, ///< sent by client to server if the player wants to resign + UNREADY, ///< sent by client to server to revoke ready state of turn orders and sent by server to client to acknowledge it + TURN_PARTIAL_ORDERS, ///< sent to the server by a client that has changes in orders to be stored + TURN_TIMEOUT, ///< sent by server to client to notify about remaining time before turn advance + PLAYER_INFO ///< sent by server to client to notify about changes in the player data ) GG_CLASS_ENUM(TurnProgressPhase, @@ -111,10 +121,10 @@ class FO_COMMON_API Message { STARTING_AIS ///< creating AI clients ) + /// \todo change to EmpireStatus on compatibility breakage GG_CLASS_ENUM(PlayerStatus, - PLAYING_TURN, ///< player is playing a turn, on the galax map - RESOLVING_COMBAT, ///< player is resolving a combat interactively - WAITING ///< player is waiting for others to submit orders, to resolve combats, or for turn processing to complete + PLAYING_TURN, ///< empire is playing a turn, on the galax map + WAITING ///< empire is waiting for others to submit orders, to resolve combats, or for turn processing to complete ) GG_CLASS_ENUM(EndGameReason, @@ -126,13 +136,11 @@ class FO_COMMON_API Message { Message(); Message(MessageType message_type, - const std::string& text, - bool synchronous_response = false); + const std::string& text); //@} /** \name Accessors */ //@{ MessageType Type() const; ///< Returns the type of the message. - bool SynchronousResponse() const;///< Returns true if this message is in reponse to a synchronous message std::size_t Size() const; ///< Returns the size of the underlying buffer. const char* Data() const; ///< Returns the underlying buffer. std::string Text() const; ///< Returns the underlying buffer as a std::string. @@ -146,7 +154,6 @@ class FO_COMMON_API Message { private: MessageType m_type; - bool m_synchronous_response; int m_message_size; boost::shared_array m_message_text; @@ -188,38 +195,42 @@ FO_COMMON_API Message HostSPGameMessage(const SinglePlayerSetupData& setup_data) /** creates a minimal HOST_MP_GAME message used to initiate multiplayer "lobby" setup*/ FO_COMMON_API Message HostMPGameMessage(const std::string& host_player_name); -/** creates a JOIN_GAME message. The sender's player name and client type are sent in the message.*/ -FO_COMMON_API Message JoinGameMessage(const std::string& player_name, Networking::ClientType client_type); +/** creates a JOIN_GAME message. The sender's player name, client type, and + * cookie are sent in the message.*/ +FO_COMMON_API Message JoinGameMessage(const std::string& player_name, + Networking::ClientType client_type, + boost::uuids::uuid cookie); /** creates a HOST_ID message. The player ID of the host is sent in the message. */ FO_COMMON_API Message HostIDMessage(int host_player_id); /** creates a GAME_START message. Contains the initial game state visible to the player.*/ -FO_COMMON_API Message GameStartMessage(bool single_player_game, int empire_id, int current_turn, - const EmpireManager& empires, const Universe& universe, - const SpeciesManager& species, CombatLogManager& combat_logs, - const SupplyManager& supply, const std::map& players, - const GalaxySetupData& galaxy_setup_data, bool use_binary_serialization); +FO_COMMON_API Message GameStartMessage( + bool single_player_game, int empire_id, int current_turn, + const EmpireManager& empires, const Universe& universe, + const SpeciesManager& species, CombatLogManager& combat_logs, + const SupplyManager& supply, const std::map& players, + GalaxySetupData galaxy_setup_data, bool use_binary_serialization); /** creates a GAME_START message. Contains the initial game state visible to * the player. Also includes data loaded from a saved game. */ -FO_COMMON_API Message GameStartMessage(bool single_player_game, int empire_id, int current_turn, - const EmpireManager& empires, const Universe& universe, - const SpeciesManager& species, CombatLogManager& combat_logs, - const SupplyManager& supply, - const std::map& players, const OrderSet& orders, - const SaveGameUIData* ui_data, - const GalaxySetupData& galaxy_setup_data, bool use_binary_serialization); +FO_COMMON_API Message GameStartMessage( + bool single_player_game, int empire_id, int current_turn, + const EmpireManager& empires, const Universe& universe, + const SpeciesManager& species, CombatLogManager& combat_logs, + const SupplyManager& supply, const std::map& players, + const OrderSet& orders, const SaveGameUIData* ui_data, + GalaxySetupData galaxy_setup_data, bool use_binary_serialization); /** creates a GAME_START message. Contains the initial game state visible to * the player. Also includes state string loaded from a saved game. */ -FO_COMMON_API Message GameStartMessage(bool single_player_game, int empire_id, int current_turn, - const EmpireManager& empires, const Universe& universe, - const SpeciesManager& species, CombatLogManager& combat_logs, - const SupplyManager& supply, - const std::map& players, const OrderSet& orders, - const std::string* save_state_string, - const GalaxySetupData& galaxy_setup_data, bool use_binary_serialization); +FO_COMMON_API Message GameStartMessage( + bool single_player_game, int empire_id, int current_turn, + const EmpireManager& empires, const Universe& universe, + const SpeciesManager& species, CombatLogManager& combat_logs, + const SupplyManager& supply, const std::map& players, + const OrderSet& orders, const std::string* save_state_string, + GalaxySetupData galaxy_setup_data, bool use_binary_serialization); /** creates a HOST_SP_GAME acknowledgement message. The \a player_id is the ID * of the receiving player. This message should only be sent by the server.*/ @@ -230,17 +241,28 @@ FO_COMMON_API Message HostSPAckMessage(int player_id); FO_COMMON_API Message HostMPAckMessage(int player_id); /** creates a JOIN_GAME acknowledgement message. The \a player_id is the ID of - * the receiving player. This message should only be sent by the server.*/ -FO_COMMON_API Message JoinAckMessage(int player_id); + * the receiving player and \a cookie is a token to quickly authenticate player. + * This message should only be sent by the server.*/ +FO_COMMON_API Message JoinAckMessage(int player_id, boost::uuids::uuid cookie); -/** creates a TURN_ORDERS message. */ -FO_COMMON_API Message TurnOrdersMessage(const OrderSet& orders); +/** creates a TURN_ORDERS message, including UI data but without a state string. */ +FO_COMMON_API Message TurnOrdersMessage(const OrderSet& orders, const SaveGameUIData& ui_data); + +/** creates a TURN_ORDERS message, without UI data but with a state string. */ +FO_COMMON_API Message TurnOrdersMessage(const OrderSet& orders, const std::string& save_state_string); + +/** creates a TURN_PARTIAL_ORDERS message with orders changes. */ +FO_COMMON_API Message TurnPartialOrdersMessage(const std::pair>& orders_updates); + +/** creates a TURN_TIMEOUT message with remaining time \a timeout_. */ +FO_COMMON_API Message TurnTimeoutMessage(int timeout_remainig); /** creates a TURN_PROGRESS message. */ FO_COMMON_API Message TurnProgressMessage(Message::TurnProgressPhase phase_id); /** creates a PLAYER_STATUS message. */ -FO_COMMON_API Message PlayerStatusMessage(int about_player_id, Message::PlayerStatus player_status); +FO_COMMON_API Message PlayerStatusMessage(Message::PlayerStatus player_status, + int about_empire_id); /** creates a TURN_UPDATE message. */ FO_COMMON_API Message TurnUpdateMessage(int empire_id, int current_turn, @@ -253,24 +275,10 @@ FO_COMMON_API Message TurnUpdateMessage(int empire_id, int current_turn, FO_COMMON_API Message TurnPartialUpdateMessage(int empire_id, const Universe& universe, bool use_binary_serialization); -/** creates a CLIENT_SAVE_DATA message, including UI data but without a state string. */ -FO_COMMON_API Message ClientSaveDataMessage(const OrderSet& orders, const SaveGameUIData& ui_data); - -/** creates a CLIENT_SAVE_DATA message, without UI data but with a state string. */ -FO_COMMON_API Message ClientSaveDataMessage(const OrderSet& orders, const std::string& save_state_string); - -/** creates a CLIENT_SAVE_DATA message, without UI data and without a state string. */ -FO_COMMON_API Message ClientSaveDataMessage(const OrderSet& orders); - /** creates a SAVE_GAME_INITIATE request message. This message should only be sent by * the host player.*/ FO_COMMON_API Message HostSaveGameInitiateMessage(const std::string& filename); -/** creates a SAVE_GAME_DATA_REQUEST data request message. This message should - only be sent by the server to get game data from a client, or to respond to - the host player requesting a save be initiated. */ -FO_COMMON_API Message ServerSaveGameDataRequestMessage(bool synchronous_response); - /** creates a SAVE_GAME_COMPLETE complete message. This message should only be sent by the server to inform clients that the last initiated save has been completed successfully. */ @@ -297,7 +305,7 @@ FO_COMMON_API Message ModeratorActionMessage(const Moderator::ModeratorAction& m FO_COMMON_API Message ShutdownServerMessage(); /** requests previews of savefiles from server */ -FO_COMMON_API Message RequestSavePreviewsMessage(std::string directory); +FO_COMMON_API Message RequestSavePreviewsMessage(std::string relative_directory); /** returns the savegame previews to the client */ FO_COMMON_API Message DispatchSavePreviewsMessage(const PreviewInformation& preview); @@ -306,7 +314,8 @@ FO_COMMON_API Message DispatchSavePreviewsMessage(const PreviewInformation& prev FO_COMMON_API Message RequestCombatLogsMessage(const std::vector& ids); /** returns combat logs to the client */ -FO_COMMON_API Message DispatchCombatLogsMessage(const std::vector>& logs); +FO_COMMON_API Message DispatchCombatLogsMessage(const std::vector>& logs, + bool use_binary_serialization); /** Sends logger configuration details to server or ai process. */ FO_COMMON_API Message LoggerConfigMessage(int sender, const std::set>& options); @@ -324,13 +333,18 @@ FO_COMMON_API Message LobbyUpdateMessage(const MultiplayerLobbyData& lobby_data) This message should only be sent by the server.*/ FO_COMMON_API Message ServerLobbyUpdateMessage(const MultiplayerLobbyData& lobby_data); +/** creates an CHAT_HISTORY message containing latest chat messages. + This message should only be sent by the server.*/ +FO_COMMON_API Message ChatHistoryMessage(const std::vector>& chat_history); + /** creates an PLAYER_CHAT message containing a chat string to be broadcast to player \a receiver, or all players if \a receiver is Networking::INVALID_PLAYER_ID. Note that the receiver of this message is always the server.*/ -FO_COMMON_API Message PlayerChatMessage(const std::string& text, int receiver = Networking::INVALID_PLAYER_ID); +FO_COMMON_API Message PlayerChatMessage(const std::string& text, std::set recipients, bool pm); -/** creates an PLAYER_CHAT message containing a chat string from \a sender to be displayed in chat. +/** creates an PLAYER_CHAT message containing a chat string from \a sender at \a timestamp to be displayed in chat. This message should only be sent by the server.*/ -FO_COMMON_API Message ServerPlayerChatMessage(int sender, const std::string& text); +FO_COMMON_API Message ServerPlayerChatMessage(int sender, const boost::posix_time::ptime& timestamp, + const std::string& text, bool pm = false); /** creates a START_MP_GAME used to finalize the multiplayer lobby setup.*/ FO_COMMON_API Message StartMPGameMessage(); @@ -338,6 +352,24 @@ FO_COMMON_API Message StartMPGameMessage(); /** creates a CHECKSUM message containing checksums of parsed content. */ FO_COMMON_API Message ContentCheckSumMessage(); +/** creates a AUTH_REQUEST message containing \a player_name to login and \a auth additional authentication data. */ +FO_COMMON_API Message AuthRequestMessage(const std::string& player_name, const std::string& auth); + +/** creates a AUTH_RESPONSE message containing \a player_name to login and \a auth credentials. */ +FO_COMMON_API Message AuthResponseMessage(const std::string& player_name, const std::string& auth); + +/** notifies client about changes in his authorization \a roles. */ +FO_COMMON_API Message SetAuthorizationRolesMessage(const Networking::AuthRoles& roles); + +/** creates a ELIMINATE_SELF message to resign from the game. */ +FO_COMMON_API Message EliminateSelfMessage(); + +/** creates a UNREADY message to revoke ready state of turn orders or acknowledge it */ +FO_COMMON_API Message UnreadyMessage(); + +/** creates a PLAYER_INFO message to notify player about changes in the player list. */ +FO_COMMON_API Message PlayerInfoMessage(const std::map& players); + //////////////////////////////////////////////// // Message data extractors //////////////////////////////////////////////// @@ -349,9 +381,13 @@ FO_COMMON_API void ExtractHostMPGameMessageData(const Message& msg, std::string& FO_COMMON_API void ExtractLobbyUpdateMessageData(const Message& msg, MultiplayerLobbyData& lobby_data); -FO_COMMON_API void ExtractPlayerChatMessageData(const Message& msg, int& receiver, std::string& data); +FO_COMMON_API void ExtractChatHistoryMessage(const Message& msg, std::vector& chat_history); -FO_COMMON_API void ExtractServerPlayerChatMessageData(const Message& msg, int& sender, std::string& data); +FO_COMMON_API void ExtractPlayerChatMessageData(const Message& msg, std::set& recipients, std::string& data, bool& pm); + +FO_COMMON_API void ExtractServerPlayerChatMessageData(const Message& msg, + int& sender, boost::posix_time::ptime& timestamp, + std::string& data, bool& pm); FO_COMMON_API void ExtractGameStartMessageData(const Message& msg, bool& single_player_game, int& empire_id, int& current_turn, EmpireManager& empires, Universe& universe, @@ -364,9 +400,20 @@ FO_COMMON_API void ExtractGameStartMessageData(const Message& msg, bool& single_ FO_COMMON_API void ExtractJoinGameMessageData(const Message& msg, std::string& player_name, Networking::ClientType& client_type, - std::string& version_string); + std::string& version_string, + boost::uuids::uuid& cookie); + +FO_COMMON_API void ExtractJoinAckMessageData(const Message& msg, int& player_id, + boost::uuids::uuid& cookie); + +FO_COMMON_API void ExtractTurnOrdersMessageData(const Message& msg, + OrderSet& orders, + bool& ui_data_available, + SaveGameUIData& ui_data, + bool& save_state_string_available, + std::string& save_state_string); -FO_COMMON_API void ExtractTurnOrdersMessageData(const Message& msg, OrderSet& orders); +FO_COMMON_API void ExtractTurnPartialOrdersMessageData(const Message& msg, OrderSet& added, std::set& deleted); FO_COMMON_API void ExtractTurnUpdateMessageData(const Message& msg, int empire_id, int& current_turn, EmpireManager& empires, Universe& universe, SpeciesManager& species, CombatLogManager& combat_logs, @@ -374,14 +421,11 @@ FO_COMMON_API void ExtractTurnUpdateMessageData(const Message& msg, int empire_i FO_COMMON_API void ExtractTurnPartialUpdateMessageData(const Message& msg, int empire_id, Universe& universe); -FO_COMMON_API void ExtractClientSaveDataMessageData(const Message& msg, OrderSet& orders, - bool& ui_data_available, SaveGameUIData& ui_data, - bool& save_state_string_available, - std::string& save_state_string); - FO_COMMON_API void ExtractTurnProgressMessageData(const Message& msg, Message::TurnProgressPhase& phase_id); -FO_COMMON_API void ExtractPlayerStatusMessageData(const Message& msg, int& about_player_id, Message::PlayerStatus& status); +FO_COMMON_API void ExtractPlayerStatusMessageData(const Message& msg, + Message::PlayerStatus& status, + int& about_empire_id); FO_COMMON_API void ExtractHostSPGameMessageData(const Message& msg, SinglePlayerSetupData& setup_data, std::string& client_version_string); @@ -407,4 +451,12 @@ FO_COMMON_API void ExtractLoggerConfigMessageData(const Message& msg, std::set& checksums); +FO_COMMON_API void ExtractAuthRequestMessageData(const Message& msg, std::string& player_name, std::string& auth); + +FO_COMMON_API void ExtractAuthResponseMessageData(const Message& msg, std::string& player_name, std::string& auth); + +FO_COMMON_API void ExtractSetAuthorizationRolesMessage(const Message &msg, Networking::AuthRoles& roles); + +FO_COMMON_API void ExtractPlayerInfoMessageData(const Message &msg, std::map& players); + #endif // _Message_h_ diff --git a/network/MessageQueue.cpp b/network/MessageQueue.cpp index a11574ebace..335f081aaa4 100644 --- a/network/MessageQueue.cpp +++ b/network/MessageQueue.cpp @@ -4,16 +4,8 @@ #include -namespace { - struct SynchronousResponseMessage { - bool operator()(const Message& message) const - { return message.SynchronousResponse(); } - }; -} - -MessageQueue::MessageQueue(boost::mutex& monitor, const bool& rx_connected) : - m_monitor{monitor}, - m_rx_connected{rx_connected} +MessageQueue::MessageQueue(boost::mutex& monitor) : + m_monitor{monitor} {} bool MessageQueue::Empty() const { @@ -35,8 +27,6 @@ void MessageQueue::PushBack(Message& message) { boost::mutex::scoped_lock lock(m_monitor); m_queue.push_back(Message()); swap(m_queue.back(), message); - if (m_queue.back().SynchronousResponse()) - m_have_synchronous_response.notify_one(); } boost::optional MessageQueue::PopFront() { @@ -49,22 +39,3 @@ boost::optional MessageQueue::PopFront() { return message; } -void MessageQueue::RxDisconnected() { - boost::mutex::scoped_lock lock(m_monitor); - m_have_synchronous_response.notify_one(); -} - -boost::optional MessageQueue::GetFirstSynchronousMessage() { - boost::mutex::scoped_lock lock(m_monitor); - std::list::iterator it = std::find_if(m_queue.begin(), m_queue.end(), SynchronousResponseMessage()); - while (it == m_queue.end()) { - if (!m_rx_connected) - return boost::none; - m_have_synchronous_response.wait(lock); - it = std::find_if(m_queue.begin(), m_queue.end(), SynchronousResponseMessage()); - } - Message message; - swap(message, *it); - m_queue.erase(it); - return message; -} diff --git a/network/MessageQueue.h b/network/MessageQueue.h index 1795ae523e2..0fc0eb64ec6 100644 --- a/network/MessageQueue.h +++ b/network/MessageQueue.h @@ -16,7 +16,7 @@ class Message; class FO_COMMON_API MessageQueue { public: - MessageQueue(boost::mutex& monitor, const bool& rx_connected); + MessageQueue(boost::mutex& monitor); /** Returns true iff the queue is empty. */ bool Empty() const; @@ -33,19 +33,9 @@ class FO_COMMON_API MessageQueue /** Return and remove the first message in the queue. */ boost::optional PopFront(); - /** Indicates that no more new message will be added to the queue. This causes any pending - GetFirstSynchronousMessage() to return boost::none.*/ - void RxDisconnected(); - - /** Return and remove the first synchronous repsonse message from the queue or return - boost::none if there is no synchronous message and the queue has stopped growwing. */ - boost::optional GetFirstSynchronousMessage(); - private: std::list m_queue; - boost::condition m_have_synchronous_response; boost::mutex& m_monitor; - const bool& m_rx_connected; }; diff --git a/network/Networking.cpp b/network/Networking.cpp index 15a1d346e97..4c918e7e34a 100644 --- a/network/Networking.cpp +++ b/network/Networking.cpp @@ -4,8 +4,8 @@ namespace { void AddOptions(OptionsDB& db) { - db.Add("network.discovery-port", UserStringNop("OPTIONS_DB_NETWORK_DISCOVERY_PORT"), 12345, RangedValidator(1025, 65535)); - db.Add("network.message-port", UserStringNop("OPTIONS_DB_NETWORK_MESSAGE_PORT"), 12346, RangedValidator(1025, 65535)); + db.Add("network.discovery.port", UserStringNop("OPTIONS_DB_NETWORK_DISCOVERY_PORT"), 12345, RangedValidator(1025, 65535)); + db.Add("network.message.port", UserStringNop("OPTIONS_DB_NETWORK_MESSAGE_PORT"), 12346, RangedValidator(1025, 65535)); } bool temp_bool = RegisterOptions(&AddOptions); } @@ -13,11 +13,38 @@ namespace { namespace Networking { const std::string DISCOVERY_QUESTION = "Yo, can I play Free-O here, dog?"; const std::string DISCOVERY_ANSWER = "Word!"; +#ifdef FREEORION_OPENBSD + // Needs to set shorter linger time on OpenBSD to be able to start the session + const int SOCKET_LINGER_TIME = 1 << (sizeof(unsigned short) * 4 - 1); +#else const int SOCKET_LINGER_TIME = 1 << (sizeof(unsigned short) * 8 - 1); +#endif const int INVALID_PLAYER_ID = -1; + const int NO_TEAM_ID = -1; int DiscoveryPort() - { return GetOptionsDB().Get("network.discovery-port"); } + { return GetOptionsDB().Get("network.discovery.port"); } int MessagePort() - { return GetOptionsDB().Get("network.message-port"); } + { return GetOptionsDB().Get("network.message.port"); } + + AuthRoles::AuthRoles(const std::initializer_list& roles) { + for (RoleType r : roles) { + m_roles.set(r, true); + } + } + + void AuthRoles::SetRole(RoleType role, bool value) + { m_roles.set(role, value); } + + void AuthRoles::Clear() + { m_roles = std::bitset(); } + + bool AuthRoles::HasRole(RoleType role) const + { return m_roles.test(role); } + + std::string AuthRoles::Text() const + { return m_roles.to_string(); } + + void AuthRoles::SetText(const std::string& text) + { m_roles = std::bitset(text); } } diff --git a/network/Networking.h b/network/Networking.h index fa5aafbedd9..a50a7639205 100644 --- a/network/Networking.h +++ b/network/Networking.h @@ -2,6 +2,7 @@ #define _Networking_h_ #include +#include #include "../util/Export.h" @@ -10,11 +11,12 @@ namespace Networking { FO_COMMON_API extern const std::string DISCOVERY_ANSWER; FO_COMMON_API extern const int SOCKET_LINGER_TIME; FO_COMMON_API extern const int INVALID_PLAYER_ID; + FO_COMMON_API extern const int NO_TEAM_ID; FO_COMMON_API int DiscoveryPort(); FO_COMMON_API int MessagePort(); - enum ClientType { + enum ClientType : int { INVALID_CLIENT_TYPE = -1, CLIENT_TYPE_AI_PLAYER, CLIENT_TYPE_HUMAN_PLAYER, @@ -22,6 +24,34 @@ namespace Networking { CLIENT_TYPE_HUMAN_MODERATOR, NUM_CLIENT_TYPES }; + + enum RoleType : size_t { + ROLE_HOST = 0, ///< allows save and load games, edit other player settings, stop server + ROLE_CLIENT_TYPE_MODERATOR, ///< allows have a client type Moderator + ROLE_CLIENT_TYPE_PLAYER, ///< allows have a client type Player + ROLE_CLIENT_TYPE_OBSERVER, ///< allows have a client type Observer + ROLE_GALAXY_SETUP, ///< allows change galaxy and AI settings in lobby + + Roles_Count + }; + + class FO_COMMON_API AuthRoles { + public: + AuthRoles() = default; + + AuthRoles(const std::initializer_list& roles); + + void SetRole(RoleType role, bool value = true); + void Clear(); + + bool HasRole(RoleType role) const; + + std::string Text() const; + void SetText(const std::string& text); + private: + std::bitset m_roles; + }; + } #endif diff --git a/Xcode/Info.plist.in b/packaging/Info.plist.in similarity index 100% rename from Xcode/Info.plist.in rename to packaging/Info.plist.in diff --git a/freeorion.desktop b/packaging/org.freeorion.FreeOrion.desktop similarity index 70% rename from freeorion.desktop rename to packaging/org.freeorion.FreeOrion.desktop index 68062658982..190cd5f6986 100644 --- a/freeorion.desktop +++ b/packaging/org.freeorion.FreeOrion.desktop @@ -2,8 +2,7 @@ Version=1.0 Name=FreeOrion GenericName=Turn-based space strategy game -Comment=turn-based space empire and galactic conquest computer game (client) -Comment[de]=Rundenbasierendes Weltraum-Strategiespiel +Comment=Turn-based space empire and galactic conquest (4X) computer game (client) Exec=freeorion TryExec=freeorion Icon=freeorion diff --git a/packaging/org.freeorion.FreeOrion.metainfo.xml b/packaging/org.freeorion.FreeOrion.metainfo.xml new file mode 100644 index 00000000000..135ad3aa5a4 --- /dev/null +++ b/packaging/org.freeorion.FreeOrion.metainfo.xml @@ -0,0 +1,41 @@ + + + org.freeorion.FreeOrion + FreeOrion + Turn-based space empire and galactic conquest (4X) computer game + FreeOrion Project + +

FreeOrion is a free, Open Source, turn-based space empire and galactic conquest computer game.

+

FreeOrion is inspired by the tradition of the Master of Orion games, but does not try to be a clone or remake of that series or any other game. It builds on the classic 4X (eXplore, eXpand, eXploit and eXterminate) model.

+
+ + Game + StrategyGame + + CC-BY-SA-3.0 + GPL-2.0 AND CC-BY-SA-3.0 + org.freeorion.FreeOrion.desktop + https://github.com/freeorion/freeorion/issues + http://www.freeorion.org/index.php/Donations + http://www.freeorion.org/index.php/FAQ + http://www.freeorion.org/forum/viewforum.php?f=25 + http://www.freeorion.org + + + Galaxy map with system statistics + https://www.freeorion.org/screenshots/FreeOrion_GalaxyMapSystemPlanetsFleets.png + + + Galaxy map with combat report + https://www.freeorion.org/screenshots/FreeOrion_GalaxyMapCombatGraph.png + + + Empirial production queue + https://www.freeorion.org/screenshots/FreeOrion_ProductionScreen.png + + + Empirial research tree + https://www.freeorion.org/screenshots/FreeOrion_ResearchScreen.png + + +
diff --git a/Installer/FreeOrion_Install_Script.nsi.in b/packaging/windows_installer.nsi.in similarity index 94% rename from Installer/FreeOrion_Install_Script.nsi.in rename to packaging/windows_installer.nsi.in index 0715162e126..d4063109d38 100644 --- a/Installer/FreeOrion_Install_Script.nsi.in +++ b/packaging/windows_installer.nsi.in @@ -29,7 +29,7 @@ SetCompressor bzip2 !insertmacro MUI_PAGE_INSTFILES ; Finish page !define MUI_FINISHPAGE_RUN "$$INSTDIR\freeorion.exe" -;!define MUI_FINISHPAGE_RUN_PARAMETERS "--fullscreen 1" +;!define MUI_FINISHPAGE_RUN_PARAMETERS "--video.fullscreen.enabled 1" !insertmacro MUI_PAGE_FINISH ; Uninstaller pages @@ -60,17 +60,17 @@ Section "MainSection" SEC01 Delete "$$INSTDIR\vcredist_x86.exe" ${FreeOrion_DLL_LIST_INSTALL} - File "..\Python27.zip" + File "..\Python${FreeOrion_PYTHON_VERSION}.zip" File "..\FreeorionCA.exe" File "..\FreeorionD.exe" File "..\Freeorion.exe" - File "..\FreeOrion.ico" + File "..\client\human\FreeOrion.ico" File "..\ChangeLog.md" File /r /x .git "..\default" CreateDirectory "$$SMPROGRAMS\FreeOrion" - CreateShortCut "$$SMPROGRAMS\FreeOrion\FreeOrion Fullscreen.lnk" "$$INSTDIR\freeorion.exe" "--fullscreen" + CreateShortCut "$$SMPROGRAMS\FreeOrion\FreeOrion Fullscreen.lnk" "$$INSTDIR\freeorion.exe" "--video.fullscreen.enabled 1" CreateShortCut "$$SMPROGRAMS\FreeOrion\FreeOrion Windowed.lnk" "$$INSTDIR\freeorion.exe" CreateShortCut "$$DESKTOP\FreeOrion.lnk" "$$INSTDIR\freeorion.exe" @@ -108,7 +108,7 @@ Section Uninstall SetShellVarContext all ${FreeOrion_DLL_LIST_UNINSTALL} - Delete "$$INSTDIR\Python27.zip" + Delete "$$INSTDIR\Python${FreeOrion_PYTHON_VERSION}.zip" Delete "$$INSTDIR\FreeorionCA.exe" Delete "$$INSTDIR\FreeorionD.exe" Delete "$$INSTDIR\Freeorion.exe" diff --git a/parse/ArithmeticRules.cpp b/parse/ArithmeticRules.cpp new file mode 100644 index 00000000000..401f68da7f5 --- /dev/null +++ b/parse/ArithmeticRules.cpp @@ -0,0 +1,216 @@ +#include "ValueRefParser.h" + +#include "MovableEnvelope.h" +#include "../universe/ValueRefs.h" + +#include + +namespace parse { namespace detail { + + template + arithmetic_rules::arithmetic_rules( + const std::string& type_name, + const parse::lexer& tok, + parse::detail::Labeller& label, + const parse::detail::condition_parser_grammar& condition_parser + ) : + statistic_type_enum(tok) + { + using boost::phoenix::construct; + using boost::phoenix::new_; + using boost::phoenix::push_back; + + boost::spirit::qi::_1_type _1; + boost::spirit::qi::_2_type _2; + boost::spirit::qi::_a_type _a; + boost::spirit::qi::_b_type _b; + boost::spirit::qi::_c_type _c; + boost::spirit::qi::_d_type _d; + boost::spirit::qi::_val_type _val; + boost::spirit::qi::lit_type lit; + boost::spirit::qi::_pass_type _pass; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + const boost::phoenix::function deconstruct_movable_vector_; + + functional_expr + = ( + ( + ( + tok.Sin_ [ _c = ValueRef::SINE ] // single-parameter math functions + | tok.Cos_ [ _c = ValueRef::COSINE ] + | tok.Log_ [ _c = ValueRef::LOGARITHM ] + | tok.Abs_ [ _c = ValueRef::ABS ] + | tok.Round_ [ _c = ValueRef::ROUND_NEAREST ] + | tok.Ceil_ [ _c = ValueRef::ROUND_UP ] + | tok.Floor_ [ _c = ValueRef::ROUND_DOWN ] + ) + >> ('(' > expr > ')') [ _val = construct_movable_(new_>(_c, deconstruct_movable_(_1, _pass))) ] + ) + | ( + tok.RandomNumber_ [ _c = ValueRef::RANDOM_UNIFORM ] // random number requires a min and max value + > ( '(' > expr > ',' > expr > ')' ) [ _val = construct_movable_( + new_>(_c, deconstruct_movable_(_1, _pass), deconstruct_movable_(_2, _pass))) ] + ) + | ( + ( + tok.OneOf_ [ _c = ValueRef::RANDOM_PICK ] // oneof, min, or max can take any number or operands + | tok.Min_ [ _c = ValueRef::MINIMUM ] + | tok.Max_ [ _c = ValueRef::MAXIMUM ] + ) + >> ( '(' >> expr [ push_back(_d, _1) ] + >>(*(',' > expr [ push_back(_d, _1) ] )) >> ')' ) + [ _val = construct_movable_(new_>(_c, deconstruct_movable_vector_(_d, _pass))) ] + ) + | ( + lit('(') >> expr [ push_back(_d, _1) ] + >> ( + ( lit("==") [ _c = ValueRef::COMPARE_EQUAL ] + | lit('=') [ _c = ValueRef::COMPARE_EQUAL ] + | lit(">=") [ _c = ValueRef::COMPARE_GREATER_THAN_OR_EQUAL ] + | lit('>') [ _c = ValueRef::COMPARE_GREATER_THAN ] + | lit("<=") [ _c = ValueRef::COMPARE_LESS_THAN_OR_EQUAL ] + | lit('<') [ _c = ValueRef::COMPARE_LESS_THAN ] + | lit("!=") [ _c = ValueRef::COMPARE_NOT_EQUAL ] + ) + > expr [ push_back(_d, _1) ] + ) + > ( + lit(')') + | ( + (lit('?') > expr [ push_back(_d, _1) ]) + > ( + lit(')') + | ( lit(':') > expr [ push_back(_d, _1) ] > ')' ) + ) + ) + ) [ _val = construct_movable_(new_>(_c, deconstruct_movable_vector_(_d, _pass))) ] + ) + | ( + lit('-') >> functional_expr + // single parameter math function with a function expression + // rather than any arbitrary expression as parameter, because + // negating more general expressions can be ambiguous + [ _val = construct_movable_(new_>(ValueRef::NEGATE, deconstruct_movable_(_1, _pass))) ] + ) + | ( + primary_expr [ _val = _1 ] + ) + ) + ; + + exponential_expr + = ( + functional_expr [ _a = _1 ] + >> + -( '^' + >> functional_expr [ + _b = construct_movable_(new_>( + ValueRef::EXPONENTIATE, + deconstruct_movable_(_a, _pass), + deconstruct_movable_(_1, _pass) )) , + _a = _b] + ) + ) [ _val = _a ] + ; + + multiplicative_expr + = ( + exponential_expr [ _a = _1 ] + >> + *( + ( + ( + lit('*') [ _c = ValueRef::TIMES ] + | lit('/') [ _c = ValueRef::DIVIDE ] + ) + >> exponential_expr [ + _b = construct_movable_(new_>( + _c, + deconstruct_movable_(_a, _pass), + deconstruct_movable_(_1, _pass))) ] + ) [ _a = _b ] + ) + ) [ _val = _a ] + ; + + additive_expr + = ( + multiplicative_expr [ _a = _1 ] + >> + *( + ( + ( + lit('+') [ _c = ValueRef::PLUS ] + | lit('-') [ _c = ValueRef::MINUS ] + ) + >> multiplicative_expr [ + _b = construct_movable_(new_>( + _c, + deconstruct_movable_(_a, _pass), + deconstruct_movable_(_1, _pass))) ] + ) [ _a = _b ] + ) + ) [ _val = _a ] + ; + + statistic_collection_expr + = (tok.Statistic_ + >> ( tok.Count_ [ _b = ValueRef::COUNT ] + | tok.If_ [ _b = ValueRef::IF ] + ) + ) + > label(tok.Condition_) > condition_parser + [ _val = construct_movable_(new_>(deconstruct_movable_(_a, _pass), _b, deconstruct_movable_(_1, _pass))) ] + ; + + statistic_value_expr + = (tok.Statistic_ >> statistic_type_enum [ _b = _1 ]) + > label(tok.Value_) > statistic_value_ref_expr [ _a = _1 ] + > label(tok.Condition_) > condition_parser + [ _val = construct_movable_(new_>(deconstruct_movable_(_a, _pass), _b, deconstruct_movable_(_1, _pass))) ] + ; + + statistic_expr + = statistic_collection_expr + | statistic_value_expr + ; + + expr + = additive_expr + ; + + #if DEBUG_VALUEREF_PARSERS + debug(functional_expr); + debug(exponential_expr); + debug(multiplicative_expr); + debug(additive_expr); + debug(primary_expr); + debug(statistic_value_ref_expr); + debug(statistic_collection_expr); + debug(statistic_value_expr); + debug(statistic_expr); + debug(expr); + #endif + + functional_expr.name(type_name + " function expression"); + exponential_expr.name(type_name + " exponential expression"); + multiplicative_expr.name(type_name + " multiplication expression"); + additive_expr.name(type_name + " additive expression"); + statistic_value_ref_expr.name(type_name + " statistic value reference"); + statistic_collection_expr.name(type_name + " collection statistic"); + statistic_value_expr.name(type_name + " value statistic"); + statistic_expr.name("real number statistic"); + primary_expr.name(type_name + " expression"); + expr.name(type_name + " expression"); + } + + // Explicit instantiation to prevent costly recompilation in multiple units + template arithmetic_rules::arithmetic_rules( + const std::string& type_name, const parse::lexer& tok, parse::detail::Labeller& label, + const parse::detail::condition_parser_grammar& condition_parser); + template arithmetic_rules::arithmetic_rules( + const std::string& type_name, const parse::lexer& tok, parse::detail::Labeller& label, + const parse::detail::condition_parser_grammar& condition_parser); + + }} diff --git a/parse/BuildingsParser.cpp b/parse/BuildingsParser.cpp index bac3dd9a51a..ee8f514a31c 100644 --- a/parse/BuildingsParser.cpp +++ b/parse/BuildingsParser.cpp @@ -4,17 +4,19 @@ #include "EnumParser.h" #include "ConditionParserImpl.h" #include "ValueRefParser.h" -#include "CommonParams.h" +#include "CommonParamsParser.h" -#include "../universe/Building.h" +#include "../universe/BuildingType.h" #include "../universe/Enums.h" +#include "../universe/Condition.h" +#include "../universe/ValueRef.h" #define DEBUG_PARSERS 0 #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector>&) { return os; } + inline ostream& operator<<(ostream& os, const parse::effects_group_payload&) { return os; } inline ostream& operator<<(ostream& os, const std::map>&) { return os; } inline ostream& operator<<(ostream& os, const std::pair>&) { return os; } } @@ -26,22 +28,34 @@ namespace { void insert_building(std::map>& building_types, const std::string& name, const std::string& description, - const CommonParams& common_params, + const parse::detail::MovableEnvelope& common_params, CaptureResult& capture_result, - const std::string& icon) + const std::string& icon, + bool& pass) { - // TODO use make_unique when converting to C++14 - auto building_type = std::unique_ptr( - new BuildingType(name, description, common_params, capture_result, icon)); + auto building_type = std::make_unique( + name, description, *common_params.OpenEnvelope(pass), capture_result, icon); building_types.insert(std::make_pair(building_type->Name(), std::move(building_type))); } - - BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_building_, insert_building, 6) - - struct rules { - rules() { + BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_building_, insert_building, 7) + + using start_rule_payload = std::map>; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, + const parse::text_iterator& last) : + grammar::base_type(start), + condition_parser(tok, label), + string_grammar(tok, label, condition_parser), + tags_parser(tok, label), + common_rules(tok, label, condition_parser, string_grammar, tags_parser), + capture_result_enum(tok) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; @@ -49,27 +63,27 @@ namespace { qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; + qi::_5_type _5; + qi::_6_type _6; qi::_pass_type _pass; + qi::_val_type _val; qi::_r1_type _r1; qi::eps_type eps; - const parse::lexer& tok = parse::lexer::instance(); + capture %= + (label(tok.CaptureResult_) >> capture_result_enum) + | eps [ _val = CR_CAPTURE ] + ; building_type - = tok.BuildingType_ - > parse::detail::label(Name_token) - > tok.string [ _pass = is_unique_(_r1, BuildingType_token, _1), _a = _1 ] - > parse::detail::label(Description_token) > tok.string [ _b = _1 ] - > ( parse::detail::label(CaptureResult_token) >> parse::capture_result_enum() [ _d = _1 ] - | eps [ _d = CR_CAPTURE ] - ) - > parse::detail::common_params_parser() [ _c = _1 ] - > parse::detail::label(Icon_token) > tok.string - [ insert_building_(_r1, _a, _b, _c, _d, _1) ] + = ( tok.BuildingType_ + > label(tok.Name_) > tok.string + > label(tok.Description_) > tok.string + > capture + > common_rules.common + > label(tok.Icon_) > tok.string) + [ _pass = is_unique_(_r1, _1, _2), + insert_building_(_r1, _2, _3, _5, _4, _6, _pass) ] ; start @@ -82,36 +96,34 @@ namespace { debug(building_type); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - void (std::map>&), - boost::spirit::qi::locals< - std::string, - std::string, - CommonParams, - CaptureResult - > - > building_type_rule; - - typedef parse::detail::rule< - void (std::map>&) - > start_rule; - - building_type_rule building_type; - start_rule start; + using building_type_rule = parse::detail::rule< + void (std::map>&)>; + + using start_rule = parse::detail::rule; + + parse::detail::Labeller label; + const parse::conditions_parser_grammar condition_parser; + const parse::string_parser_grammar string_grammar; + parse::detail::tags_grammar tags_parser; + parse::detail::common_params_rules common_rules; + parse::capture_result_enum_grammar capture_result_enum; + parse::detail::rule capture; + building_type_rule building_type; + start_rule start; }; } namespace parse { - bool buildings(std::map>& building_types) { - bool result = true; - - for (const boost::filesystem::path& file : ListScripts("scripting/buildings")) { - result &= detail::parse_file>>(file, building_types); + start_rule_payload buildings(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload building_types; + for (const boost::filesystem::path& file : ListScripts(path)) { + /*auto success =*/ detail::parse_file(lexer, file, building_types); } - return result; + return building_types; } } diff --git a/parse/CMakeLists.txt b/parse/CMakeLists.txt index bf6fec3d131..69cb74d987b 100644 --- a/parse/CMakeLists.txt +++ b/parse/CMakeLists.txt @@ -2,18 +2,32 @@ target_sources(freeorionparseobj PUBLIC ${CMAKE_CURRENT_LIST_DIR}/Parse.h PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/CommonParams.h + ${CMAKE_CURRENT_LIST_DIR}/CommonParamsParser.h ${CMAKE_CURRENT_LIST_DIR}/ConditionParser.h + ${CMAKE_CURRENT_LIST_DIR}/ConditionParser1.h + ${CMAKE_CURRENT_LIST_DIR}/ConditionParser2.h + ${CMAKE_CURRENT_LIST_DIR}/ConditionParser3.h + ${CMAKE_CURRENT_LIST_DIR}/ConditionParser4.h + ${CMAKE_CURRENT_LIST_DIR}/ConditionParser5.h + ${CMAKE_CURRENT_LIST_DIR}/ConditionParser6.h + ${CMAKE_CURRENT_LIST_DIR}/ConditionParser7.h ${CMAKE_CURRENT_LIST_DIR}/ConditionParserImpl.h ${CMAKE_CURRENT_LIST_DIR}/EffectParser.h + ${CMAKE_CURRENT_LIST_DIR}/EffectParser1.h + ${CMAKE_CURRENT_LIST_DIR}/EffectParser2.h + ${CMAKE_CURRENT_LIST_DIR}/EffectParser3.h + ${CMAKE_CURRENT_LIST_DIR}/EffectParser4.h + ${CMAKE_CURRENT_LIST_DIR}/EffectParser5.h ${CMAKE_CURRENT_LIST_DIR}/EffectParserImpl.h ${CMAKE_CURRENT_LIST_DIR}/EnumParser.h + ${CMAKE_CURRENT_LIST_DIR}/EnumValueRefRules.h ${CMAKE_CURRENT_LIST_DIR}/Lexer.h + ${CMAKE_CURRENT_LIST_DIR}/MovableEnvelope.h ${CMAKE_CURRENT_LIST_DIR}/ParseImpl.h ${CMAKE_CURRENT_LIST_DIR}/ReportParseError.h ${CMAKE_CURRENT_LIST_DIR}/Tokens.h ${CMAKE_CURRENT_LIST_DIR}/ValueRefParser.h - ${CMAKE_CURRENT_LIST_DIR}/ValueRefParserImpl.h + ${CMAKE_CURRENT_LIST_DIR}/ArithmeticRules.cpp ${CMAKE_CURRENT_LIST_DIR}/BuildingsParser.cpp ${CMAKE_CURRENT_LIST_DIR}/CommonParamsParser.cpp ${CMAKE_CURRENT_LIST_DIR}/ConditionParser1.cpp @@ -58,7 +72,7 @@ target_sources(freeorionparseobj ${CMAKE_CURRENT_LIST_DIR}/StringComplexValueRefParser.cpp ${CMAKE_CURRENT_LIST_DIR}/StringValueRefParser.cpp ${CMAKE_CURRENT_LIST_DIR}/TechsParser.cpp - ${CMAKE_CURRENT_LIST_DIR}/Tokens.cpp ${CMAKE_CURRENT_LIST_DIR}/UniverseObjectTypeValueRefParser.cpp - ${CMAKE_CURRENT_LIST_DIR}/ValueRefParserImpl.cpp + ${CMAKE_CURRENT_LIST_DIR}/ValueRefParser.cpp + ${CMAKE_CURRENT_LIST_DIR}/VisibilityValueRefParser.cpp ) diff --git a/parse/CommonParams.h b/parse/CommonParams.h deleted file mode 100644 index d1bffb51957..00000000000 --- a/parse/CommonParams.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef _Common_Params_h_ -#define _Common_Params_h_ - -#include "Lexer.h" -#include "ParseImpl.h" -#include "../universe/EnumsFwd.h" -#include "../universe/ShipDesign.h" -#include "../universe/Condition.h" -#include "../universe/Effect.h" - -#include - - -namespace parse { namespace detail { - - typedef rule< - bool () - > producible_rule; - const producible_rule& producible_parser(); - - typedef rule< - void (Condition::ConditionBase*&) - > location_rule; - const location_rule& location_parser(); - - typedef rule< - CommonParams (), - boost::spirit::qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - bool, - std::set, - Condition::ConditionBase*, - std::vector>, - std::map*, Condition::ConditionBase*>>, - std::map*, Condition::ConditionBase*>>, - Condition::ConditionBase* - > - > common_params_rule; - const common_params_rule& common_params_parser(); - - typedef rule< - MoreCommonParams (), - boost::spirit::qi::locals< - std::string, - std::string, - std::set - > - > more_common_params_rule; - const more_common_params_rule& more_common_params_parser(); - -} } - -#endif diff --git a/parse/CommonParamsParser.cpp b/parse/CommonParamsParser.cpp index d9002f2e51c..266d8cb61c5 100644 --- a/parse/CommonParamsParser.cpp +++ b/parse/CommonParamsParser.cpp @@ -1,14 +1,17 @@ #define PHOENIX_LIMIT 11 #define BOOST_RESULT_OF_NUM_ARGS PHOENIX_LIMIT -#include "CommonParams.h" +#include "CommonParamsParser.h" #include "ParseImpl.h" #include "EnumParser.h" #include "ConditionParserImpl.h" +#include "EffectParser.h" #include "ValueRefParser.h" -#include "../universe/Condition.h" +#include "../universe/ConditionAll.h" +#include "../universe/Effect.h" +#include "../universe/ValueRefs.h" #include @@ -16,186 +19,162 @@ namespace phoenix = boost::phoenix; namespace parse { namespace detail { - struct rules { - typedef std::pair*, Condition::ConditionBase*> val_cond_pair; - - - rules() { - namespace qi = boost::spirit::qi; - - using phoenix::new_; - using phoenix::construct; - using phoenix::insert; - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_g_type _g; - qi::_h_type _h; - qi::_i_type _i; - qi::_r1_type _r1; - qi::_r2_type _r2; - qi::_val_type _val; - qi::eps_type eps; - - const parse::lexer& tok = parse::lexer::instance(); - - producible - = tok.Unproducible_ [ _val = false ] - | tok.Producible_ [ _val = true ] - | eps [ _val = true ] - ; - - location - = (label(Location_token) > condition_parser [ _r1 = _1 ]) - | eps [ _r1 = new_() ] - ; - - enqueue_location - = (label(EnqueueLocation_token) > condition_parser [ _r1 = _1 ]) - | eps [ _r1 = new_() ] - ; - - exclusions - = -( - label(Exclusions_token) - >> ( - ('[' > +tok.string [ insert(_r1, _1) ] > ']') - | tok.string [ insert(_r1, _1) ] - ) - ) - ; - - more_common - = - ( label(Name_token) > tok.string [ _a = _1 ] - > label(Description_token) > tok.string [ _b = _1 ] - > exclusions(_c) - ) [ _val = construct(_a, _b, _c) ] - ; - - common - = - ( label(BuildCost_token) > parse::double_value_ref() [ _a = _1 ] - > label(BuildTime_token) > parse::flexible_int_value_ref() [ _b = _1 ] - > producible [ _c = _1 ] - > parse::detail::tags_parser()(_d) - > location(_e) - > enqueue_location(_i) - > -consumption(_g, _h) - > -(label(EffectsGroups_token)> parse::detail::effects_group_parser() [ _f = _1 ]) - ) [ _val = construct(_a, _b, _c, _d, _e, _f, _g, _h, _i) ] - ; - - consumption - = label(Consumption_token) > - ( consumable_meter(_r1, _r2) - | consumable_special(_r1, _r2) - | - ( - ( '[' >> * - ( consumable_meter(_r1, _r2) - | consumable_special(_r1, _r2) - ) + /** Open parsed envelopes of consumption pairs. Return a map of unique_ptr. */ + template + ConsumptionMap OpenConsumptionEnvelopes( + const common_params_rules::ConsumptionMapPackaged& in, bool& pass) + { + ConsumptionMap retval; + for (auto&& name_and_values : in) + retval[name_and_values.first] = { + name_and_values.second.first.OpenEnvelope(pass), + (name_and_values.second.second ? name_and_values.second.second->OpenEnvelope(pass) : nullptr)}; + return retval; + } + + common_params_rules::common_params_rules( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar, + const tags_grammar_type& tags_parser + ) : + castable_int_rules(tok, label, condition_parser, string_grammar), + double_rules(tok, label, condition_parser, string_grammar), + effects_group_grammar(tok, label, condition_parser, string_grammar), + non_ship_part_meter_type_enum(tok), + repeated_string(tok) + { + namespace qi = boost::spirit::qi; + + using phoenix::new_; + using phoenix::construct; + using phoenix::insert; + + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_5_type _5; + qi::_6_type _6; + qi::_7_type _7; + qi::_a_type _a; + qi::_b_type _b; + qi::_r1_type _r1; + qi::_r2_type _r2; + qi::_val_type _val; + qi::eps_type eps; + qi::_pass_type _pass; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + const boost::phoenix::function deconstruct_movable_vector_; + + producible + = tok.Unproducible_ [ _val = false ] + | tok.Producible_ [ _val = true ] + | eps [ _val = true ] + ; + + location + %= (label(tok.Location_) > condition_parser) + | eps [ _val = construct_movable_(new_()) ] + ; + + enqueue_location + %= (label(tok.EnqueueLocation_) > condition_parser) + | eps [ _val = construct_movable_(new_()) ] + ; + + exclusions + = + -(label(tok.Exclusions_) >> repeated_string) + ; + + more_common + = + ( label(tok.Name_) > tok.string + > label(tok.Description_) > tok.string + > exclusions + ) [ _val = construct(_1, _2, _3) ] + ; + + common + = + ( label(tok.BuildCost_) > double_rules.expr + > label(tok.BuildTime_) > castable_int_rules.flexible_int + > producible + > tags_parser + > location + > enqueue_location + > -consumption(_a, _b) + > -(label(tok.EffectsGroups_)> effects_group_grammar ) + ) [ _val = construct_movable_( + new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + _3, _4, + deconstruct_movable_(_5, _pass), + deconstruct_movable_vector_(_7, _pass), + phoenix::bind(&parse::detail::OpenConsumptionEnvelopes, _a, _pass), + phoenix::bind(&parse::detail::OpenConsumptionEnvelopes, _b, _pass), + deconstruct_movable_(_6, _pass))) ] + ; + + consumption + = label(tok.Consumption_) > + ( consumable_meter(_r1) + | consumable_special(_r2) + | + ( + ( '[' >> * + ( consumable_meter(_r1) + | consumable_special(_r2) ) - > ']' ) + > ']' ) - ; - - typedef std::map::value_type special_consumable_map_value_type; - consumable_special - = tok.Special_ - > ( - label(Name_token) > tok.string [ _b = _1 ] - > label(Consumption_token) > parse::double_value_ref() [ _c = _1 ] - > -(label(Condition_token) > parse::detail::condition_parser [ _d = _1 ]) - ) - [ insert(_r2, construct(_b, construct(_c, _d))) ] - ; - - typedef std::map::value_type meter_consumable_map_value_type; - consumable_meter - = ( - parse::non_ship_part_meter_type_enum() [ _a = _1 ] - > label(Consumption_token) > parse::double_value_ref() [ _c = _1 ] - > -(label(Condition_token) > parse::detail::condition_parser [ _d = _1 ]) - ) - [ insert(_r1, construct(_a, construct(_c, _d))) ] - ; - - producible.name("Producible or Unproducible"); - location.name("Location"); - enqueue_location.name("Enqueue Location"); - exclusions.name("Exclusions"); - more_common.name("More Common Parameters"); - common.name("Common Paramaters"); - consumption.name("Consumption"); - consumable_special.name("Consumable Special"); - consumable_meter.name("Consumable Meter"); + ) + ; + + consumable_special + = tok.Special_ + > ( + label(tok.Name_) > tok.string + > label(tok.Consumption_) > double_rules.expr + > -(label(tok.Condition_) > condition_parser ) + ) + [ insert(_r1, construct::value_type>(_1, construct::mapped_type>(_2, _3))) ] + ; + + consumable_meter + = ( + non_ship_part_meter_type_enum + > label(tok.Consumption_) > double_rules.expr + > -(label(tok.Condition_) > condition_parser ) + ) + [ insert(_r1, construct::value_type>(_1, construct::mapped_type>(_2, _3))) ] + ; + + producible.name("Producible or Unproducible"); + location.name("Location"); + enqueue_location.name("Enqueue Location"); + exclusions.name("Exclusions"); + more_common.name("More Common Parameters"); + common.name("Common Paramaters"); + consumption.name("Consumption"); + consumable_special.name("Consumable Special"); + consumable_meter.name("Consumable Meter"); #if DEBUG_PARSERS - debug(producible); - debug(location); - debug(enqueue_location); - debug(exclusions); - debug(more_common); - debug(common); - debug(consumption); - debug(con_special); - debug(consumable_meter); + debug(producible); + debug(location); + debug(enqueue_location); + debug(exclusions); + debug(more_common); + debug(common); + debug(consumption); + debug(con_special); + debug(consumable_meter); #endif - } - - typedef rule< - void (std::map&, - std::map&) - > consumption_rule; - - typedef rule< - void (std::map&, std::map&), - boost::spirit::qi::locals< - MeterType, - std::string, - ValueRef::ValueRefBase*, - Condition::ConditionBase* - > - > consumable_rule; - - typedef rule< - void (std::set&) - > exclusions_rule; - - producible_rule producible; - location_rule location; - location_rule enqueue_location; - exclusions_rule exclusions; - more_common_params_rule more_common; - common_params_rule common; - consumption_rule consumption; - consumable_rule consumable_special; - consumable_rule consumable_meter; - }; - - rules& GetRules() { - static rules retval; - return retval; } - - const producible_rule& producible_parser() - { return GetRules().producible; } - - const location_rule& location_parser() - { return GetRules().location; } - - const common_params_rule& common_params_parser() - { return GetRules().common; } - - const more_common_params_rule& more_common_params_parser() - { return GetRules().more_common; } } } - diff --git a/parse/CommonParamsParser.h b/parse/CommonParamsParser.h new file mode 100644 index 00000000000..70b2174d7de --- /dev/null +++ b/parse/CommonParamsParser.h @@ -0,0 +1,64 @@ +#ifndef _Common_Params_Parser_h_ +#define _Common_Params_Parser_h_ + +#include "Lexer.h" +#include "ParseImpl.h" +#include "ValueRefParser.h" +#include "EffectParser.h" +#include "EnumParser.h" +#include "MovableEnvelope.h" +#include "../universe/EnumsFwd.h" +#include "../universe/CommonParams.h" + +#include + + +namespace parse { namespace detail { + + struct common_params_rules { + template + using ConsumptionMapPackaged = std::map, boost::optional>>; + + using common_params_rule = rule< + MovableEnvelope (), + boost::spirit::qi::locals< + ConsumptionMapPackaged, + ConsumptionMapPackaged + > + >; + + using consumption_rule = rule< + void (ConsumptionMapPackaged&, ConsumptionMapPackaged&) + >; + + template + using consumable_rule = rule< + void (ConsumptionMapPackaged&) + >; + + common_params_rules(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar, + const tags_grammar_type& tags_parser); + + parse::castable_as_int_parser_rules castable_int_rules; + parse::double_parser_rules double_rules; + parse::effects_group_grammar effects_group_grammar; + parse::non_ship_part_meter_enum_grammar non_ship_part_meter_type_enum; + rule producible; + condition_parser_rule location; + condition_parser_rule enqueue_location; + single_or_repeated_string> repeated_string; + rule ()> exclusions; + rule more_common; + common_params_rule common; + consumption_rule consumption; + consumable_rule consumable_special; + consumable_rule consumable_meter; + }; + +} } + +#endif // _Common_Params_Parser_h_ diff --git a/parse/ConditionParser.cpp b/parse/ConditionParser.cpp index 8844b44ea83..32eaef4a052 100644 --- a/parse/ConditionParser.cpp +++ b/parse/ConditionParser.cpp @@ -1,29 +1,59 @@ #include "ConditionParserImpl.h" +#include "ConditionParser1.h" +#include "ConditionParser2.h" +#include "ConditionParser3.h" +#include "ConditionParser4.h" +#include "ConditionParser5.h" +#include "ConditionParser6.h" +#include "ConditionParser7.h" + namespace parse { - namespace detail { - condition_parser_rule condition_parser; - } + struct conditions_parser_grammar::Impl { + Impl(conditions_parser_grammar& conditions_parser_grammar, + const parse::lexer& tok, + detail::Labeller& label + ) : + string_grammar(tok, label, conditions_parser_grammar), + condition_parser_1(tok, label, conditions_parser_grammar, string_grammar), + condition_parser_2(tok, label, conditions_parser_grammar, string_grammar), + condition_parser_3(tok, label, conditions_parser_grammar, string_grammar), + condition_parser_4(tok, label, conditions_parser_grammar, string_grammar), + condition_parser_5(tok, label, conditions_parser_grammar, string_grammar), + condition_parser_6(tok, label, conditions_parser_grammar, string_grammar), + condition_parser_7(tok, label, conditions_parser_grammar, string_grammar) + {} - condition_parser_rule& condition_parser() { - static bool once = true; - if (once) { - once = false; - detail::condition_parser - %= detail::condition_parser_1() - | detail::condition_parser_2() - | detail::condition_parser_3() - | detail::condition_parser_4() - | detail::condition_parser_5() - | detail::condition_parser_6() - | detail::condition_parser_7() - ; - detail::condition_parser.name("Condition"); -#if DEBUG_CONDITION_PARSERS - debug(detail::condition_parser); -#endif - } - return detail::condition_parser; + const parse::string_parser_grammar string_grammar; + detail::condition_parser_rules_1 condition_parser_1; + detail::condition_parser_rules_2 condition_parser_2; + detail::condition_parser_rules_3 condition_parser_3; + detail::condition_parser_rules_4 condition_parser_4; + detail::condition_parser_rules_5 condition_parser_5; + detail::condition_parser_rules_6 condition_parser_6; + detail::condition_parser_rules_7 condition_parser_7; + }; + + conditions_parser_grammar::conditions_parser_grammar( + const parse::lexer& tok, + detail::Labeller& label + ) : + conditions_parser_grammar::base_type(start, "conditions_parser_grammar"), + m_impl(std::make_unique(*this, tok, label)) + { + start + = m_impl->condition_parser_1 + | m_impl->condition_parser_2 + | m_impl->condition_parser_3 + | m_impl->condition_parser_4 + | m_impl->condition_parser_5 + | m_impl->condition_parser_6 + | m_impl->condition_parser_7 + ; + start.name("Condition"); } + + conditions_parser_grammar::~conditions_parser_grammar() + {} } diff --git a/parse/ConditionParser.h b/parse/ConditionParser.h index 9d00bc91391..008705b9444 100644 --- a/parse/ConditionParser.h +++ b/parse/ConditionParser.h @@ -3,20 +3,39 @@ #include "Lexer.h" #include "ParseImpl.h" +#include "ValueRefParser.h" +#include "EnumParser.h" +#include "MovableEnvelope.h" #include namespace Condition { - struct ConditionBase; + enum SortingMethod : int; + enum ComparisonType : int; + enum ContentType : int; + struct Condition; } +namespace parse { namespace detail { + using condition_payload = MovableEnvelope; + using condition_signature = condition_payload (); + using condition_parser_rule = rule; + using condition_parser_grammar = grammar; +}} + namespace parse { - typedef detail::rule< - Condition::ConditionBase* () - > condition_parser_rule; + struct conditions_parser_grammar : public detail::condition_parser_grammar { + conditions_parser_grammar( + const parse::lexer& tok, + detail::Labeller& label); + ~conditions_parser_grammar(); + + detail::condition_parser_rule start; - /** Returns a const reference to the Condition parser. */ - condition_parser_rule& condition_parser(); + private: + struct Impl; + std::unique_ptr m_impl; + }; } #endif diff --git a/parse/ConditionParser1.cpp b/parse/ConditionParser1.cpp index 73c49573da5..8fc9aa49e95 100644 --- a/parse/ConditionParser1.cpp +++ b/parse/ConditionParser1.cpp @@ -1,250 +1,219 @@ -#include "ConditionParserImpl.h" +#include "ConditionParser1.h" -#include "ParseImpl.h" -#include "EnumParser.h" -#include "ValueRefParser.h" -#include "../universe/Condition.h" -#include "../universe/ValueRef.h" +#include "../universe/Conditions.h" #include "../universe/Enums.h" +#include "../universe/ValueRef.h" #include namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; - #if DEBUG_CONDITION_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector&) { return os; } } #endif -namespace { - struct condition_parser_rules_1 { - condition_parser_rules_1() { - const parse::lexer& tok = parse::lexer::instance(); - - qi::_1_type _1; - qi::_a_type _a; - qi::_val_type _val; - qi::eps_type eps; - qi::lit_type lit; - using phoenix::new_; - using phoenix::push_back; - - all - = tok.All_ [ _val = new_() ] - ; - - none - = tok.None_ [ _val = new_() ] - ; - - source - = tok.Source_ [ _val = new_() ] - ; - - root_candidate - = tok.RootCandidate_ [ _val = new_() ] - ; - - target - = tok.Target_ [ _val = new_() ] - ; - - stationary - = tok.Stationary_ [ _val = new_() ] - ; - - aggressive - = ((tok.Aggressive_ [ _val = new_(true) ]) - |(tok.Passive_ [ _val = new_(false) ]) - ) - ; - - can_colonize - = tok.CanColonize_ [ _val = new_() ] - ; - - can_produce_ships - = tok.CanProduceShips_ [ _val = new_() ] - ; - - capital - = tok.Capital_ [ _val = new_() ] - ; - - monster - = tok.Monster_ [ _val = new_() ] - ; - - armed - = tok.Armed_ [ _val = new_() ] - ; - - owned_by_1 - = (tok.OwnedBy_ - >> parse::detail::label(Empire_token) - ) > parse::int_value_ref() - [ _val = new_(_1) ] - ; - - owned_by_2 - = tok.OwnedBy_ - >> parse::detail::label(Affiliation_token) >> tok.AnyEmpire_ - [ _val = new_( AFFIL_ANY ) ] - ; - - owned_by_3 - = tok.Unowned_ - [ _val = new_( AFFIL_NONE ) ] - ; - - owned_by_4 - = tok.Human_ - [ _val = new_( AFFIL_HUMAN ) ] - ; - - owned_by_5 - = (tok.OwnedBy_ - >> parse::detail::label(Affiliation_token) >> parse::empire_affiliation_type_enum() [ _a = _1 ] - >> parse::detail::label(Empire_token) ) > parse::int_value_ref() - [ _val = new_(_1, _a) ] - ; - - owned_by - %= owned_by_1 - | owned_by_2 - | owned_by_3 - | owned_by_4 - | owned_by_5 - ; - - and_ - = tok.And_ - > '[' > +parse::detail::condition_parser [ push_back(_a, _1) ] > lit(']') - [ _val = new_(_a) ] - ; - - or_ - = tok.Or_ - > '[' > +parse::detail::condition_parser [ push_back(_a, _1) ] > lit(']') - [ _val = new_(_a) ] - ; - - not_ - = tok.Not_ - > parse::detail::condition_parser [ _val = new_(_1) ] - ; - - described - = tok.Described_ - > parse::detail::label(Description_token) > tok.string [ _a = _1 ] - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_1, _a) ] - ; - - start - %= all - | none - | source - | root_candidate - | target - | stationary - | aggressive - | can_colonize - | can_produce_ships - | capital - | monster - | armed - | owned_by - | and_ - | or_ - | not_ - | described - ; - - all.name("All"); - none.name("None"); - source.name("Source"); - root_candidate.name("RootCandidate"); - target.name("Target"); - stationary.name("Stationary"); - aggressive.name("Aggressive"); - can_colonize.name("CanColonize"); - can_produce_ships.name("CanProduceShips"); - capital.name("Capital"); - monster.name("Monster"); - armed.name("Armed"); - owned_by.name("OwnedBy"); // TODO: Should this be renamed Affilated or similar? - and_.name("And"); - or_.name("Or"); - not_.name("Not"); - described.name("Described"); +namespace parse { namespace detail { + condition_parser_rules_1::condition_parser_rules_1( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + condition_parser_rules_1::base_type(start, "condition_parser_rules_1"), + int_rules(tok, label, condition_parser, string_grammar), + empire_affiliation_type_enum(tok) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_val_type _val; + qi::eps_type eps; + qi::lit_type lit; + qi::_pass_type _pass; + qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + const boost::phoenix::function deconstruct_movable_vector_; + + using phoenix::new_; + using phoenix::construct; + using phoenix::push_back; + + all + = tok.All_ [ _val = construct_movable_(new_()) ] + ; + + none + = tok.None_ [ _val = construct_movable_(new_()) ] + ; + + source + = tok.Source_ [ _val = construct_movable_(new_()) ] + ; + + root_candidate + = tok.RootCandidate_ [ _val = construct_movable_(new_()) ] + ; + + target + = tok.Target_ [ _val = construct_movable_(new_()) ] + ; + + stationary + = tok.Stationary_ [ _val = construct_movable_(new_()) ] + ; + + aggressive + = ((tok.Aggressive_ [ _val = construct_movable_(new_(true)) ]) + |(tok.Passive_ [ _val = construct_movable_(new_(false)) ]) + ) + ; + + can_colonize + = tok.CanColonize_ [ _val = construct_movable_(new_()) ] + ; + + can_produce_ships + = tok.CanProduceShips_ [ _val = construct_movable_(new_()) ] + ; + + capital + = tok.Capital_ [ _val = construct_movable_(new_()) ] + ; + + monster + = tok.Monster_ [ _val = construct_movable_(new_()) ] + ; + + armed + = tok.Armed_ [ _val = construct_movable_(new_()) ] + ; + + owned_by_1 + = ( tok.OwnedBy_ + >> label(tok.Empire_) + ) > int_rules.expr + [ _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + owned_by_2 + = tok.OwnedBy_ + >> label(tok.Affiliation_) >> tok.AnyEmpire_ + [ _val = construct_movable_(new_( AFFIL_ANY )) ] + ; + + owned_by_3 + = tok.Unowned_ + [ _val = construct_movable_(new_( AFFIL_NONE )) ] + ; + + owned_by_4 + = tok.Human_ + [ _val = construct_movable_(new_( AFFIL_HUMAN )) ] + ; + + owned_by_5 + = ((omit_[tok.OwnedBy_] + >> label(tok.Affiliation_) >> empire_affiliation_type_enum + >> label(tok.Empire_) ) > int_rules.expr) + [ _val = construct_movable_(new_(deconstruct_movable_(_2, _pass), _1)) ] + ; + + owned_by + %= owned_by_1 + | owned_by_2 + | owned_by_3 + | owned_by_4 + | owned_by_5 + ; + + and_ + = ( omit_[tok.And_] > '[' > +condition_parser > lit(']')) + [ _val = construct_movable_(new_(deconstruct_movable_vector_(_1, _pass))) ] + ; + + or_ + = ( omit_[tok.Or_] > '[' > +condition_parser > lit(']')) + [ _val = construct_movable_(new_(deconstruct_movable_vector_(_1, _pass))) ] + ; + + not_ + = tok.Not_ > condition_parser + [ _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + ordered_alternatives_of + = ( omit_[tok.OrderedAlternativesOf_] > '[' > +condition_parser > lit(']')) + [ _val = construct_movable_(new_(deconstruct_movable_vector_(_1, _pass))) ] + ; + + described + = ( omit_[tok.Described_] + > label(tok.Description_) > tok.string + > label(tok.Condition_) > condition_parser) + [ _val = construct_movable_( new_(deconstruct_movable_(_2, _pass), _1)) ] + ; + + start + %= all + | none + | source + | root_candidate + | target + | stationary + | aggressive + | can_colonize + | can_produce_ships + | capital + | monster + | armed + | owned_by + | and_ + | or_ + | not_ + | ordered_alternatives_of + | described + ; + + all.name("All"); + none.name("None"); + source.name("Source"); + root_candidate.name("RootCandidate"); + target.name("Target"); + stationary.name("Stationary"); + aggressive.name("Aggressive"); + can_colonize.name("CanColonize"); + can_produce_ships.name("CanProduceShips"); + capital.name("Capital"); + monster.name("Monster"); + armed.name("Armed"); + owned_by.name("OwnedBy"); // TODO: Should this be renamed Affilated or similar? + and_.name("And"); + or_.name("Or"); + not_.name("Not"); + ordered_alternatives_of.name("OrderedAlternativesOf"); + described.name("Described"); #if DEBUG_CONDITION_PARSERS - debug(all); - debug(none); - debug(source); - debug(root_candidate); - debug(target); - debug(stationary); - debug(aggressive); - debug(capital); - debug(monster); - debug(armed); - debug(owned_by); - debug(and_); - debug(or_); - debug(not_); - debug(described); + debug(all); + debug(none); + debug(source); + debug(root_candidate); + debug(target); + debug(stationary); + debug(aggressive); + debug(capital); + debug(monster); + debug(armed); + debug(owned_by); + debug(and_); + debug(or_); + debug(not_); + debug(ordered_alternatives_of); + debug(described); #endif - } - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals - > owned_by_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals> - > and_or_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals - > described_rule; - - parse::condition_parser_rule all; - parse::condition_parser_rule none; - parse::condition_parser_rule source; - parse::condition_parser_rule root_candidate; - parse::condition_parser_rule target; - parse::condition_parser_rule stationary; - parse::condition_parser_rule aggressive; - parse::condition_parser_rule can_colonize; - parse::condition_parser_rule can_produce_ships; - parse::condition_parser_rule capital; - parse::condition_parser_rule monster; - parse::condition_parser_rule armed; - parse::condition_parser_rule owned_by_1; - parse::condition_parser_rule owned_by_2; - parse::condition_parser_rule owned_by_3; - parse::condition_parser_rule owned_by_4; - owned_by_rule owned_by_5; - parse::condition_parser_rule owned_by; - and_or_rule and_; - and_or_rule or_; - parse::condition_parser_rule not_; - described_rule described; - parse::condition_parser_rule start; - }; -} - -namespace parse { namespace detail { - const condition_parser_rule& condition_parser_1() { - static condition_parser_rules_1 retval; - return retval.start; } + } } diff --git a/parse/ConditionParser1.h b/parse/ConditionParser1.h new file mode 100644 index 00000000000..3871906cf44 --- /dev/null +++ b/parse/ConditionParser1.h @@ -0,0 +1,45 @@ +#ifndef _ConditionParser1_h_ +#define _ConditionParser1_h_ + +#include "ConditionParserImpl.h" + +namespace parse { namespace detail { + + struct condition_parser_rules_1 : public condition_parser_grammar { + condition_parser_rules_1(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + parse::int_arithmetic_rules int_rules; + parse::empire_affiliation_enum_grammar empire_affiliation_type_enum; + condition_parser_rule all; + condition_parser_rule none; + condition_parser_rule source; + condition_parser_rule root_candidate; + condition_parser_rule target; + condition_parser_rule stationary; + condition_parser_rule aggressive; + condition_parser_rule can_colonize; + condition_parser_rule can_produce_ships; + condition_parser_rule capital; + condition_parser_rule monster; + condition_parser_rule armed; + condition_parser_rule owned_by_1; + condition_parser_rule owned_by_2; + condition_parser_rule owned_by_3; + condition_parser_rule owned_by_4; + condition_parser_rule owned_by_5; + condition_parser_rule owned_by; + condition_parser_rule and_; + condition_parser_rule or_; + condition_parser_rule not_; + condition_parser_rule ordered_alternatives_of; + condition_parser_rule described; + condition_parser_rule start; + }; + + } +} + +#endif // _ConditionParser1_h_ diff --git a/parse/ConditionParser2.cpp b/parse/ConditionParser2.cpp index 10533466777..6cf05e9994c 100644 --- a/parse/ConditionParser2.cpp +++ b/parse/ConditionParser2.cpp @@ -1,158 +1,163 @@ -#include "ConditionParserImpl.h" +#include "ConditionParser2.h" -#include "ParseImpl.h" -#include "EnumParser.h" -#include "ValueRefParser.h" -#include "../universe/Condition.h" +#include "../universe/Conditions.h" #include "../universe/Enums.h" +#include "../universe/ValueRef.h" #include namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; - -namespace { - struct condition_parser_rules_2 { - condition_parser_rules_2() { - const parse::lexer& tok = parse::lexer::instance(); - - qi::_1_type _1; - qi::_a_type _a; // intref - qi::_b_type _b; // intref - qi::_c_type _c; // intref - qi::_d_type _d; // intref - qi::_e_type _e; // string - qi::_val_type _val; - qi::eps_type eps; - using phoenix::new_; - - has_special_since_turn - = ( tok.HasSpecialSinceTurn_ - > parse::detail::label(Name_token) > parse::string_value_ref() [ _e = _1 ] - > -(parse::detail::label(Low_token) > parse::flexible_int_value_ref() [ _a = _1 ] ) - > -(parse::detail::label(High_token) > parse::flexible_int_value_ref() [ _b = _1 ] ) - ) [ _val = new_(_e, _a, _b) ] - ; - - enqueued - = enqueued1 - | enqueued3 - | enqueued2 /* enqueued2 must come after enqueued3 or enqueued2 would always dominate because of its optional components*/ - | enqueued4 - ; - - enqueued1 - = ( (tok.Enqueued_ - >> parse::detail::label(Type_token) >> tok.Building_) - > -(parse::detail::label(Name_token) > parse::string_value_ref() [ _e = _1 ]) - > -(parse::detail::label(Empire_token) > parse::int_value_ref() [ _a = _1 ]) - > -(parse::detail::label(Low_token) > parse::flexible_int_value_ref() [ _b = _1 ]) - > -(parse::detail::label(High_token) > parse::flexible_int_value_ref() [ _c = _1 ]) - ) [ _val = new_(BT_BUILDING, _e, _a, _b, _c) ] - ; - - enqueued2 - = ( (tok.Enqueued_ - >> parse::detail::label(Type_token) >> tok.Ship_) - > -(parse::detail::label(Design_token) > parse::int_value_ref() [ _d = _1 ]) - > -(parse::detail::label(Empire_token) > parse::int_value_ref() [ _a = _1 ]) - > -(parse::detail::label(Low_token) > parse::flexible_int_value_ref() [ _b = _1 ]) - > -(parse::detail::label(High_token) > parse::flexible_int_value_ref() [ _c = _1 ]) - ) [ _val = new_(_d, _a, _b, _c) ] - ; - - enqueued3 - = ( (tok.Enqueued_ - >> parse::detail::label(Type_token) >> tok.Ship_ - >> parse::detail::label(Name_token) ) > parse::string_value_ref() [ _e = _1 ] - > -(parse::detail::label(Empire_token) > parse::int_value_ref() [ _a = _1 ]) - > -(parse::detail::label(Low_token) > parse::flexible_int_value_ref() [ _b = _1 ]) - > -(parse::detail::label(High_token) > parse::flexible_int_value_ref() [ _c = _1 ]) - ) [ _val = new_(BT_SHIP, _e, _a, _b, _c) ] - ; - - enqueued4 - = ( tok.Enqueued_ - > -(parse::detail::label(Empire_token) > parse::int_value_ref() [ _a = _1 ]) - > -(parse::detail::label(Low_token) > parse::flexible_int_value_ref() [ _b = _1 ]) - > -(parse::detail::label(High_token) > parse::flexible_int_value_ref() [ _c = _1 ]) - ) [ _val = new_(INVALID_BUILD_TYPE, _e, _a, _b, _c) ] - ; - - design_has_part - = ( tok.DesignHasPart_ - > -(parse::detail::label(Low_token) > parse::flexible_int_value_ref() [ _a = _1 ]) - > -(parse::detail::label(High_token) > parse::flexible_int_value_ref() [ _b = _1 ]) - ) > parse::detail::label(Name_token) > parse::string_value_ref() - [ _val = new_(_1, _a, _b) ] - ; - - design_has_part_class - = ( tok.DesignHasPartClass_ - > -(parse::detail::label(Low_token) > parse::flexible_int_value_ref() [ _a = _1 ]) - > -(parse::detail::label(High_token) > parse::flexible_int_value_ref() [ _b = _1 ]) - ) > parse::detail::label(Class_token) > parse::ship_part_class_enum() - [ _val = new_(_1, _a, _b) ] - ; - - in_system - = ( tok.InSystem_ - > -(parse::detail::label(ID_token) > parse::int_value_ref() [ _a = _1 ]) - ) - [ _val = new_(_a) ] - ; - - start - %= has_special_since_turn - | enqueued - | design_has_part - | design_has_part_class - | in_system - ; - - has_special_since_turn.name("HasSpecialSinceTurn"); - enqueued.name("Enqueued"); - design_has_part.name("DesignHasPart"); - design_has_part_class.name("DesignHasPartClass"); - in_system.name("InSystem"); +namespace parse { namespace detail { + condition_parser_rules_2::condition_parser_rules_2( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + condition_parser_rules_2::base_type(start, "condition_parser_rules_2"), + int_rules(tok, label, condition_parser, string_grammar), + castable_int_rules(tok, label, condition_parser, string_grammar), + ship_part_class_enum(tok) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_val_type _val; + qi::eps_type eps; + qi::_pass_type _pass; + qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + using phoenix::new_; + using phoenix::construct; + + has_special_since_turn + = ( omit_[tok.HasSpecialSinceTurn_] + > label(tok.Name_) > string_grammar + > -(label(tok.Low_) > castable_int_rules.flexible_int ) + > -(label(tok.High_) > castable_int_rules.flexible_int ) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass))) ] + ; + + enqueued + %= enqueued1 + | enqueued3 + | enqueued2 /* enqueued2 must come after enqueued3 or enqueued2 would always dominate because of its optional components*/ + | enqueued4 + ; + + enqueued1 + = ( (omit_[tok.Enqueued_] + >> label(tok.Type_) >> omit_[tok.Building_]) + > -(label(tok.Name_) > string_grammar) + > -(label(tok.Empire_) > int_rules.expr) + > -(label(tok.Low_) > castable_int_rules.flexible_int) + > -(label(tok.High_) > castable_int_rules.flexible_int) + ) [ _val = construct_movable_(new_( + BT_BUILDING, + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_4, _pass))) ] + ; + + enqueued2 + = ( (omit_[tok.Enqueued_] + >> label(tok.Type_) >> omit_[tok.Ship_]) + > -(label(tok.Design_) > int_rules.expr) + > -(label(tok.Empire_) > int_rules.expr) + > -(label(tok.Low_) > castable_int_rules.flexible_int) + > -(label(tok.High_) > castable_int_rules.flexible_int) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_4, _pass))) ] + ; + + enqueued3 + = ( (omit_[tok.Enqueued_] + >> label(tok.Type_) >> omit_[tok.Ship_] + >> label(tok.Name_) ) > string_grammar + > -(label(tok.Empire_) > int_rules.expr) + > -(label(tok.Low_) > castable_int_rules.flexible_int) + > -(label(tok.High_) > castable_int_rules.flexible_int) + ) [ _val = construct_movable_(new_( + BT_SHIP, + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_4, _pass))) ] + ; + + enqueued4 + = ( omit_[tok.Enqueued_] + > -(label(tok.Empire_) > int_rules.expr) + > -(label(tok.Low_) > castable_int_rules.flexible_int) + > -(label(tok.High_) > castable_int_rules.flexible_int) + ) [ _val = construct_movable_(new_( + INVALID_BUILD_TYPE, + nullptr, + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass))) ] + ; + + design_has_part + = ( omit_[tok.DesignHasPart_] + > -(label(tok.Low_) > castable_int_rules.flexible_int) + > -(label(tok.High_) > castable_int_rules.flexible_int) + > label(tok.Name_) > string_grammar + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + design_has_part_class + = ( omit_[tok.DesignHasPartClass_] + > -(label(tok.Low_) > castable_int_rules.flexible_int) + > -(label(tok.High_) > castable_int_rules.flexible_int) + > label(tok.Class_) > ship_part_class_enum + ) [ _val = construct_movable_(new_( + _3, + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + in_system + = ( omit_[tok.InSystem_] + > -(label(tok.ID_) > int_rules.expr) + ) [ _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + start + %= has_special_since_turn + | enqueued + | design_has_part + | design_has_part_class + | in_system + ; + + has_special_since_turn.name("HasSpecialSinceTurn"); + enqueued.name("Enqueued"); + design_has_part.name("DesignHasPart"); + design_has_part_class.name("DesignHasPartClass"); + in_system.name("InSystem"); #if DEBUG_CONDITION_PARSERS - debug(has_special_since_turn); - debug(enqueued); - debug(design_has_part); - debug(design_has_part_class); - debug(in_system); + debug(has_special_since_turn); + debug(enqueued); + debug(design_has_part); + debug(design_has_part_class); + debug(in_system); #endif - } - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > - > common_rule; - - common_rule has_special_since_turn; - common_rule enqueued; - common_rule enqueued1; - common_rule enqueued2; - common_rule enqueued3; - common_rule enqueued4; - common_rule design_has_part; - common_rule design_has_part_class; - common_rule in_system; - parse::condition_parser_rule start; - }; -} - -namespace parse { namespace detail { - const condition_parser_rule& condition_parser_2() { - static condition_parser_rules_2 retval; - return retval.start; } + } } diff --git a/parse/ConditionParser2.h b/parse/ConditionParser2.h new file mode 100644 index 00000000000..c4ad1e8d1c3 --- /dev/null +++ b/parse/ConditionParser2.h @@ -0,0 +1,31 @@ +#ifndef _ConditionParser2_h_ +#define _ConditionParser2_h_ + +#include "ConditionParserImpl.h" + +namespace parse { namespace detail { + struct condition_parser_rules_2 : public condition_parser_grammar { + condition_parser_rules_2(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + parse::int_arithmetic_rules int_rules; + parse::castable_as_int_parser_rules castable_int_rules; + parse::ship_part_class_enum_grammar ship_part_class_enum; + condition_parser_rule has_special_since_turn; + condition_parser_rule enqueued; + condition_parser_rule enqueued1; + condition_parser_rule enqueued2; + condition_parser_rule enqueued3; + condition_parser_rule enqueued4; + condition_parser_rule design_has_part; + condition_parser_rule design_has_part_class; + condition_parser_rule in_system; + condition_parser_rule start; + }; + + } +} + +#endif // _ConditionParser2_h_ diff --git a/parse/ConditionParser3.cpp b/parse/ConditionParser3.cpp index 14cc9d9cc5b..e0b5faab9b6 100644 --- a/parse/ConditionParser3.cpp +++ b/parse/ConditionParser3.cpp @@ -1,197 +1,229 @@ -#include "ConditionParserImpl.h" +#include "ConditionParser3.h" -#include "ParseImpl.h" -#include "ValueRefParser.h" -#include "../universe/Condition.h" +#include "../universe/Conditions.h" #include "../universe/Enums.h" +#include "../universe/ValueRef.h" #include namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; - -namespace { - struct condition_parser_rules_3 { - condition_parser_rules_3() { - const parse::lexer& tok = parse::lexer::instance(); - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_g_type _g; - qi::_h_type _h; - qi::_val_type _val; - qi::lit_type lit; - using phoenix::new_; - using phoenix::push_back; - - has_special_capacity - = ( tok.HasSpecialCapacity_ - > parse::detail::label(Name_token) > parse::string_value_ref() [ _c = _1 ] - > -(parse::detail::label(Low_token) > parse::double_value_ref() [ _a = _1 ] ) - > -(parse::detail::label(High_token) > parse::double_value_ref() [ _b = _1 ] ) - ) [ _val = new_(_c, _a, _b) ] - ; - - within_distance - = tok.WithinDistance_ - > parse::detail::label(Distance_token) > parse::double_value_ref() [ _a = _1 ] - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_a, _1) ] - ; - - within_starlane_jumps - = tok.WithinStarlaneJumps_ - > parse::detail::label(Jumps_token) > parse::flexible_int_value_ref() [ _a = _1 ] - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_a, _1) ] - ; - - number - = tok.Number_ - > -(parse::detail::label(Low_token) > parse::flexible_int_value_ref() [ _a = _1 ]) - > -(parse::detail::label(High_token) > parse::flexible_int_value_ref() [ _b = _1 ]) - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_a, _b, _1) ] - ; - - value_test_1 - = '(' - >> parse::double_value_ref() [ _a = _1 ] - >> ( lit("==") [ _d = Condition::EQUAL ] - | lit('=') [ _d = Condition::EQUAL ] - | lit(">=") [ _d = Condition::GREATER_THAN_OR_EQUAL ] - | lit('>') [ _d = Condition::GREATER_THAN ] - | lit("<=") [ _d = Condition::LESS_THAN_OR_EQUAL ] - | lit('<') [ _d = Condition::LESS_THAN ] - | lit("!=") [ _d = Condition::NOT_EQUAL ]) - > parse::double_value_ref() // assuming the trinary form already didn't pass, can expect a (double) here, though it might be an (int) casted to (double). By matching the (int) cases first, can assume that at least one of the parameters here is not an (int) casted to double. - [ _val = new_(_a, _d, _1) ] - >> ')' - ; - - value_test_2 - = ('(' - >> parse::double_value_ref() [ _a = _1 ] - >> ( lit("==") [ _d = Condition::EQUAL ] - | lit('=') [ _d = Condition::EQUAL ] - | lit(">=") [ _d = Condition::GREATER_THAN_OR_EQUAL ] - | lit('>') [ _d = Condition::GREATER_THAN ] - | lit("<=") [ _d = Condition::LESS_THAN_OR_EQUAL ] - | lit('<') [ _d = Condition::LESS_THAN ] - | lit("!=") [ _d = Condition::NOT_EQUAL ]) - >> parse::double_value_ref() [ _b = _1 ] - >> ( lit("==") [ _d = Condition::EQUAL ] - | lit('=') [ _d = Condition::EQUAL ] - | lit(">=") [ _e = Condition::GREATER_THAN_OR_EQUAL ] - | lit('>') [ _e = Condition::GREATER_THAN ] - | lit("<=") [ _e = Condition::LESS_THAN_OR_EQUAL ] - | lit('<') [ _e = Condition::LESS_THAN ] - | lit("!=") [ _e = Condition::NOT_EQUAL ]) - ) > parse::double_value_ref() // if already seen (double) (operator) (double) (operator) can expect to see another (double). Some of these (double) may be (int) casted to double, though not all of them can be, as in that case, the (int) parser should have matched. - [ _val = new_(_a, _d, _b, _e, _1) ] - > ')' - ; - - value_test_3 - = '(' - >> parse::string_value_ref() [ _c = _1 ] - >> ( lit("==") [ _d = Condition::EQUAL ] - | lit('=') [ _d = Condition::EQUAL ] - | lit("!=") [ _d = Condition::NOT_EQUAL ]) - > parse::string_value_ref() // assuming the trinary (string) form already didn't parse, if already seen (string) (operator) can expect another (string) - [ _val = new_(_c, _d, _1) ] - >> ')' - ; - - value_test_4 - = ( '(' - >> parse::string_value_ref() [ _c = _1 ] - >> ( lit("==") [ _d = Condition::EQUAL ] - | lit('=') [ _d = Condition::EQUAL ] - | lit("!=") [ _d = Condition::NOT_EQUAL ]) - >> parse::string_value_ref() [ _f = _1 ] - >> ( lit("==") [ _d = Condition::EQUAL ] - | lit('=') [ _d = Condition::EQUAL ] - | lit("!=") [ _e = Condition::NOT_EQUAL ]) - ) > parse::string_value_ref() // if already seen (string) (operator) (string) (operator) can expect to see another (string) - [ _val = new_(_c, _d, _f, _e, _1) ] - > ')' - ; - - value_test_5 - = '(' - >> parse::int_value_ref() [ _g = _1 ] - >> ( lit("==") [ _d = Condition::EQUAL ] - | lit('=') [ _d = Condition::EQUAL ] - | lit(">=") [ _d = Condition::GREATER_THAN_OR_EQUAL ] - | lit('>') [ _d = Condition::GREATER_THAN ] - | lit("<=") [ _d = Condition::LESS_THAN_OR_EQUAL ] - | lit('<') [ _d = Condition::LESS_THAN ] - | lit("!=") [ _d = Condition::NOT_EQUAL ]) - >> parse::int_value_ref() // can't expect an (int) here, as it could actually be a (double) comparision with the first (double) cased from an (int) - [ _val = new_(_g, _d, _1) ] - >> ')' - ; - - value_test_6 - = ('(' - >> parse::int_value_ref() [ _g = _1 ] - >> ( lit("==") [ _d = Condition::EQUAL ] - | lit('=') [ _d = Condition::EQUAL ] - | lit(">=") [ _d = Condition::GREATER_THAN_OR_EQUAL ] - | lit('>') [ _d = Condition::GREATER_THAN ] - | lit("<=") [ _d = Condition::LESS_THAN_OR_EQUAL ] - | lit('<') [ _d = Condition::LESS_THAN ] - | lit("!=") [ _d = Condition::NOT_EQUAL ]) - >> parse::int_value_ref() [ _h = _1 ] - >> ( lit("==") [ _d = Condition::EQUAL ] - | lit('=') [ _d = Condition::EQUAL ] - | lit(">=") [ _e = Condition::GREATER_THAN_OR_EQUAL ] - | lit('>') [ _e = Condition::GREATER_THAN ] - | lit("<=") [ _e = Condition::LESS_THAN_OR_EQUAL ] - | lit('<') [ _e = Condition::LESS_THAN ] - | lit("!=") [ _e = Condition::NOT_EQUAL ]) - ) >> parse::int_value_ref() // only treat as trinary (int) comparison if all parameters are (int). otherwise fall back to (double) comparison, which allows some of the parameters to be (int) casted to (double) - [ _val = new_(_g, _d, _h, _e, _1) ] - > ')' - ; +namespace parse { namespace detail { + condition_parser_rules_3::condition_parser_rules_3( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + condition_parser_rules_3::base_type(start, "condition_parser_rules_3"), + int_rules(tok, label, condition_parser, string_grammar), + castable_int_rules(tok, label, condition_parser, string_grammar), + double_rules(tok, label, condition_parser, string_grammar) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_5_type _5; + qi::_val_type _val; + qi::lit_type lit; + qi::_pass_type _pass; + qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + using phoenix::new_; + using phoenix::construct; + + has_special_capacity + = (omit_[tok.HasSpecialCapacity_] + > label(tok.Name_) > string_grammar + > -(label(tok.Low_) > double_rules.expr) + > -(label(tok.High_) > double_rules.expr) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass))) ] + ; + + within_distance + = (omit_[tok.WithinDistance_] + > label(tok.Distance_) > double_rules.expr + > label(tok.Condition_) > condition_parser + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + within_starlane_jumps + = (omit_[tok.WithinStarlaneJumps_] + > label(tok.Jumps_) > castable_int_rules.flexible_int + > label(tok.Condition_) > condition_parser + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + number + = (omit_[tok.Number_] + > -(label(tok.Low_) > castable_int_rules.flexible_int) + > -(label(tok.High_) > castable_int_rules.flexible_int) + > label(tok.Condition_) > condition_parser + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass))) ] + ; + + comparison_operator = + lit("==") [ _val = Condition::EQUAL ] + | lit('=') [ _val = Condition::EQUAL ] + | lit(">=") [ _val = Condition::GREATER_THAN_OR_EQUAL ] + | lit('>') [ _val = Condition::GREATER_THAN ] + | lit("<=") [ _val = Condition::LESS_THAN_OR_EQUAL ] + | lit('<') [ _val = Condition::LESS_THAN ] + | lit("!=") [ _val = Condition::NOT_EQUAL ] + ; + + string_comparison_operator = + lit("==") [ _val = Condition::EQUAL ] + | lit('=') [ _val = Condition::EQUAL ] + | lit("!=") [ _val = Condition::NOT_EQUAL ] + ; + + comparison_binary_double + = (('(' + >> double_rules.expr + >> comparison_operator + >> double_rules.expr // assuming the trinary form already didn't pass, can expect a (double) here, though it might be an (int) casted to (double). By matching the (int) cases first, can assume that at least one of the parameters here is not an (int) casted to double. + ) > ')' + ) [ _val = construct_movable_( + new_( + deconstruct_movable_(_1, _pass), + _2, + deconstruct_movable_(_3, _pass))) ] + ; + + comparison_trinary_double + = (( '(' + >> double_rules.expr + >> comparison_operator + >> double_rules.expr + >> comparison_operator + >> double_rules.expr // if already seen (double) (operator) (double) (operator) can expect to see another (double). Some of these (double) may be (int) casted to double, though not all of them can be, as in that case, the (int) parser should have matched. + ) > ')' + ) [ _val = construct_movable_( + new_( + deconstruct_movable_(_1, _pass), + _2, + deconstruct_movable_(_3, _pass), + _4, + deconstruct_movable_(_5, _pass))) ] + ; + + comparison_binary_string + = (( '(' + >> string_grammar + >> string_comparison_operator + >> string_grammar // assuming the trinary (string) form already didn't parse, if already seen (string) (operator) can expect another (string) + ) > ')' + ) [ _val = construct_movable_( + new_( + deconstruct_movable_(_1, _pass), + _2, + deconstruct_movable_(_3, _pass))) ] + ; + + comparison_trinary_string + = + (( '(' + >> string_grammar + >> string_comparison_operator + >> string_grammar + >> string_comparison_operator + >> string_grammar // if already seen (string) (operator) (string) (operator) can expect to see another (string) + ) > ')' + ) [ _val = construct_movable_( + new_( + deconstruct_movable_(_1, _pass), + _2, + deconstruct_movable_(_3, _pass), + _4, + deconstruct_movable_(_5, _pass))) ] + ; + + comparison_binary_int + = (( '(' + >> int_rules.expr + >> comparison_operator + >> int_rules.expr // can't expect an (int) here, as it could actually be a (double) comparision with the first (double) cased from an (int) + ) > ')' + ) [ _val = construct_movable_( + new_( + deconstruct_movable_(_1, _pass), + _2, + deconstruct_movable_(_3, _pass))) ] + ; + + comparison_trinary_int + = (( '(' + >> int_rules.expr + >> comparison_operator + >> int_rules.expr + >> comparison_operator + >> int_rules.expr // only treat as trinary (int) comparison if all parameters are (int). otherwise fall back to (double) comparison, which allows some of the parameters to be (int) casted to (double) + ) > ')' + ) [ _val = construct_movable_( + new_( + deconstruct_movable_(_1, _pass), + _2, + deconstruct_movable_(_3, _pass), + _4, + deconstruct_movable_(_5, _pass))) ] + ; turn - = (tok.Turn_ - > -(parse::detail::label(Low_token) > (parse::flexible_int_value_ref() [ _a = _1 ])) - > -(parse::detail::label(High_token) > (parse::flexible_int_value_ref() [ _b = _1 ]))) - [ _val = new_(_a, _b) ] + = ( omit_[tok.Turn_] + > -(label(tok.Low_) > (castable_int_rules.flexible_int )) + > -(label(tok.High_) > (castable_int_rules.flexible_int ))) + [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] ; created_on_turn - = (tok.CreatedOnTurn_ - > -(parse::detail::label(Low_token) > parse::flexible_int_value_ref() [ _a = _1 ]) - > -(parse::detail::label(High_token) > parse::flexible_int_value_ref() [ _b = _1 ])) - [ _val = new_(_a, _b) ] + = ( omit_[tok.CreatedOnTurn_] + > -(label(tok.Low_) > castable_int_rules.flexible_int ) + > -(label(tok.High_) > castable_int_rules.flexible_int )) + [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] ; + sorting_operator = + tok.MaximumNumberOf_ [ _val = Condition::SORT_MAX ] + | tok.MinimumNumberOf_ [ _val = Condition::SORT_MIN ] + | tok.ModeNumberOf_ [ _val = Condition::SORT_MODE ]; + number_of1 - = tok.NumberOf_ - > parse::detail::label(Number_token) > parse::flexible_int_value_ref() [ _a = _1 ] - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_a, _1) ] + = ( omit_[tok.NumberOf_] + > label(tok.Number_) > castable_int_rules.flexible_int + > label(tok.Condition_) > condition_parser) + [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] ; number_of2 - = ( tok.MaximumNumberOf_ [ _b = Condition::SORT_MAX ] - | tok.MinimumNumberOf_ [ _b = Condition::SORT_MIN ] - | tok.ModeNumberOf_ [ _b = Condition::SORT_MODE ] - ) - > parse::detail::label(Number_token) > parse::flexible_int_value_ref() [ _a = _1 ] - > parse::detail::label(SortKey_token) > parse::double_value_ref() [ _c = _1 ] - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_a, _c, _b, _1) ] + = (sorting_operator + > label(tok.Number_) > castable_int_rules.flexible_int + > label(tok.SortKey_) > double_rules.expr + > label(tok.Condition_) > condition_parser) + [ _val = construct_movable_(new_( + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + _1, + deconstruct_movable_(_4, _pass))) ] ; number_of @@ -201,46 +233,52 @@ namespace { random = tok.Random_ - > parse::detail::label(Probability_token) > parse::double_value_ref() - [ _val = new_(_1) ] + > label(tok.Probability_) > double_rules.expr + [ _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] ; - owner_stockpile - = tok.OwnerTradeStockpile_ [ _a = RE_TRADE ] - > parse::detail::label(Low_token) > parse::double_value_ref() [ _b = _1 ] - > parse::detail::label(High_token) > parse::double_value_ref() - [ _val = new_(_a, _b, _1) ] + stockpile + = ( omit_[tok.EmpireStockpile_] + > label(tok.Low_) > double_rules.expr + > label(tok.High_) > double_rules.expr) + [ _val = construct_movable_(new_( + RE_INDUSTRY, + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] ; resource_supply_connected - = tok.ResourceSupplyConnected_ - > parse::detail::label(Empire_token) > parse::int_value_ref() [ _a = _1 ] - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_a, _1) ] + = ( omit_[tok.ResourceSupplyConnected_] + > label(tok.Empire_) > int_rules.expr + > label(tok.Condition_) > condition_parser) + [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] ; can_add_starlane - = tok.CanAddStarlanesTo_ - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_1) ] + = ( omit_[tok.CanAddStarlanesTo_] + > label(tok.Condition_) > condition_parser) + [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] ; start - = has_special_capacity + %= has_special_capacity | within_distance | within_starlane_jumps | number - | value_test_5 // more complicated format that is strict extension of value_test_3 format, so needs to be tested before it - | value_test_6 // first int ... - | value_test_2 // more complicated format that is strict extension of value_test_1 format, so needs to be tested before it - | value_test_1 // ... then double (which may include int(s) casted to double(s))... - | value_test_4 // more complicated format that is strict extension of value_test_3 format, so needs to be tested before it - | value_test_3 // ... then string + | comparison_trinary_int // more complicated format that is strict extension of comparison_binary_int format, so needs to be tested before it + | comparison_binary_int // first int ... + | comparison_trinary_double // more complicated format that is strict extension of comparison_binary_double format, so needs to be tested before it + | comparison_binary_double // ... then double (which may include int(s) casted to double(s))... + | comparison_trinary_string // more complicated format that is strict extension of comparison_binary_string format, so needs to be tested before it + | comparison_binary_string // ... then string | turn | created_on_turn | number_of | random - | owner_stockpile + | stockpile | resource_supply_connected | can_add_starlane ; @@ -249,13 +287,21 @@ namespace { within_distance.name("WithinDistance"); within_starlane_jumps.name("WithinStarlaneJumps"); number.name("Number"); - value_test_1.name("ValueTest Binary"); - value_test_2.name("ValueTest Trinary"); + comparison_operator.name("comparison operator"); + string_comparison_operator.name("string comparison operator"); + comparison_operator.name("comparison operator"); + comparison_binary_double.name("ValueTest Binary dou ble"); + comparison_trinary_double.name("ValueTest Trinary double"); + comparison_binary_string.name("ValueTest Binary string"); + comparison_trinary_string.name("ValueTest Trinary string"); + comparison_binary_int.name("ValueTest Binary int"); + comparison_trinary_int.name("ValueTest Trinary int"); turn.name("Turn"); created_on_turn.name("CreatedOnTurn"); + sorting_operator.name("sorting operator"); number_of.name("NumberOf"); random.name("Random"); - owner_stockpile.name("OwnerStockpile"); + stockpile.name("EmpireStockpile"); resource_supply_connected.name("ResourceSupplyConnected"); can_add_starlane.name("CanAddStarlanesTo"); @@ -264,83 +310,14 @@ namespace { debug(within_distance); debug(within_starlane_jumps); debug(number); - debug(value_test_1); - debug(value_test_2); debug(turn); debug(created_on_turn); debug(number_of); debug(random); - debug(owner_stockpile); + debug(stockpile); debug(resource_supply_connected); debug(can_add_starlane); #endif } - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - Condition::ComparisonType, - Condition::ComparisonType, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > - > double_ref_double_ref_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > - > int_ref_int_ref_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals< - ValueRef::ValueRefBase*, - Condition::SortingMethod, - ValueRef::ValueRefBase* - > - > int_ref_sorting_method_double_ref_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals< - ResourceType, - ValueRef::ValueRefBase* - > - > resource_type_double_ref_rule; - - double_ref_double_ref_rule has_special_capacity; - double_ref_double_ref_rule within_distance; - int_ref_int_ref_rule within_starlane_jumps; - int_ref_int_ref_rule number; - double_ref_double_ref_rule value_test_1; - double_ref_double_ref_rule value_test_2; - double_ref_double_ref_rule value_test_3; - double_ref_double_ref_rule value_test_4; - double_ref_double_ref_rule value_test_5; - double_ref_double_ref_rule value_test_6; - int_ref_int_ref_rule turn; - int_ref_int_ref_rule created_on_turn; - int_ref_sorting_method_double_ref_rule number_of; - int_ref_sorting_method_double_ref_rule number_of1; - int_ref_sorting_method_double_ref_rule number_of2; - parse::condition_parser_rule random; - resource_type_double_ref_rule owner_stockpile; - int_ref_int_ref_rule resource_supply_connected; - parse::condition_parser_rule can_add_starlane; - parse::condition_parser_rule start; - }; -} - -namespace parse { namespace detail { - const condition_parser_rule& condition_parser_3() { - static condition_parser_rules_3 retval; - return retval.start; - } } } diff --git a/parse/ConditionParser3.h b/parse/ConditionParser3.h new file mode 100644 index 00000000000..795dc5c65f1 --- /dev/null +++ b/parse/ConditionParser3.h @@ -0,0 +1,47 @@ +#ifndef _ConditionParser3_h_ +#define _ConditionParser3_h_ + +#include "ConditionParserImpl.h" + +namespace parse { namespace detail { + struct condition_parser_rules_3 : public condition_parser_grammar { + condition_parser_rules_3(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + using comparison_operator_rule = rule; + using sorting_operator_rule = rule; + + parse::int_arithmetic_rules int_rules; + parse::castable_as_int_parser_rules castable_int_rules; + parse::double_parser_rules double_rules; + condition_parser_rule has_special_capacity; + condition_parser_rule within_distance; + condition_parser_rule within_starlane_jumps; + condition_parser_rule number; + comparison_operator_rule comparison_operator; + comparison_operator_rule string_comparison_operator; + condition_parser_rule comparison_binary_double; + condition_parser_rule comparison_trinary_double; + condition_parser_rule comparison_binary_int; + condition_parser_rule comparison_trinary_int; + condition_parser_rule comparison_binary_string; + condition_parser_rule comparison_trinary_string; + condition_parser_rule turn; + condition_parser_rule created_on_turn; + sorting_operator_rule sorting_operator; + condition_parser_rule number_of; + condition_parser_rule number_of1; + condition_parser_rule number_of2; + condition_parser_rule random; + condition_parser_rule stockpile; + condition_parser_rule resource_supply_connected; + condition_parser_rule can_add_starlane; + condition_parser_rule start; + }; + + } +} + +#endif // _ConditionParser3_h_ diff --git a/parse/ConditionParser4.cpp b/parse/ConditionParser4.cpp index e432099bc50..942aa9bab1f 100644 --- a/parse/ConditionParser4.cpp +++ b/parse/ConditionParser4.cpp @@ -1,132 +1,116 @@ -#include "ConditionParserImpl.h" +#include "ConditionParser4.h" -#include "ParseImpl.h" -#include "EnumParser.h" -#include "ValueRefParser.h" -#include "../universe/Condition.h" +#include "../universe/Conditions.h" #include "../universe/ValueRef.h" #include - namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; - #if DEBUG_CONDITION_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector&) { return os; } } #endif -namespace { - struct condition_parser_rules_4 { - condition_parser_rules_4() { - const parse::lexer& tok = parse::lexer::instance(); - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_val_type _val; - qi::eps_type eps; - using phoenix::new_; - using phoenix::push_back; - - meter_value - = ( - parse::non_ship_part_meter_type_enum() [ _a = _1 ] - > -(parse::detail::label(Low_token) > parse::double_value_ref() [ _b = _1 ]) - > -(parse::detail::label(High_token) > parse::double_value_ref() [ _c = _1 ]) - ) [ _val = new_(_a, _b, _c) ] - ; - - ship_part_meter_value - = ( - tok.ShipPartMeter_ - > parse::detail::label(Part_token) > parse::string_value_ref() [ _e = _1 ] - > parse::ship_part_meter_type_enum() [ _a = _1 ] - > -(parse::detail::label(Low_token) > parse::double_value_ref() [ _b = _1 ]) - > -(parse::detail::label(High_token) > parse::double_value_ref() [ _c = _1 ]) - ) [ _val = new_(_e, _a, _b, _c) ] - ; - - empire_meter_value1 - = ( - (tok.EmpireMeter_ - >> parse::detail::label(Empire_token)) > parse::int_value_ref() [ _b = _1 ] - > parse::detail::label(Meter_token) > tok.string [ _a = _1 ] - > -(parse::detail::label(Low_token) > parse::double_value_ref() [ _c = _1 ]) - > -(parse::detail::label(High_token) > parse::double_value_ref() [ _d = _1 ]) - ) [ _val = new_(_b, _a, _c, _d) ] - ; - - empire_meter_value2 - = ( - (tok.EmpireMeter_ - >> parse::detail::label(Meter_token)) > tok.string [ _a = _1 ] - > -(parse::detail::label(Low_token) > parse::double_value_ref() [ _c = _1 ]) - > -(parse::detail::label(High_token) > parse::double_value_ref() [ _d = _1 ]) - ) [ _val = new_(_a, _c, _d) ] - ; - - empire_meter_value - = empire_meter_value1 - | empire_meter_value2 - ; - - start - %= meter_value - | ship_part_meter_value - | empire_meter_value - ; - - meter_value.name("MeterValue"); - ship_part_meter_value.name("ShipPartMeterValue"); - empire_meter_value.name("EmpireMeterValue"); +namespace parse { namespace detail { + condition_parser_rules_4::condition_parser_rules_4( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + condition_parser_rules_4::base_type(start, "condition_parser_rules_4"), + int_rules(tok, label, condition_parser, string_grammar), + double_rules(tok, label, condition_parser, string_grammar), + non_ship_part_meter_type_enum(tok), + ship_part_meter_type_enum(tok) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_val_type _val; + qi::eps_type eps; + qi::_pass_type _pass; + qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + using phoenix::new_; + using phoenix::construct; + + meter_value + = ( + non_ship_part_meter_type_enum + > -(label(tok.Low_) > double_rules.expr) + > -(label(tok.High_) > double_rules.expr) + ) [ _val = construct_movable_(new_( + _1, + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass))) ] + ; + + ship_part_meter_value + = ( + omit_[tok.ShipPartMeter_] + > label(tok.Part_) > string_grammar + > label(tok.Meter_) > ship_part_meter_type_enum + > -(label(tok.Low_) > double_rules.expr) + > -(label(tok.High_) > double_rules.expr) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + _2, + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_4, _pass))) ] + ; + + empire_meter_value1 + = ( + (omit_[tok.EmpireMeter_] + >> label(tok.Empire_)) > int_rules.expr + > label(tok.Meter_) > tok.string + > -(label(tok.Low_) > double_rules.expr) + > -(label(tok.High_) > double_rules.expr) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + _2, + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_4, _pass))) ] + ; + + empire_meter_value2 + = ( + (omit_[tok.EmpireMeter_] + >> label(tok.Meter_)) > tok.string + > -(label(tok.Low_) > double_rules.expr) + > -(label(tok.High_) > double_rules.expr) + ) [ _val = construct_movable_(new_( + _1, + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass))) ] + ; + + empire_meter_value + %= empire_meter_value1 + | empire_meter_value2 + ; + + start + %= meter_value + | ship_part_meter_value + | empire_meter_value + ; + + meter_value.name("MeterValue"); + ship_part_meter_value.name("ShipPartMeterValue"); + empire_meter_value.name("EmpireMeterValue"); #if DEBUG_CONDITION_PARSERS - debug(meter_value); - debug(ship_part_meter_value); - debug(empire_meter_value); + debug(meter_value); + debug(ship_part_meter_value); + debug(empire_meter_value); #endif - } - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals< - MeterType, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - std::string, - ValueRef::ValueRefBase* - > - > meter_value_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals< - std::string, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > - > empire_meter_value_rule; - - meter_value_rule meter_value; - meter_value_rule ship_part_meter_value; - empire_meter_value_rule empire_meter_value; - empire_meter_value_rule empire_meter_value1; - empire_meter_value_rule empire_meter_value2; - parse::condition_parser_rule start; - }; -} - -namespace parse { namespace detail { - const condition_parser_rule& condition_parser_4() { - static condition_parser_rules_4 retval; - return retval.start; } + } } diff --git a/parse/ConditionParser4.h b/parse/ConditionParser4.h new file mode 100644 index 00000000000..54ce59b8138 --- /dev/null +++ b/parse/ConditionParser4.h @@ -0,0 +1,28 @@ +#ifndef _ConditionParser4_h_ +#define _ConditionParser4_h_ + +#include "ConditionParserImpl.h" + +namespace parse { namespace detail { + struct condition_parser_rules_4 : public condition_parser_grammar { + condition_parser_rules_4(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + parse::int_arithmetic_rules int_rules; + parse::double_parser_rules double_rules; + parse::non_ship_part_meter_enum_grammar non_ship_part_meter_type_enum; + parse::ship_part_meter_enum_grammar ship_part_meter_type_enum; + condition_parser_rule meter_value; + condition_parser_rule ship_part_meter_value; + condition_parser_rule empire_meter_value; + condition_parser_rule empire_meter_value1; + condition_parser_rule empire_meter_value2; + condition_parser_rule start; + }; + + } +} + +#endif // _ConditionParser4_h_ diff --git a/parse/ConditionParser5.cpp b/parse/ConditionParser5.cpp index ef38e2e2b47..c4d5d4819f1 100644 --- a/parse/ConditionParser5.cpp +++ b/parse/ConditionParser5.cpp @@ -1,148 +1,145 @@ -#include "ConditionParserImpl.h" +#include "ConditionParser5.h" -#include "EnumParser.h" -#include "ValueRefParser.h" -#include "../universe/Condition.h" +#include "../universe/Conditions.h" +#include "../universe/ValueRef.h" #include - namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; - -namespace { - struct condition_parser_rules_5 { - condition_parser_rules_5() { - const parse::lexer& tok = parse::lexer::instance(); - - qi::_1_type _1; - qi::_val_type _val; - qi::eps_type eps; - using phoenix::new_; - - has_special - = ( (tok.HasSpecial_ - >> parse::detail::label(Name_token) - ) > parse::string_value_ref() [ _val = new_(_1) ] - ) - | tok.HasSpecial_ [ _val = new_() ] - ; - - has_tag - = ( (tok.HasTag_ - >> parse::detail::label(Name_token) - ) > parse::string_value_ref() [ _val = new_(_1) ] - ) - | tok.HasTag_ [ _val = new_() ] - ; - - owner_has_tech - = tok.OwnerHasTech_ - > parse::detail::label(Name_token) > parse::string_value_ref() [ _val = new_(_1) ] - ; - - design_has_hull - = tok.DesignHasHull_ - > parse::detail::label(Name_token) > parse::string_value_ref() [ _val = new_(_1) ] - ; - - predefined_design - = (tok.Design_ - >> parse::detail::label(Name_token) - ) > parse::string_value_ref() [ _val = new_(_1) ] - ; - - design_number - = (tok.Design_ - >> parse::detail::label(Design_token) - ) > parse::int_value_ref() [ _val = new_(_1) ] - ; - - produced_by_empire // TODO: Lose "empire" part. - = tok.ProducedByEmpire_ - > parse::detail::label(Empire_token) > parse::int_value_ref() [ _val = new_(_1) ] - ; - - visible_to_empire // TODO: Lose "empire" part. - = tok.VisibleToEmpire_ - > parse::detail::label(Empire_token) > parse::int_value_ref() [ _val = new_(_1) ] - ; - - explored_by_empire // TODO: Lose "empire" part. - = tok.ExploredByEmpire_ - > parse::detail::label(Empire_token) > parse::int_value_ref() [ _val = new_(_1) ] - ; - - resupplyable_by - = tok.ResupplyableBy_ - > parse::detail::label(Empire_token) > parse::int_value_ref() [ _val = new_(_1) ] - ; - - object_id - = tok.Object_ - > parse::detail::label(ID_token) > parse::int_value_ref() [ _val = new_(_1) ] - ; - - start - %= has_special - | has_tag - | owner_has_tech - | design_has_hull - | predefined_design - | design_number - | produced_by_empire - | visible_to_empire - | explored_by_empire - | resupplyable_by - | object_id - ; - - has_special.name("HasSpecial"); - has_tag.name("HasTag"); - owner_has_tech.name("OwnerHasTech"); - design_has_hull.name("DesignHasHull"); - predefined_design.name("PredefinedDesign"); - design_number.name("DesignNumber"); - produced_by_empire.name("ProducedByEmpire"); - visible_to_empire.name("VisibleToEmpire"); - explored_by_empire.name("ExploredByEmpire"); - resupplyable_by.name("ResupplyableBy"); - object_id.name("ID"); +namespace parse { namespace detail { + condition_parser_rules_5::condition_parser_rules_5( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + condition_parser_rules_5::base_type(start, "condition_parser_rules_5"), + int_rules(tok, label, condition_parser, string_grammar) + { + qi::_1_type _1; + qi::_val_type _val; + qi::eps_type eps; + qi::_pass_type _pass; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + using phoenix::new_; + using phoenix::construct; + + has_special + = ( (tok.HasSpecial_ + >> label(tok.Name_) + ) > string_grammar [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ) + | tok.HasSpecial_ [ _val = construct_movable_(new_()) ] + ; + + has_tag + = ( (tok.HasTag_ + >> label(tok.Name_) + ) > string_grammar [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ) + | tok.HasTag_ [ _val = construct_movable_(new_()) ] + ; + + owner_has_tech + = tok.OwnerHasTech_ + > label(tok.Name_) > string_grammar [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + design_has_hull + = tok.DesignHasHull_ + > label(tok.Name_) > string_grammar [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + predefined_design + = (tok.Design_ + >> label(tok.Name_) + ) > string_grammar [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + design_number + = (tok.Design_ + >> label(tok.Design_) + ) > int_rules.expr [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + produced_by_empire // TODO: Lose "empire" part. + = tok.ProducedByEmpire_ + > label(tok.Empire_) > int_rules.expr [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + visible_to_empire // TODO: Lose "empire" part. + = tok.VisibleToEmpire_ + > label(tok.Empire_) > int_rules.expr [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + explored_by_empire // TODO: Lose "empire" part. + = tok.ExploredByEmpire_ + > label(tok.Empire_) > int_rules.expr [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + resupplyable_by + = tok.ResupplyableBy_ + > label(tok.Empire_) > int_rules.expr [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + object_id + = tok.Object_ + > label(tok.ID_) > int_rules.expr [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + start + %= has_special + | has_tag + | owner_has_tech + | design_has_hull + | predefined_design + | design_number + | produced_by_empire + | visible_to_empire + | explored_by_empire + | resupplyable_by + | object_id + ; + + has_special.name("HasSpecial"); + has_tag.name("HasTag"); + owner_has_tech.name("OwnerHasTech"); + design_has_hull.name("DesignHasHull"); + predefined_design.name("PredefinedDesign"); + design_number.name("DesignNumber"); + produced_by_empire.name("ProducedByEmpire"); + visible_to_empire.name("VisibleToEmpire"); + explored_by_empire.name("ExploredByEmpire"); + resupplyable_by.name("ResupplyableBy"); + object_id.name("ID"); #if DEBUG_CONDITION_PARSERS - debug(has_special); - debug(has_tag); - debug(owner_has_tech); - debug(design_has_hull); - debug(predefined_design); - debug(design_number); - debug(produced_by_empire); - debug(visible_to_empire); - debug(explored_by_empire); - debug(resupplyable_by); - debug(object_id); + debug(has_special); + debug(has_tag); + debug(owner_has_tech); + debug(design_has_hull); + debug(predefined_design); + debug(design_number); + debug(produced_by_empire); + debug(visible_to_empire); + debug(explored_by_empire); + debug(resupplyable_by); + debug(object_id); #endif - } - - parse::condition_parser_rule has_special; - parse::condition_parser_rule has_tag; - parse::condition_parser_rule owner_has_tech; - parse::condition_parser_rule design_has_hull; - parse::condition_parser_rule predefined_design; - parse::condition_parser_rule design_number; - parse::condition_parser_rule produced_by_empire; - parse::condition_parser_rule visible_to_empire; - parse::condition_parser_rule explored_by_empire; - parse::condition_parser_rule resupplyable_by; - parse::condition_parser_rule object_id; - parse::condition_parser_rule start; - }; -} - -namespace parse { namespace detail { - const condition_parser_rule& condition_parser_5() { - static condition_parser_rules_5 retval; - return retval.start; } + } } diff --git a/parse/ConditionParser5.h b/parse/ConditionParser5.h new file mode 100644 index 00000000000..9f33641ec8c --- /dev/null +++ b/parse/ConditionParser5.h @@ -0,0 +1,31 @@ +#ifndef _ConditionParser5_h_ +#define _ConditionParser5_h_ + +#include "ConditionParserImpl.h" + +namespace parse { namespace detail { + struct condition_parser_rules_5 : public condition_parser_grammar { + condition_parser_rules_5(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + parse::int_arithmetic_rules int_rules; + condition_parser_rule has_special; + condition_parser_rule has_tag; + condition_parser_rule owner_has_tech; + condition_parser_rule design_has_hull; + condition_parser_rule predefined_design; + condition_parser_rule design_number; + condition_parser_rule produced_by_empire; + condition_parser_rule visible_to_empire; + condition_parser_rule explored_by_empire; + condition_parser_rule resupplyable_by; + condition_parser_rule object_id; + condition_parser_rule start; + }; + + } +} + +#endif // _ConditionParser5_h_ diff --git a/parse/ConditionParser6.cpp b/parse/ConditionParser6.cpp index 3c1c966d707..2e1e9c5d79a 100644 --- a/parse/ConditionParser6.cpp +++ b/parse/ConditionParser6.cpp @@ -1,11 +1,9 @@ -#include "ConditionParserImpl.h" +#include "ConditionParser6.h" -#include "ParseImpl.h" -#include "EnumParser.h" #include "ValueRefParser.h" -#include "ValueRefParserImpl.h" -#include "../universe/Condition.h" -#include "../universe/ValueRef.h" + +#include "../universe/Conditions.h" +#include "../universe/ValueRefs.h" #include @@ -16,184 +14,145 @@ namespace phoenix = boost::phoenix; #if DEBUG_CONDITION_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector&) { return os; } - inline ostream& operator<<(ostream& os, const std::vector*>&) { return os; } - inline ostream& operator<<(ostream& os, const std::vector*>&) { return os; } - inline ostream& operator<<(ostream& os, const std::vector*>&) { return os; } - inline ostream& operator<<(ostream& os, const std::vector*>&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector*>&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector*>&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector*>&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector*>&) { return os; } } #endif -namespace { - struct condition_parser_rules_6 { - condition_parser_rules_6() { - const parse::lexer& tok = parse::lexer::instance(); - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_val_type _val; - qi::eps_type eps; - using phoenix::new_; - using phoenix::push_back; - - string_ref_vec - = ('[' > +parse::string_value_ref() [ push_back(_val, _1) ] > ']') - | parse::string_value_ref() [ push_back(_val, _1) ] - ; - - homeworld - = tok.Homeworld_ - > ( - (parse::detail::label(Name_token) > string_ref_vec [ _val = new_(_1) ]) - | eps [ _val = new_() ] - ) - ; - - building - = ( - tok.Building_ - > -(parse::detail::label(Name_token) > string_ref_vec [ _a = _1 ]) - ) - [ _val = new_(_a) ] - ; - - species - = tok.Species_ - > ( - (parse::detail::label(Name_token) > string_ref_vec [ _val = new_(_1) ]) - | eps [ _val = new_() ] - ) - ; - - focus_type - = tok.Focus_ - > ( - (parse::detail::label(Type_token) > string_ref_vec [ _val = new_(_1) ]) - | eps [ _val = new_(std::vector*>()) ] - ) - ; - - planet_type - = (tok.Planet_ - >> parse::detail::label(Type_token) - ) - > ( - ('[' > +parse::detail::planet_type_rules().expr [ push_back(_a, _1) ] > ']') - | parse::detail::planet_type_rules().expr [ push_back(_a, _1) ] - ) - [ _val = new_(_a) ] - ; - - planet_size - = (tok.Planet_ - >> parse::detail::label(Size_token) - ) - > ( - ('[' > +parse::detail::planet_size_rules().expr [ push_back(_a, _1) ] > ']') - | parse::detail::planet_size_rules().expr [ push_back(_a, _1) ] - ) - [ _val = new_(_a) ] - ; - - planet_environment - = ((tok.Planet_ - >> parse::detail::label(Environment_token) - ) - > ( - ('[' > +parse::detail::planet_environment_rules().expr [ push_back(_a, _1) ] > ']') - | parse::detail::planet_environment_rules().expr [ push_back(_a, _1) ] - ) - > -(parse::detail::label(Species_token) > parse::string_value_ref() [_b = _1])) - [ _val = new_(_a, _b) ] - ; - - object_type - = parse::detail::universe_object_type_rules().enum_expr [ _val = new_(new_>(_1)) ] - | ( - tok.ObjectType_ - > parse::detail::label(Type_token) > parse::detail::universe_object_type_rules().expr [ _val = new_(_1) ] - ) - ; - - - start - %= homeworld - | building - | species - | focus_type - | planet_type - | planet_size - | planet_environment - | object_type - ; - - string_ref_vec.name("sequence of string expressions"); - homeworld.name("Homeworld"); - building.name("Building"); - species.name("Species"); - focus_type.name("Focus"); - planet_type.name("PlanetType"); - planet_size.name("PlanetSize"); - planet_environment.name("PlanetEnvironment"); - object_type.name("ObjectType"); +namespace parse { namespace detail { + condition_parser_rules_6::condition_parser_rules_6( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + condition_parser_rules_6::base_type(start, "condition_parser_rules_6"), + one_or_more_string_values(string_grammar), + universe_object_type_rules(tok, label, condition_parser), + planet_type_rules(tok, label, condition_parser), + planet_size_rules(tok, label, condition_parser), + planet_environment_rules(tok, label, condition_parser), + one_or_more_planet_types(planet_type_rules.expr), + one_or_more_planet_sizes(planet_size_rules.expr), + one_or_more_planet_environments(planet_environment_rules.expr) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_val_type _val; + qi::eps_type eps; + qi::_pass_type _pass; + qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + const boost::phoenix::function deconstruct_movable_vector_; + + using phoenix::new_; + using phoenix::push_back; + using phoenix::construct; + + homeworld + = tok.Homeworld_ + > ( + (label(tok.Name_) > one_or_more_string_values + [ _val = construct_movable_(new_(deconstruct_movable_vector_(_1, _pass))) ]) + | eps [ _val = construct_movable_(new_()) ] + ) + ; + + building + = ( omit_[tok.Building_] + > -(label(tok.Name_) > one_or_more_string_values) + ) [ _val = construct_movable_(new_(deconstruct_movable_vector_(_1, _pass))) ] + ; + + species + = ( omit_[tok.Species_] + > -(label(tok.Name_) > one_or_more_string_values) + ) [ _val = construct_movable_(new_(deconstruct_movable_vector_(_1, _pass))) ] + ; + + focus_type + = tok.Focus_ + > -(label(tok.Type_) > one_or_more_string_values) + [ _val = construct_movable_(new_(deconstruct_movable_vector_(_1, _pass))) ] + ; + + planet_type + = (tok.Planet_ + >> label(tok.Type_) + ) + > one_or_more_planet_types + [ _val = construct_movable_(new_(deconstruct_movable_vector_(_1, _pass))) ] + ; + + planet_size + = (tok.Planet_ + >> label(tok.Size_) + ) + > one_or_more_planet_sizes + [ _val = construct_movable_(new_(deconstruct_movable_vector_(_1, _pass))) ] + ; + + planet_environment + = ((omit_[tok.Planet_] + >> label(tok.Environment_) + ) + > one_or_more_planet_environments + > -(label(tok.Species_) > string_grammar)) + [ _val = construct_movable_(new_( + deconstruct_movable_vector_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + object_type + = universe_object_type_rules.enum_expr [ + _val = construct_movable_( + new_( + construct>>( + new_>(_1)))) ] + | ( + tok.ObjectType_ + > label(tok.Type_) > universe_object_type_rules.expr [ + _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ) + ; + + + start + %= homeworld + | building + | species + | focus_type + | planet_type + | planet_size + | planet_environment + | object_type + ; + + one_or_more_string_values.name("sequence of string expressions"); + homeworld.name("Homeworld"); + building.name("Building"); + species.name("Species"); + focus_type.name("Focus"); + planet_type.name("PlanetType"); + planet_size.name("PlanetSize"); + planet_environment.name("PlanetEnvironment"); + object_type.name("ObjectType"); #if DEBUG_CONDITION_PARSERS - debug(string_ref_vec); - debug(homeworld); - debug(building); - debug(species); - debug(focus_type); - debug(planet_type); - debug(planet_size); - debug(planet_environment); - debug(object_type); + debug(one_or_more_string_values); + debug(homeworld); + debug(building); + debug(species); + debug(focus_type); + debug(planet_type); + debug(planet_size); + debug(planet_environment); + debug(object_type); #endif - } - - typedef parse::detail::rule< - std::vector*> () - > string_ref_vec_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals*>> - > building_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals*>> - > planet_type_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals*>> - > planet_size_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals< - std::vector*>, - ValueRef::ValueRefBase* - > - > planet_environment_rule; - - string_ref_vec_rule string_ref_vec; - parse::condition_parser_rule homeworld; - building_rule building; - parse::condition_parser_rule species; - parse::condition_parser_rule focus_type; - planet_type_rule planet_type; - planet_size_rule planet_size; - planet_environment_rule planet_environment; - parse::condition_parser_rule object_type; - parse::condition_parser_rule start; - }; -} - -namespace parse { namespace detail { - const condition_parser_rule& condition_parser_6() { - static condition_parser_rules_6 retval; - return retval.start; } + } } diff --git a/parse/ConditionParser6.h b/parse/ConditionParser6.h new file mode 100644 index 00000000000..97b9aac74d6 --- /dev/null +++ b/parse/ConditionParser6.h @@ -0,0 +1,40 @@ +#ifndef _ConditionParser6_h_ +#define _ConditionParser6_h_ + +#include "ConditionParserImpl.h" +#include "EnumValueRefRules.h" + +namespace parse { namespace detail { + struct condition_parser_rules_6 : public condition_parser_grammar { + condition_parser_rules_6(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + typedef rule< + void (std::vector>&) + > string_ref_vec_rule; + + single_or_bracketed_repeat> one_or_more_string_values; + condition_parser_rule homeworld; + condition_parser_rule building; + condition_parser_rule species; + condition_parser_rule focus_type; + condition_parser_rule planet_type; + condition_parser_rule planet_size; + condition_parser_rule planet_environment; + condition_parser_rule object_type; + condition_parser_rule start; + universe_object_type_parser_rules universe_object_type_rules; + planet_type_parser_rules planet_type_rules; + planet_size_parser_rules planet_size_rules; + planet_environment_parser_rules planet_environment_rules; + single_or_bracketed_repeat> one_or_more_planet_types; + single_or_bracketed_repeat> one_or_more_planet_sizes; + single_or_bracketed_repeat> one_or_more_planet_environments; + }; + + } +} + +#endif // _ConditionParser6_h_ diff --git a/parse/ConditionParser7.cpp b/parse/ConditionParser7.cpp index d162f3cb323..84136ecfe4a 100644 --- a/parse/ConditionParser7.cpp +++ b/parse/ConditionParser7.cpp @@ -1,9 +1,9 @@ -#include "ConditionParserImpl.h" +#include "ConditionParser7.h" -#include "ParseImpl.h" #include "ValueRefParser.h" -#include "ValueRefParserImpl.h" -#include "../universe/Condition.h" + +#include "../universe/Conditions.h" +#include "../universe/ValueRef.h" #include @@ -14,128 +14,189 @@ namespace phoenix = boost::phoenix; #if DEBUG_CONDITION_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector*>&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector*>&) { return os; } } #endif -namespace { - struct condition_parser_rules_7 { - condition_parser_rules_7() { - const parse::lexer& tok = parse::lexer::instance(); - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_val_type _val; - using phoenix::new_; - using phoenix::push_back; - - ordered_bombarded_by - = tok.OrderedBombardedBy_ - > -parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_1) ] - ; - - contains - = tok.Contains_ - > -parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_1) ] - ; - - contained_by - = tok.ContainedBy_ - > -parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_1) ] - ; - - star_type - = tok.Star_ - > parse::detail::label(Type_token) - > ( - ('[' > +parse::detail::star_type_rules().expr [ push_back(_a, _1) ] > ']') - | parse::detail::star_type_rules().expr [ push_back(_a, _1) ] - ) - [ _val = new_(_a) ] - ; - - location - = (tok.Location_ - > parse::detail::label(Type_token) > - ( - tok.Building_ [ _a = Condition::CONTENT_BUILDING ] - | tok.Species_ [ _a = Condition::CONTENT_SPECIES ] - | tok.Hull_ [ _a = Condition::CONTENT_SHIP_HULL ] - | tok.Part_ [ _a = Condition::CONTENT_SHIP_PART ] - | tok.Special_ [ _a = Condition::CONTENT_SPECIAL ] - | tok.Focus_ [ _a = Condition::CONTENT_FOCUS ] - ) - > parse::detail::label(Name_token) > parse::string_value_ref() [ _b = _1 ] - > -(parse::detail::label(Name_token) > parse::string_value_ref() [ _c = _1 ])) - [ _val = new_(_a, _b, _c) ] - ; - - owner_has_shippart_available - = (tok.OwnerHasShipPartAvailable_ - >> (parse::detail::label(Name_token) - > parse::string_value_ref() [ _val = new_(_1) ] - ) - ) - ; - - start - %= ordered_bombarded_by - | contains - | contained_by - | star_type - | location - | owner_has_shippart_available - ; - - ordered_bombarded_by.name("OrderedBombardedBy"); - contains.name("Contains"); - contained_by.name("ContainedBy"); - star_type.name("StarType"); - location.name("Location"); - owner_has_shippart_available.name("OwnerHasShipPartAvailable"); +namespace parse { namespace detail { + condition_parser_rules_7::condition_parser_rules_7( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + condition_parser_rules_7::base_type(start, "condition_parser_rules_7"), + int_rules(tok, label, condition_parser, string_grammar), + star_type_rules(tok, label, condition_parser), + one_or_more_star_types(star_type_rules.expr) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_val_type _val; + qi::_pass_type _pass; + qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + const boost::phoenix::function deconstruct_movable_vector_; + + using phoenix::new_; + using phoenix::push_back; + using phoenix::construct; + + ordered_bombarded_by + = tok.OrderedBombardedBy_ + > -label(tok.Condition_) > condition_parser + [ _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + contains + = tok.Contains_ + > -label(tok.Condition_) > condition_parser + [ _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + contained_by + = tok.ContainedBy_ + > -label(tok.Condition_) > condition_parser + [ _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + star_type + = tok.Star_ + > label(tok.Type_) + > one_or_more_star_types + [ _val = construct_movable_(new_(deconstruct_movable_vector_(_1, _pass))) ] + ; + + content_type = + tok.Building_ [ _val = Condition::CONTENT_BUILDING ] + | tok.Species_ [ _val = Condition::CONTENT_SPECIES ] + | tok.Hull_ [ _val = Condition::CONTENT_SHIP_HULL ] + | tok.Part_ [ _val = Condition::CONTENT_SHIP_PART ] + | tok.Special_ [ _val = Condition::CONTENT_SPECIAL ] + | tok.Focus_ [ _val = Condition::CONTENT_FOCUS ]; + + location + = (omit_[tok.Location_] + > label(tok.Type_) > content_type + > label(tok.Name_) > string_grammar + > -(label(tok.Name_) > string_grammar)) + [ _val = construct_movable_(new_( + _1, + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass))) ] + ; + + combat_targets + = (omit_[tok.CombatTargets_] + > label(tok.Type_) > content_type + > label(tok.Name_) > string_grammar) + [ _val = construct_movable_(new_( + _1, + deconstruct_movable_(_2, _pass))) ] + ; + + empire_has_buildingtype_available1 + = ( + omit_[tok.EmpireHasBuildingAvailable_] + > label(tok.Name_) > string_grammar + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + empire_has_buildingtype_available2 + = ( + omit_[tok.EmpireHasBuildingAvailable_] + >> label(tok.Empire_) > int_rules.expr + > label(tok.Name_) > string_grammar + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + empire_has_buildingtype_available + %= empire_has_buildingtype_available1 + | empire_has_buildingtype_available2 + ; + + empire_has_shipdesign_available1 + = ( + omit_[tok.EmpireHasShipDesignAvailable_] + >> label(tok.DesignID_) > int_rules.expr + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + empire_has_shipdesign_available2 + = ( + omit_[tok.EmpireHasShipDesignAvailable_] + >> label(tok.Empire_) > int_rules.expr + > label(tok.DesignID_) > int_rules.expr + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + empire_has_shipdesign_available + %= empire_has_shipdesign_available1 + | empire_has_shipdesign_available2 + ; + + empire_has_shippart_available1 + = ( + omit_[tok.OwnerHasShipPartAvailable_] + > label(tok.Name_) > string_grammar + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass))) ] + ; + + empire_has_shippart_available2 + = ( + omit_[tok.EmpireHasShipPartAvailable_] + >> label(tok.Empire_) > int_rules.expr + > label(tok.Name_) > string_grammar + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + empire_has_shippart_available + %= empire_has_shippart_available1 + | empire_has_shippart_available2 + ; + + start + %= ordered_bombarded_by + | contains + | contained_by + | star_type + | location + | combat_targets + | empire_has_buildingtype_available + | empire_has_shipdesign_available + | empire_has_shippart_available + ; + + ordered_bombarded_by.name("OrderedBombardedBy"); + contains.name("Contains"); + contained_by.name("ContainedBy"); + star_type.name("StarType"); + location.name("Location"); + combat_targets.name("CombatTargets"); + empire_has_buildingtype_available.name("EmpireHasBuildingTypeAvailable"); + empire_has_shipdesign_available.name("EmpireHasShipDesignAvailable"); + empire_has_shippart_available.name("EmpireHasShipPartAvailable"); #if DEBUG_CONDITION_PARSERS - debug(ordered_bombarded_by); - debug(contains); - debug(contained_by); - debug(star_type); - debug(location); - debug(owner_has_shippart_available); + debug(ordered_bombarded_by); + debug(contains); + debug(contained_by); + debug(star_type); + debug(location); + debug(combat_targets); + debug(owner_has_shippart_available); #endif - } - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals*>> - > star_type_vec_rule; - - typedef parse::detail::rule< - Condition::ConditionBase* (), - qi::locals< - Condition::ContentType, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > - > string_ref_rule; - - parse::condition_parser_rule ordered_bombarded_by; - parse::condition_parser_rule contains; - parse::condition_parser_rule contained_by; - star_type_vec_rule star_type; - string_ref_rule location; - parse::condition_parser_rule owner_has_shippart_available; - parse::condition_parser_rule start; - }; -} - -namespace parse { namespace detail { - const condition_parser_rule& condition_parser_7() { - static condition_parser_rules_7 retval; - return retval.start; } + } } diff --git a/parse/ConditionParser7.h b/parse/ConditionParser7.h new file mode 100644 index 00000000000..d8a8852eab6 --- /dev/null +++ b/parse/ConditionParser7.h @@ -0,0 +1,40 @@ +#ifndef _ConditionParser7_h_ +#define _ConditionParser7_h_ + +#include "ConditionParserImpl.h" +#include "EnumValueRefRules.h" + +namespace parse { namespace detail { + struct condition_parser_rules_7 : public condition_parser_grammar { + condition_parser_rules_7(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + parse::int_arithmetic_rules int_rules; + star_type_parser_rules star_type_rules; + single_or_bracketed_repeat> + one_or_more_star_types; + condition_parser_rule ordered_bombarded_by; + condition_parser_rule contains; + condition_parser_rule contained_by; + condition_parser_rule star_type; + rule content_type; + condition_parser_rule location; + condition_parser_rule combat_targets; + condition_parser_rule empire_has_buildingtype_available; + condition_parser_rule empire_has_buildingtype_available1; + condition_parser_rule empire_has_buildingtype_available2; + condition_parser_rule empire_has_shipdesign_available; + condition_parser_rule empire_has_shipdesign_available1; + condition_parser_rule empire_has_shipdesign_available2; + condition_parser_rule empire_has_shippart_available; + condition_parser_rule empire_has_shippart_available1; + condition_parser_rule empire_has_shippart_available2; + condition_parser_rule start; + }; + + } +} + +#endif // _ConditionParser7_h_ diff --git a/parse/ConditionParserImpl.h b/parse/ConditionParserImpl.h index f8217151df3..1d9850ec9b9 100644 --- a/parse/ConditionParserImpl.h +++ b/parse/ConditionParserImpl.h @@ -3,21 +3,6 @@ #include "ConditionParser.h" -#include - #define DEBUG_CONDITION_PARSERS 0 -namespace parse { namespace detail { - - extern condition_parser_rule condition_parser; - - const condition_parser_rule& condition_parser_1(); - const condition_parser_rule& condition_parser_2(); - const condition_parser_rule& condition_parser_3(); - const condition_parser_rule& condition_parser_4(); - const condition_parser_rule& condition_parser_5(); - const condition_parser_rule& condition_parser_6(); - const condition_parser_rule& condition_parser_7(); -} } - #endif diff --git a/parse/DoubleComplexValueRefParser.cpp b/parse/DoubleComplexValueRefParser.cpp index 33dc7be3f32..7312d9b5758 100644 --- a/parse/DoubleComplexValueRefParser.cpp +++ b/parse/DoubleComplexValueRefParser.cpp @@ -1,129 +1,229 @@ -#include "ValueRefParserImpl.h" +#include "ValueRefParser.h" +#include "MovableEnvelope.h" +#include "../universe/ValueRefs.h" +#include "EnumParser.h" +#include namespace parse { - struct double_complex_parser_rules { - double_complex_parser_rules() { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::construct; - using phoenix::new_; - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_val_type _val; - - static const std::string TOK_SPECIES_EMPIRE_OPINION{"SpeciesEmpireOpinion"}; - static const std::string TOK_SPECIES_SPECIES_OPINION{"SpeciesSpeciesOpinion"}; - - const parse::lexer& tok = parse::lexer::instance(); - - const parse::value_ref_rule& simple_int = int_simple(); - - game_rule - = tok.GameRule_ [ _a = construct(_1) ] - > detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - part_capacity - = ( - ( tok.PartCapacity_ - | tok.PartSecondaryStat_ ) [ _a = construct(_1) ] - > parse::detail::label(Name_token) > tok.string [ _d = new_>(_1) ] - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - empire_meter_value - = ( tok.EmpireMeterValue_ [ _a = construct(_1)] - > parse::detail::label(Empire_token) > simple_int[ _b = _1] - > parse::detail::label(Meter_token) > tok.string[ _d = new_>(_1)] - ) [_val = new_>(_a, _b, _c, _f, _d, _e)] - ; - - direct_distance - = ( tok.DirectDistanceBetween_ [ _a = construct(_1) ] - > parse::detail::label(Object_token) > simple_int [ _b = _1 ] - > parse::detail::label(Object_token) > simple_int [ _c = _1 ] - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - // in shortest_path would have liked to be able to use - // > parse::detail::label(Object_token) > (simple_int [ _b = _1 ] | int_var_statistic() [ _b = _1 ]) - // > parse::detail::label(Object_token) > (simple_int [ _c = _1 ] | int_var_statistic() [ _c = _1 ]) - // but getting crashes upon program start, presumably due to initialization order problems - - shortest_path - = ( - tok.ShortestPath_ [ _a = construct(_1) ] - > parse::detail::label(Object_token) > simple_int [ _b = _1 ] - > parse::detail::label(Object_token) > simple_int [ _c = _1 ] - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_empire_opinion - = ( - (( tok.SpeciesOpinion_ [ _a = construct(TOK_SPECIES_EMPIRE_OPINION) ] - > parse::detail::label(Species_token) > parse::string_value_ref() [ _d = _1 ] - ) - >> parse::detail::label(Empire_token) - ) > simple_int [ _b = _1 ] - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_species_opinion - = ( - (( tok.SpeciesOpinion_ [ _a = construct(TOK_SPECIES_SPECIES_OPINION) ] - > parse::detail::label(Species_token) > parse::string_value_ref() [ _d = _1 ] - ) - >> parse::detail::label(Species_token) - ) > parse::string_value_ref() [ _e = _1 ] - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - start - %= game_rule - | part_capacity - | empire_meter_value - | direct_distance - | shortest_path - | species_empire_opinion - | species_species_opinion - ; - - game_rule.name("GameRule"); - part_capacity.name("PartCapacity"); - empire_meter_value.name("EmpireMeterValue"); - direct_distance.name("DirectDistanceBetween"); - shortest_path.name("ShortestPathBetween"); - species_empire_opinion.name("SpeciesOpinion (of empire)"); - species_species_opinion.name("SpeciesOpinion (of species)"); + + BOOST_PHOENIX_ADAPT_FUNCTION(std::string, MeterToName_, ValueRef::MeterToName, 1) + + double_complex_parser_grammar::double_complex_parser_grammar( + const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser, + const detail::value_ref_grammar& string_grammar + ) : + double_complex_parser_grammar::base_type(start, "double_complex_parser_grammar"), + simple_int_rules(tok), + ship_part_meter_type_enum(tok) + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::construct; + using phoenix::new_; + + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_val_type _val; + qi::_pass_type _pass; + qi::omit_type omit_; + + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + const std::string TOK_SPECIES_EMPIRE_OPINION{"SpeciesEmpireOpinion"}; + const std::string TOK_SPECIES_SPECIES_OPINION{"SpeciesSpeciesOpinion"}; + + const detail::value_ref_rule& simple_int = simple_int_rules.simple; + + name_property_rule + = (( tok.GameRule_ + | tok.HullFuel_ + | tok.HullStructure_ + | tok.HullStealth_ + | tok.HullSpeed_ + | tok.PartCapacity_ + | tok.PartSecondaryStat_ + ) > label(tok.Name_) > string_grammar + ) [ _val = construct_movable_(new_>( + _1, + nullptr, + nullptr, + nullptr, + deconstruct_movable_(_2, _pass), + nullptr)) ] + ; + + id_empire_location_rule + = ( tok.ShipDesignCost_ + > label(tok.Design_) > simple_int + > label(tok.Empire_) > simple_int + > label(tok.Location_) > simple_int + ) [ _val = construct_movable_(new_>( + _1, + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_4, _pass), + nullptr, nullptr)) ] + ; + + empire_meter_value + = ( tok.EmpireMeterValue_ + > label(tok.Empire_) > simple_int + > label(tok.Meter_) > tok.string + ) [_val = construct_movable_(new_>( + _1, + deconstruct_movable_(_2, _pass), + nullptr, + nullptr, + deconstruct_movable_(construct_movable_(new_>(_3)), _pass), + nullptr)) ] + ; + + direct_distance + = ( tok.DirectDistanceBetween_ + > label(tok.Object_) > simple_int + > label(tok.Object_) > simple_int + ) [ _val = construct_movable_(new_>( + _1, + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + nullptr, nullptr, nullptr)) ] + ; + + // in shortest_path would have liked to be able to use + // > label(tok.Object_) > (simple_int [ _b = _1 ] | int_rules.statistic_expr [ _b = _1 ]) + // > label(tok.Object_) > (simple_int [ _c = _1 ] | int_rules.statistic_expr [ _c = _1 ]) + // but getting crashes upon program start, presumably due to initialization order problems + + shortest_path + = ( + tok.ShortestPath_ + > label(tok.Object_) > simple_int + > label(tok.Object_) > simple_int + ) [ _val = construct_movable_(new_>( + _1, + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + nullptr, nullptr, nullptr)) ] + ; + + species_opinion + = omit_[tok.SpeciesOpinion_] > label(tok.Species_) > string_grammar; + + + species_empire_opinion + = ( species_opinion + >> (label(tok.Empire_) > simple_int) + ) [ _val = construct_movable_(new_>( + construct(TOK_SPECIES_EMPIRE_OPINION), + deconstruct_movable_(_2, _pass), + nullptr, + nullptr, + deconstruct_movable_(_1, _pass), + nullptr)) ] + ; + + species_species_opinion + = ( species_opinion + >> (label(tok.Species_) > string_grammar) + ) [ _val = construct_movable_(new_>( + construct(TOK_SPECIES_SPECIES_OPINION), + nullptr, + nullptr, + nullptr, + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + unwrapped_part_meter + = ( tok.ShipPartMeter_ + > label(tok.Part_) > string_grammar + > label(tok.Meter_) > ship_part_meter_type_enum + > label(tok.Object_) > simple_int + ) [ _val = construct_movable_(new_>( + _1, // variable_name + deconstruct_movable_(_4, _pass), // int_ref1 + nullptr, // int_ref2 + nullptr, // int_ref3 + deconstruct_movable_(_2, _pass), // string_ref1 + deconstruct_movable_(construct_movable_(new_>(MeterToName_(_3))), _pass), // string_ref2 + false // return_immediate_value + ))] + ; + + value_wrapped_part_meter + = ( omit_[tok.Value_] >> '(' + > tok.ShipPartMeter_ + > label(tok.Part_) > string_grammar + > label(tok.Meter_) > ship_part_meter_type_enum + > label(tok.Object_) > simple_int + >> ')' + ) [ _val = construct_movable_(new_>( + _1, // variable_name + deconstruct_movable_(_4, _pass), // int_ref1 + nullptr, // int_ref2 + nullptr, // int_ref3 + deconstruct_movable_(_2, _pass), // string_ref1 + deconstruct_movable_(construct_movable_(new_>(MeterToName_(_3))), _pass), // string_ref2 + true // return_immediate_value + ))] + ; + + special_capacity + = ( tok.SpecialCapacity_ + > label(tok.Name_) > string_grammar + >> label(tok.Object_) + > simple_int + ) [ _val = construct_movable_(new_>( + _1, // variable_name + deconstruct_movable_(_3, _pass), // int_ref1 + nullptr, // int_ref2 + nullptr, // int_ref3 + deconstruct_movable_(_2, _pass), // string_ref1 + nullptr ))] // string_ref2 + ; + + start + %= name_property_rule + | id_empire_location_rule + | empire_meter_value + | direct_distance + | shortest_path + | species_empire_opinion + | species_species_opinion + | unwrapped_part_meter + | value_wrapped_part_meter + | special_capacity + ; + + name_property_rule.name("GameRule; Hull Fuel, Stealth, Structure, or Speed; or PartCapacity or PartSecondaryStat"); + id_empire_location_rule.name("ShipDesignCost"); + empire_meter_value.name("EmpireMeterValue"); + direct_distance.name("DirectDistanceBetween"); + shortest_path.name("ShortestPathBetween"); + species_empire_opinion.name("SpeciesOpinion (of empire)"); + species_species_opinion.name("SpeciesOpinion (of species)"); + special_capacity.name("SpecialCapacity"); + unwrapped_part_meter.name("ShipPartMeter"); + value_wrapped_part_meter.name("ShipPartMeter (immediate value)"); #if DEBUG_DOUBLE_COMPLEX_PARSERS - debug(part_capacity); + debug(name_property_rule); + debug(id_empire_location_rule); + debug(empire_meter_value); + debug(direct_distance); + debug(shortest_path); + debug(species_empire_opinion); + debug(species_species_opinion); + debug(special_capacity); + debug(unwrapped_part_meter); + debug(value_wrapped_part_meter); #endif - } - - complex_variable_rule game_rule; - complex_variable_rule part_capacity; - complex_variable_rule empire_meter_value; - complex_variable_rule direct_distance; - complex_variable_rule shortest_path; - complex_variable_rule species_empire_opinion; - complex_variable_rule species_species_opinion; - complex_variable_rule start; - }; - - namespace detail { - double_complex_parser_rules double_complex_parser; } } - -const complex_variable_rule& double_var_complex() -{ return parse::detail::double_complex_parser.start; } diff --git a/parse/DoubleValueRefParser.cpp b/parse/DoubleValueRefParser.cpp index 9328251fd94..a89038ef811 100644 --- a/parse/DoubleValueRefParser.cpp +++ b/parse/DoubleValueRefParser.cpp @@ -1,167 +1,165 @@ -#include "ValueRefParserImpl.h" - - -namespace { - struct simple_double_parser_rules : - public parse::detail::simple_variable_rules - { - simple_double_parser_rules() : - simple_variable_rules("double") - { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::new_; - - qi::_1_type _1; - qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - bound_variable_name - = tok.Industry_ - | tok.TargetIndustry_ - | tok.Research_ - | tok.TargetResearch_ - | tok.Trade_ - | tok.TargetTrade_ - | tok.Construction_ - | tok.TargetConstruction_ - | tok.Population_ - | tok.TargetPopulation_ - | tok.TargetHappiness_ - | tok.Happiness_ - | tok.MaxFuel_ - | tok.Fuel_ - | tok.MaxShield_ - | tok.Shield_ - | tok.MaxDefense_ - | tok.Defense_ - | tok.MaxTroops_ - | tok.Troops_ - | tok.RebelTroops_ - | tok.MaxStructure_ - | tok.Structure_ - | tok.Supply_ - | tok.Stealth_ - | tok.Detection_ - | tok.Speed_ - | tok.TradeStockpile_ - | tok.X_ - | tok.Y_ - | tok.SizeAsDouble_ - | tok.NextTurnPopGrowth_ - | tok.Size_ - | tok.DistanceFromOriginalType_ - | tok.Attack_ - | tok.PropagatedSupplyRange_ - ; - - free_variable_name - = tok.UniverseCentreX_ - | tok.UniverseCentreY_ - | tok.UniverseWidth_ - ; - - constant - = tok.double_ [ _val = new_>(_1) ] - ; - } - }; - - - simple_double_parser_rules& get_simple_double_parser_rules() { - static simple_double_parser_rules retval; - return retval; - } - - - struct double_parser_rules : public arithmetic_rules { - double_parser_rules() : - arithmetic_rules("real number") - { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::new_; - using phoenix::static_cast_; - - qi::_1_type _1; - qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - const parse::value_ref_rule& simple = double_simple(); - - int_constant_cast - = tok.int_ [ _val = new_>(static_cast_(_1)) ] - ; - - int_bound_variable_cast - = int_bound_variable() [ _val = new_>(_1) ] - ; - - int_free_variable_cast - = int_free_variable() [ _val = new_>(_1) ] - ; - - int_statistic_cast - = int_var_statistic() [ _val = new_>(_1) ] - ; - - int_complex_variable_cast - = int_var_complex() [ _val = new_>(_1) ] - ; - - statistic_value_ref_expr - = primary_expr.alias(); - - primary_expr - = ('(' > expr > ')') - | simple - | statistic_expr - | int_statistic_cast - | double_var_complex() - | int_constant_cast - | int_free_variable_cast - | int_bound_variable_cast - | int_complex_variable_cast - ; +#include "ValueRefParser.h" + +#include "Parse.h" +#include "MovableEnvelope.h" +#include "../universe/ValueRef.h" + +#include + +parse::detail::simple_double_parser_rules::simple_double_parser_rules(const parse::lexer& tok) : + simple_variable_rules("double", tok) +{ + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::new_; + + qi::_1_type _1; + qi::_val_type _val; + const boost::phoenix::function construct_movable_; + + bound_variable_name + = tok.Industry_ + | tok.TargetIndustry_ + | tok.Research_ + | tok.TargetResearch_ + | tok.Trade_ + | tok.TargetTrade_ + | tok.Construction_ + | tok.TargetConstruction_ + | tok.Population_ + | tok.TargetPopulation_ + | tok.TargetHappiness_ + | tok.Happiness_ + | tok.MaxFuel_ + | tok.Fuel_ + | tok.MaxShield_ + | tok.Shield_ + | tok.MaxDefense_ + | tok.Defense_ + | tok.MaxTroops_ + | tok.Troops_ + | tok.RebelTroops_ + | tok.MaxStructure_ + | tok.Structure_ + | tok.MaxSupply_ + | tok.Supply_ + | tok.MaxStockpile_ + | tok.Stockpile_ + | tok.Stealth_ + | tok.Detection_ + | tok.Speed_ + | tok.X_ + | tok.Y_ + | tok.SizeAsDouble_ + | tok.HabitableSize_ + | tok.Size_ + | tok.DistanceFromOriginalType_ + | tok.Attack_ + | tok.PropagatedSupplyRange_ + ; + + free_variable_name + = tok.UniverseCentreX_ + | tok.UniverseCentreY_ + | tok.UniverseWidth_ + ; + + constant + = tok.double_ [ _val = construct_movable_(new_>(_1)) ] + ; +} - int_free_variable_cast.name("integer free variable"); - int_bound_variable_cast.name("integer bound variable"); - int_statistic_cast.name("integer statistic"); - int_complex_variable_cast.name("integer complex variable"); +parse::double_parser_rules::double_parser_rules( + const parse::lexer& tok, + parse::detail::Labeller& label, + const parse::detail::condition_parser_grammar& condition_parser, + const parse::detail::value_ref_grammar& string_grammar +) : + arithmetic_rules("real number", tok, label, condition_parser), + int_rules(tok, label, condition_parser, string_grammar), + simple_int_rules(tok), + simple_double_rules(tok), + int_complex_grammar(tok, label, int_rules, string_grammar), + double_complex_grammar(tok, label, condition_parser, string_grammar) +{ + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::new_; + using phoenix::static_cast_; + + qi::_1_type _1; + qi::_val_type _val; + qi::_pass_type _pass; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + const parse::detail::value_ref_rule& simple = simple_double_rules.simple; + + int_constant_cast + = tok.int_ [ _val = construct_movable_(new_>(static_cast_(_1))) ] + ; + + int_bound_variable_cast + = simple_int_rules.bound_variable [ _val = construct_movable_(new_>(deconstruct_movable_(_1, _pass))) ] + ; + + int_free_variable_cast + = simple_int_rules.free_variable [ _val = construct_movable_(new_>(deconstruct_movable_(_1, _pass))) ] + ; + + int_statistic_cast + = int_rules.statistic_expr [ _val = construct_movable_(new_>(deconstruct_movable_(_1, _pass))) ] + ; + + int_complex_variable_cast + = int_complex_grammar [ _val = construct_movable_(new_>(deconstruct_movable_(_1, _pass))) ] + ; + + statistic_value_ref_expr + = primary_expr.alias(); + + primary_expr + = ('(' > expr > ')') + | simple + | statistic_expr + | int_statistic_cast + | double_complex_grammar + | int_constant_cast + | int_free_variable_cast + | int_bound_variable_cast + | int_complex_variable_cast + ; + + int_free_variable_cast.name("integer free variable"); + int_bound_variable_cast.name("integer bound variable"); + int_statistic_cast.name("integer statistic"); + int_complex_variable_cast.name("integer complex variable"); #if DEBUG_VALUEREF_PARSERS - debug(int_constant_cast); - debug(int_free_variable_cast); - debug(int_bound_variable_cast); - debug(int_statistic_cast); - debug(int_complex_variable_cast); - debug(double_complex_variable); + debug(int_constant_cast); + debug(int_free_variable_cast); + debug(int_bound_variable_cast); + debug(int_statistic_cast); + debug(int_complex_variable_cast); + debug(double_complex_variable); #endif - } - - parse::value_ref_rule int_constant_cast; - parse::value_ref_rule int_bound_variable_cast; - parse::value_ref_rule int_free_variable_cast; - parse::value_ref_rule int_statistic_cast; - parse::value_ref_rule int_complex_variable_cast; - }; - - double_parser_rules& get_double_parser_rules() { - static double_parser_rules retval; - return retval; - } } +namespace parse { + bool double_free_variable(std::string& text) { + const lexer tok; + boost::spirit::qi::in_state_type in_state; + parse::detail::simple_double_parser_rules simple_double_rules(tok); + text_iterator first = text.begin(); + text_iterator last = text.end(); + token_iterator it = tok.begin(first, last); -const parse::value_ref_rule& double_simple() -{ return get_simple_double_parser_rules().simple; } - + bool success = boost::spirit::qi::phrase_parse( + it, tok.end(), simple_double_rules.free_variable_name, in_state("WS")[tok.self]); -namespace parse { - value_ref_rule& double_value_ref() - { return get_double_parser_rules().expr; } + return success; + } } diff --git a/parse/EffectParser.cpp b/parse/EffectParser.cpp index 1e7a0384238..0e0afe04a48 100644 --- a/parse/EffectParser.cpp +++ b/parse/EffectParser.cpp @@ -1,28 +1,139 @@ #include "EffectParserImpl.h" +#include "EffectParser1.h" +#include "EffectParser2.h" +#include "EffectParser3.h" +#include "EffectParser4.h" +#include "EffectParser5.h" + +#include "../universe/Condition.h" +#include "../universe/Effect.h" + +#include namespace parse { - namespace detail { - effect_parser_rule effect_parser; + + detail::MovableEnvelope construct_EffectsGroup( + const detail::MovableEnvelope& scope, + const detail::MovableEnvelope& activation, + const std::vector& effects, + const std::string& accounting_label, + const std::string& stacking_group, + int priority, + const std::string& description, + bool& pass) + { + return detail::MovableEnvelope( + std::make_unique( + scope.OpenEnvelope(pass), + activation.OpenEnvelope(pass), + OpenEnvelopes(effects, pass), + accounting_label, + stacking_group, + priority, + description + )); + } + BOOST_PHOENIX_ADAPT_FUNCTION(detail::MovableEnvelope, + construct_EffectsGroup_, construct_EffectsGroup, 8) + + /** effects_parser_grammar::Impl holds the rules for + effects_parser_grammar. */ + struct effects_parser_grammar::Impl { + Impl(const effects_parser_grammar& effects_parser_grammar, + const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser, + const detail::value_ref_grammar& string_grammar + ) : + effect_parser_1(tok, label, condition_parser, string_grammar), + effect_parser_2(tok, label, condition_parser, string_grammar), + effect_parser_3(tok, label, condition_parser, string_grammar), + effect_parser_4(tok, effects_parser_grammar, label, condition_parser, string_grammar), + effect_parser_5(tok, effects_parser_grammar, label, condition_parser) + {} + + detail::effect_parser_rules_1 effect_parser_1; + detail::effect_parser_rules_2 effect_parser_2; + detail::effect_parser_rules_3 effect_parser_3; + detail::effect_parser_rules_4 effect_parser_4; + detail::effect_parser_rules_5 effect_parser_5; + }; + + effects_parser_grammar::effects_parser_grammar( + const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser, + const detail::value_ref_grammar& string_grammar + ) : + effects_parser_grammar::base_type(start, "effects_parser_grammar"), + m_impl(std::make_unique(*this, tok, label, condition_parser, string_grammar)) + { + start + = m_impl->effect_parser_1 + | m_impl->effect_parser_2 + | m_impl->effect_parser_3 + | m_impl->effect_parser_4 + | m_impl->effect_parser_5 + ; + start.name("Effect"); } - effect_parser_rule& effect_parser() { - static bool once = true; - if (once) { - once = false; - detail::effect_parser - = detail::effect_parser_1() - | detail::effect_parser_2() - | detail::effect_parser_3() - | detail::effect_parser_4() - | detail::effect_parser_5() - ; - detail::effect_parser.name("Effect"); -#if DEBUG_EFFECT_PARSERS - debug(detail::effect_parser); + effects_parser_grammar::~effects_parser_grammar() + {}; + + effects_group_grammar::effects_group_grammar( + const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser, + const detail::value_ref_grammar& string_grammar + ) : + effects_group_grammar::base_type(start, "effects_group_grammar"), + effects_grammar(tok, label, condition_parser, string_grammar), + one_or_more_effects(effects_grammar), + one_or_more_groups(effects_group) + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::construct; + using phoenix::new_; + using phoenix::push_back; + + qi::_1_type _1; + qi::_a_type _a; + qi::_b_type _b; + qi::_c_type _c; + qi::_d_type _d; + qi::_e_type _e; + qi::_f_type _f; + qi::_val_type _val; + qi::lit_type lit; + qi::eps_type eps; + qi::_pass_type _pass; + const boost::phoenix::function construct_movable_; + + effects_group + = tok.EffectsGroup_ + > -(label(tok.Description_) > tok.string [ _f = _1 ]) + > label(tok.Scope_) > condition_parser [ _a = _1 ] + > -(label(tok.Activation_) > condition_parser [ _b = _1 ]) + > -(label(tok.StackingGroup_) > tok.string [ _c = _1 ]) + > -(label(tok.AccountingLabel_) > tok.string [ _d = _1 ]) + > ((label(tok.Priority_) > tok.int_ [ _e = _1 ]) | eps [ _e = 100 ]) + > label(tok.Effects_) + > one_or_more_effects + [ _val = construct_EffectsGroup_(_a, _b, _1, _d, _c, _e, _f, _pass) ] + ; + + start %= one_or_more_groups; + + effects_group.name("EffectsGroup"); + start.name("EffectsGroups"); + +#if DEBUG_PARSERS + debug(effects_group); + debug(start); #endif - } - return detail::effect_parser; } } - diff --git a/parse/EffectParser.h b/parse/EffectParser.h index 7b19b71c5a6..b7707d90285 100644 --- a/parse/EffectParser.h +++ b/parse/EffectParser.h @@ -1,23 +1,69 @@ #ifndef _EffectParser_h_ #define _EffectParser_h_ -#include "Lexer.h" -#include "ParseImpl.h" +#include "ConditionParser.h" +#include "MovableEnvelope.h" #include namespace Effect { - class EffectBase; + class Effect; + class EffectsGroup; } +namespace parse { namespace detail { + using effect_payload = MovableEnvelope; + using effect_signature = effect_payload (); + using effect_parser_rule = rule; + using effect_parser_grammar = grammar; + + } //end namespace detail +} //end namespace parse + namespace parse { - typedef detail::rule< - // TODO: Change this type to Effect::Base in the FO code. - Effect::EffectBase* () - > effect_parser_rule; + struct effects_parser_grammar : public detail::effect_parser_grammar { + effects_parser_grammar(const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser, + const detail::value_ref_grammar& string_grammar); + ~effects_parser_grammar(); + + detail::effect_parser_rule start; + + private: + struct Impl; + std::unique_ptr m_impl; + }; + + using effects_group_payload = std::vector>; + using effects_group_signature = effects_group_payload (); + using effects_group_rule = detail::rule; + + struct effects_group_grammar : public detail::grammar { + effects_group_grammar(const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser, + const detail::value_ref_grammar& string_grammar); + + typedef detail::rule< + parse::detail::MovableEnvelope (), + boost::spirit::qi::locals< + parse::detail::condition_payload, + parse::detail::condition_payload, + std::string, + std::string, + int, + std::string + > + > effect_group_rule; + + effects_parser_grammar effects_grammar; + detail::single_or_bracketed_repeat one_or_more_effects; + effect_group_rule effects_group; + detail::single_or_bracketed_repeat one_or_more_groups; + effects_group_rule start; + }; - /** Returns a const reference to the Effect parser. */ - effect_parser_rule& effect_parser(); } #endif diff --git a/parse/EffectParser1.cpp b/parse/EffectParser1.cpp index f2b8c03962c..105e7333df7 100644 --- a/parse/EffectParser1.cpp +++ b/parse/EffectParser1.cpp @@ -1,217 +1,257 @@ -#include "EffectParserImpl.h" +#include "EffectParser1.h" -#include "ParseImpl.h" -#include "ConditionParserImpl.h" -#include "EnumParser.h" -#include "ValueRefParser.h" -#include "../universe/Effect.h" +#include "../universe/Effects.h" #include "../universe/ValueRef.h" #include "../universe/Enums.h" #include +#include namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; -namespace { - struct effect_parser_rules_1 { - effect_parser_rules_1() { - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_val_type _val; - qi::eps_type eps; - using phoenix::new_; - using phoenix::construct; - using phoenix::push_back; - - const parse::lexer& tok = parse::lexer::instance(); - - set_empire_meter_1 - = (tok.SetEmpireMeter_ >> parse::detail::label(Empire_token)) - > parse::int_value_ref() [ _b = _1 ] - > parse::detail::label(Meter_token) > tok.string [ _a = _1 ] - > parse::detail::label(Value_token) > parse::double_value_ref() [ _val = new_(_b, _a, _1) ] - ; - - set_empire_meter_2 - = (tok.SetEmpireMeter_ >> parse::detail::label(Meter_token)) - > tok.string [ _a = _1 ] - > parse::detail::label(Value_token) > parse::double_value_ref() [ _val = new_(_a, _1) ] - ; - - give_empire_tech - = ( tok.GiveEmpireTech_ - > parse::detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] - > -(parse::detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ]) - ) [ _val = new_(_d, _b) ] - ; - - set_empire_tech_progress - = tok.SetEmpireTechProgress_ - > parse::detail::label(Name_token) > parse::string_value_ref() [ _a = _1 ] - > parse::detail::label(Progress_token) > parse::double_value_ref() [ _b = _1 ] - > ( - (parse::detail::label(Empire_token) > parse::int_value_ref() [ _val = new_(_a, _b, _1) ]) - | eps [ _val = new_(_a, _b) ] +namespace parse { namespace detail { + using PassedMessageParams = + std::vector>>>; + + effect_payload construct_GenerateSitRepMessage1( + const std::string& message_string, const std::string& icon, + const PassedMessageParams& message_parameters, + const MovableEnvelope>& recipient_empire_id, + EmpireAffiliationType affiliation, + const std::string label, + bool stringtable_lookup, + bool& pass) + { + return effect_payload( + new Effect::GenerateSitRepMessage( + message_string, + icon, + OpenEnvelopes(message_parameters, pass), + recipient_empire_id.OpenEnvelope(pass), + affiliation, + label, + stringtable_lookup + ) + ); + } + + effect_payload construct_GenerateSitRepMessage2( + const std::string& message_string, const std::string& icon, + const PassedMessageParams& message_parameters, + EmpireAffiliationType affiliation, + const parse::detail::condition_payload& condition, + const std::string label, + bool stringtable_lookup, + bool& pass) + { + return effect_payload( + new Effect::GenerateSitRepMessage( + message_string, + icon, + OpenEnvelopes(message_parameters, pass), + affiliation, + condition.OpenEnvelope(pass), + label, + stringtable_lookup + ) + ); + } + + effect_payload construct_GenerateSitRepMessage3( + const std::string& message_string, const std::string& icon, + const PassedMessageParams& message_parameters, + EmpireAffiliationType affiliation, + const std::string& label, + bool stringtable_lookup, + bool& pass) + { + return effect_payload( + new Effect::GenerateSitRepMessage( + message_string, + icon, + OpenEnvelopes(message_parameters, pass), + affiliation, + label, + stringtable_lookup + ) + ); + } + + BOOST_PHOENIX_ADAPT_FUNCTION(effect_payload, construct_GenerateSitRepMessage1_, construct_GenerateSitRepMessage1, 8) + BOOST_PHOENIX_ADAPT_FUNCTION(effect_payload, construct_GenerateSitRepMessage2_, construct_GenerateSitRepMessage2, 8) + BOOST_PHOENIX_ADAPT_FUNCTION(effect_payload, construct_GenerateSitRepMessage3_, construct_GenerateSitRepMessage3, 7) + + + effect_parser_rules_1::effect_parser_rules_1( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + effect_parser_rules_1::base_type(start, "effect_parser_rules_1"), + int_rules(tok, label, condition_parser, string_grammar), + double_rules(tok, label, condition_parser, string_grammar), + empire_affiliation_type_enum(tok), + one_or_more_string_and_string_ref_pair(string_and_string_ref) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_a_type _a; + qi::_b_type _b; + qi::_c_type _c; + qi::_d_type _d; + qi::_e_type _e; + qi::_f_type _f; + qi::_val_type _val; + qi::eps_type eps; + qi::_pass_type _pass; + qi::omit_type omit_; + using phoenix::new_; + using phoenix::construct; + using phoenix::push_back; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + set_empire_meter_1 = + (( omit_[tok.SetEmpireMeter_] >> label(tok.Empire_)) + > int_rules.expr + > label(tok.Meter_) > tok.string + > label(tok.Value_) > double_rules.expr + ) [ _val = construct_movable_( + new_( + deconstruct_movable_(_1, _pass), + _2, + deconstruct_movable_(_3, _pass))) ] + ; + + set_empire_meter_2 + = (( omit_[tok.SetEmpireMeter_] + >> label(tok.Meter_)) > tok.string + > label(tok.Value_) > double_rules.expr + ) [ _val = construct_movable_(new_( + _1, + deconstruct_movable_(_2, _pass))) ] + ; + + give_empire_tech + = ( omit_[tok.GiveEmpireTech_] + > label(tok.Name_) > string_grammar + > -(label(tok.Empire_) > int_rules.expr) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + set_empire_tech_progress + = ( omit_[tok.SetEmpireTechProgress_] + > label(tok.Name_) > string_grammar + > label(tok.Progress_) > double_rules.expr + > -(label(tok.Empire_) > int_rules.expr) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass))) ] + ; + + // Note: the NoStringtableLookup flag controls the lookup both of template in Vartext and of the label in SitrepPanel. + generate_sitrep_message + = tok.GenerateSitrepMessage_ + > label(tok.Message_) > tok.string [ _a = _1 ] + > label(tok.Label_) > tok.string [ _e = _1 ] + > ( + tok.NoStringtableLookup_ [ _f = false ] + | eps [ _f = true ] + ) + > -(label(tok.Icon_) > tok.string [ _b = _1 ] ) + > -(label(tok.Parameters_) > one_or_more_string_and_string_ref_pair [_c = _1] ) + > ( + ( // empire id specified, optionally with an affiliation type: + // useful to specify a single recipient empire, or the allies + // or enemies of a single empire + (( (label(tok.Affiliation_) > empire_affiliation_type_enum [ _d = _1 ]) + | eps [ _d = AFFIL_SELF ] ) - ; - - // Note: the NoStringtableLookup flag controls the lookup both of template in Vartext and of the label in SitrepPanel. - generate_sitrep_message - = tok.GenerateSitrepMessage_ - > parse::detail::label(Message_token) > tok.string [ _a = _1 ] - > parse::detail::label(Label_token) > tok.string [ _e = _1 ] - > ( - tok.NoStringtableLookup_ [ _f = false ] - | eps [ _f = true ] - ) - > -(parse::detail::label(Icon_token) > tok.string [ _b = _1 ] ) - > -(parse::detail::label(Parameters_token) > string_and_string_ref_vector [ _c = _1 ] ) - > ( - ( // empire id specified, optionally with an affiliation type: - // useful to specify a single recipient empire, or the allies - // or enemies of a single empire - (( (parse::detail::label(Affiliation_token) > parse::empire_affiliation_type_enum() [ _d = _1 ]) - | eps [ _d = AFFIL_SELF ] - ) - >> parse::detail::label(Empire_token) - ) > parse::int_value_ref() - [ _val = new_(_a, _b, _c, _1, _d, _e, _f) ] - ) - | ( // condition specified, with an affiliation type of CanSee: - // used to specify CanSee affiliation - (parse::detail::label(Affiliation_token) >> tok.CanSee_) - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_a, _b, _c, AFFIL_CAN_SEE, _1, _e, _f) ] - ) - | ( // condition specified, with an affiliation type of CanSee: - // used to specify CanSee affiliation - (parse::detail::label(Affiliation_token) >> tok.Human_) - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_(_a, _b, _c, AFFIL_HUMAN, _1, _e, _f) ] - ) - | ( // no empire id or condition specified, with or without an - // affiliation type: useful to specify no or all empires - ( (parse::detail::label(Affiliation_token) > parse::empire_affiliation_type_enum() [ _d = _1 ]) - | eps [ _d = AFFIL_ANY ] - ) - [ _val = new_(_a, _b, _c, _d, _e, _f) ] - ) + >> label(tok.Empire_) + ) > int_rules.expr + + [ _val = construct_GenerateSitRepMessage1_(_a, _b, _c, _1, _d, _e, _f, _pass) ] + ) + | ( // condition specified, with an affiliation type of CanSee: + // used to specify CanSee affiliation + ( label(tok.Affiliation_) >> tok.CanSee_) + > label(tok.Condition_) > condition_parser + [ _val = construct_GenerateSitRepMessage2_(_a, _b, _c, AFFIL_CAN_SEE, _1, _e, _f, _pass) ] + ) + | ( // condition specified, with an affiliation type of CanSee: + // used to specify CanSee affiliation + ( label(tok.Affiliation_) >> tok.Human_) + > label(tok.Condition_) > condition_parser + [ _val = construct_GenerateSitRepMessage2_(_a, _b, _c, AFFIL_HUMAN, _1, _e, _f, _pass) ] + ) + | ( // no empire id or condition specified, with or without an + // affiliation type: useful to specify no or all empires + ( (label(tok.Affiliation_) > empire_affiliation_type_enum [ _d = _1 ]) + | eps [ _d = AFFIL_ANY ] ) - ; - - set_overlay_texture - = tok.SetOverlayTexture_ - > parse::detail::label(Name_token) > tok.string [ _a = _1 ] - > parse::detail::label(Size_token) > parse::double_value_ref() [ _val = new_(_a, _1) ] - ; - - string_and_string_ref - = parse::detail::label(Tag_token) > tok.string [ _a = _1 ] - > parse::detail::label(Data_token) - > ( parse::int_value_ref() [ _val = construct(_a, new_>(_1)) ] - | parse::double_value_ref() [ _val = construct(_a, new_>(_1)) ] - | tok.string [ _val = construct(_a, new_>(_1)) ] - | parse::string_value_ref() [ _val = construct(_a, _1) ] + [ _val = construct_GenerateSitRepMessage3_( + _a, _b, _c, + _d, _e, _f, _pass) ] + ) + + ) + ; + + set_overlay_texture + = ( omit_[tok.SetOverlayTexture_] + > label(tok.Name_) > tok.string + > label(tok.Size_) > double_rules.expr + ) [ _val = construct_movable_(new_( + _1, + deconstruct_movable_(_2, _pass))) ] + ; + + string_and_string_ref + = + ( + label(tok.Tag_) > tok.string [ _a = _1 ] > + label(tok.Data_) + > ( int_rules.expr [ _b = construct_movable_(new_>(deconstruct_movable_(_1, _pass))) ] + | double_rules.expr [ _b = construct_movable_(new_>(deconstruct_movable_(_1, _pass))) ] + | tok.string [ _b = construct_movable_(new_>(_1)) ] + | string_grammar [ _b = _1 ] ) - ; - - string_and_string_ref_vector - = ('[' > *string_and_string_ref [ push_back(_val, _1) ] > ']') - | string_and_string_ref [ push_back(_val, _1) ] - ; - - start - = set_empire_meter_1 - | set_empire_meter_2 - | give_empire_tech - | set_empire_tech_progress - | generate_sitrep_message - | set_overlay_texture - ; - - set_empire_meter_1.name("SetEmpireMeter (w/empire ID)"); - set_empire_meter_2.name("SetEmpireMeter"); - give_empire_tech.name("GiveEmpireTech"); - set_empire_tech_progress.name("SetEmpireTechProgress"); - generate_sitrep_message.name("GenerateSitrepMessage"); - set_overlay_texture.name("SetOverlayTexture"); - string_and_string_ref.name("Tag and Data (string reference)"); - string_and_string_ref_vector.name("List of Tags and Data"); + ) + [_val = construct(_a, _b)] + ; + + start + %= set_empire_meter_1 + | set_empire_meter_2 + | give_empire_tech + | set_empire_tech_progress + | generate_sitrep_message + | set_overlay_texture + ; + + set_empire_meter_1.name("SetEmpireMeter (w/empire ID)"); + set_empire_meter_2.name("SetEmpireMeter"); + give_empire_tech.name("GiveEmpireTech"); + set_empire_tech_progress.name("SetEmpireTechProgress"); + generate_sitrep_message.name("GenerateSitrepMessage"); + set_overlay_texture.name("SetOverlayTexture"); + string_and_string_ref.name("Tag and Data (string reference)"); #if DEBUG_EFFECT_PARSERS - debug(set_empire_meter_1); - debug(set_empire_meter_2); - debug(give_empire_tech); - debug(set_empire_tech_progress); - debug(generate_sitrep_message); - debug(set_overlay_texture); + debug(set_empire_meter_1); + debug(set_empire_meter_2); + debug(give_empire_tech); + debug(set_empire_tech_progress); + debug(generate_sitrep_message); + debug(set_overlay_texture); #endif - } - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - std::string, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > - > string_and_intref_and_intref_rule; - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > - > stringref_and_doubleref_rule; - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - std::string, - std::string, - std::vector*>>, - EmpireAffiliationType, - std::string, - bool - > - > generate_sitrep_message_rule; - - typedef std::pair*> string_and_string_ref_pair; - - typedef parse::detail::rule< - string_and_string_ref_pair (), - qi::locals - > string_and_string_ref_rule; - - typedef parse::detail::rule< - std::vector () - > string_and_string_ref_vector_rule; - - string_and_intref_and_intref_rule set_empire_meter_1; - string_and_intref_and_intref_rule set_empire_meter_2; - string_and_intref_and_intref_rule give_empire_tech; - stringref_and_doubleref_rule set_empire_tech_progress; - generate_sitrep_message_rule generate_sitrep_message; - string_and_intref_and_intref_rule set_overlay_texture; - string_and_string_ref_rule string_and_string_ref; - string_and_string_ref_vector_rule string_and_string_ref_vector; - parse::effect_parser_rule start; - }; -} - -namespace parse { namespace detail { - const effect_parser_rule& effect_parser_1() { - static effect_parser_rules_1 retval; - return retval.start; } + } } diff --git a/parse/EffectParser1.h b/parse/EffectParser1.h new file mode 100644 index 00000000000..b127827c892 --- /dev/null +++ b/parse/EffectParser1.h @@ -0,0 +1,53 @@ +#ifndef _EffectParser1_h_ +#define _EffectParser1_h_ + +#include "EffectParserImpl.h" +#include "MovableEnvelope.h" + +namespace parse { namespace detail { + struct effect_parser_rules_1 : public effect_parser_grammar { + effect_parser_rules_1(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + typedef rule< + effect_signature, + boost::spirit::qi::locals< + std::string, + std::string, + std::vector>>, + EmpireAffiliationType, + std::string, + bool, + value_ref_payload, + parse::detail::MovableEnvelope + > + > generate_sitrep_message_rule; + + typedef std::pair> string_and_string_ref_pair; + + typedef rule< + string_and_string_ref_pair (), + boost::spirit::qi::locals< + std::string, + value_ref_payload> + > string_and_string_ref_rule; + + parse::int_arithmetic_rules int_rules; + parse::double_parser_rules double_rules; + parse::empire_affiliation_enum_grammar empire_affiliation_type_enum; + effect_parser_rule set_empire_meter_1; + effect_parser_rule set_empire_meter_2; + effect_parser_rule give_empire_tech; + effect_parser_rule set_empire_tech_progress; + generate_sitrep_message_rule generate_sitrep_message; + effect_parser_rule set_overlay_texture; + string_and_string_ref_rule string_and_string_ref; + single_or_bracketed_repeat one_or_more_string_and_string_ref_pair; + effect_parser_rule start; + }; + + } +} +#endif // _EffectParser1_h_ diff --git a/parse/EffectParser2.cpp b/parse/EffectParser2.cpp index 5e4ca4200d4..d7145869fe5 100644 --- a/parse/EffectParser2.cpp +++ b/parse/EffectParser2.cpp @@ -1,220 +1,207 @@ -#include "EffectParserImpl.h" +#include "EffectParser2.h" -#include "ParseImpl.h" -#include "EnumParser.h" #include "ValueRefParser.h" -#include "ValueRefParserImpl.h" -#include "ConditionParserImpl.h" -#include "../universe/Effect.h" +#include "../universe/Condition.h" +#include "../universe/Effects.h" #include "../universe/Enums.h" +#include "../universe/ValueRef.h" #include namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; -namespace { - struct effect_parser_rules_2 { - effect_parser_rules_2() { - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_val_type _val; - qi::eps_type eps; - using phoenix::new_; - - const parse::lexer& tok = parse::lexer::instance(); - - set_meter = - ( - /* has some overlap with parse::set_ship_part_meter_type_enum() so can't use '>' */ - parse::set_non_ship_part_meter_type_enum() [ _a = _1 ] - >> parse::detail::label(Value_token) +namespace parse { namespace detail { + effect_parser_rules_2::effect_parser_rules_2( + const parse::lexer& tok, Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + effect_parser_rules_2::base_type(start, "effect_parser_rules_2"), + int_rules(tok, label, condition_parser, string_grammar), + double_rules(tok, label, condition_parser, string_grammar), + visibility_rules(tok, label, condition_parser), + planet_type_rules(tok, label, condition_parser), + planet_size_rules(tok, label, condition_parser), + empire_affiliation_type_enum(tok), + set_non_ship_part_meter_type_enum(tok), + set_ship_part_meter_type_enum(tok) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_a_type _a; + qi::_b_type _b; + qi::_c_type _c; + qi::_d_type _d; + qi::_e_type _e; + qi::_val_type _val; + qi::eps_type eps; + qi::_pass_type _pass; + using phoenix::new_; + using phoenix::construct; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + set_meter = + (( + /* has some overlap with set_ship_part_meter_type_enum so can't use '>' */ + set_non_ship_part_meter_type_enum [ _a = _1 ] + >> label(tok.Value_) + ) + > double_rules.expr [ _b = _1 ] + > -(label(tok.AccountingLabel_) > tok.string) [ _c = _1] + ) [ _val = construct_movable_(new_( + _a, + deconstruct_movable_(_b, _pass), + _c)) ] + ; + + set_ship_part_meter + = ((set_ship_part_meter_type_enum >> label(tok.PartName_)) > string_grammar + > label(tok.Value_) > double_rules.expr + ) [ _val = construct_movable_(new_( + _1, + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass))) ] + ; + + set_empire_stockpile + = tok.SetEmpireStockpile_ [ _a = RE_INDUSTRY ] + > ( + ( label(tok.Empire_) > int_rules.expr [ _b = _1 ] + > label(tok.Value_) > double_rules.expr [ _val = construct_movable_(new_( + deconstruct_movable_(_b, _pass), + _a, + deconstruct_movable_(_1, _pass))) ] ) - > parse::double_value_ref() [ _c = _1 ] - > ( - (parse::detail::label(AccountingLabel_token) > tok.string [ _val = new_(_a, _c, _1) ] ) - | eps [ _val = new_(_a, _c) ] - ) - ; - - set_ship_part_meter - = (parse::set_ship_part_meter_type_enum() [ _a = _1 ] >> parse::detail::label(PartName_token)) > parse::string_value_ref() [ _b = _1 ] - > parse::detail::label(Value_token) > parse::double_value_ref() [ _val = new_(_a, _b, _1) ] - ; - - set_empire_stockpile - = tok.SetEmpireTradeStockpile_ [ _a = RE_TRADE ] - > ( - ( parse::detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] - > parse::detail::label(Value_token) > parse::double_value_ref() [ _val = new_(_b, _a, _1) ] - ) - | (parse::detail::label(Value_token) > parse::double_value_ref() [ _val = new_(_a, _1) ]) - ) - ; - - set_empire_capital - = tok.SetEmpireCapital_ - > ( - (parse::detail::label(Empire_token) > parse::int_value_ref() [ _val = new_(_1) ]) - | eps [ _val = new_() ] - ) - ; - - set_planet_type - = tok.SetPlanetType_ - > parse::detail::label(Type_token) > parse::detail::planet_type_rules().expr [ _val = new_(_1) ] - ; - - set_planet_size - = tok.SetPlanetSize_ - > parse::detail::label(PlanetSize_token) > parse::detail::planet_size_rules().expr [ _val = new_(_1) ] - ; - - set_species - = tok.SetSpecies_ - > parse::detail::label(Name_token) > parse::string_value_ref() [ _val = new_(_1) ] - ; - - set_owner - = tok.SetOwner_ - > parse::detail::label(Empire_token) > parse::int_value_ref() [ _val = new_(_1) ] - ; - - set_species_opinion - = tok.SetSpeciesOpinion_ - > parse::detail::label(Species_token) > parse::string_value_ref() [ _a = _1 ] - > ( - ( parse::detail::label(Empire_token) > parse::int_value_ref() [ _c = _1 ] - > parse::detail::label(Opinion_token) > parse::double_value_ref() - [ _val = new_(_a, _c, _1) ]) - | - ( parse::detail::label(Species_token) > parse::string_value_ref() [ _b = _1 ] - > parse::detail::label(Opinion_token) > parse::double_value_ref() - [ _val = new_(_a, _b, _1) ]) - ) - ; - - set_visibility - = tok.SetVisibility_ - > ( - parse::detail::label(Visibility_token) - > ( tok.Invisible_ [ _c = VIS_NO_VISIBILITY ] - | tok.Basic_ [ _c = VIS_BASIC_VISIBILITY ] - | tok.Partial_ [ _c = VIS_PARTIAL_VISIBILITY ] - | tok.Full_ [ _c = VIS_FULL_VISIBILITY ] - ) - > ( - ( // empire id specified, optionally with an affiliation type: - // useful to specify a single recipient empire, or the allies - // or enemies of a single empire - ( - ( (parse::detail::label(Affiliation_token) > parse::empire_affiliation_type_enum() [ _d = _1 ]) + | (label(tok.Value_) > double_rules.expr [ _val = construct_movable_(new_( + _a, + deconstruct_movable_(_1, _pass))) ] + ) + ) + ; + + set_empire_capital + = tok.SetEmpireCapital_ + > ( + (label(tok.Empire_) > int_rules.expr [ _val = construct_movable_( + new_(deconstruct_movable_(_1, _pass))) ]) + | eps [ _val = construct_movable_(new_()) ] + ) + ; + + set_planet_type + = tok.SetPlanetType_ + > label(tok.Type_) > planet_type_rules.expr [ _val = construct_movable_( + new_(deconstruct_movable_(_1, _pass))) ] + ; + + set_planet_size + = tok.SetPlanetSize_ + > label(tok.PlanetSize_) > planet_size_rules.expr [ + _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + set_species + = tok.SetSpecies_ + > label(tok.Name_) > string_grammar [ + _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + set_owner + = tok.SetOwner_ + > label(tok.Empire_) > int_rules.expr [ + _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + set_species_opinion + = tok.SetSpeciesOpinion_ + > label(tok.Species_) > string_grammar [ _a = _1 ] + > ( + ( label(tok.Empire_) > int_rules.expr [ _c = _1 ] + > label(tok.Opinion_) > double_rules.expr + [ _val = construct_movable_(new_( + deconstruct_movable_(_a, _pass), + deconstruct_movable_(_c, _pass), + deconstruct_movable_(_1, _pass))) ]) + | + ( label(tok.Species_) > string_grammar [ _b = _1 ] + > label(tok.Opinion_) > double_rules.expr + [ _val = construct_movable_(new_( + deconstruct_movable_(_a, _pass), + deconstruct_movable_(_b, _pass), + deconstruct_movable_(_1, _pass))) ]) + ) + ; + + set_visibility + = tok.SetVisibility_ + > ( + ( + ( // empire id specified, optionally with an affiliation type: + // useful to specify a single recipient empire, or the allies + // or enemies of a single empire + ( + ( (label(tok.Affiliation_) > empire_affiliation_type_enum [ _d = _1 ]) | eps [ _d = AFFIL_SELF ] - ) - >> parse::detail::label(Empire_token) - ) > parse::int_value_ref() [ _b = _1 ] - ) - | ( // no empire id or condition specified, with or without an - // affiliation type: useful to specify no or all empires - ( (parse::detail::label(Affiliation_token) > parse::empire_affiliation_type_enum() [ _d = _1 ]) - | eps [ _d = AFFIL_ANY ] ) + >> label(tok.Empire_) + ) > int_rules.expr [ _b = _1 ] + ) + | ( // no empire id or condition specified, with or without an + // affiliation type: useful to specify no or all empires + ( (label(tok.Affiliation_) > empire_affiliation_type_enum [ _d = _1 ]) + | eps [ _d = AFFIL_ANY ] ) - ) - >-(parse::detail::label(Condition_token) > parse::detail::condition_parser [ _e = _1 ]) - ) [ _val = new_(_c, _d, _b, _e) ] - ; - - start - %= set_ship_part_meter - | set_meter - | set_empire_stockpile - | set_empire_capital - | set_planet_type - | set_planet_size - | set_species - | set_species_opinion - | set_owner - | set_visibility - ; - - set_meter.name("SetMeter"); - set_ship_part_meter.name("SetShipPartMeter"); - set_empire_stockpile.name("SetEmpireStockpile"); - set_empire_capital.name("SetEmpireCapital"); - set_planet_type.name("SetPlanetType"); - set_planet_size.name("SetPlanetSize"); - set_species.name("SetSpecies"); - set_species_opinion.name("SetSpeciesOpinion"); - set_owner.name("SetOwner"); - set_visibility.name("SetVisibility"); + ) + ) + > label(tok.Visibility_) > visibility_rules.expr [ _c = _1 ] + >-(label(tok.Condition_) > condition_parser [ _e = _1 ]) + ) [ _val = construct_movable_( + new_(deconstruct_movable_(_c, _pass), + _d, + deconstruct_movable_(_b, _pass), + deconstruct_movable_(_e, _pass))) ] + ; + + start + %= set_ship_part_meter + | set_meter + | set_empire_stockpile + | set_empire_capital + | set_planet_type + | set_planet_size + | set_species + | set_species_opinion + | set_owner + | set_visibility + ; + + set_meter.name("SetMeter"); + set_ship_part_meter.name("SetShipPartMeter"); + set_empire_stockpile.name("SetEmpireStockpile"); + set_empire_capital.name("SetEmpireCapital"); + set_planet_type.name("SetPlanetType"); + set_planet_size.name("SetPlanetSize"); + set_species.name("SetSpecies"); + set_species_opinion.name("SetSpeciesOpinion"); + set_owner.name("SetOwner"); + set_visibility.name("SetVisibility"); #if DEBUG_EFFECT_PARSERS - debug(set_meter); - debug(set_ship_part_meter); - debug(set_empire_stockpile); - debug(set_empire_capital); - debug(set_planet_type); - debug(set_planet_size); - debug(set_species); - debug(set_species_opinion); - debug(set_owner); - debug(set_visibility); + debug(set_meter); + debug(set_ship_part_meter); + debug(set_empire_stockpile); + debug(set_empire_capital); + debug(set_planet_type); + debug(set_planet_size); + debug(set_species); + debug(set_species_opinion); + debug(set_owner); + debug(set_visibility); #endif - } - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - MeterType, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - std::string - > - > set_meter_rule; - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - ResourceType, - ValueRef::ValueRefBase*, - Visibility, - EmpireAffiliationType, - Condition::ConditionBase* - > - > set_stockpile_or_vis_rule; - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > - > string_string_int_rule; - - set_meter_rule set_meter; - set_meter_rule set_ship_part_meter; - set_stockpile_or_vis_rule set_empire_stockpile; - parse::effect_parser_rule set_empire_capital; - parse::effect_parser_rule set_planet_type; - parse::effect_parser_rule set_planet_size; - parse::effect_parser_rule set_species; - string_string_int_rule set_species_opinion; - parse::effect_parser_rule set_owner; - set_stockpile_or_vis_rule set_visibility; - parse::effect_parser_rule start; - }; -} - -namespace parse { namespace detail { - const effect_parser_rule& effect_parser_2() { - static effect_parser_rules_2 retval; - return retval.start; } + } } diff --git a/parse/EffectParser2.h b/parse/EffectParser2.h new file mode 100644 index 00000000000..d034fbd7b1e --- /dev/null +++ b/parse/EffectParser2.h @@ -0,0 +1,66 @@ +#ifndef _EffectParser2_h_ +#define _EffectParser2_h_ + +#include "EffectParserImpl.h" +#include "EnumValueRefRules.h" + +namespace parse { namespace detail { + struct effect_parser_rules_2 : public effect_parser_grammar { + effect_parser_rules_2(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + typedef rule< + effect_signature, + boost::spirit::qi::locals< + MeterType, + value_ref_payload, + boost::optional + > + > set_meter_rule; + + typedef rule< + effect_signature, + boost::spirit::qi::locals< + ResourceType, + value_ref_payload, + value_ref_payload, + EmpireAffiliationType, + condition_payload + > + > set_stockpile_or_vis_rule; + + typedef rule< + effect_signature, + boost::spirit::qi::locals< + value_ref_payload, + value_ref_payload, + value_ref_payload + > + > string_string_int_rule; + + parse::int_arithmetic_rules int_rules; + parse::double_parser_rules double_rules; + visibility_parser_rules visibility_rules; + set_meter_rule set_meter; + effect_parser_rule set_ship_part_meter; + set_stockpile_or_vis_rule set_empire_stockpile; + effect_parser_rule set_empire_capital; + effect_parser_rule set_planet_type; + effect_parser_rule set_planet_size; + effect_parser_rule set_species; + string_string_int_rule set_species_opinion; + effect_parser_rule set_owner; + set_stockpile_or_vis_rule set_visibility; + effect_parser_rule start; + planet_type_parser_rules planet_type_rules; + planet_size_parser_rules planet_size_rules; + parse::empire_affiliation_enum_grammar empire_affiliation_type_enum; + parse::set_non_ship_part_meter_enum_grammar set_non_ship_part_meter_type_enum; + parse::set_ship_part_meter_enum_grammar set_ship_part_meter_type_enum; + }; + + } +} +#endif // _EffectParser2_h_ diff --git a/parse/EffectParser3.cpp b/parse/EffectParser3.cpp index 7ee5357922b..9253cf3cd2c 100644 --- a/parse/EffectParser3.cpp +++ b/parse/EffectParser3.cpp @@ -1,205 +1,200 @@ -#include "EffectParserImpl.h" +#include "EffectParser3.h" -#include "ParseImpl.h" -#include "EnumParser.h" -#include "ConditionParserImpl.h" #include "ValueRefParser.h" -#include "ValueRefParserImpl.h" -#include "../universe/Effect.h" +#include "../universe/Condition.h" +#include "../universe/Effects.h" +#include "../universe/ValueRef.h" #include namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; -namespace { - struct effect_parser_rules_3 { - effect_parser_rules_3() { - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_val_type _val; - using phoenix::new_; - - const parse::lexer& tok = parse::lexer::instance(); - - move_to - = tok.MoveTo_ - > parse::detail::label(Destination_token) > parse::detail::condition_parser [ _val = new_(_1) ] - ; - - move_in_orbit - = tok.MoveInOrbit_ - > parse::detail::label(Speed_token) > parse::double_value_ref() [ _a = _1 ] - > ( - (parse::detail::label(Focus_token) > parse::detail::condition_parser [ _val = new_(_a, _1) ]) - | - ( - parse::detail::label(X_token) > parse::double_value_ref() [ _b = _1 ] - > parse::detail::label(Y_token) > parse::double_value_ref() [ _val = new_(_a, _b, _1) ] - ) - ) - ; - - move_towards - = tok.MoveTowards_ - > parse::detail::label(Speed_token) > parse::double_value_ref() [ _a = _1 ] - > ( - (parse::detail::label(Target_token) > parse::detail::condition_parser [ _val = new_(_a, _1) ]) - | - ( - parse::detail::label(X_token) > parse::double_value_ref() [ _b = _1 ] - > parse::detail::label(Y_token) > parse::double_value_ref() [ _val = new_(_a, _b, _1) ] - ) - ) - ; - - set_destination - = tok.SetDestination_ - > parse::detail::label(Destination_token) > parse::detail::condition_parser [ _val = new_(_1) ] - ; - - set_aggression - = tok.SetAggressive_ [ _val = new_(true) ] - | tok.SetPassive_ [ _val = new_(false) ] - ; - - destroy - = tok.Destroy_ [ _val = new_() ] - ; - - noop - = tok.NoOp_ [ _val = new_() ] - ; - - victory - = tok.Victory_ - > parse::detail::label(Reason_token) > tok.string [ _val = new_(_1) ] - ; - - add_special_1 - = tok.AddSpecial_ - > parse::detail::label(Name_token) > parse::string_value_ref() [ _val = new_(_1) ] - ; - - add_special_2 - = ((tok.AddSpecial_ | tok.SetSpecialCapacity_) - >> parse::detail::label(Name_token) >> parse::string_value_ref() [ _c = _1 ] - >> (parse::detail::label(Capacity_token) | parse::detail::label(Value_token)) - ) - > parse::double_value_ref() [ _val = new_(_c, _1) ] - ; - - remove_special - = tok.RemoveSpecial_ - > parse::detail::label(Name_token) > parse::string_value_ref() [ _val = new_(_1) ] - ; - - add_starlanes - = tok.AddStarlanes_ - > parse::detail::label(Endpoint_token) > parse::detail::condition_parser [ _val = new_(_1) ] - ; - - remove_starlanes - = tok.RemoveStarlanes_ - > parse::detail::label(Endpoint_token) > parse::detail::condition_parser [ _val = new_(_1) ] - ; - - set_star_type - = tok.SetStarType_ - > parse::detail::label(Type_token) > parse::detail::star_type_rules().expr [ _val = new_(_1) ] - ; - - set_texture - = tok.SetTexture_ - > parse::detail::label(Name_token) > tok.string [ _val = new_(_1) ] - ; - - start - %= move_to - | move_in_orbit - | move_towards - | set_destination - | set_aggression - | destroy - | noop - | victory - | add_special_2 - | add_special_1 - | remove_special - | add_starlanes - | remove_starlanes - | set_star_type - | set_texture - ; - - move_to.name("MoveTo"); - move_in_orbit.name("MoveInOrbit"); - move_towards.name("MoveTowards"); - set_destination.name("SetDestination"); - set_aggression.name("SetAggression"); - destroy.name("Destroy"); - noop.name("NoOp"); - victory.name("Victory"); - add_special_1.name("AddSpecial"); - add_special_2.name("AddSpecial"); - remove_special.name("RemoveSpecial"); - add_starlanes.name("AddStarlanes"); - remove_starlanes.name("RemoveStarlanes"); - set_star_type.name("SetStarType"); - set_texture.name("SetTexture"); +namespace parse { namespace detail { + effect_parser_rules_3::effect_parser_rules_3( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + effect_parser_rules_3::base_type(start, "effect_parser_rules_3"), + double_rules(tok, label, condition_parser, string_grammar), + star_type_rules(tok, label, condition_parser) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_a_type _a; + qi::_b_type _b; + qi::_val_type _val; + qi::_pass_type _pass; + qi::omit_type omit_; + using phoenix::new_; + using phoenix::construct; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + move_to + = tok.MoveTo_ + > label(tok.Destination_) > condition_parser + [ _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + move_in_orbit + = tok.MoveInOrbit_ + > label(tok.Speed_) > double_rules.expr [ _a = _1 ] + > ( + (label(tok.Focus_) > condition_parser [ _val = construct_movable_(new_( + deconstruct_movable_(_a, _pass), + deconstruct_movable_(_1, _pass))) ]) + | + ( + label(tok.X_) > double_rules.expr [ _b = _1 ] + > label(tok.Y_) > double_rules.expr [ _val = construct_movable_(new_( + deconstruct_movable_(_a, _pass), + deconstruct_movable_(_b, _pass), + deconstruct_movable_(_1, _pass))) ] + ) + ) + ; + + move_towards + = tok.MoveTowards_ + > label(tok.Speed_) > double_rules.expr [ _a = _1 ] + > ( + (label(tok.Target_) > condition_parser [ _val = construct_movable_(new_( + deconstruct_movable_(_a, _pass), + deconstruct_movable_(_1, _pass))) ]) + | + ( + label(tok.X_) > double_rules.expr [ _b = _1 ] + > label(tok.Y_) > double_rules.expr [ _val = construct_movable_(new_( + deconstruct_movable_(_a, _pass), + deconstruct_movable_(_b, _pass), + deconstruct_movable_(_1, _pass))) ] + ) + ) + ; + + set_destination + = tok.SetDestination_ + > label(tok.Destination_) > condition_parser [ + _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + set_aggression + = tok.SetAggressive_ [ _val = construct_movable_(new_(true)) ] + | tok.SetPassive_ [ _val = construct_movable_(new_(false)) ] + ; + + destroy + = tok.Destroy_ [ _val = construct_movable_(new_()) ] + ; + + noop + = tok.NoOp_ [ _val = construct_movable_(new_()) ] + ; + + victory + = tok.Victory_ + > label(tok.Reason_) > tok.string [ _val = construct_movable_(new_(_1)) ] + ; + + add_special_1 + = tok.AddSpecial_ + > label(tok.Name_) > string_grammar [ + _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + add_special_2 + = ((omit_[(tok.AddSpecial_ | tok.SetSpecialCapacity_)] + >> label(tok.Name_) >> string_grammar + >> (label(tok.Capacity_) | label(tok.Value_)) + ) + > double_rules.expr + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass))) ] + ; + + remove_special + = tok.RemoveSpecial_ + > label(tok.Name_) > string_grammar [ + _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + add_starlanes + = tok.AddStarlanes_ + > label(tok.Endpoint_) > condition_parser [ + _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + remove_starlanes + = tok.RemoveStarlanes_ + > label(tok.Endpoint_) > condition_parser [ + _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + set_star_type + = tok.SetStarType_ + > label(tok.Type_) > star_type_rules.expr [ + _val = construct_movable_(new_(deconstruct_movable_(_1, _pass))) ] + ; + + set_texture + = tok.SetTexture_ + > label(tok.Name_) > tok.string [ _val = construct_movable_(new_(_1)) ] + ; + + start + %= move_to + | move_in_orbit + | move_towards + | set_destination + | set_aggression + | destroy + | noop + | victory + | add_special_2 + | add_special_1 + | remove_special + | add_starlanes + | remove_starlanes + | set_star_type + | set_texture + ; + + move_to.name("MoveTo"); + move_in_orbit.name("MoveInOrbit"); + move_towards.name("MoveTowards"); + set_destination.name("SetDestination"); + set_aggression.name("SetAggression"); + destroy.name("Destroy"); + noop.name("NoOp"); + victory.name("Victory"); + add_special_1.name("AddSpecial"); + add_special_2.name("AddSpecial"); + remove_special.name("RemoveSpecial"); + add_starlanes.name("AddStarlanes"); + remove_starlanes.name("RemoveStarlanes"); + set_star_type.name("SetStarType"); + set_texture.name("SetTexture"); #if DEBUG_EFFECT_PARSERS - debug(move_to); - debug(move_in_orbit); - debug(move_towards) + debug(move_to); + debug(move_in_orbit); + debug(move_towards) debug(set_destination); - debug(set_aggression); - debug(destroy); - debug(noop); - debug(victory); - debug(add_special_1); - debug(add_special_2); - debug(remove_special); - debug(add_starlanes); - debug(remove_starlanes); - debug(set_star_type); - debug(set_texture); + debug(set_aggression); + debug(destroy); + debug(noop); + debug(victory); + debug(add_special_1); + debug(add_special_2); + debug(remove_special); + debug(add_starlanes); + debug(remove_starlanes); + debug(set_star_type); + debug(set_texture); #endif - } - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > - > doubles_string_rule; - - parse::effect_parser_rule move_to; - doubles_string_rule move_in_orbit; - doubles_string_rule move_towards; - parse::effect_parser_rule set_destination; - parse::effect_parser_rule set_aggression; - parse::effect_parser_rule destroy; - parse::effect_parser_rule noop; - parse::effect_parser_rule victory; - parse::effect_parser_rule add_special_1; - doubles_string_rule add_special_2; - parse::effect_parser_rule remove_special; - parse::effect_parser_rule add_starlanes; - parse::effect_parser_rule remove_starlanes; - parse::effect_parser_rule set_star_type; - parse::effect_parser_rule set_texture; - parse::effect_parser_rule start; - }; -} - -namespace parse { namespace detail { - const effect_parser_rule& effect_parser_3() { - static effect_parser_rules_3 retval; - return retval.start; } } } diff --git a/parse/EffectParser3.h b/parse/EffectParser3.h new file mode 100644 index 00000000000..3ddb664726e --- /dev/null +++ b/parse/EffectParser3.h @@ -0,0 +1,45 @@ +#ifndef _EffectParser3_h_ +#define _EffectParser3_h_ + +#include "EffectParserImpl.h" +#include "EnumValueRefRules.h" + +namespace parse { namespace detail { + struct effect_parser_rules_3 : public effect_parser_grammar { + effect_parser_rules_3(const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + typedef rule< + effect_signature, + boost::spirit::qi::locals< + value_ref_payload, + value_ref_payload, + value_ref_payload + > + > doubles_string_rule; + + parse::double_parser_rules double_rules; + effect_parser_rule move_to; + doubles_string_rule move_in_orbit; + doubles_string_rule move_towards; + effect_parser_rule set_destination; + effect_parser_rule set_aggression; + effect_parser_rule destroy; + effect_parser_rule noop; + effect_parser_rule victory; + effect_parser_rule add_special_1; + effect_parser_rule add_special_2; + effect_parser_rule remove_special; + effect_parser_rule add_starlanes; + effect_parser_rule remove_starlanes; + effect_parser_rule set_star_type; + effect_parser_rule set_texture; + effect_parser_rule start; + star_type_parser_rules star_type_rules; + }; + + } +} +#endif // _EffectParser3_h_ diff --git a/parse/EffectParser4.cpp b/parse/EffectParser4.cpp index 5cbf03e7a4e..9ee07e1da15 100644 --- a/parse/EffectParser4.cpp +++ b/parse/EffectParser4.cpp @@ -1,265 +1,201 @@ -#include "EffectParserImpl.h" +#include "EffectParser4.h" -#include "ParseImpl.h" -#include "EnumParser.h" #include "ValueRefParser.h" -#include "ValueRefParserImpl.h" -#include "../universe/Effect.h" +#include "EnumValueRefRules.h" +#include "../universe/Effects.h" +#include "../universe/ValueRef.h" #include namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; -namespace { - struct effect_parser_rules_4 { - effect_parser_rules_4() { - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_val_type _val; - qi::eps_type eps; - using phoenix::new_; - using phoenix::construct; - using phoenix::push_back; - - const parse::lexer& tok = parse::lexer::instance(); - - create_planet - = ( tok.CreatePlanet_ - > parse::detail::label(Type_token) > parse::detail::planet_type_rules().expr [ _a = _1 ] - > parse::detail::label(PlanetSize_token) > parse::detail::planet_size_rules().expr [ _b = _1 ] - > -(parse::detail::label(Name_token) > parse::string_value_ref() [ _c = _1 ]) - > -(parse::detail::label(Effects_token) - > ( - ('[' > +parse::effect_parser() [ push_back(_d, _1) ] > ']') - | parse::effect_parser() [ push_back(_d, _1) ] - ) - ) - ) [ _val = new_(_a, _b, _c, _d) ] - ; - - create_building - = ( tok.CreateBuilding_ - > parse::detail::label(Type_token) > parse::string_value_ref() [ _a = _1 ] - > -(parse::detail::label(Name_token) > parse::string_value_ref() [ _b = _1 ]) - > -(parse::detail::label(Effects_token) - > ( - ('[' > +parse::effect_parser() [ push_back(_c, _1) ] > ']') - | parse::effect_parser() [ push_back(_c, _1) ] - ) - ) - ) [ _val = new_(_a, _b, _c) ] - ; - - create_ship_1 - = (( tok.CreateShip_ - >> parse::detail::label(DesignID_token) - ) > parse::int_value_ref() [ _b = _1 ] - > -(parse::detail::label(Empire_token) > parse::int_value_ref() [ _c = _1 ]) - > -(parse::detail::label(Species_token) > parse::string_value_ref() [ _d = _1 ]) - > -(parse::detail::label(Name_token) > parse::string_value_ref() [ _e = _1 ]) - > -(parse::detail::label(Effects_token) - > ( - ('[' > +parse::effect_parser() [ push_back(_f, _1) ] > ']') - | parse::effect_parser() [ push_back(_f, _1) ] - ) - ) - ) [ _val = new_(_b, _c, _d, _e, _f) ] - ; - - create_ship_2 - = (( tok.CreateShip_ - >> parse::detail::label(DesignName_token) >> parse::string_value_ref() [ _a = _1 ] - ) - > -(parse::detail::label(Empire_token) > parse::int_value_ref() [ _c = _1 ]) - > -(parse::detail::label(Species_token) > parse::string_value_ref() [ _d = _1 ]) - > -(parse::detail::label(Name_token) > parse::string_value_ref() [ _e = _1 ]) - > -(parse::detail::label(Effects_token) - > ( - ('[' > +parse::effect_parser() [ push_back(_f, _1) ] > ']') - | parse::effect_parser() [ push_back(_f, _1) ] - ) - ) - ) [ _val = new_(_a, _c, _d, _e, _f) ] - ; - - create_field_1 - = (( tok.CreateField_ - >> parse::detail::label(Type_token) >> parse::string_value_ref() [ _a = _1 ] - >> parse::detail::label(Size_token) - ) - > parse::double_value_ref() [ _b = _1 ] - > -(parse::detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ]) - > -(parse::detail::label(Effects_token) - > - ( - ('[' > +parse::effect_parser() [ push_back(_f, _1) ] > ']') - | parse::effect_parser() [ push_back(_f, _1) ] - ) - ) - ) [ _val = new_(_a, _b, _d, _f) ] - ; - - create_field_2 - = (( tok.CreateField_ - >> parse::detail::label(Type_token) >> parse::string_value_ref() [ _a = _1 ] - >> parse::detail::label(X_token) - ) - > parse::double_value_ref() [ _b = _1 ] - > parse::detail::label(Y_token) > parse::double_value_ref() [ _c = _1 ] - > parse::detail::label(Size_token) > parse::double_value_ref() [ _e = _1 ] - > -(parse::detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ]) - > -(parse::detail::label(Effects_token) - > - ( - ('[' > +parse::effect_parser() [ push_back(_f, _1) ] > ']') - | parse::effect_parser() [ push_back(_f, _1) ] - ) - ) - ) [ _val = new_(_a, _b, _c, _e, _d, _f) ] - ; - - create_system_1 - = (( tok.CreateSystem_ - >> parse::detail::label(Type_token) - ) - > parse::detail::star_type_rules().expr [ _a = _1 ] - > parse::detail::label(X_token) > parse::double_value_ref() [ _b = _1 ] - > parse::detail::label(Y_token) > parse::double_value_ref() [ _c = _1 ] - > -(parse::detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ]) - > -(parse::detail::label(Effects_token) - > - ( - ('[' > +parse::effect_parser() [ push_back(_e, _1) ] > ']') - | parse::effect_parser() [ push_back(_e, _1) ] - ) - ) - ) [ _val = new_(_a, _b, _c, _d, _e) ] - ; - - create_system_2 - = (( tok.CreateSystem_ - >> parse::detail::label(X_token) - ) - > parse::double_value_ref() [ _b = _1 ] - > parse::detail::label(Y_token) > parse::double_value_ref() [ _c = _1 ] - > -(parse::detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ]) - > -(parse::detail::label(Effects_token) - > - ( - ('[' > +parse::effect_parser() [ push_back(_e, _1) ] > ']') - | parse::effect_parser() [ push_back(_e, _1) ] - ) - ) - ) [ _val = new_(_b, _c, _d, _e) ] - ; - - start - = create_planet - | create_building - | create_ship_1 - | create_ship_2 - | create_field_1 - | create_field_2 - | create_system_1 - | create_system_2 - ; - - create_planet.name("CreatePlanet"); - create_building.name("CreateBuilding"); - create_ship_1.name("CreateShip"); - create_ship_2.name("CreateShip"); - create_field_1.name("CreateField"); - create_field_2.name("CreateField"); - create_system_1.name("CreateSystem"); - create_system_2.name("CreateSystem"); +namespace parse { namespace detail { + effect_parser_rules_4::effect_parser_rules_4( + const parse::lexer& tok, + const effect_parser_grammar& effect_parser, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar + ) : + effect_parser_rules_4::base_type(start, "effect_parser_rules_4"), + int_rules(tok, label, condition_parser, string_grammar), + double_rules(tok, label, condition_parser, string_grammar), + star_type_rules(tok, label, condition_parser), + planet_type_rules(tok, label, condition_parser), + planet_size_rules(tok, label, condition_parser), + one_or_more_effects(effect_parser) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_5_type _5; + qi::_6_type _6; + qi::_val_type _val; + qi::eps_type eps; + qi::_pass_type _pass; + qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + const boost::phoenix::function deconstruct_movable_vector_; + + using phoenix::new_; + using phoenix::construct; + using phoenix::push_back; + + create_planet + = ( omit_[tok.CreatePlanet_] + > label(tok.Type_) > planet_type_rules.expr + > label(tok.PlanetSize_) > planet_size_rules.expr + > -(label(tok.Name_) > string_grammar ) + > -(label(tok.Effects_) > one_or_more_effects ) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_vector_(_4, _pass))) ] + ; + + create_building + = ( omit_[tok.CreateBuilding_] + > label(tok.Type_) > string_grammar + > -(label(tok.Name_) > string_grammar ) + > -(label(tok.Effects_) > one_or_more_effects ) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_vector_(_3, _pass))) ] + ; + + create_ship_1 + = (( omit_[tok.CreateShip_] + >> label(tok.DesignID_) + ) > int_rules.expr + > -(label(tok.Empire_) > int_rules.expr ) + > -(label(tok.Species_) > string_grammar ) + > -(label(tok.Name_) > string_grammar ) + > -(label(tok.Effects_) > one_or_more_effects ) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_4, _pass), + deconstruct_movable_vector_(_5, _pass))) ] + ; + + create_ship_2 + = (( omit_[tok.CreateShip_] + >> label(tok.DesignName_) >> string_grammar + ) + > -(label(tok.Empire_) > int_rules.expr ) + > -(label(tok.Species_) > string_grammar ) + > -(label(tok.Name_) > string_grammar ) + > -(label(tok.Effects_) > one_or_more_effects ) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_4, _pass), + deconstruct_movable_vector_(_5, _pass))) ] + ; + + create_field_1 + = (( omit_[tok.CreateField_] + >> label(tok.Type_) >> string_grammar + >> label(tok.Size_) + ) + > double_rules.expr + > -(label(tok.Name_) > string_grammar ) + > -(label(tok.Effects_) > one_or_more_effects ) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_vector_(_4, _pass))) ] + ; + + create_field_2 + = (( omit_[tok.CreateField_] + >> label(tok.Type_) >> string_grammar + >> label(tok.X_) + ) + > double_rules.expr + > label(tok.Y_) > double_rules.expr + > label(tok.Size_) > double_rules.expr + > -(label(tok.Name_) > string_grammar ) + > -(label(tok.Effects_) > one_or_more_effects ) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_4, _pass), + deconstruct_movable_(_5, _pass), + deconstruct_movable_vector_(_6, _pass))) ] + ; + + create_system_1 + = (( omit_[tok.CreateSystem_] + >> label(tok.Type_) + ) + > star_type_rules.expr + > label(tok.X_) > double_rules.expr + > label(tok.Y_) > double_rules.expr + > -(label(tok.Name_) > string_grammar ) + > -(label(tok.Effects_) > one_or_more_effects ) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_(_4, _pass), + deconstruct_movable_vector_(_5, _pass))) ] + ; + + create_system_2 + = (( omit_[tok.CreateSystem_] + >> label(tok.X_) + ) + > double_rules.expr + > label(tok.Y_) > double_rules.expr + > -(label(tok.Name_) > string_grammar ) + > -(label(tok.Effects_) > one_or_more_effects ) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_(_2, _pass), + deconstruct_movable_(_3, _pass), + deconstruct_movable_vector_(_4, _pass))) ] + ; + + start + %= create_planet + | create_building + | create_ship_1 + | create_ship_2 + | create_field_1 + | create_field_2 + | create_system_1 + | create_system_2 + ; + + create_planet.name("CreatePlanet"); + create_building.name("CreateBuilding"); + create_ship_1.name("CreateShip"); + create_ship_2.name("CreateShip"); + create_field_1.name("CreateField"); + create_field_2.name("CreateField"); + create_system_1.name("CreateSystem"); + create_system_2.name("CreateSystem"); #if DEBUG_EFFECT_PARSERS - debug(create_planet); - debug(create_building); - debug(create_ship_1); - debug(create_ship_2); - debug(create_field_1); - debug(create_field_2); - debug(create_system_1); - debug(create_system_2); + debug(create_planet); + debug(create_building); + debug(create_ship_1); + debug(create_ship_2); + debug(create_field_1); + debug(create_field_2); + debug(create_system_1); + debug(create_system_2); #endif - } - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - ValueRef::ValueRefBase< ::PlanetType>*, - ValueRef::ValueRefBase< ::PlanetSize>*, - ValueRef::ValueRefBase*, - std::vector - > - > create_planet_rule; - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - std::vector - > - > create_building_rule; - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - ValueRef::ValueRefBase< ::StarType>*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - std::vector - > - > create_system_rule; - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - std::vector - > - > create_ship_rule; - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - std::vector - > - > create_field_rule; - - create_planet_rule create_planet; - create_building_rule create_building; - create_ship_rule create_ship_1; - create_ship_rule create_ship_2; - create_field_rule create_field_1; - create_field_rule create_field_2; - create_system_rule create_system_1; - create_system_rule create_system_2; - parse::effect_parser_rule start; - }; -} - -namespace parse { namespace detail { - const effect_parser_rule& effect_parser_4() { - static effect_parser_rules_4 retval; - return retval.start; } + } } diff --git a/parse/EffectParser4.h b/parse/EffectParser4.h new file mode 100644 index 00000000000..df7a20f173f --- /dev/null +++ b/parse/EffectParser4.h @@ -0,0 +1,34 @@ +#ifndef _EffectParser4_h_ +#define _EffectParser4_h_ + +#include "EffectParserImpl.h" +#include "EnumValueRefRules.h" + +namespace parse { namespace detail { + struct effect_parser_rules_4 : public effect_parser_grammar { + effect_parser_rules_4(const parse::lexer& tok, + const effect_parser_grammar& effect_parser, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_grammar& string_grammar); + + parse::int_arithmetic_rules int_rules; + parse::double_parser_rules double_rules; + effect_parser_rule create_planet; + effect_parser_rule create_building; + effect_parser_rule create_ship_1; + effect_parser_rule create_ship_2; + effect_parser_rule create_field_1; + effect_parser_rule create_field_2; + effect_parser_rule create_system_1; + effect_parser_rule create_system_2; + effect_parser_rule start; + star_type_parser_rules star_type_rules; + planet_type_parser_rules planet_type_rules; + planet_size_parser_rules planet_size_rules; + single_or_bracketed_repeat one_or_more_effects; + }; + } +} + +#endif // _EffectParser4_h_ diff --git a/parse/EffectParser5.cpp b/parse/EffectParser5.cpp index 14e71bc7697..2ece9a4cc9b 100644 --- a/parse/EffectParser5.cpp +++ b/parse/EffectParser5.cpp @@ -1,73 +1,55 @@ -#include "EffectParserImpl.h" +#include "EffectParser5.h" -#include "ParseImpl.h" #include "ConditionParserImpl.h" -#include "../universe/Effect.h" +#include "../universe/Effects.h" +#include "../universe/Condition.h" #include namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; -namespace { - struct effect_parser_rules_5 { - effect_parser_rules_5() { - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_val_type _val; - qi::eps_type eps; - using phoenix::new_; - using phoenix::push_back; - - const parse::lexer& tok = parse::lexer::instance(); - - conditional - = ( tok.If_ - > parse::detail::label(Condition_token) > parse::detail::condition_parser [ _a = _1 ] - > parse::detail::label(Effects_token) - > ( - ('[' > +parse::effect_parser() [ push_back(_b, _1) ] > ']') - | parse::effect_parser() [ push_back(_b, _1) ] - ) - > -(parse::detail::label(Else_token) - > ( - ('[' > +parse::effect_parser() [ push_back(_c, _1) ] > ']') - | parse::effect_parser() [ push_back(_c, _1) ] - ) - ) - ) [ _val = new_(_a, _b, _c) ] - ; - - start - = conditional - ; - - conditional.name("Conditional"); +namespace parse { namespace detail { + effect_parser_rules_5::effect_parser_rules_5(const parse::lexer& tok, + const effect_parser_grammar& effect_parser, + Labeller& label, + const condition_parser_grammar& condition_parser) : + effect_parser_rules_5::base_type(start, "effect_parser_rules_5"), + one_or_more_effects(effect_parser) + { + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_val_type _val; + qi::eps_type eps; + qi::_pass_type _pass; + qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + const boost::phoenix::function deconstruct_movable_vector_; + + using phoenix::new_; + using phoenix::construct; + + conditional + = ( omit_[tok.If_] + > label(tok.Condition_) > condition_parser + > label(tok.Effects_) > one_or_more_effects + > -(label(tok.Else_) > one_or_more_effects) + ) [ _val = construct_movable_(new_( + deconstruct_movable_(_1, _pass), + deconstruct_movable_vector_(_2, _pass), + deconstruct_movable_vector_(_3, _pass))) ] + ; + + start + = conditional + ; + + conditional.name("Conditional"); #if DEBUG_EFFECT_PARSERS - debug(conditional); + debug(conditional); #endif - } - - typedef parse::detail::rule< - Effect::EffectBase* (), - qi::locals< - Condition::ConditionBase*, - std::vector, - std::vector - > - > conditional_rule; - - conditional_rule conditional; - parse::effect_parser_rule start; - }; -} - -namespace parse { namespace detail { - const effect_parser_rule& effect_parser_5() { - static effect_parser_rules_5 retval; - return retval.start; } } } diff --git a/parse/EffectParser5.h b/parse/EffectParser5.h new file mode 100644 index 00000000000..62c46ae6d9d --- /dev/null +++ b/parse/EffectParser5.h @@ -0,0 +1,20 @@ +#ifndef _EffectParser5_h_ +#define _EffectParser5_h_ + +#include "EffectParserImpl.h" + + namespace parse { namespace detail { + struct effect_parser_rules_5 : public effect_parser_grammar { + effect_parser_rules_5(const parse::lexer& tok, + const effect_parser_grammar& effect_parser, + Labeller& label, + const condition_parser_grammar& condition_parser); + + single_or_bracketed_repeat one_or_more_effects; + effect_parser_rule conditional; + effect_parser_rule start; + }; + } + } + +#endif // _EffectParser5_h_ diff --git a/parse/EffectParserImpl.h b/parse/EffectParserImpl.h index bf599fae227..dbef4b7f08c 100644 --- a/parse/EffectParserImpl.h +++ b/parse/EffectParserImpl.h @@ -3,19 +3,6 @@ #include "EffectParser.h" - #define DEBUG_EFFECT_PARSERS 0 -namespace parse { namespace detail { - - extern effect_parser_rule effect_parser; - - const effect_parser_rule& effect_parser_1(); - const effect_parser_rule& effect_parser_2(); - const effect_parser_rule& effect_parser_3(); - const effect_parser_rule& effect_parser_4(); - const effect_parser_rule& effect_parser_5(); - -} } - #endif diff --git a/parse/EmpireStatsParser.cpp b/parse/EmpireStatsParser.cpp index 2ee3e27c52a..98eb053672b 100644 --- a/parse/EmpireStatsParser.cpp +++ b/parse/EmpireStatsParser.cpp @@ -2,6 +2,9 @@ #include "ParseImpl.h" #include "ValueRefParser.h" +#include "ConditionParserImpl.h" +#include "MovableEnvelope.h" +#include "../universe/ValueRef.h" #include @@ -9,14 +12,29 @@ #define DEBUG_PARSERS 0 #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::map*>&) + inline ostream& operator<<(ostream& os, const std::map>>&) + { return os; } + inline ostream& operator<<(ostream& os, const std::map>>&) { return os; } } #endif namespace { - struct rules { - rules() { + using start_rule_payload = std::map>>; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar< + start_rule_signature, + boost::spirit::qi::locals>>> + { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + condition_parser(tok, label), + string_grammar(tok, label, condition_parser), + double_rules(tok, label, condition_parser, string_grammar) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; @@ -30,18 +48,20 @@ namespace { qi::_a_type _a; qi::_r1_type _r1; qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); + qi::_pass_type _pass; + qi::omit_type omit_; + const boost::phoenix::function deconstruct_movable_; stat - = tok.Statistic_ - > parse::detail::label(Name_token) > tok.string [ _a = _1 ] - > parse::detail::label(Value_token) > parse::double_value_ref() - [ _val = construct*>>(_a, _1) ] + = ( omit_[tok.Statistic_] + > label(tok.Name_) > tok.string + > label(tok.Value_) > double_rules.expr + ) [ _val = construct>>(_1, _2) ] ; start - = +stat [ insert(_r1, _1) ] + = (+stat [ insert(_a, _1) ]) + [ _r1 = phoenix::bind(&parse::detail::OpenEnvelopes>, _a, _pass) ] ; stat.name("Double Statistic ValueRef"); @@ -50,31 +70,45 @@ namespace { debug(stat); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - std::pair*> (), - boost::spirit::qi::locals - > stat_rule; + using stat_rule = parse::detail::rule< + std::pair> () + >; - typedef parse::detail::rule< - void (std::map*>&) - > start_rule; + using start_rule = parse::detail::rule< + start_rule_signature, + boost::spirit::qi::locals>> + >; + parse::detail::Labeller label; + parse::conditions_parser_grammar condition_parser; + const parse::string_parser_grammar string_grammar; + parse::double_parser_rules double_rules; stat_rule stat; start_rule start; }; } namespace parse { - bool statistics(std::map*>& stats_) { - bool result = true; - - for (const boost::filesystem::path& file : ListScripts("scripting/empire_statistics")) { - result &= detail::parse_file*>>(file, stats_); + start_rule_payload statistics(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload all_stats; + + for (const boost::filesystem::path& file : ListScripts(path)) { + start_rule_payload stats_; + if (/*auto success =*/ detail::parse_file(lexer, file, stats_)) { + for (auto&& stat : stats_) { + auto maybe_inserted = all_stats.emplace(stat.first, std::move(stat.second)); + if (!maybe_inserted.second) { + WarnLogger() << "Addition of second statistic with name " << maybe_inserted.first->first + << " failed. Keeping first statistic found."; + } + } + } } - return result; + return all_stats; } } diff --git a/parse/EncyclopediaParser.cpp b/parse/EncyclopediaParser.cpp index 24e9612f19a..ac7f1bbf44e 100644 --- a/parse/EncyclopediaParser.cpp +++ b/parse/EncyclopediaParser.cpp @@ -17,16 +17,25 @@ namespace std { #endif namespace { + using ArticleMap = Encyclopedia::ArticleMap; + struct insert_ { typedef void result_type; - void operator()(Encyclopedia& enc, const EncyclopediaArticle& article) const - { enc.articles[article.category].push_back(article); } + void operator()(ArticleMap& articles, const EncyclopediaArticle& article) const + { articles[article.category].push_back(article); } }; const boost::phoenix::function insert; - struct rules { - rules() { + using start_rule_payload = ArticleMap; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; @@ -36,22 +45,18 @@ namespace { qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; + qi::_5_type _5; qi::_r1_type _r1; - - const parse::lexer& tok = parse::lexer::instance(); + qi::omit_type omit_; article - = tok.Article_ - > parse::detail::label(Name_token) > tok.string [ _a = _1 ] - > parse::detail::label(Category_token) > tok.string [ _b = _1 ] - > parse::detail::label(Short_Description_token) > tok.string [ _c = _1 ] - > parse::detail::label(Description_token) > tok.string [ _d = _1 ] - > parse::detail::label(Icon_token) > tok.string - [ insert(_r1, construct(_a, _b, _c, _d, _1)) ] + = ( omit_[tok.Article_] + > label(tok.Name_) > tok.string + > label(tok.Category_) > tok.string + > label(tok.Short_Description_) > tok.string + > label(tok.Description_) > tok.string + > label(tok.Icon_) > tok.string ) + [ insert(_r1, construct(_1, _2, _3, _4, _5)) ] ; start @@ -64,39 +69,29 @@ namespace { debug(article); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - void (Encyclopedia&), - boost::spirit::qi::locals< - std::string, - std::string, - std::string, - std::string - > - > strings_rule; - - typedef parse::detail::rule< - void (Encyclopedia&) - > start_rule; + using strings_rule = parse::detail::rule; + using start_rule = parse::detail::rule; + parse::detail::Labeller label; strings_rule article; start_rule start; }; } namespace parse { - bool encyclopedia_articles(Encyclopedia& enc) { - bool result = true; - - std::vector file_list = ListScripts("scripting/encyclopedia"); + ArticleMap encyclopedia_articles(const boost::filesystem::path& path) { + const lexer lexer; + std::vector file_list = ListScripts(path); + ArticleMap articles; for (const boost::filesystem::path& file : file_list) { - result &= detail::parse_file(file, enc); + /*auto success =*/ detail::parse_file(lexer, file, articles); } - return result; + return articles; } } diff --git a/parse/EnumParser.cpp b/parse/EnumParser.cpp index 7d588e9a5b7..784c7ebe733 100644 --- a/parse/EnumParser.cpp +++ b/parse/EnumParser.cpp @@ -1,6 +1,8 @@ #include "EnumParser.h" #include "../universe/Enums.h" +#include "../universe/ShipPart.h" +#include "../universe/UnlockableItem.h" namespace qi = boost::spirit::qi; @@ -12,73 +14,47 @@ namespace { } namespace parse { - enum_rule& empire_affiliation_type_enum() + empire_affiliation_enum_grammar::empire_affiliation_enum_grammar(const parse::lexer& tok) : + empire_affiliation_enum_grammar::base_type(rule, "empire_affiliation_enum_grammar") { - const parse::lexer& tok = parse::lexer::instance(); - static enum_rule retval + rule = tok.TheEmpire_ [ _val = AFFIL_SELF ] | tok.EnemyOf_ [ _val = AFFIL_ENEMY ] + | tok.PeaceWith_ [ _val = AFFIL_PEACE ] | tok.AllyOf_ [ _val = AFFIL_ALLY ] | tok.AnyEmpire_ [ _val = AFFIL_ANY ] | tok.None_ [ _val = AFFIL_NONE ] | tok.CanSee_ [ _val = AFFIL_CAN_SEE ] | tok.Human_ [ _val = AFFIL_HUMAN ] ; - static bool once = true; - if (once) { - retval.name("EmpireAffiliationType"); -#if DEBUG_PARSERS - debug(retval); -#endif - once = false; - } - return retval; } - enum_rule& unlockable_item_type_enum() + unlockable_item_enum_grammar::unlockable_item_enum_grammar(const parse::lexer& tok) : + unlockable_item_enum_grammar::base_type(rule, "unlockable_item_enum_grammar") { - const parse::lexer& tok = parse::lexer::instance(); - static enum_rule retval + rule = tok.Building_ [ _val = UIT_BUILDING ] | tok.ShipPart_ [ _val = UIT_SHIP_PART ] | tok.ShipHull_ [ _val = UIT_SHIP_HULL ] | tok.ShipDesign_ [ _val = UIT_SHIP_DESIGN ] | tok.Tech_ [ _val = UIT_TECH ] ; - static bool once = true; - if (once) { - retval.name("UnlockableItemType"); -#if DEBUG_PARSERS - debug(retval); -#endif - once = false; - } - return retval; } - enum_rule& ship_slot_type_enum() + ship_slot_enum_grammar::ship_slot_enum_grammar(const parse::lexer& tok) : + ship_slot_enum_grammar::base_type(rule, "ship_slot_enum_grammar") { - const parse::lexer& tok = parse::lexer::instance(); - static enum_rule retval + rule = tok.External_ [ _val = SL_EXTERNAL ] | tok.Internal_ [ _val = SL_INTERNAL ] | tok.Core_ [ _val = SL_CORE ] ; - static bool once = true; - if (once) { - retval.name("ShipSlotType"); -#if DEBUG_PARSERS - debug(retval); -#endif - once = false; - } - return retval; } - enum_rule& ship_part_class_enum() + ship_part_class_enum_grammar::ship_part_class_enum_grammar(const parse::lexer& tok) : + ship_part_class_enum_grammar::base_type(rule, "ship_part_class_enum_grammar") { - const parse::lexer& tok = parse::lexer::instance(); - static enum_rule retval + rule = tok.ShortRange_ [ _val = PC_DIRECT_WEAPON ] | tok.FighterBay_ [ _val = PC_FIGHTER_BAY ] | tok.FighterHangar_ [ _val = PC_FIGHTER_HANGAR ] @@ -97,40 +73,22 @@ namespace parse { | tok.Trade_ [ _val = PC_TRADE ] | tok.ProductionLocation_ [ _val = PC_PRODUCTION_LOCATION ] ; - static bool once = true; - if (once) { - retval.name("ShipPartClass"); -#if DEBUG_PARSERS - debug(retval); -#endif - once = false; - } - return retval; } - enum_rule& capture_result_enum() + capture_result_enum_grammar::capture_result_enum_grammar(const parse::lexer& tok) : + capture_result_enum_grammar::base_type(rule, "capture_result_enum_grammar") { - const parse::lexer& tok = parse::lexer::instance(); - static enum_rule retval + rule = tok.Capture_ [ _val = CR_CAPTURE ] | tok.Retain_ [ _val = CR_RETAIN ] | tok.Destroy_ [ _val = CR_DESTROY ] ; - static bool once = true; - if (once) { - retval.name("CaptureResult"); -#if DEBUG_PARSERS - debug(retval); -#endif - once = false; - } - return retval; } - enum_rule& statistic_type_enum() + statistic_enum_grammar::statistic_enum_grammar(const parse::lexer& tok) : + statistic_enum_grammar::base_type(rule, "statistic_enum_grammar") { - const parse::lexer& tok = parse::lexer::instance(); - static enum_rule retval + rule = tok.CountUnique_ [ _val = ValueRef::UNIQUE_COUNT ] | tok.Count_ [ _val = ValueRef::COUNT ] | tok.If_ [ _val = ValueRef::IF ] @@ -144,88 +102,67 @@ namespace parse { | tok.StDev_ [ _val = ValueRef::STDEV ] | tok.Product_ [ _val = ValueRef::PRODUCT ] ; - static bool once = true; - if (once) { - retval.name("StatisticType"); -#if DEBUG_PARSERS - debug(retval); -#endif - once = false; - } - return retval; } - enum_rule& non_ship_part_meter_type_enum() { - const parse::lexer& tok = parse::lexer::instance(); - static enum_rule retval + non_ship_part_meter_enum_grammar::non_ship_part_meter_enum_grammar(const parse::lexer& tok) : + non_ship_part_meter_enum_grammar::base_type(rule, "non_ship_part_meter_enum_grammar") + { + rule = tok.TargetConstruction_ [ _val = METER_TARGET_CONSTRUCTION ] | tok.TargetIndustry_ [ _val = METER_TARGET_INDUSTRY ] | tok.TargetPopulation_ [ _val = METER_TARGET_POPULATION ] | tok.TargetResearch_ [ _val = METER_TARGET_RESEARCH ] | tok.TargetTrade_ [ _val = METER_TARGET_TRADE ] | tok.TargetHappiness_ [ _val = METER_TARGET_HAPPINESS ] - - | tok.MaxDefense_ [ _val = METER_MAX_DEFENSE ] - | tok.MaxFuel_ [ _val = METER_MAX_FUEL ] - | tok.MaxShield_ [ _val = METER_MAX_SHIELD ] - | tok.MaxStructure_ [ _val = METER_MAX_STRUCTURE ] - | tok.MaxTroops_ [ _val = METER_MAX_TROOPS ] - | tok.MaxSupply_ [ _val = METER_MAX_SUPPLY ] - - | tok.Construction_ [ _val = METER_CONSTRUCTION ] - | tok.Industry_ [ _val = METER_INDUSTRY ] - | tok.Population_ [ _val = METER_POPULATION ] - | tok.Research_ [ _val = METER_RESEARCH ] - | tok.Trade_ [ _val = METER_TRADE ] - | tok.Happiness_ [ _val = METER_HAPPINESS ] - - | tok.Defense_ [ _val = METER_DEFENSE ] - | tok.Fuel_ [ _val = METER_FUEL ] - | tok.Shield_ [ _val = METER_SHIELD ] - | tok.Structure_ [ _val = METER_STRUCTURE ] - | tok.Troops_ [ _val = METER_TROOPS ] - | tok.Supply_ [ _val = METER_SUPPLY ] - - | tok.RebelTroops_ [ _val = METER_REBEL_TROOPS ] - | tok.Stealth_ [ _val = METER_STEALTH ] - | tok.Detection_ [ _val = METER_DETECTION ] - | tok.Speed_ [ _val = METER_SPEED ] - - | tok.Size_ [ _val = METER_SIZE ] + | tok.MaxDefense_ [ _val = METER_MAX_DEFENSE ] + | tok.MaxFuel_ [ _val = METER_MAX_FUEL ] + | tok.MaxShield_ [ _val = METER_MAX_SHIELD ] + | tok.MaxStructure_ [ _val = METER_MAX_STRUCTURE ] + | tok.MaxTroops_ [ _val = METER_MAX_TROOPS ] + | tok.MaxSupply_ [ _val = METER_MAX_SUPPLY ] + | tok.MaxStockpile_ [ _val = METER_MAX_STOCKPILE ] + + | tok.Construction_ [ _val = METER_CONSTRUCTION ] + | tok.Industry_ [ _val = METER_INDUSTRY ] + | tok.Population_ [ _val = METER_POPULATION ] + | tok.Research_ [ _val = METER_RESEARCH ] + | tok.Trade_ [ _val = METER_TRADE ] + | tok.Happiness_ [ _val = METER_HAPPINESS ] + + | tok.Defense_ [ _val = METER_DEFENSE ] + | tok.Fuel_ [ _val = METER_FUEL ] + | tok.Shield_ [ _val = METER_SHIELD ] + | tok.Structure_ [ _val = METER_STRUCTURE ] + | tok.Troops_ [ _val = METER_TROOPS ] + | tok.Supply_ [ _val = METER_SUPPLY ] + | tok.Stockpile_ [ _val = METER_STOCKPILE ] + + | tok.RebelTroops_ [ _val = METER_REBEL_TROOPS ] + | tok.Stealth_ [ _val = METER_STEALTH ] + | tok.Detection_ [ _val = METER_DETECTION ] + | tok.Speed_ [ _val = METER_SPEED ] + + | tok.Size_ [ _val = METER_SIZE ] ; - static bool once = true; - if (once) { - retval.name("non-ship-part MeterType"); -#if DEBUG_PARSERS - debug(retval); -#endif - once = false; - } - return retval; } - enum_rule& ship_part_meter_type_enum() { - const parse::lexer& tok = parse::lexer::instance(); - static enum_rule retval + ship_part_meter_enum_grammar::ship_part_meter_enum_grammar(const parse::lexer& tok) : + ship_part_meter_enum_grammar::base_type(rule, "ship_part_meter_enum_grammar") + { + rule = tok.MaxCapacity_ [ _val = METER_MAX_CAPACITY ] | tok.MaxDamage_ [ _val = METER_MAX_CAPACITY ] | tok.Capacity_ [ _val = METER_CAPACITY ] | tok.Damage_ [ _val = METER_CAPACITY ] + | tok.SecondaryStat_ [ _val = METER_SECONDARY_STAT ] + | tok.MaxSecondaryStat_ [ _val = METER_MAX_SECONDARY_STAT ] ; - static bool once = true; - if (once) { - retval.name("ship-part MeterType"); -#if DEBUG_PARSERS - debug(retval); -#endif - once = false; - } - return retval; } - enum_rule& set_non_ship_part_meter_type_enum() { - const parse::lexer& tok = parse::lexer::instance(); - static enum_rule retval + set_non_ship_part_meter_enum_grammar::set_non_ship_part_meter_enum_grammar(const parse::lexer& tok) : + set_non_ship_part_meter_enum_grammar::base_type(rule, "set_non_ship_part_meter_enum_grammar") + { + rule = tok.SetTargetConstruction_ [ _val = METER_TARGET_CONSTRUCTION ] | tok.SetTargetIndustry_ [ _val = METER_TARGET_INDUSTRY ] | tok.SetTargetPopulation_ [ _val = METER_TARGET_POPULATION ] @@ -239,6 +176,7 @@ namespace parse { | tok.SetMaxStructure_ [ _val = METER_MAX_STRUCTURE ] | tok.SetMaxTroops_ [ _val = METER_MAX_TROOPS ] | tok.SetMaxSupply_ [ _val = METER_MAX_SUPPLY ] + | tok.SetMaxStockpile_ [ _val = METER_MAX_STOCKPILE ] | tok.SetConstruction_ [ _val = METER_CONSTRUCTION ] | tok.SetIndustry_ [ _val = METER_INDUSTRY ] @@ -253,6 +191,7 @@ namespace parse { | tok.SetStructure_ [ _val = METER_STRUCTURE ] | tok.SetTroops_ [ _val = METER_TROOPS ] | tok.SetSupply_ [ _val = METER_SUPPLY ] + | tok.SetStockpile_ [ _val = METER_STOCKPILE ] | tok.SetRebelTroops_ [ _val = METER_REBEL_TROOPS ] | tok.SetStealth_ [ _val = METER_STEALTH ] @@ -261,21 +200,12 @@ namespace parse { | tok.SetSize_ [ _val = METER_SIZE ] ; - static bool once = true; - if (once) { - retval.name("non-ship-part MeterType"); -#if DEBUG_PARSERS - debug(retval); -#endif - once = false; - } - return retval; } - enum_rule& set_ship_part_meter_type_enum() + set_ship_part_meter_enum_grammar::set_ship_part_meter_enum_grammar(const parse::lexer& tok) : + set_ship_part_meter_enum_grammar::base_type(rule, "set_ship_part_meter_enum_grammar") { - const parse::lexer& tok = parse::lexer::instance(); - static enum_rule retval + rule = tok.SetMaxCapacity_ [ _val = METER_MAX_CAPACITY ] | tok.SetMaxDamage_ [ _val = METER_MAX_CAPACITY ] | tok.SetMaxSecondaryStat_ [ _val = METER_MAX_SECONDARY_STAT ] @@ -283,14 +213,5 @@ namespace parse { | tok.SetDamage_ [ _val = METER_CAPACITY ] | tok.SetSecondaryStat_ [ _val = METER_SECONDARY_STAT ] ; - static bool once = true; - if (once) { - retval.name("ship-part MeterType"); -#if DEBUG_PARSERS - debug(retval); -#endif - once = false; - } - return retval; } } diff --git a/parse/EnumParser.h b/parse/EnumParser.h index 161d88e4098..02de1511de5 100644 --- a/parse/EnumParser.h +++ b/parse/EnumParser.h @@ -2,39 +2,77 @@ #define _EnumParser_h_ #include "Lexer.h" -#include "ParseImpl.h" -#include "../universe/ValueRefFwd.h" +#include "ParseImpl.h" +#include "../universe/ValueRef.h" #include "../universe/EnumsFwd.h" #include +struct UnlockableItem; namespace parse { - template - using enum_rule = detail::rule< - E () - >; - - enum_rule& empire_affiliation_type_enum(); - - enum_rule& unlockable_item_type_enum(); - - enum_rule& ship_slot_type_enum(); - - enum_rule& ship_part_class_enum(); - - enum_rule& capture_result_enum(); - - enum_rule& statistic_type_enum(); - - enum_rule& non_ship_part_meter_type_enum(); - - enum_rule& ship_part_meter_type_enum(); - - enum_rule& set_non_ship_part_meter_type_enum(); - - enum_rule& set_ship_part_meter_type_enum(); + struct empire_affiliation_enum_grammar : public detail::enum_grammar { + empire_affiliation_enum_grammar(const parse::lexer& tok); + detail::enum_rule rule; + }; + + struct unlockable_item_enum_grammar : public detail::enum_grammar { + unlockable_item_enum_grammar(const parse::lexer& tok); + detail::enum_rule rule; + }; + + struct ship_slot_enum_grammar : public detail::enum_grammar { + ship_slot_enum_grammar(const parse::lexer& tok); + detail::enum_rule rule; + }; + + struct ship_part_class_enum_grammar : public detail::enum_grammar { + ship_part_class_enum_grammar(const parse::lexer& tok); + detail::enum_rule rule; + }; + + struct capture_result_enum_grammar : public detail::enum_grammar { + capture_result_enum_grammar(const parse::lexer& tok); + detail::enum_rule rule; + }; + + struct statistic_enum_grammar : public detail::enum_grammar { + statistic_enum_grammar(const parse::lexer& tok); + detail::enum_rule rule; + }; + + struct non_ship_part_meter_enum_grammar : public detail::enum_grammar { + non_ship_part_meter_enum_grammar(const parse::lexer& tok); + detail::enum_rule rule; + }; + + struct ship_part_meter_enum_grammar : public detail::enum_grammar { + ship_part_meter_enum_grammar(const parse::lexer& tok); + detail::enum_rule rule; + }; + + struct set_non_ship_part_meter_enum_grammar : public detail::enum_grammar { + set_non_ship_part_meter_enum_grammar(const parse::lexer& tok); + detail::enum_rule rule; + }; + + struct set_ship_part_meter_enum_grammar : public detail::enum_grammar { + set_ship_part_meter_enum_grammar(const parse::lexer& tok); + detail::enum_rule rule; + }; + + namespace detail { + using unlockable_item_rule_type = rule; + using unlockable_item_grammar_type = grammar; + + struct unlockable_item_grammar : public unlockable_item_grammar_type { + unlockable_item_grammar(const parse::lexer& tok, + Labeller& label); + parse::unlockable_item_enum_grammar unlockable_item_type_enum; + unlockable_item_rule_type start; + }; + } } #endif diff --git a/parse/EnumValueRefRules.h b/parse/EnumValueRefRules.h new file mode 100644 index 00000000000..d6d1be2f64a --- /dev/null +++ b/parse/EnumValueRefRules.h @@ -0,0 +1,226 @@ +#ifndef _EnumValueRefRules_h_ +#define _EnumValueRefRules_h_ + +#include "ValueRefParser.h" +#include "MovableEnvelope.h" + +namespace parse { + namespace detail { + + template + void initialize_nonnumeric_statistic_parser( + parse::detail::statistic_rule& statistic, + const parse::lexer& tok, + parse::detail::Labeller& label, + const parse::detail::condition_parser_grammar& condition_parser, + const typename parse::detail::value_ref_rule& value_ref); + + template + struct enum_value_ref_rules { + enum_value_ref_rules(const std::string& type_name, + const lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser); + + rule selection_operator; + value_ref_rule selection_expr; + name_token_rule variable_name; + enum_rule enum_expr; + value_ref_rule constant_expr; + value_ref_rule free_variable_expr; + variable_rule bound_variable_expr; + variable_rule unwrapped_bound_variable_expr; + variable_rule value_wrapped_bound_variable_expr; + expression_rule functional_expr; + value_ref_rule primary_expr; + value_ref_rule statistic_sub_value_ref; + statistic_rule statistic_expr; + complex_variable_rule complex_expr; + value_ref_rule expr; + reference_token_rule variable_scope_rule; + name_token_rule container_type_rule; + }; + + template + void initialize_nonnumeric_statistic_parser( + statistic_rule& statistic, + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser, + const value_ref_rule& value_ref) + { + using boost::phoenix::construct; + using boost::phoenix::new_; + using boost::phoenix::push_back; + + boost::spirit::qi::_1_type _1; + boost::spirit::qi::_2_type _2; + boost::spirit::qi::_val_type _val; + boost::spirit::qi::_pass_type _pass; + boost::spirit::qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + statistic + = ( (omit_[tok.Statistic_] >> omit_[tok.Mode_]) + > label(tok.Value_) > value_ref + > label(tok.Condition_) > condition_parser) + [ _val = construct_movable_(new_>( + deconstruct_movable_(_1, _pass), + ValueRef::MODE, + deconstruct_movable_(_2, _pass))) ] + ; + } + + template + enum_value_ref_rules::enum_value_ref_rules( + const std::string& type_name, + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser) + { + using boost::phoenix::new_; + using boost::phoenix::push_back; + + boost::spirit::qi::_1_type _1; + boost::spirit::qi::_2_type _2; + boost::spirit::qi::_val_type _val; + boost::spirit::qi::_pass_type _pass; + boost::spirit::qi::lit_type lit; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + const boost::phoenix::function deconstruct_movable_vector_; + + constant_expr + = enum_expr [ _val = construct_movable_(new_>(_1)) ] + ; + + variable_scope_rule = variable_scope(tok); + container_type_rule = container_type(tok); + initialize_bound_variable_parser( + bound_variable_expr, unwrapped_bound_variable_expr, + value_wrapped_bound_variable_expr, variable_name, + variable_scope_rule, container_type_rule, tok); + + statistic_sub_value_ref + = constant_expr + | bound_variable_expr + | free_variable_expr + | complex_expr + ; + + selection_operator + = tok.OneOf_ [ _val = ValueRef::RANDOM_PICK ] + | tok.Min_ [ _val = ValueRef::MINIMUM ] + | tok.Max_ [ _val = ValueRef::MAXIMUM ]; + + selection_expr + = (selection_operator > '(' > (expr % ',') > ')') + [ _val = construct_movable_(new_>(_1, deconstruct_movable_vector_(_2, _pass))) ]; + + functional_expr %= selection_expr | primary_expr; + + expr + = functional_expr + ; + + initialize_nonnumeric_statistic_parser( + statistic_expr, tok, label, condition_parser, statistic_sub_value_ref); + + primary_expr + = constant_expr + | bound_variable_expr + | free_variable_expr + | statistic_expr + | complex_expr + ; + +#if DEBUG_VALUEREF_PARSERS + debug(variable_name); + debug(enum_expr); + debug(constant_expr); + debug(free_variable_expr); + debug(bound_variable_expr); + debug(statistic_value_ref_expr); + debug(statistic_expr); + debug(functional_expr); + debug(primary_expr); + debug(expr); +#endif + + variable_name.name(type_name + " variable name"); + enum_expr.name(type_name); + constant_expr.name(type_name + " constant"); + free_variable_expr.name(type_name + " free variable"); + bound_variable_expr.name(type_name + " variable"); + statistic_sub_value_ref.name(type_name + " statistic subvalue"); + statistic_expr.name(type_name + " statistic"); + primary_expr.name(type_name + " expression"); + expr.name(type_name + " expression"); + } + + /* The following parsers are defined in separate compilation units to + avoid MSVC running out of memory and throwing: + fatal error C1060: compiler is out of heap space + */ + struct planet_environment_parser_rules : + public enum_value_ref_rules + { + planet_environment_parser_rules(const lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser); + }; + + struct planet_size_parser_rules : + public enum_value_ref_rules + { + planet_size_parser_rules(const lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser); + }; + + struct planet_type_parser_rules : + public enum_value_ref_rules + { + planet_type_parser_rules(const lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser); + }; + + struct star_type_parser_rules : + public enum_value_ref_rules + { + star_type_parser_rules(const lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser); + }; + + struct visibility_complex_parser_grammar : public complex_variable_grammar { + visibility_complex_parser_grammar(const lexer& tok, Labeller& label); + + simple_int_parser_rules simple_int_rules; + complex_variable_rule empire_object_visibility; + complex_variable_rule start; + }; + + struct visibility_parser_rules : + public enum_value_ref_rules + { + visibility_parser_rules(const lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser); + + visibility_complex_parser_grammar visibility_var_complex_grammar; + }; + + struct universe_object_type_parser_rules : + public enum_value_ref_rules + { + universe_object_type_parser_rules(const lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser); + }; + +}} + +#endif // _EnumValueRefRules_h_ diff --git a/parse/FieldsParser.cpp b/parse/FieldsParser.cpp index 34783054965..9ceec56081e 100644 --- a/parse/FieldsParser.cpp +++ b/parse/FieldsParser.cpp @@ -1,9 +1,10 @@ #include "Parse.h" #include "ParseImpl.h" +#include "EffectParser.h" -#include "../universe/Condition.h" -#include "../universe/Field.h" +#include "../universe/Effect.h" +#include "../universe/FieldType.h" #include @@ -12,48 +13,70 @@ #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector>&) { return os; } - inline ostream& operator<<(ostream& os, const std::map&) { return os; } - inline ostream& operator<<(ostream& os, const std::pair&) { return os; } + inline ostream& operator<<(ostream& os, const parse::effects_group_payload&) { return os; } + inline ostream& operator<<(ostream& os, const std::map>&) { return os; } + inline ostream& operator<<(ostream& os, const std::pair>&) { return os; } } #endif namespace { const boost::phoenix::function is_unique_; - const boost::phoenix::function insert_; + void insert_fieldtype(std::map>& fieldtypes, + const std::string& name, const std::string& description, + float stealth, const std::set& tags, + const boost::optional& effects, + const std::string& graphic, + bool& pass) + { + auto fieldtype_ptr = std::make_unique( + name, description, stealth, tags, + (effects ? OpenEnvelopes(*effects, pass) : std::vector>()), + graphic); + + fieldtypes.insert(std::make_pair(fieldtype_ptr->Name(), std::move(fieldtype_ptr))); + } - struct rules { - rules() { + BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_fieldtype_, insert_fieldtype, 8) + + using start_rule_payload = std::map>; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + condition_parser(tok, label), + string_grammar(tok, label, condition_parser), + effects_group_grammar(tok, label, condition_parser, string_grammar), + tags_parser(tok, label), + double_rule(tok) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; - using phoenix::new_; - qi::_1_type _1; qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; + qi::_5_type _5; + qi::_6_type _6; + qi::_7_type _7; qi::_pass_type _pass; qi::_r1_type _r1; - const parse::lexer& tok = parse::lexer::instance(); - field - = tok.FieldType_ - > parse::detail::label(Name_token) - > tok.string [ _pass = is_unique_(_r1, FieldType_token, _1), _a = _1 ] - > parse::detail::label(Description_token) > tok.string [ _b = _1 ] - > parse::detail::label(Stealth_token) > parse::detail::double_ [ _c = _1] - > parse::detail::tags_parser()(_d) - > -(parse::detail::label(EffectsGroups_token) > parse::detail::effects_group_parser() [ _e = _1 ]) - > parse::detail::label(Graphic_token) > tok.string - [ insert_(_r1, _a, new_(_a, _b, _c, _d, _e, _1)) ] + = ( tok.FieldType_ + > label(tok.Name_) + > tok.string + > label(tok.Description_) > tok.string + > label(tok.Stealth_) > double_rule + > tags_parser + > -(label(tok.EffectsGroups_) > effects_group_grammar ) + > label(tok.Graphic_) > tok.string ) + [ _pass = is_unique_(_r1, _1, _2), + insert_fieldtype_(_r1, _2, _3, _4, _5, _6, _7, _pass) ] ; start @@ -66,37 +89,35 @@ namespace { debug(field); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - void (std::map&), - boost::spirit::qi::locals< - std::string, - std::string, - float, - std::set, - std::vector> - > - > field_rule; - - typedef parse::detail::rule< - void (std::map&) - > start_rule; + using field_rule = parse::detail::rule< + void (std::map>&) + >; + + using start_rule = parse::detail::rule; + parse::detail::Labeller label; + const parse::conditions_parser_grammar condition_parser; + const parse::string_parser_grammar string_grammar; + parse::effects_group_grammar effects_group_grammar; + parse::detail::tags_grammar tags_parser; + parse::detail::double_grammar double_rule; field_rule field; start_rule start; }; } namespace parse { - bool fields(std::map& field_types) { - bool result = true; + start_rule_payload fields(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload field_types; - for (const boost::filesystem::path& file : ListScripts("scripting/fields")) { - result &= detail::parse_file>(file, field_types); + for (const boost::filesystem::path& file : ListScripts(path)) { + /*auto success =*/ detail::parse_file(lexer, file, field_types); } - return result; + return field_types; } } diff --git a/parse/FleetPlansParser.cpp b/parse/FleetPlansParser.cpp index 38a9fd6986f..262f06716cb 100644 --- a/parse/FleetPlansParser.cpp +++ b/parse/FleetPlansParser.cpp @@ -1,25 +1,45 @@ #include "Parse.h" #include "ParseImpl.h" +#include "MovableEnvelope.h" -#include "../universe/UniverseGenerator.h" +#include "../universe/Condition.h" +#include "../universe/FleetPlan.h" #include "../util/Directories.h" #include - #define DEBUG_PARSERS 0 #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector>&) { return os; } inline ostream& operator<<(ostream& os, const std::vector&) { return os; } } #endif namespace { - struct rules { - rules() { + void insert_fleet_plan( + std::vector>& plans, + const std::string& fleet_name, const std::vector& ship_design_names, + bool lookup_names) + { + plans.push_back( + std::make_unique( + fleet_name, ship_design_names, lookup_names)); + } + BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_fleet_plan_, insert_fleet_plan, 4) + + using start_rule_payload = std::vector>; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + one_or_more_string_tokens(tok) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; @@ -30,26 +50,18 @@ namespace { qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; - qi::_a_type _a; - qi::_b_type _b; qi::_r1_type _r1; - - const parse::lexer& tok = parse::lexer::instance(); + qi::omit_type omit_; fleet_plan - = tok.Fleet_ - > parse::detail::label(Name_token) > tok.string [ _a = _1 ] - > parse::detail::label(Ships_token) - > ( - ('[' > +tok.string [ push_back(_b, _1) ] > ']') - | tok.string [ push_back(_b, _1) ] - ) - [ push_back(_r1, new_(_a, _b, phoenix::val(true))) ] + = ( omit_[tok.Fleet_] + > label(tok.Name_) > tok.string + > label(tok.Ships_) > one_or_more_string_tokens ) + [ insert_fleet_plan_(_r1, _1, _2, phoenix::val(true)) ] ; start - = +fleet_plan(_r1) - ; + = (+fleet_plan(_r1)); fleet_plan.name("Fleet"); @@ -57,29 +69,23 @@ namespace { debug(fleet_plan); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - void (std::vector&), - boost::spirit::qi::locals< - std::string, - std::vector - > - > fleet_plan_rule; - - typedef parse::detail::rule< - void (std::vector&) - > start_rule; + using start_rule = parse::detail::rule; - fleet_plan_rule fleet_plan; + parse::detail::Labeller label; + parse::detail::single_or_repeated_string> one_or_more_string_tokens; + start_rule fleet_plan; start_rule start; }; } namespace parse { - bool fleet_plans(std::vector& fleet_plans_) { - const boost::filesystem::path& path = GetResourceDir() / "scripting/starting_unlocks/fleets.inf"; - return detail::parse_file>(path, fleet_plans_); + start_rule_payload fleet_plans(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload fleet_plans_; + /*auto success =*/ detail::parse_file(lexer, path, fleet_plans_); + return fleet_plans_; } } diff --git a/parse/GameRulesParser.cpp b/parse/GameRulesParser.cpp index 3e2412efc14..9bc123964cb 100644 --- a/parse/GameRulesParser.cpp +++ b/parse/GameRulesParser.cpp @@ -3,7 +3,7 @@ #include "ParseImpl.h" #include "../util/Directories.h" -#include "../util/MultiplayerCommon.h" +#include "../util/GameRules.h" #include @@ -74,8 +74,18 @@ namespace { }; const boost::phoenix::function add_rule; - struct rules { - rules() { + using start_rule_payload = GameRules; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + one_or_more_string_tokens(tok), + double_rule(tok), + int_rule(tok) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; @@ -98,17 +108,16 @@ namespace { qi::_j_type _j; qi::_r1_type _r1; qi::eps_type eps; - - const parse::lexer& tok = parse::lexer::instance(); + boost::spirit::qi::repeat_type repeat_; game_rule_bool = (tok.GameRule_ - >> (parse::detail::label(Name_token) > tok.string [ _a = _1 ]) - >> (parse::detail::label(Description_token) > tok.string [ _b = _1 ]) - >> (parse::detail::label(Category_token) > tok.string [ _j = _1 ]) - >> parse::detail::label(Type_token) >> tok.Toggle_ + >> (label(tok.Name_) > tok.string [ _a = _1 ]) + >> (label(tok.Description_) > tok.string [ _b = _1 ]) + >> (label(tok.Category_) > tok.string [ _j = _1 ]) + >> label(tok.Type_) >> tok.Toggle_ ) - > ((parse::detail::label(Default_token) + > ((label(tok.Default_) > ( tok.On_ [ _i = true ] | tok.Off_ [ _i = false ] @@ -120,45 +129,39 @@ namespace { game_rule_int = (tok.GameRule_ - >> (parse::detail::label(Name_token) > tok.string [ _a = _1 ]) - >> (parse::detail::label(Description_token) > tok.string [ _b = _1 ]) - >> (parse::detail::label(Category_token) > tok.string [ _j = _1 ]) - >> parse::detail::label(Type_token) >> tok.Integer_ + >> (label(tok.Name_) > tok.string [ _a = _1 ]) + >> (label(tok.Description_) > tok.string [ _b = _1 ]) + >> (label(tok.Category_) > tok.string [ _j = _1 ]) + >> label(tok.Type_) >> tok.Integer_ ) - > parse::detail::label(Default_token) > parse::detail::int_ [ _f = _1 ] - > parse::detail::label(Min_token) > parse::detail::int_ [ _g = _1 ] - > parse::detail::label(Max_token) > parse::detail::int_ + > label(tok.Default_) > int_rule [ _f = _1 ] + > label(tok.Min_) > int_rule [ _g = _1 ] + > label(tok.Max_) > int_rule [ add_rule(_r1, _a, _b, _j, _f, _g, _1 ) ] ; game_rule_double = (tok.GameRule_ - >> (parse::detail::label(Name_token) > tok.string [ _a = _1 ]) - >> (parse::detail::label(Description_token) > tok.string [ _b = _1 ]) - >> (parse::detail::label(Category_token) > tok.string [ _j = _1 ]) - >> parse::detail::label(Type_token) >> tok.Real_ + >> (label(tok.Name_) > tok.string [ _a = _1 ]) + >> (label(tok.Description_) > tok.string [ _b = _1 ]) + >> (label(tok.Category_) > tok.string [ _j = _1 ]) + >> label(tok.Type_) >> tok.Real_ ) - > parse::detail::label(Default_token) > parse::detail::double_ [ _c = _1 ] - > parse::detail::label(Min_token) > parse::detail::double_ [ _d = _1 ] - > parse::detail::label(Max_token) > parse::detail::double_ + > label(tok.Default_) > double_rule [ _c = _1 ] + > label(tok.Min_) > double_rule [ _d = _1 ] + > label(tok.Max_) > double_rule [ add_rule(_r1, _a, _b, _j, _c, _d, _1 ) ] ; game_rule_string = (tok.GameRule_ - >> (parse::detail::label(Name_token) > tok.string [ _a = _1 ]) - >> (parse::detail::label(Description_token) > tok.string [ _b = _1 ]) - >> (parse::detail::label(Category_token) > tok.string [ _j = _1 ]) - >> parse::detail::label(Type_token) >> tok.String_ - ) - > parse::detail::label(Default_token) > tok.string [ _e = _1 ] - > -( (parse::detail::label(Allowed_token) - > '[' - > +tok.string [ insert(_h, _1) ] - > ']' - ) - |tok.string [ insert(_h, _1) ] + >> (label(tok.Name_) > tok.string [ _a = _1 ]) + >> (label(tok.Description_) > tok.string [ _b = _1 ]) + >> (label(tok.Category_) > tok.string [ _j = _1 ]) + >> label(tok.Type_) >> tok.String_ ) + > label(tok.Default_) > tok.string [ _e = _1 ] + > -( label(tok.Allowed_) > one_or_more_string_tokens [_h = _1]) [ add_rule(_r1, _a, _b, _j, _e, _h) ] ; @@ -189,7 +192,7 @@ namespace { debug(game_rule_string_list); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } typedef parse::detail::rule< @@ -208,10 +211,12 @@ namespace { > > game_rule_rule; - typedef parse::detail::rule< - void (GameRules&) - > start_rule; + using start_rule = parse::detail::rule; + parse::detail::Labeller label; + parse::detail::single_or_repeated_string> one_or_more_string_tokens; + parse::detail::double_grammar double_rule; + parse::detail::int_grammar int_rule; game_rule_rule game_rule_bool; game_rule_rule game_rule_int; game_rule_rule game_rule_double; @@ -223,8 +228,10 @@ namespace { } namespace parse { - bool game_rules(GameRules& game_rules) { - boost::filesystem::path path = GetResourceDir() / "scripting/game_rules.focs.txt"; - return detail::parse_file(path, game_rules); + GameRules game_rules(const boost::filesystem::path& path) { + GameRules game_rules; + const lexer lexer; + /*auto success =*/ detail::parse_file(lexer, path, game_rules); + return game_rules; } } diff --git a/parse/IntComplexValueRefParser.cpp b/parse/IntComplexValueRefParser.cpp index 5f4fcd4dda7..00ef67d1def 100644 --- a/parse/IntComplexValueRefParser.cpp +++ b/parse/IntComplexValueRefParser.cpp @@ -1,378 +1,228 @@ -#include "ValueRefParserImpl.h" +#include "ValueRefParser.h" +#include "MovableEnvelope.h" +#include "../universe/ValueRefs.h" +#include +#include namespace parse { - struct int_complex_parser_rules { - int_complex_parser_rules() { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::construct; - using phoenix::new_; - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - game_rule - = tok.GameRule_ [ _a = construct(_1) ] - > detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - building_types_owned - = ( - tok.BuildingTypesOwned_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - building_types_produced - = ( - tok.BuildingTypesProduced_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - building_types_scrapped - = ( - tok.BuildingTypesScrapped_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - empire_ships_destroyed - = ( - tok.EmpireShipsDestroyed_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Empire_token) > parse::int_value_ref() [ _c = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - jumps_between - = ( - tok.JumpsBetween_ [ _a = construct(_1) ] - > detail::label(Object_token) > (parse::int_value_ref() [ _b = _1 ] | int_var_statistic() [ _b = _1 ]) - > detail::label(Object_token) > (parse::int_value_ref() [ _c = _1 ] | int_var_statistic() [ _c = _1 ]) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - //jumps_between_by_empire_supply - // = ( - // tok.JumpsBetweenByEmpireSupplyConnections_ [ _a = construct(_1) ] - // > detail::label(Object_token) >> parse::int_value_ref() [ _b = _1 ] - // > detail::label(Object_token) >> parse::int_value_ref() [ _c = _1 ] - // > detail::label(Empire_token) >> parse::int_value_ref() [ _f = _1 ] - // ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - // ; - - outposts_owned - = ( - tok.OutpostsOwned_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - parts_in_ship_design - = ( - tok.PartsInShipDesign_[ _a = construct(_1) ] - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - > ( detail::label(Design_token) > parse::int_value_ref() [ _b = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - part_class_in_ship_design - = ( - tok.PartOfClassInShipDesign_ [ _a = construct(_1) ] - //> ( detail::label(Class_token) >> - // as_string [ parse::ship_part_class_enum() ] - // [ _d = new_>(_1) ] - // ) - > ( detail::label(Class_token) > - ( tok.ShortRange_ | tok.FighterBay_ | tok.FighterWeapon_ - | tok.Shield_ | tok.Armour_ - | tok.Troops_ | tok.Detection_ | tok.Stealth_ - | tok.Fuel_ | tok.Colony_ | tok.Speed_ - | tok.General_ | tok.Bombard_ | tok.Research_ - | tok.Industry_ | tok.Trade_ | tok.ProductionLocation_ - ) [ _d = new_>(_1) ] - ) - > ( detail::label(Design_token) > parse::int_value_ref() [ _b = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - ship_parts_owned - = ( - tok.ShipPartsOwned_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( ( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - | ( detail::label(Class_token) >> - parse::ship_part_class_enum() [ _c = new_>(_1) ] - ) - ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - ship_designs_destroyed - = ( - tok.ShipDesignsDestroyed_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Design_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - ship_designs_lost - = ( - tok.ShipDesignsLost_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Design_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - ship_designs_owned - = ( - tok.ShipDesignsOwned_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Design_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - ship_designs_produced - = ( - tok.ShipDesignsProduced_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Design_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - ship_designs_scrapped - = ( - tok.ShipDesignsScrapped_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Design_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - slots_in_hull - = ( - tok.SlotsInHull_ [ _a = construct(_1) ] - > detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - slots_in_ship_design - = ( - tok.SlotsInShipDesign_ [ _a = construct(_1) ] - > detail::label(Design_token) > parse::int_value_ref() [ _b = _1 ] - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_colonies_owned - = ( - tok.SpeciesColoniesOwned_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_planets_bombed - = ( - tok.SpeciesPlanetsBombed_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_planets_depoped - = ( - tok.SpeciesPlanetsDepoped_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_planets_invaded - = ( - tok.SpeciesPlanetsInvaded_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_ships_destroyed - = ( - tok.SpeciesShipsDestroyed_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_ships_lost - = ( - tok.SpeciesShipsLost_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_ships_owned - = ( - tok.SpeciesShipsOwned_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_ships_produced - = ( - tok.SpeciesShipsProduced_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - species_ships_scrapped - = ( - tok.SpeciesShipsScrapped_ [ _a = construct(_1) ] - >-( detail::label(Empire_token) > parse::int_value_ref() [ _b = _1 ] ) - >-( detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] ) - ) [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - start - %= game_rule - | building_types_owned - | building_types_produced - | building_types_scrapped - | empire_ships_destroyed - | jumps_between - //| jumps_between_by_empire_supply - | outposts_owned - | parts_in_ship_design - | part_class_in_ship_design - | ship_parts_owned - | ship_designs_destroyed - | ship_designs_lost - | ship_designs_owned - | ship_designs_produced - | ship_designs_scrapped - | slots_in_hull - | slots_in_ship_design - | species_colonies_owned - | species_planets_bombed - | species_planets_depoped - | species_planets_invaded - | species_ships_destroyed - | species_ships_lost - | species_ships_owned - | species_ships_produced - | species_ships_scrapped - ; - - game_rule.name("GameRule"); - building_types_owned.name("BuildingTypesOwned"); - building_types_produced.name("BuildingTypesProduced"); - building_types_scrapped.name("BuildingTypesScrapped"); - empire_ships_destroyed.name("EmpireShipsDestroyed"); - jumps_between.name("JumpsBetween"); - //jumps_between_by_empire_supply.name("JumpsBetweenByEmpireSupplyConnections"); - outposts_owned.name("OutpostsOwned"); - parts_in_ship_design.name("PartsInShipDesign"); - part_class_in_ship_design.name("PartOfClassInShipDesign"); - ship_parts_owned.name("ShipPartsOwned"); - ship_designs_destroyed.name("ShipDesignsDestroyed"); - ship_designs_lost.name("ShipDesignsLost"); - ship_designs_owned.name("ShipDesignsOwned"); - ship_designs_produced.name("ShipDesignsProduced"); - ship_designs_scrapped.name("ShipDesignsScrapped"); - slots_in_hull.name("SlotsInHull"); - slots_in_ship_design.name("SlotsInShipDesign"); - species_colonies_owned.name("SpeciesColoniesOwned"); - species_planets_bombed.name("SpeciesPlanetsBombed"); - species_planets_depoped.name("SpeciesPlanetsDepoped"); - species_planets_invaded.name("SpeciesPlanetsInvaded"); - species_ships_destroyed.name("SpeciesShipsDestroyed"); - species_ships_lost.name("SpeciesShipsLost"); - species_ships_owned.name("SpeciesShipsOwned"); - species_ships_produced.name("SpeciesShipsProduced"); - species_ships_scrapped.name("SpeciesShipsScrapped"); + int_complex_parser_grammar::int_complex_parser_grammar( + const parse::lexer& tok, + detail::Labeller& label, + const int_arithmetic_rules& _int_arith_rules, + const detail::value_ref_grammar& string_grammar + ) : + int_complex_parser_grammar::base_type(start, "int_complex_parser_grammar"), + int_rules(_int_arith_rules), + ship_part_class_enum(tok) + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::construct; + using phoenix::new_; + + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_val_type _val; + qi::eps_type eps; + qi::_pass_type _pass; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + game_rule + = ( tok.GameRule_ + > label(tok.Name_) > string_grammar + ) [ _val = construct_movable_(new_>(_1, nullptr, nullptr, nullptr, deconstruct_movable_(_2, _pass), nullptr)) ] + ; + empire_name_ref + = ( + ( tok.BuildingTypesOwned_ + | tok.BuildingTypesProduced_ + | tok.BuildingTypesScrapped_ + | tok.SpeciesColoniesOwned_ + | tok.SpeciesPlanetsBombed_ + | tok.SpeciesPlanetsDepoped_ + | tok.SpeciesPlanetsInvaded_ + | tok.SpeciesShipsDestroyed_ + | tok.SpeciesShipsLost_ + | tok.SpeciesShipsOwned_ + | tok.SpeciesShipsProduced_ + | tok.SpeciesShipsScrapped_ + | tok.TurnTechResearched_ + ) + > -( label(tok.Empire_) > int_rules.expr) + > -( label(tok.Name_) > string_grammar) + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_2, _pass), nullptr, nullptr, deconstruct_movable_(_3, _pass), nullptr)) ] + ; + + empire_ships_destroyed + = ( + tok.EmpireShipsDestroyed_ + >-( label(tok.Empire_) > int_rules.expr ) + >-( label(tok.Empire_) > int_rules.expr ) + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_2, _pass), deconstruct_movable_(_3, _pass), nullptr, nullptr, nullptr)) ] + ; + + jumps_between + = ( tok.JumpsBetween_ + > label(tok.Object_) + > ( int_rules.expr + // "cast" the ValueRef::Statistic into + // ValueRef::ValueRef so the alternative contains a + // single type + | qi::as>>()[int_rules.statistic_expr]) + > label(tok.Object_) + > (int_rules.expr + | qi::as>>()[int_rules.statistic_expr]) + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_2, _pass), deconstruct_movable_(_3, _pass), nullptr, nullptr, nullptr)) ] + ; + + //jumps_between_by_empire_supply + // = ( + // tok.JumpsBetweenByEmpireSupplyConnections_ [ _a = construct(_1) ] + // > label(tok.Object_) >> int_rules.expr [ _b = _1 ] + // > label(tok.Object_) >> int_rules.expr [ _c = _1 ] + // > label(tok.Empire_) >> int_rules.expr [ _f = _1 ] + // ) [ _val = construct_movable_(new_>(_a, deconstruct_movable_(_b, _pass), deconstruct_movable_(_c, _pass), deconstruct_movable_(_f, _pass), deconstruct_movable_(_d, _pass), deconstruct_movable_(_e, _pass))) ] + // ; + + outposts_owned + = ( + tok.OutpostsOwned_ + >-( label(tok.Empire_) > int_rules.expr ) + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_2, _pass), nullptr, nullptr, nullptr, nullptr)) ] + ; + + parts_in_ship_design + = ( + tok.PartsInShipDesign_ + >-( label(tok.Name_) > string_grammar ) + > ( label(tok.Design_) > int_rules.expr ) + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_3, _pass), nullptr, nullptr, deconstruct_movable_(_2, _pass), nullptr)) ] + ; + + part_class_in_ship_design + = ( + tok.PartOfClassInShipDesign_ + //> ( label(tok.Class_) >> + // as_string [ ship_part_class_enum ] + // [ _d = construct_movable_(new_>(_1)) ] + // ) + > ( label(tok.Class_) > + ( tok.ShortRange_ | tok.FighterBay_ | tok.FighterWeapon_ + | tok.Shield_ | tok.Armour_ + | tok.Troops_ | tok.Detection_ | tok.Stealth_ + | tok.Fuel_ | tok.Colony_ | tok.Speed_ + | tok.General_ | tok.Bombard_ | tok.Research_ + | tok.Industry_ | tok.Trade_ | tok.ProductionLocation_ + ) + ) + > ( label(tok.Design_) > int_rules.expr) + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_3, _pass), nullptr, nullptr, deconstruct_movable_(construct_movable_(new_>(_2)), _pass), nullptr)) ] + ; + + part_class_as_int + = ( label(tok.Class_) > ship_part_class_enum ) + [ _val = construct_movable_(new_>(_1)) ] + ; + + ship_parts_owned + = ( + tok.ShipPartsOwned_ + > -( label(tok.Empire_) > int_rules.expr ) + > -( label(tok.Name_) > string_grammar ) + > -part_class_as_int + ) [ _val = construct_movable_(new_>( + _1, + deconstruct_movable_(_2, _pass), deconstruct_movable_(_4, _pass), nullptr, + deconstruct_movable_(_3, _pass), nullptr)) ] + ; + + empire_design_ref + = ( + ( tok.ShipDesignsDestroyed_ + | tok.ShipDesignsLost_ + | tok.ShipDesignsInProduction_ + | tok.ShipDesignsOwned_ + | tok.ShipDesignsProduced_ + | tok.ShipDesignsScrapped_ + ) + > -( label(tok.Empire_) > int_rules.expr ) + > -( label(tok.Design_) > string_grammar ) + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_2, _pass), nullptr, nullptr, deconstruct_movable_(_3, _pass), nullptr)) ] + ; + + slots_in_hull + = ( + tok.SlotsInHull_ + > label(tok.Name_) > string_grammar + ) [ _val = construct_movable_(new_>(_1, nullptr, nullptr, nullptr, deconstruct_movable_(_2, _pass), nullptr)) ] + ; + + slots_in_ship_design + = ( + tok.SlotsInShipDesign_ + > label(tok.Design_) > int_rules.expr + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_2, _pass), nullptr, nullptr, nullptr, nullptr)) ] + ; + + special_added_on_turn + = ( + tok.SpecialAddedOnTurn_ + >-( label(tok.Name_) > string_grammar ) + >-( label(tok.Object_) > int_rules.expr ) + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_3, _pass), nullptr, nullptr, deconstruct_movable_(_2, _pass), nullptr)) ] + ; + + start + %= game_rule + | empire_name_ref + | empire_ships_destroyed + | jumps_between + //| jumps_between_by_empire_supply + | outposts_owned + | parts_in_ship_design + | part_class_in_ship_design + | ship_parts_owned + | empire_design_ref + | slots_in_hull + | slots_in_ship_design + | special_added_on_turn + ; + + game_rule.name("GameRule"); + empire_name_ref.name("..."); + empire_ships_destroyed.name("EmpireShipsDestroyed"); + jumps_between.name("JumpsBetween"); + //jumps_between_by_empire_supply.name("JumpsBetweenByEmpireSupplyConnections"); + outposts_owned.name("OutpostsOwned"); + parts_in_ship_design.name("PartsInShipDesign"); + part_class_in_ship_design.name("PartOfClassInShipDesign"); + part_class_as_int.name("PartClass"); + ship_parts_owned.name("ShipPartsOwned"); + empire_design_ref.name("ShipDesignsDestroyed, ShipDesignsInProduction, ShipDesignsLost, ShipDesignsOwned, ShipDesignsProduced, or ShipDesignsScrapped"); + slots_in_hull.name("SlotsInHull"); + slots_in_ship_design.name("SlotsInShipDesign"); + special_added_on_turn.name("SpecialAddedOnTurn"); #if DEBUG_INT_COMPLEX_PARSERS - debug(game_rule); - debug(building_types_owned); - debug(building_types_produced); - debug(building_types_scrapped); - debug(empire_ships_destroyed); - debug(jumps_between); - //debug(jumps_between_by_empire_supply); - debug(outposts_owned); - debug(parts_in_ship_design); - debug(part_class_in_ship_design); - debug(ship_parts_owned); - debug(ship_designs_destroyed); - debug(ship_designs_lost); - debug(ship_designs_owned); - debug(ship_designs_produced); - debug(ship_designs_scrapped); - debug(slots_in_hull); - debug(slots_in_ship_design); - debug(species_colonies_owned); - debug(species_planets_bombed); - debug(species_planets_depoped); - debug(species_planets_invaded); - debug(species_ships_destroyed); - debug(species_ships_lost); - debug(species_ships_owned); - debug(species_ships_produced); - debug(species_ships_scrapped); - + debug(game_rule); + debug(empire_name_ref); + debug(empire_ships_destroyed); + debug(jumps_between); + //debug(jumps_between_by_empire_supply); + debug(outposts_owned); + debug(parts_in_ship_design); + debug(part_class_in_ship_design); + debug(ship_parts_owned_by_name); + debug(ship_parts_owned_by_class); + debug(empire_design_ref); + debug(slots_in_hull); + debug(slots_in_ship_design); + debug(special_added_on_turn); #endif - } - - complex_variable_rule game_rule; - complex_variable_rule building_types_owned; - complex_variable_rule building_types_produced; - complex_variable_rule building_types_scrapped; - complex_variable_rule empire_ships_destroyed; - complex_variable_rule jumps_between; - //complex_variable_rule jumps_between_by_empire_supply; - complex_variable_rule outposts_owned; - complex_variable_rule parts_in_ship_design; - complex_variable_rule part_class_in_ship_design; - complex_variable_rule ship_parts_owned; - complex_variable_rule ship_designs_destroyed; - complex_variable_rule ship_designs_lost; - complex_variable_rule ship_designs_owned; - complex_variable_rule ship_designs_produced; - complex_variable_rule ship_designs_scrapped; - complex_variable_rule slots_in_hull; - complex_variable_rule slots_in_ship_design; - complex_variable_rule species_colonies_owned; - complex_variable_rule species_planets_bombed; - complex_variable_rule species_planets_depoped; - complex_variable_rule species_planets_invaded; - complex_variable_rule species_ships_destroyed; - complex_variable_rule species_ships_lost; - complex_variable_rule species_ships_owned; - complex_variable_rule species_ships_produced; - complex_variable_rule species_ships_scrapped; - complex_variable_rule start; - }; - - namespace detail { - int_complex_parser_rules int_complex_parser; } } - -const complex_variable_rule& int_var_complex() -{ return parse::detail::int_complex_parser.start; } diff --git a/parse/IntValueRefParser.cpp b/parse/IntValueRefParser.cpp index e361d36d2cb..93e14dae272 100644 --- a/parse/IntValueRefParser.cpp +++ b/parse/IntValueRefParser.cpp @@ -1,158 +1,152 @@ -#include "ValueRefParserImpl.h" - - -namespace { - struct simple_int_parser_rules : - public parse::detail::simple_variable_rules - { - simple_int_parser_rules() : - simple_variable_rules("integer") - { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::new_; - - qi::_1_type _1; - qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - // TODO: Should we apply elements of this list only to certain - // objects? For example, if one writes "Source.Planet.", - // "NumShips" should not follow. - bound_variable_name - = tok.Owner_ - | tok.SupplyingEmpire_ - | tok.ID_ - | tok.CreationTurn_ - | tok.Age_ - | tok.ProducedByEmpireID_ - | tok.ArrivedOnTurn_ - | tok.DesignID_ - | tok.FleetID_ - | tok.PlanetID_ - | tok.SystemID_ - | tok.FinalDestinationID_ - | tok.NextSystemID_ - | tok.NearestSystemID_ - | tok.PreviousSystemID_ - | tok.NumShips_ - | tok.NumStarlanes_ - | tok.LastTurnBattleHere_ - | tok.LastTurnActiveInBattle_ - | tok.Orbit_ - | tok.SpeciesID_ - | tok.TurnsSinceFocusChange_ - | tok.ETA_ - ; - - free_variable_name - = tok.CurrentTurn_ - | tok.GalaxyAge_ - | tok.GalaxyMaxAIAggression_ - | tok.GalaxyMonsterFrequency_ - | tok.GalaxyNativeFrequency_ - | tok.GalaxyPlanetDensity_ - | tok.GalaxyShape_ - | tok.GalaxySize_ - | tok.GalaxySpecialFrequency_ - | tok.GalaxyStarlaneFrequency_ - ; - - constant - = tok.int_ [ _val = new_>(_1) ] - ; - } - }; - - - simple_int_parser_rules& get_simple_int_parser_rules() { - static simple_int_parser_rules retval; - return retval; - } - - - struct int_arithmetic_rules : public arithmetic_rules { - int_arithmetic_rules() : - arithmetic_rules("integer") - { - const parse::value_ref_rule& simple = int_simple(); - - statistic_value_ref_expr - = simple - | int_var_complex() - ; - - primary_expr - = '(' >> expr >> ')' - | simple - | statistic_expr - | int_var_complex() - ; - } - }; - - int_arithmetic_rules& get_int_arithmetic_rules() { - static int_arithmetic_rules retval; - return retval; - } - - struct castable_as_int_parser_rules { - castable_as_int_parser_rules() { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::new_; - - qi::_1_type _1; - qi::_val_type _val; - - castable_expr - = parse::double_value_ref() [ _val = new_>(_1) ] - ; - - flexible_int - = parse::int_value_ref() - | castable_expr - ; +#include "ValueRefParser.h" + +#include "Parse.h" +#include "MovableEnvelope.h" +#include "../universe/ValueRef.h" + +#include + +parse::detail::simple_int_parser_rules::simple_int_parser_rules(const parse::lexer& tok) : + simple_variable_rules("integer", tok) +{ + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::new_; + + qi::_1_type _1; + qi::_val_type _val; + const boost::phoenix::function construct_movable_; + + // TODO: Should we apply elements of this list only to certain + // objects? For example, if one writes "Source.Planet.", + // "NumShips" should not follow. + bound_variable_name + = tok.Owner_ + | tok.SupplyingEmpire_ + | tok.ID_ + | tok.CreationTurn_ + | tok.Age_ + | tok.ProducedByEmpireID_ + | tok.ArrivedOnTurn_ + | tok.DesignID_ + | tok.FleetID_ + | tok.PlanetID_ + | tok.SystemID_ + | tok.FinalDestinationID_ + | tok.NextSystemID_ + | tok.NearestSystemID_ + | tok.PreviousSystemID_ + | tok.NumShips_ + | tok.NumStarlanes_ + | tok.LastTurnActiveInBattle_ + | tok.LastTurnAttackedByShip_ + | tok.LastTurnBattleHere_ + | tok.LastTurnColonized_ + | tok.LastTurnConquered_ + | tok.LastTurnResupplied_ + | tok.Orbit_ + | tok.SpeciesID_ + | tok.TurnsSinceFocusChange_ + | tok.ETA_ + | tok.LaunchedFrom_ + ; + + free_variable_name + = tok.CombatBout_ + | tok.CurrentTurn_ + | tok.GalaxyAge_ + | tok.GalaxyMaxAIAggression_ + | tok.GalaxyMonsterFrequency_ + | tok.GalaxyNativeFrequency_ + | tok.GalaxyPlanetDensity_ + | tok.GalaxyShape_ + | tok.GalaxySize_ + | tok.GalaxySpecialFrequency_ + | tok.GalaxyStarlaneFrequency_ + | tok.UsedInDesignID_ + ; + + constant + = tok.int_ [ _val = construct_movable_(new_>(_1)) ] + ; +} - castable_expr.name("castable as integer expression"); - flexible_int.name("integer or castable as integer"); +parse::castable_as_int_parser_rules::castable_as_int_parser_rules( + const parse::lexer& tok, + parse::detail::Labeller& label, + const parse::detail::condition_parser_grammar& condition_parser, + const parse::detail::value_ref_grammar& string_grammar +) : + int_rules(tok, label, condition_parser, string_grammar), + double_rules(tok, label, condition_parser, string_grammar) +{ + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::new_; + + qi::_1_type _1; + qi::_val_type _val; + qi::_pass_type _pass; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + castable_expr + = double_rules.expr [ _val = construct_movable_(new_>(deconstruct_movable_(_1, _pass))) ] + ; + + flexible_int + = int_rules.expr + | castable_expr + ; + + castable_expr.name("castable as integer expression"); + flexible_int.name("integer or castable as integer"); #if DEBUG_VALUEREF_PARSERS - debug(castable_expr); + debug(castable_expr); #endif - } - - parse::value_ref_rule castable_expr; - parse::value_ref_rule flexible_int; - }; - - castable_as_int_parser_rules& get_castable_as_int_parser_rules() { - static castable_as_int_parser_rules retval; - return retval; - } } +parse::int_arithmetic_rules::int_arithmetic_rules( + const parse::lexer& tok, + parse::detail::Labeller& label, + const parse::detail::condition_parser_grammar& condition_parser, + const parse::detail::value_ref_grammar& string_grammar +) : + arithmetic_rules("integer", tok, label, condition_parser), + simple_int_rules(tok), + int_complex_grammar(tok, label, *this, string_grammar) +{ + const parse::detail::value_ref_rule& simple = simple_int_rules.simple; + + statistic_value_ref_expr + = simple + | int_complex_grammar + ; + + primary_expr + = '(' >> expr >> ')' + | simple + | statistic_expr + | int_complex_grammar + ; +} -const variable_rule& int_bound_variable() -{ return get_simple_int_parser_rules().bound_variable; } - -const variable_rule& int_free_variable() -{ return get_simple_int_parser_rules().free_variable; } - -const parse::value_ref_rule& int_simple() -{ return get_simple_int_parser_rules().simple; } +namespace parse { + bool int_free_variable(std::string& text) { + const lexer tok; + boost::spirit::qi::in_state_type in_state; + parse::detail::simple_int_parser_rules simple_int_rules(tok); -const statistic_rule& int_var_statistic() -{ return get_int_arithmetic_rules().statistic_expr; } + text_iterator first = text.begin(); + text_iterator last = text.end(); + token_iterator it = tok.begin(first, last); + bool success = boost::spirit::qi::phrase_parse( + it, tok.end(), simple_int_rules.free_variable_name, in_state("WS")[tok.self]); -namespace parse { - value_ref_rule& int_value_ref() - { return get_int_arithmetic_rules().expr; } - - value_ref_rule& flexible_int_value_ref() - { return get_castable_as_int_parser_rules().flexible_int; } + return success; + } } diff --git a/parse/ItemsParser.cpp b/parse/ItemsParser.cpp index 772e83769b9..a7f9f3d5803 100644 --- a/parse/ItemsParser.cpp +++ b/parse/ItemsParser.cpp @@ -1,6 +1,8 @@ #include "Parse.h" #include "ParseImpl.h" +#include "EnumParser.h" +#include "../universe/UnlockableItem.h" #include "../util/Directories.h" @@ -11,13 +13,21 @@ #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector&) { return os; } } #endif namespace { - struct rules { - rules() { + using start_rule_payload = std::vector; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + unlockable_item_parser(tok, label) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; @@ -30,30 +40,34 @@ namespace { qi::_r1_type _r1; start - = +parse::detail::item_spec_parser() [ push_back(_r1, _1) ] + = +unlockable_item_parser [ push_back(_r1, _1) ] ; start.name("start"); - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - void (std::vector&) - > start_rule; + using start_rule = parse::detail::rule; + parse::detail::Labeller label; + parse::detail::unlockable_item_grammar unlockable_item_parser; start_rule start; }; } namespace parse { - bool items(std::vector& items_) { - const boost::filesystem::path& path = GetResourceDir() / "scripting/starting_unlocks/items.inf"; - return detail::parse_file>(path, items_); + start_rule_payload items(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload items_; + /*auto success =*/ detail::parse_file(lexer, path, items_); + return items_; } - bool starting_buildings(std::vector& starting_buildings_) { - const boost::filesystem::path& path = GetResourceDir() / "scripting/starting_unlocks/buildings.inf"; - return detail::parse_file >(path, starting_buildings_); + start_rule_payload starting_buildings(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload starting_buildings_; + /*auto success =*/ detail::parse_file(lexer, path, starting_buildings_); + return starting_buildings_; } } diff --git a/parse/KeymapParser.cpp b/parse/KeymapParser.cpp index d766cf9ac6a..6d7d370ad8d 100644 --- a/parse/KeymapParser.cpp +++ b/parse/KeymapParser.cpp @@ -33,15 +33,24 @@ namespace { struct insert_key_map_ { typedef void result_type; - void operator()(NamedKeymaps& named_keymaps, const NamedKeymaps::value_type& name_keymap) const { + void operator()(NamedKeymaps& named_keymaps, + const NamedKeymaps::value_type& name_keymap) const + { named_keymaps[name_keymap.first] = name_keymap.second; //std::cout << "inserted keymap: " << name_keymap.first << std::endl; } }; const boost::phoenix::function insert_key_map; - struct rules { - rules() { + using start_rule_payload = NamedKeymaps; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; @@ -52,22 +61,20 @@ namespace { qi::_3_type _3; qi::_4_type _4; qi::_a_type _a; - qi::_b_type _b; qi::_r1_type _r1; - - const parse::lexer& tok = parse::lexer::instance(); + qi::omit_type omit_; int_pair - = tok.int_ [ _a = _1 ] >> tok.int_ [ _b = _1 ] - [ insert_key_pair(_r1, construct(_a, _b)) ] + = (tok.int_ >> tok.int_) + [ insert_key_pair(_r1, construct(_1, _2)) ] ; keymap - = tok.Keymap_ - > parse::detail::label(Name_token) > tok.string [ _a = _1 ] - > parse::detail::label(Keys_token) - > ( '[' > *(int_pair(_b)) > ']' ) - [ insert_key_map(_r1, construct(_a, _b)) ] + = ( omit_[tok.Keymap_] + > label(tok.Name_) > tok.string + > label(tok.Keys_) + > ( '[' > *(int_pair(_a)) > ']' ) + ) [ insert_key_map(_r1, construct(_1, _a)) ] ; start @@ -82,23 +89,19 @@ namespace { debug(article); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - void (Keymap&), - boost::spirit::qi::locals - > int_pair_rule; + using int_pair_rule = parse::detail::rule; - typedef parse::detail::rule< + using keymap_rule = parse::detail::rule< void (NamedKeymaps&), - boost::spirit::qi::locals - > keymap_rule; + boost::spirit::qi::locals + >; - typedef parse::detail::rule< - void (NamedKeymaps&) - > start_rule; + using start_rule = parse::detail::rule; + parse::detail::Labeller label; int_pair_rule int_pair; keymap_rule keymap; start_rule start; @@ -106,8 +109,10 @@ namespace { } namespace parse { - bool keymaps(NamedKeymaps& nkm) { - boost::filesystem::path path = GetResourceDir() / "scripting/keymaps.inf"; - return detail::parse_file(path, nkm); + NamedKeymaps keymaps(const boost::filesystem::path& path) { + const lexer lexer; + NamedKeymaps nkm; + /*auto success =*/ detail::parse_file(lexer, path, nkm); + return nkm; } } diff --git a/parse/Lexer.cpp b/parse/Lexer.cpp index e13321136e6..35731492995 100644 --- a/parse/Lexer.cpp +++ b/parse/Lexer.cpp @@ -11,9 +11,11 @@ namespace { struct strip_quotes_ { typedef std::string result_type; - std::string operator()(const parse::text_iterator& start, const parse::text_iterator& end) const { - std::string::const_iterator start_ = start; - std::string::const_iterator end_ = end; + std::string operator()(const parse::text_iterator& start, + const parse::text_iterator& end) const + { + auto start_ = start; + auto end_ = end; return std::string(++start_, --end_); } }; @@ -52,6 +54,8 @@ lexer::lexer() : BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_13) BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_14) BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_15) + BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_16) + BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_17) #undef DEFINE_TOKEN error_token("\\S+?") @@ -95,12 +99,7 @@ lexer::lexer() : | '?' ; -#define REGISTER_TOKEN(r, _, name) \ - { \ - const char* n(BOOST_PP_CAT(name, _token)); \ - self += BOOST_PP_CAT(name, _) [ _val = n ]; \ - m_name_tokens[n] = &BOOST_PP_CAT(name, _); \ - } +#define REGISTER_TOKEN(r, _, name) self += BOOST_PP_CAT(name, _); BOOST_PP_SEQ_FOR_EACH(REGISTER_TOKEN, _, TOKEN_SEQ_1) BOOST_PP_SEQ_FOR_EACH(REGISTER_TOKEN, _, TOKEN_SEQ_2) BOOST_PP_SEQ_FOR_EACH(REGISTER_TOKEN, _, TOKEN_SEQ_3) @@ -116,6 +115,8 @@ lexer::lexer() : BOOST_PP_SEQ_FOR_EACH(REGISTER_TOKEN, _, TOKEN_SEQ_13) BOOST_PP_SEQ_FOR_EACH(REGISTER_TOKEN, _, TOKEN_SEQ_14) BOOST_PP_SEQ_FOR_EACH(REGISTER_TOKEN, _, TOKEN_SEQ_15) + BOOST_PP_SEQ_FOR_EACH(REGISTER_TOKEN, _, TOKEN_SEQ_16) + BOOST_PP_SEQ_FOR_EACH(REGISTER_TOKEN, _, TOKEN_SEQ_17) #undef REGISTER_TOKEN self @@ -129,30 +130,6 @@ lexer::lexer() : ; } -const lexer& lexer::instance() { - static const lexer retval; - return retval; -} - -const boost::spirit::lex::token_def& lexer::name_token(const char* name) const { - std::map*>::const_iterator it = m_name_tokens.find(name); - assert(it != m_name_tokens.end()); - return *it->second; -} - namespace boost { namespace spirit { namespace traits { - // This template specialization is required by Spirit.Lex to automatically - // convert an iterator pair to an const char* in the lexer. - void assign_to_attribute_from_iterators:: - call(const parse::text_iterator& first, const parse::text_iterator& last, const char*& attr) { - std::string str(first, last); - boost::algorithm::to_lower(str); - attr = str.c_str(); - } - - void assign_to_attribute_from_iterators:: - call(const parse::text_iterator& first, const parse::text_iterator& last, bool& attr) - { attr = *first == 't' || *first == 'T' ? true : false; } - } } } diff --git a/parse/Lexer.h b/parse/Lexer.h index df613284ca2..e5a0381cded 100644 --- a/parse/Lexer.h +++ b/parse/Lexer.h @@ -8,6 +8,7 @@ #include "Tokens.h" +#include /** \namespace parse \brief The namespace that encloses the script file lexer and parser. */ @@ -23,7 +24,6 @@ typedef boost::spirit::lex::lexertl::position_token< bool, int, double, - const char*, std::string > > token_type; @@ -34,13 +34,16 @@ typedef boost::spirit::lex::lexertl::actor_lexer spirit_lexer_base_t struct lexer : boost::spirit::lex::lexer { - static const lexer& instance(); + /** Ctor. */ + lexer(); /** \name Comment tokens */ ///@{ boost::spirit::lex::token_def inline_comment; boost::spirit::lex::token_def end_of_line_comment; //@} + using string_token_def = boost::spirit::lex::token_def; + /** \name Tokens for common C++ types and builtins. */ ///@{ boost::spirit::lex::token_def bool_; boost::spirit::lex::token_def int_; @@ -51,7 +54,7 @@ struct lexer : /** \name Keyword tokens. These should be kept in lexicographically sorted order, so that finding, adding, and removing tokens is a bit easier. See the note above the Enum tokens section. */ ///@{ -#define DECLARE_TOKEN(r, _, name) boost::spirit::lex::token_def BOOST_PP_CAT(name, _); +#define DECLARE_TOKEN(r, _, name) string_token_def BOOST_PP_CAT(name, _); BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_1) BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_2) BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_3) @@ -67,6 +70,8 @@ struct lexer : BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_13) BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_14) BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_15) + BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_16) + BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_17) #undef DECLARE_TOKEN //@} @@ -74,19 +79,12 @@ struct lexer : boost::spirit::lex::token_def error_token; //@} - /** Returns the token_def associated with \a name. */ - const boost::spirit::lex::token_def& name_token(const char* name) const; - static const char* bool_regex; static const char* int_regex; static const char* double_regex; static const char* string_regex; private: - /** Ctor. */ - lexer(); - - std::map*> m_name_tokens; }; /** The type of iterator passed to the script file parser by the script file @@ -105,19 +103,6 @@ namespace boost { namespace spirit { namespace traits { // If you want to create a token with a custom value type, you must // declare the conversion handler here, and define it in the .cpp file. - - // These template specializations are required by Spirit.Lex to automatically - // convert an iterator pair to an const char* in the lexer. - template <> - struct assign_to_attribute_from_iterators - { static void call(const parse::text_iterator& first, const parse::text_iterator& last, const char*& attr); }; - - // HACK! This is only necessary because of a bug in Spirit in Boost - // versions <= 1.45. - template <> - struct assign_to_attribute_from_iterators - { static void call(const parse::text_iterator& first, const parse::text_iterator& last, bool& attr); }; - } } } #endif diff --git a/parse/MonsterFleetPlansParser.cpp b/parse/MonsterFleetPlansParser.cpp index dc6e4f58a3e..41f1961fa7c 100644 --- a/parse/MonsterFleetPlansParser.cpp +++ b/parse/MonsterFleetPlansParser.cpp @@ -1,132 +1,133 @@ #include "Parse.h" #include "ParseImpl.h" +#include "MovableEnvelope.h" #include "ConditionParserImpl.h" +#include "../universe/Condition.h" #include "../universe/Universe.h" -#include "../universe/UniverseGenerator.h" +#include "../universe/FleetPlan.h" #include "../util/Directories.h" #include - #define DEBUG_PARSERS 0 #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector>&) { return os; } inline ostream& operator<<(ostream& os, const std::vector&) { return os; } } #endif namespace { - struct new_monster_fleet_plan_ { - typedef MonsterFleetPlan* result_type; - - MonsterFleetPlan* operator()(const std::string& fleet_name, const std::vector& ship_design_names, - double spawn_rate, int spawn_limit, Condition::ConditionBase* location) const - { return new MonsterFleetPlan(fleet_name, ship_design_names, spawn_rate, spawn_limit, location); } + void insert_monster_fleet_plan( + std::vector>& plans, + const std::string& fleet_name, const std::vector& ship_design_names, + const boost::optional& spawn_rate, + const boost::optional& spawn_limit, + const boost::optional& location, + bool& pass) + { + plans.push_back( + std::make_unique( + fleet_name, ship_design_names, + (spawn_rate ? *spawn_rate : 1.0), + (spawn_limit ? *spawn_limit : 9999), + (location ? location->OpenEnvelope(pass) : nullptr) + )); }; - const boost::phoenix::function new_monster_fleet_plan; - - struct rules { - rules() { + BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_monster_fleet_plan_, insert_monster_fleet_plan, 7) + + using start_rule_payload = std::vector>; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + condition_parser(tok, label), + string_grammar(tok, label, condition_parser), + double_rule(tok), + int_rule(tok), + one_or_more_string_tokens(tok) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; - using phoenix::clear; - using phoenix::push_back; - qi::_1_type _1; qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; + qi::_5_type _5; qi::_r1_type _r1; - qi::_val_type _val; qi::eps_type eps; + qi::_pass_type _pass; + qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; - const parse::lexer& tok = parse::lexer::instance(); - - monster_fleet_plan_prefix - = tok.MonsterFleet_ - > parse::detail::label(Name_token) > tok.string [ phoenix::ref(_a) = _1 ] + ships + = label(tok.Ships_) > one_or_more_string_tokens ; - ships - = parse::detail::label(Ships_token) - > eps [ clear(phoenix::ref(_b)) ] - > ( - ('[' > +tok.string [ push_back(phoenix::ref(_b), _1) ] > ']') - | tok.string [ push_back(phoenix::ref(_b), _1) ] - ) + spawn_rate = + label(tok.SpawnRate_) > double_rule ; - spawns - = ( - (parse::detail::label(SpawnRate_token) > parse::detail::double_ [ phoenix::ref(_c) = _1 ]) - | eps [ phoenix::ref(_c) = 1.0 ] - ) - > ( - (parse::detail::label(SpawnLimit_token) > parse::detail::int_ [ phoenix::ref(_d) = _1 ]) - | eps [ phoenix::ref(_d) = 9999 ] - ) + spawn_limit = + label(tok.SpawnLimit_) > int_rule ; monster_fleet_plan - = ( - monster_fleet_plan_prefix - > ships - > spawns - > -(parse::detail::label(Location_token) > parse::detail::condition_parser [ phoenix::ref(_e) = _1 ]) - ) - [ _val = new_monster_fleet_plan(phoenix::ref(_a), phoenix::ref(_b), phoenix::ref(_c), phoenix::ref(_d), phoenix::ref(_e)) ] + = ( omit_[tok.MonsterFleet_] + > label(tok.Name_) > tok.string + > ships + > -spawn_rate + > -spawn_limit + > -(label(tok.Location_) > condition_parser) + ) [ insert_monster_fleet_plan_(_r1, _1, _2, _3, _4, _5, _pass) ] ; - start - = (+monster_fleet_plan) [ _r1 = _1 ] - ; + start = (+monster_fleet_plan(_r1)); - monster_fleet_plan_prefix.name("MonsterFleet"); ships.name("Ships"); - spawns.name("spawn rate and spawn limit"); + spawn_rate.name("spawn rate"); + spawn_limit.name("spawn limit"); monster_fleet_plan.name("MonsterFleet"); #if DEBUG_PARSERS debug(monster_fleet_plan); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule<> generic_rule; - - typedef parse::detail::rule< - MonsterFleetPlan* () - > monster_fleet_plan_rule; - - typedef parse::detail::rule< - void (std::vector&) - > start_rule; - - generic_rule monster_fleet_plan_prefix; - generic_rule ships; - generic_rule spawns; - monster_fleet_plan_rule monster_fleet_plan; - start_rule start; - - // locals - std::string _a; - std::vector _b; - double _c; - int _d; - Condition::ConditionBase* _e; + using monster_fleet_plan_rule = parse::detail::rule; + + using start_rule = parse::detail::rule; + + parse::detail::Labeller label; + parse::conditions_parser_grammar condition_parser; + const parse::string_parser_grammar string_grammar; + parse::detail::double_grammar double_rule; + parse::detail::int_grammar int_rule; + parse::detail::single_or_repeated_string> one_or_more_string_tokens; + parse::detail::rule()> ships; + parse::detail::rule spawn_rate; + parse::detail::rule spawn_limit; + monster_fleet_plan_rule monster_fleet_plan; + start_rule start; }; } namespace parse { - bool monster_fleet_plans(std::vector& monster_fleet_plans_) { - boost::filesystem::path path = GetResourceDir() / "scripting/monster_fleets.inf"; - return detail::parse_file>(path, monster_fleet_plans_); + start_rule_payload monster_fleet_plans(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload monster_fleet_plans_; + /*auto success =*/ detail::parse_file(lexer, path, monster_fleet_plans_); + return monster_fleet_plans_; } } diff --git a/parse/MovableEnvelope.h b/parse/MovableEnvelope.h new file mode 100644 index 00000000000..282730bb5af --- /dev/null +++ b/parse/MovableEnvelope.h @@ -0,0 +1,408 @@ +#ifndef _MovableEnvelope_h_ +#define _MovableEnvelope_h_ + +#include "../util/Logger.h" + +#include + +#include +#include + +namespace parse { namespace detail { + /** \p MovableEnvelope enables the boost::spirit parser to handle a + \p T with move semantics. + + boost::spirit is designed only work with value semantics. The + boost::phoenix actors only handle value semantics. Types with move + only semantics like unique_ptr will not work as expected. + + boost::spirit is designed to work with compile time polymorphism. With + run-time polymorphism using raw pointers to track heap objects, each + time the parser backtracks it leaks memory. + + \p MovableEnvelope makes a \p unique_ptr with move only semantics + appear to have copy semantics, by moving it each time that it is copied. + This allows boost::phoenix actors to handle it correctly if the movement is + one way from creation at the parse location to consumption in a larger + parsed component. + + \p MovableEnvelope is a work around. It can be removed if + boost::spirit supports move semantics, or the parser is changed to use + compile time polymorphism. + */ + template + class MovableEnvelope { + public: + using enveloped_type = T; + + /** \name Rule of Five constructors and operators. + MovableEnvelope satisfies the rule of five with the following + constructors and operator=(). + */ //@{ + + // Default constructor + MovableEnvelope() {} + + // Copy constructor + // This leaves \p other in an emptied state + MovableEnvelope(const MovableEnvelope& other) : + MovableEnvelope(std::move(other.obj)) + {} + + // Move constructor + MovableEnvelope(MovableEnvelope&& other) : + MovableEnvelope(std::move(other.obj)) + {} + + // Move operator + MovableEnvelope& operator= (MovableEnvelope&& other) { + obj = std::move(other.obj); + original_obj = other.original_obj; + // Intentionally leave other.original_obj != other.obj.get() + return *this; + } + + // Copy operator + // This leaves \p other in an emptied state + MovableEnvelope& operator= (const MovableEnvelope& other) { + obj = std::move(other.obj); + original_obj = other.original_obj; + // Intentionally leave other.original_obj != other.obj.get() + return *this; + } + //@} + + virtual ~MovableEnvelope() {}; + + /** \name Converting constructors and operators. + MovableEnvelope allows conversion between compatible types with the following + constructors and operators. + */ //@{ + + // nullptr constructor + MovableEnvelope(std::nullptr_t) {} + + template , + std::unique_ptr>::value>::type* = nullptr> + explicit MovableEnvelope(std::unique_ptr&& obj_) : + obj(std::move(obj_)), + original_obj(obj.get()) + {} + + // This takes ownership of obj_ + template , + std::unique_ptr>::value>::type* = nullptr> + explicit MovableEnvelope(U* obj_) : + obj(obj_), + original_obj(obj.get()) + {} + + // Converting copy constructor + // This leaves \p other in an emptied state + template , + std::unique_ptr>::value>::type* = nullptr> + MovableEnvelope(const MovableEnvelope& other) : + MovableEnvelope(std::move(other.obj)) + {} + + // Converting move constructor + template , + std::unique_ptr>::value>::type* = nullptr> + MovableEnvelope(MovableEnvelope&& other) : + MovableEnvelope(std::move(other.obj)) + {} + + template , + std::unique_ptr>::value>::type* = nullptr> + MovableEnvelope& operator= (MovableEnvelope&& other) { + obj = std::move(other.obj); + original_obj = other.original_obj; + // Intentionally leave other.original_obj != other.obj.get() + return *this; + } + + // Copy operator + // This leaves \p other in an emptied state + template , + std::unique_ptr>::value>::type* = nullptr> + MovableEnvelope& operator= (const MovableEnvelope& other) { + obj = std::move(other.obj); + original_obj = other.original_obj; + // Intentionally leave other.original_obj != other.obj.get() + return *this; + } + //@} + + // Return false if the original \p obj has been moved away from this + // MovableEnvelope. + bool IsEmptiedEnvelope() const + { return (original_obj != obj.get());} + + /** OpenEnvelope returns the enclosed \p obj and throws an expectation + exception if the wrapped pointer was already moved out of this \p + MovableEnvelope. + + \p OpenEnvelope is a one-shot. Calling OpenEnvelope a second time + will throw, since the obj has already been removed. + + \p pass should refer to qi::_pass_type to facilitate the + expectation exception. + */ + + std::unique_ptr OpenEnvelope(bool& pass) const { + if (IsEmptiedEnvelope()) { + ErrorLogger() << + "The parser attempted to extract the unique_ptr from a MovableEnvelope more than once. " + "Until boost::spirit supports move semantics MovableEnvelope requires that unique_ptr be used only once. " + "Check that a parser is not back tracking over an actor containing an opened MovableEnvelope. " + "Check that set, map or vector parses are not repeatedly extracting the same unique_ptr."; + + pass = false; + } + return std::move(obj); + } + + private: + template + friend class MovableEnvelope; + + mutable std::unique_ptr obj; + + mutable T* original_obj = nullptr; + }; + + /** \p construct_movable is a functor that constructs a MovableEnvelope */ + struct construct_movable { + template + using result_type = MovableEnvelope; + + template + result_type operator() (T* obj) const + { return MovableEnvelope(obj); } + + template + result_type operator() (std::unique_ptr&& obj) const + { return MovableEnvelope(std::move(obj)); } + + template + result_type operator() (std::unique_ptr& obj) const + { return MovableEnvelope(std::move(obj)); } + + template + result_type operator() (MovableEnvelope&& obj) const + { return MovableEnvelope(std::move(obj)); } + + template + result_type operator() (const MovableEnvelope& obj) const + { return MovableEnvelope(obj); } + }; + + /** Free functions converting containers of MovableEnvelope to unique_ptrs. */ + template + std::vector> OpenEnvelopes(const std::vector>& envelopes, bool& pass) { + std::vector> retval; + for (auto&& envelope : envelopes) + retval.push_back(envelope.OpenEnvelope(pass)); + return retval; + } + + template + std::vector>> OpenEnvelopes( + const std::vector>>& in, bool& pass) + { + std::vector>> retval; + for (auto&& name_and_value : in) + retval.emplace_back(name_and_value.first, name_and_value.second.OpenEnvelope(pass)); + return retval; + } + + template + std::map> OpenEnvelopes(const std::map>& in, bool& pass) { + std::map> retval; + for (auto&& name_and_value : in) + retval.insert(std::make_pair(name_and_value.first, name_and_value.second.OpenEnvelope(pass))); + return retval; + } + + + /** + Note: This simpler to use version of deconstruct_movable works with clang + version 4.0.1 and gcc version 7.2.1 with boost 1.63. It does not work + with clang 3.6.0 or gcc 4.8.4 or MSVC2015, with boost 1.58 on the + continuous integration servers. It is because of improved support for + decltype in the newer compilers. + + When the project moves the required versions past these versions this + simpler, more uniform code could be adopted. + + / ** \p deconstruct_movable is a functor that extracts the unique_ptr from a + MovableEnvelope. This is a one time operation that empties the + MovableEnvelope. It is typically done while calling the constructor + from outside of boost::spirit that expects a unique_ptr */ + class deconstruct_movable_simple_version_that_works_gcc7p2p1_and_clang4p0p1 { + public: + template + std::unique_ptr operator() (const MovableEnvelope& obj, bool& pass) const + { return obj.OpenEnvelope(pass); } + + template + std::vector> operator() (const std::vector>& objs, bool& pass) const + { return OpenEnvelopes(objs, pass); } + + template + std::vector>> operator() ( + const std::vector>>& objs, bool& pass) + { return OpenEnvelopes(objs, pass); } + }; + + class deconstruct_movable { + public: + template struct result; + + template + struct result { + using type = std::unique_ptr::type::enveloped_type>; + }; + + template + typename result&, bool&)>::type operator() + ( + const MovableEnvelope& obj, bool& pass) const + { return obj.OpenEnvelope(pass); } + + template + typename result&, bool&)>::type operator() + ( + const MovableEnvelope& obj, const bool& pass) const + { + // Note: this ignores the constness of pass because older compilers + // pass the incorrect constness. + return obj.OpenEnvelope(pass); + } + + // Unwrap ::optional> to return + // unique_ptr(nullptr) for none + template + typename result&, bool&)>::type operator() + ( + const boost::optional>& obj, bool& pass) const + { + if (!obj) + return nullptr; + return obj->OpenEnvelope(pass); + } + + template + typename result&, bool&)>::type operator() + ( + const boost::optional>& obj, const bool& pass) const + { + // Note: this ignores the constness of pass because older compilers + // pass the incorrect constness. + if (!obj) + return nullptr; + return obj->OpenEnvelope(pass); + } + }; + + class deconstruct_movable_vector { + public: + template struct result; + + template + struct result { + using type = std::vector::type::value_type::enveloped_type>>; + }; + + template + typename result>&, bool&)>::type operator() + ( + const std::vector>& objs, bool& pass) const + { return OpenEnvelopes(objs, pass); } + + template + typename result>&, bool&)>::type operator() + ( + const std::vector>& objs, const bool& pass) const + { + // Note: this ignores the constness of pass because older compilers + // pass the incorrect constness. + return OpenEnvelopes(objs, pass); + } + + // Unwrap ::optional>> and return + // an empty vector for none + template + typename result>&, bool&)>::type operator() + ( + const boost::optional>>& objs, bool& pass) const + { + if (!objs) + return typename result>&, bool&)>::type(); + return OpenEnvelopes(*std::move(objs), pass); + } + + template + typename result>&, bool&)>::type operator() + ( + const boost::optional>>& objs, const bool& pass) const + { + // Note: this ignores the constness of pass because older compilers + // pass the incorrect constness. + if (!objs) + return typename result>&, bool&)>::type(); + return OpenEnvelopes(*std::move(objs), pass); + } + }; + + + class deconstruct_movable_vector_pair { + public: + template struct result; + + template + struct result { + using type = std::vector< + std::pair::type::value_type::second_type::enveloped_type>>>; + }; + + template + typename result>>&, bool&)>::type operator() + ( + const std::vector>>& objs, bool& pass) const + { return OpenEnvelopes(objs, pass); } + + template + typename result>>&, bool&)>::type operator() + ( + const std::vector>>& objs, const bool& pass) const + { + // Note: this ignores the constness of pass because older compilers + // pass the incorrect constness. + return OpenEnvelopes(objs, pass); + } + }; + + } // end namespace detail +} // end namespace parse + +#endif // _MovableEnvelope_h_ diff --git a/parse/Parse.cpp b/parse/Parse.cpp index 544629762e1..b7fc7ef8504 100644 --- a/parse/Parse.cpp +++ b/parse/Parse.cpp @@ -6,7 +6,7 @@ #include "EnumParser.h" #include "ValueRefParser.h" -#include "../universe/Effect.h" +#include "../universe/UnlockableItem.h" #include "../util/Logger.h" #include "../util/Directories.h" @@ -21,235 +21,13 @@ #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector&) { return os; } inline ostream& operator<<(ostream& os, const GG::Clr&) { return os; } - inline ostream& operator<<(ostream& os, const ItemSpec&) { return os; } + inline ostream& operator<<(ostream& os, const UnlockableItem&) { return os; } } #endif -namespace { - struct tags_rules { - tags_rules() { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::insert; - - qi::_1_type _1; - qi::_r1_type _r1; - - const parse::lexer& tok = parse::lexer::instance(); - - start - = -( - parse::detail::label(Tags_token) - >> ( - ('[' > +tok.string [ insert(_r1, _1) ] > ']') - | tok.string [ insert(_r1, _1) ] - ) - ) - ; - - start.name("Tags"); - -#if DEBUG_PARSERS - debug(start); -#endif - } - - parse::detail::tags_rule start; - }; - - struct effects_group_rules { - effects_group_rules() { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::construct; - using phoenix::new_; - using phoenix::push_back; - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_g_type _g; - qi::_val_type _val; - qi::lit_type lit; - qi::eps_type eps; - - const parse::lexer& tok = parse::lexer::instance(); - - effects_group - = tok.EffectsGroup_ - > -(parse::detail::label(Description_token) > tok.string [ _g = _1 ]) - > parse::detail::label(Scope_token) > parse::detail::condition_parser [ _a = _1 ] - > -(parse::detail::label(Activation_token) > parse::detail::condition_parser [ _b = _1 ]) - > -(parse::detail::label(StackingGroup_token) > tok.string [ _c = _1 ]) - > -(parse::detail::label(AccountingLabel_token) > tok.string [ _e = _1 ]) - > ((parse::detail::label(Priority_token) > tok.int_ [ _f = _1 ]) | eps [ _f = 100 ]) - > parse::detail::label(Effects_token) - > ( - ('[' > +parse::effect_parser() [ push_back(_d, _1) ] > ']') - | parse::effect_parser() [ push_back(_d, _1) ] - ) - [ _val = new_(_a, _b, _d, _e, _c, _f, _g) ] - ; - - start - = ('[' > +effects_group [ push_back(_val, construct>(_1)) ] > ']') - | effects_group [ push_back(_val, construct>(_1)) ] - ; - - effects_group.name("EffectsGroup"); - start.name("EffectsGroups"); - -#if DEBUG_PARSERS - debug(effects_group); - debug(start); -#endif - } - - typedef parse::detail::rule< - Effect::EffectsGroup* (), - boost::spirit::qi::locals< - Condition::ConditionBase*, - Condition::ConditionBase*, - std::string, - std::vector, - std::string, - int, - std::string - > - > effects_group_rule; - - effects_group_rule effects_group; - parse::detail::effects_group_rule start; - }; - - struct color_parser_rules { - color_parser_rules() { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::construct; - using phoenix::if_; - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_pass_type _pass; - qi::_val_type _val; - qi::eps_type eps; - qi::uint_type uint_; - - const parse::lexer& tok = parse::lexer::instance(); - - channel - = tok.int_ [ _val = _1, _pass = 0 <= _1 && _1 <= 255 ] - ; - - start - = ('(' >> channel [ _a = _1 ]) - > (',' >> channel [ _b = _1 ]) - > (',' >> channel [ _c = _1 ]) - > ( - ( - ',' > channel [ _val = construct(_a, _b, _c, _1) ] - ) - | eps [ _val = construct(_a, _b, _c, phoenix::val(255)) ] - ) - > ')' - ; - - channel.name("colour channel (0 to 255)"); - start.name("Colour"); - -#if DEBUG_PARSERS - debug(channel); - debug(start); -#endif - } - - typedef parse::detail::rule< - unsigned int () - > rule; - - rule channel; - parse::detail::color_parser_rule start; - }; - - struct item_spec_parser_rules { - item_spec_parser_rules() { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::construct; - - qi::_1_type _1; - qi::_a_type _a; - qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - start - = tok.Item_ - > parse::detail::label(Type_token) > parse::unlockable_item_type_enum() [ _a = _1 ] - > parse::detail::label(Name_token) > tok.string [ _val = construct(_a, _1) ] - ; - - start.name("ItemSpec"); - -#if DEBUG_PARSERS - debug(start); -#endif - } - - parse::detail::item_spec_parser_rule start; - }; -} - namespace parse { - void init() { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::static_cast_; - - qi::_1_type _1; - qi::_val_type _val; - - const lexer& tok = lexer::instance(); - - detail::int_ - = '-' >> tok.int_ [ _val = -_1 ] - | tok.int_ [ _val = _1 ] - ; - - detail::double_ - = '-' >> tok.int_ [ _val = -static_cast_(_1) ] - | tok.int_ [ _val = static_cast_(_1) ] - | '-' >> tok.double_ [ _val = -_1 ] - | tok.double_ [ _val = _1 ] - ; - - detail::int_.name("integer"); - detail::double_.name("real number"); - -#if DEBUG_PARSERS - debug(detail::int_); - debug(detail::double_); -#endif - - int_value_ref(); - - condition_parser(); - } - using namespace boost::xpressive; const sregex MACRO_KEY = +_w; // word character (alnum | _), one or more times, greedy const sregex MACRO_TEXT = -*_; // any character, zero or more times, not greedy @@ -278,7 +56,7 @@ namespace parse { //DebugLogger() << "text:\n" << macro_text; // store macro - if (macros.find(macro_key) == macros.end()) { + if (!macros.count(macro_key)) { macros[macro_key] = macro_text; } else { ErrorLogger() << "Duplicate macro key foud: " << macro_key << ". Ignoring duplicate."; @@ -316,17 +94,17 @@ namespace parse { bool macro_deep_referenced_in_text(const std::string& macro_to_find, const std::string& text, const std::map& macros) { - //DebugLogger() << "Checking if " << macro_to_find << " deep referenced in text: " << text; + TraceLogger() << "Checking if " << macro_to_find << " deep referenced in text: " << text; // check of text directly references macro_to_find std::set macros_directly_referenced_in_input_text = macros_directly_referenced_in_text(text); if (macros_directly_referenced_in_input_text.empty()) return false; - if (macros_directly_referenced_in_input_text.find(macro_to_find) != macros_directly_referenced_in_input_text.end()) + if (macros_directly_referenced_in_input_text.count(macro_to_find)) return true; // check if macros referenced in text reference macro_to_find for (const std::string& direct_referenced_macro_key : macros_directly_referenced_in_input_text) { // get text of directly referenced macro - std::map::const_iterator macro_it = macros.find(direct_referenced_macro_key); + auto macro_it = macros.find(direct_referenced_macro_key); if (macro_it == macros.end()) { ErrorLogger() << "macro_deep_referenced_in_text couldn't find referenced macro: " << direct_referenced_macro_key; continue; @@ -341,13 +119,15 @@ namespace parse { } void check_for_cyclic_macro_references(const std::map& macros) { - for (const std::map::value_type& macro : macros) { + for (const auto& macro : macros) { if (macro_deep_referenced_in_text(macro.first, macro.second, macros)) ErrorLogger() << "Cyclic macro found: " << macro.first << " references itself (eventually)"; } } - void replace_macro_references(std::string& text, const std::map& macros) { + void replace_macro_references(std::string& text, + const std::map& macros) + { try { std::size_t position = 0; // position in the text, past the already processed part smatch match; @@ -356,7 +136,7 @@ namespace parse { const std::string& matched_text = match.str(); // [[MACRO_KEY]] or [[MACRO_KEY(foo,bar,...)]] const std::string& macro_key = match[1]; // just MACRO_KEY // look up macro key to insert - std::map::const_iterator macro_lookup_it = macros.find(macro_key); + auto macro_lookup_it = macros.find(macro_key); if (macro_lookup_it != macros.end()) { // verify that macro is safe: check for cyclic reference of macro to itself if (macro_deep_referenced_in_text(macro_key, macro_lookup_it->second, macros)) { @@ -405,7 +185,7 @@ namespace parse { // recursively expand macro keys: replace [[MACRO_KEY]] in other macro // text with the macro text corresponding to MACRO_KEY. - for (std::map::value_type& macro : macros) + for (auto& macro : macros) { replace_macro_references(macro.second, macros); } // substitute macro keys - replace [[MACRO_KEY]] in the input text with @@ -459,7 +239,7 @@ namespace parse { // If in permissive mode and no scripts are found allow all files to be scripts. if (allow_permissive && scripts.empty() && !files.empty()) { - WarnLogger() << PathString(path) << " does not contain scripts with the expected suffix .focs.txt. " + WarnLogger() << PathToString(path) << " does not contain scripts with the expected suffix .focs.txt. " << " Trying a more permissive mode and ignoring file suffix."; scripts = files; } @@ -567,9 +347,9 @@ namespace parse { file_content.append("\n"); process_include_substitutions(file_content, match_path.parent_path(), files_included); text = regex_replace(text, INCL_ONCE_SEARCH, file_content, regex_constants::format_first_only); - } else if (missing_include_files.insert(PathString(match_path)).second) { - ErrorLogger() << "Parse: " << PathString(match_path) << " was not found for inclusion (Path:" - << PathString(base_path) << ") (File:" << fn_str << ")"; + } else if (missing_include_files.insert(PathToString(match_path)).second) { + ErrorLogger() << "Parse: " << PathToString(match_path) << " was not found for inclusion (Path:" + << PathToString(base_path) << ") (File:" << fn_str << ")"; } } // remove any remaining includes of this file @@ -579,86 +359,221 @@ namespace parse { } namespace detail { - double_rule double_; - - int_rule int_; - - label_rule& label(const char* name) { - static const bool PARSING_LABELS_OPTIONAL = false; - static std::map rules; - std::map::iterator it = rules.find(name); - if (it == rules.end()) { - const lexer& l = lexer::instance(); - label_rule& retval = rules[name]; - if (PARSING_LABELS_OPTIONAL) { - retval = -(l.name_token(name) >> '='); - } else { - retval = (l.name_token(name) >> '='); - } - retval.name(std::string(name) + " ="); - return retval; - } else { - return it->second; - } - } + double_grammar::double_grammar(const parse::lexer& tok) : + double_grammar::base_type(double_, "double_grammar") + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; - tags_rule& tags_parser() { - static tags_rules rules; - return rules.start; - } + using phoenix::static_cast_; - effects_group_rule& effects_group_parser() { - static effects_group_rules rules; - return rules.start; - } + qi::_1_type _1; + qi::_val_type _val; + + double_ + = '-' >> tok.int_ [ _val = -static_cast_(_1) ] + | tok.int_ [ _val = static_cast_(_1) ] + | '-' >> tok.double_ [ _val = -_1 ] + | tok.double_ [ _val = _1 ] + ; + + double_.name("real number"); - color_parser_rule& color_parser() { - static color_parser_rules rules; - return rules.start; +#if DEBUG_PARSERS + debug(double_); +#endif + + } + + int_grammar::int_grammar(const parse::lexer& tok) : + int_grammar::base_type(int_, "int_grammar") + { + + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::static_cast_; + + qi::_1_type _1; + qi::_val_type _val; + + int_ + = '-' >> tok.int_ [ _val = -_1 ] + | tok.int_ [ _val = _1 ] + ; + + int_.name("integer"); + +#if DEBUG_PARSERS + debug(detail::int_); + debug(detail::double_); +#endif + } + +#define PARSING_LABELS_OPTIONAL false + label_rule& Labeller::operator()(const parse::lexer::string_token_def& token) { + auto it = m_rules.find(&token); + if (it != m_rules.end()) + return it->second; + + label_rule& retval = m_rules[&token]; + if (PARSING_LABELS_OPTIONAL) { + retval = -(token >> '='); + } else { + retval = (token >> '='); } + return retval; + } + + tags_grammar::tags_grammar(const parse::lexer& tok, + Labeller& label) : + tags_grammar::base_type(start, "tags_grammar"), + one_or_more_string_tokens(tok) + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::insert; + + start %= + -( + label(tok.Tags_) + >> one_or_more_string_tokens + ) + ; + + start.name("Tags"); + +#if DEBUG_PARSERS + debug(start); +#endif + } + + color_parser_grammar::color_parser_grammar(const parse::lexer& tok) : + color_parser_grammar::base_type(start, "color_parser_grammar") + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::construct; + using phoenix::if_; + + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_pass_type _pass; + qi::_val_type _val; + qi::eps_type eps; + + channel = tok.int_ [ _val = _1, _pass = 0 <= _1 && _1 <= 255 ]; + alpha = (',' > channel) [ _val = _1 ] | eps [ _val = 255 ]; + start + = ( ('(' >> channel ) + > (',' >> channel ) + > (',' >> channel ) + > alpha + > ')' + ) [ _val = construct(_1, _2, _3, _4)] + ; + + channel.name("colour channel (0 to 255)"); + alpha.name("alpha channel (0 to 255) defaults to 255"); + start.name("Colour"); + +#if DEBUG_PARSERS + debug(channel); + debug(start); +#endif + } + + unlockable_item_grammar::unlockable_item_grammar(const parse::lexer& tok, Labeller& label) : + unlockable_item_grammar::base_type(start, "unlockable_item_grammar"), + unlockable_item_type_enum(tok) + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::construct; + + qi::_1_type _1; + qi::_2_type _2; + qi::_val_type _val; + qi::omit_type omit_; - item_spec_parser_rule& item_spec_parser() { - static item_spec_parser_rules rules; - return rules.start; + start + = ( omit_[tok.Item_] + > label(tok.Type_) > unlockable_item_type_enum + > label(tok.Name_) > tok.string + ) [ _val = construct(_1, _2) ] + ; + + start.name("UnlockableItem"); + +#if DEBUG_PARSERS + debug(start); +#endif + } + + + /** \brief Load and parse script file(s) from given path + * + * @param[in] path absolute path to a regular file + * @param[in] l lexer instance to use + * @param[out] filename filename of the given path + * @param[out] file_contents parsed contents of file(s) + * @param[out] first content iterator + * @param[out] it lexer iterator + */ + void parse_file_common(const boost::filesystem::path& path, const parse::lexer& lexer, + std::string& filename, std::string& file_contents, + parse::text_iterator& first, parse::text_iterator& last, parse::token_iterator& it) + { + filename = path.string(); + + bool read_success = read_file(path, file_contents); + if (!read_success) { + ErrorLogger() << "Unable to open data file " << filename; + return; } - /** \brief Load and parse script file(s) from given path - * - * @param[in] path absolute path to a regular file - * @param[in] l lexer instance to use - * @param[out] filename filename of the given path - * @param[out] file_contents parsed contents of file(s) - * @param[out] first content iterator - * @param[out] it lexer iterator - */ - void parse_file_common(const boost::filesystem::path& path, const parse::lexer& l, - std::string& filename, std::string& file_contents, - parse::text_iterator& first, parse::token_iterator& it) - { - filename = path.string(); + // add newline at end to avoid errors when one is left out, but is expected by parsers + file_contents += "\n"; - bool read_success = read_file(path, file_contents); - if (!read_success) { - ErrorLogger() << "Unable to open data file " << filename; - return; - } + file_substitution(file_contents, path.parent_path()); + macro_substitution(file_contents); + + first = file_contents.begin(); + last = file_contents.end(); + + it = lexer.begin(first, last); + } - // add newline at end to avoid errors when one is left out, but is expected by parsers - file_contents += "\n"; - file_substitution(file_contents, path.parent_path()); - macro_substitution(file_contents); + bool parse_file_end_of_file_warnings(const boost::filesystem::path& path, + bool parser_success, + std::string& file_contents, + text_iterator& first, + text_iterator& last) + { + if (!parser_success) + WarnLogger() << "A parser failed while parsing " << path; - first = parse::text_iterator(file_contents.begin()); - parse::text_iterator last(file_contents.end()); + auto length_of_unparsed_file = std::distance(first, last); + bool parse_length_good = ((length_of_unparsed_file == 0) + || (length_of_unparsed_file == 1 && *first == '\n')); - // WARNING: These static global values assume that only one file is parsed at a time. - // That assumption is incorrect, and these values will be unreliable. - parse::detail::s_text_it = &first; - parse::detail::s_begin = first; - parse::detail::s_end = last; - parse::detail::s_filename = filename.c_str(); - it = l.begin(first, last); + if (!parse_length_good + && length_of_unparsed_file > 0 + && static_cast(length_of_unparsed_file) <= file_contents.size()) + { + auto unparsed_section = file_contents.substr(file_contents.size() - std::abs(length_of_unparsed_file)); + std::copy(first, last, std::back_inserter(unparsed_section)); + WarnLogger() << "File \"" << path << "\" was incompletely parsed. " << std::endl + << "Unparsed section of file, " << length_of_unparsed_file <<" characters:" << std::endl + << unparsed_section; } + + return parser_success && parse_length_good; } -} +}} diff --git a/parse/Parse.h b/parse/Parse.h index 79210a4696b..85be3bed59e 100644 --- a/parse/Parse.h +++ b/parse/Parse.h @@ -7,84 +7,84 @@ # define FO_PARSE_API #endif -#include "../universe/Tech.h" - #include #include +#include +#include +#include + class BuildingType; class FieldType; class FleetPlan; -class HullType; +class ShipHull; class MonsterFleetPlan; -class PartType; -class ShipDesign; +class ShipPart; +struct ParsedShipDesign; class Special; class Species; -struct Encyclopedia; +struct EncyclopediaArticle; class GameRules; +struct UnlockableItem; -namespace parse { - FO_PARSE_API void init(); - - FO_PARSE_API bool buildings(std::map>& building_types); - - FO_PARSE_API bool fields(std::map& field_types); - - FO_PARSE_API bool specials(std::map& specials_); - - FO_PARSE_API bool species(std::map& species_); - - FO_PARSE_API bool techs(TechManager::TechContainer& techs_, - std::map& tech_categories, - std::set& categories_seen); - - FO_PARSE_API bool items(std::vector& items_); - - FO_PARSE_API bool starting_buildings(std::vector& starting_buildings_); - - FO_PARSE_API bool ship_parts(std::map& parts); +namespace ValueRef { + template + struct ValueRef; +} - FO_PARSE_API bool ship_hulls(std::map& hulls); +namespace parse { + FO_PARSE_API std::map> buildings(const boost::filesystem::path& path); + FO_PARSE_API std::map> fields(const boost::filesystem::path& path); + FO_PARSE_API std::map> specials(const boost::filesystem::path& path); + + /** Parse all species in directory \p path, store them with their name in \p + species_by_name. If a file exists called SpeciesCensusOrdering.focs.txt, parse it and + store the census order in \p ordering. */ + using species_type = std::pair< + std::map>, // species_by_name, + std::vector // ordering + >; + FO_PARSE_API species_type species(const boost::filesystem::path& path); + + /* T in techs can only be TechManager::TechParseTuple. This decouples + Parse.h from Tech.h so that all parsers are not recompiled when Tech.h changes.*/ + template + FO_PARSE_API T techs(const boost::filesystem::path& path); + + FO_PARSE_API std::vector items(const boost::filesystem::path& path); + FO_PARSE_API std::vector starting_buildings(const boost::filesystem::path& path); + FO_PARSE_API std::map> ship_parts(const boost::filesystem::path& path); + FO_PARSE_API std::map> ship_hulls(const boost::filesystem::path& path); /** Parse all ship designs in directory \p path, store them with their filename in \p design_and_path. If a file exists called ShipDesignOrdering.focs.txt, parse it and store the order in \p ordering. */ - FO_PARSE_API bool ship_designs(const boost::filesystem::path& path, - std::vector, - boost::filesystem::path>>& designs_and_paths, - std::vector& ordering); - - FO_PARSE_API bool ship_designs(std::vector>& designs, - std::vector& ordering); - - FO_PARSE_API bool monster_designs(std::vector>& designs, - std::vector& ordering); - - FO_PARSE_API bool fleet_plans(std::vector& fleet_plans_); - - FO_PARSE_API bool monster_fleet_plans(std::vector& monster_fleet_plans_); - - FO_PARSE_API bool statistics(std::map*>& stats_); - - FO_PARSE_API bool encyclopedia_articles(Encyclopedia& enc); - - FO_PARSE_API bool keymaps(std::map>& nkm); - - FO_PARSE_API bool game_rules(GameRules& rules); - + using ship_designs_type = std::pair< + std::vector, boost::filesystem::path>>, // designs_and_paths, + std::vector // ordering + >; + FO_PARSE_API ship_designs_type ship_designs(const boost::filesystem::path& path); + + FO_PARSE_API std::vector> fleet_plans(const boost::filesystem::path& path); + FO_PARSE_API std::vector> monster_fleet_plans(const boost::filesystem::path& path); + FO_PARSE_API std::map>> statistics(const boost::filesystem::path& path); + FO_PARSE_API std::map> encyclopedia_articles(const boost::filesystem::path& path); + FO_PARSE_API std::map> keymaps(const boost::filesystem::path& path); + FO_PARSE_API GameRules game_rules(const boost::filesystem::path& path); FO_PARSE_API bool read_file(const boost::filesystem::path& path, std::string& file_contents); /** Find all FOCS scripts (files with .focs.txt suffix) in \p path. If \p allow_permissive = true then if \p path is not empty and there are no .focs.txt files allow all files to qualify.*/ - FO_PARSE_API std::vector< boost::filesystem::path > ListScripts(const boost::filesystem::path& path, bool permissive = false); + FO_PARSE_API std::vector ListScripts(const boost::filesystem::path& path, bool permissive = false); FO_PARSE_API void file_substitution(std::string& text, const boost::filesystem::path& file_search_path); - - FO_PARSE_API void process_include_substitutions(std::string& text, - const boost::filesystem::path& file_search_path, + FO_PARSE_API void process_include_substitutions(std::string& text, + const boost::filesystem::path& file_search_path, std::set& files_included); + + FO_PARSE_API bool int_free_variable(std::string& text); + FO_PARSE_API bool double_free_variable(std::string& text); + FO_PARSE_API bool string_free_variable(std::string& text); } #endif diff --git a/parse/ParseImpl.h b/parse/ParseImpl.h index ca333a4e3e1..9c276e05f0e 100644 --- a/parse/ParseImpl.h +++ b/parse/ParseImpl.h @@ -2,41 +2,47 @@ #define _ParseImpl_h_ #include "ReportParseError.h" -#include "../universe/Tech.h" #include "../util/Logger.h" +#include "../util/ScopedTimer.h" +#include #include #include -#include #include +namespace Condition { + struct Condition; +} + +namespace Effect { + class Effect; +} + namespace parse { namespace detail { + template + void emplace_back_1(std::vector& vect, U&& item) { + return vect.emplace_back(std::forward(item)); + } + + BOOST_PHOENIX_ADAPT_FUNCTION(void, emplace_back_1_, emplace_back_1, 2) /// A functor to determine if \p key will be unique in \p map of \p type, and log an error otherwise. struct is_unique { typedef bool result_type; template - result_type operator() (const Map& map, const char* const type, const std::string& key) const { + result_type operator() (const Map& map, const std::string& type, const std::string& key) const { // Will this key be unique? auto will_be_unique = (map.count(key) == 0); if (!will_be_unique) - ErrorLogger() << "More than one " << type << " has the same name, " << key << "."; + ErrorLogger() << "More than one " << boost::algorithm::to_lower_copy(type) + << " has the same name, " << key << "."; return will_be_unique; } }; - /// A functor to insert a \p value with key \p key into \p map. - struct insert { - typedef void result_type; - - template - result_type operator() (Map& map, const std::string& key, Value* value) const - { map.insert(std::make_pair(key, value)); } - }; - template < typename signature = boost::spirit::qi::unused_type, typename locals = boost::spirit::qi::unused_type @@ -48,92 +54,174 @@ namespace parse { namespace detail { locals >; - - using double_rule = detail::rule< - double () + template < + typename signature = boost::spirit::qi::unused_type, + typename locals = boost::spirit::qi::unused_type + > + using grammar = boost::spirit::qi::grammar< + parse::token_iterator, + parse::skipper_type, + signature, + locals >; - extern double_rule double_; + template + struct BracketedParserSignature; + template + struct BracketedParserSignature { + using Synthesized_t = Synthesized; + using Signature_t = std::vector(Inherited...); + }; - using int_rule = detail::rule< - int () - >; - extern int_rule int_; + /** single_or_bracketed_repeat uses \p Parser to parser expressions with + either a single element or repeated elements within square brackets. */ + template + struct single_or_bracketed_repeat : public grammar< + typename BracketedParserSignature::Signature_t + > + { + single_or_bracketed_repeat(const Parser& repeated_parser) + : single_or_bracketed_repeat::base_type(start) + { + boost::spirit::qi::repeat_type repeat_; + start + = ('[' > +repeated_parser > ']') + | repeat_(1)[repeated_parser] + ; + + this->name("List of " + repeated_parser.name()); + } + rule::Signature_t> start; + }; - typedef detail::rule<> label_rule; - label_rule& label(const char* name); + template > + struct single_or_repeated_string : public grammar { + single_or_repeated_string(const parse::lexer& tok) : single_or_repeated_string::base_type(start) { + boost::spirit::qi::repeat_type repeat_; + start + = ('[' > +tok.string > ']') + | repeat_(1)[tok.string] + ; + this->name("List of strings"); + } - typedef rule< - void (std::set&) - > tags_rule; - tags_rule& tags_parser(); + rule start; + }; + struct double_grammar : public grammar { + double_grammar(const parse::lexer& tok); + rule double_; + }; - typedef rule< - std::vector> () - > effects_group_rule; - effects_group_rule& effects_group_parser(); + struct int_grammar : public grammar { + int_grammar(const parse::lexer& tok); + rule int_; + }; + using label_rule = rule<>; + /** Store label_rules. */ + class Labeller { + public: + /** Retrieve or create a label rule for \p name.*/ + label_rule& operator()(const parse::lexer::string_token_def& token); + private: + std::unordered_map m_rules; + }; - typedef rule< - GG::Clr (), - boost::spirit::qi::locals< - unsigned int, - unsigned int, - unsigned int - > - > color_parser_rule; - color_parser_rule& color_parser(); + template + using enum_rule = rule; + template + using enum_grammar = grammar; + using tags_rule_type = rule ()>; + using tags_grammar_type = grammar ()>; + + struct tags_grammar : public tags_grammar_type { + tags_grammar(const parse::lexer& tok, + Labeller& label); + tags_rule_type start; + single_or_repeated_string> one_or_more_string_tokens; + }; - typedef rule< - ItemSpec (), - boost::spirit::qi::locals - > item_spec_parser_rule; - item_spec_parser_rule& item_spec_parser(); + using color_parser_signature = GG::Clr (); + using color_rule_type = rule; + using color_grammar_type = grammar; + struct color_parser_grammar : public color_grammar_type { + color_parser_grammar(const parse::lexer& tok); + using channel_rule = rule; + + channel_rule channel; + channel_rule alpha; + color_rule_type start; + }; void parse_file_common(const boost::filesystem::path& path, - const lexer& l, + const lexer& lexer, std::string& filename, std::string& file_contents, text_iterator& first, + text_iterator& last, token_iterator& it); - template - bool parse_file(const boost::filesystem::path& path, Arg1& arg1) - { + /** Report warnings about unparsed end of file and return true for a good + parse. */ + bool parse_file_end_of_file_warnings(const boost::filesystem::path& path, + bool parser_success, + std::string& file_contents, + text_iterator& first, + text_iterator& last); + + template + bool parse_file(const lexer& lexer, const boost::filesystem::path& path, Arg1& arg1) { + SectionedScopedTimer timer("parse_file \"" + path.filename().string() + "\"", std::chrono::milliseconds(10)); + std::string filename; std::string file_contents; text_iterator first; + text_iterator last; token_iterator it; - boost::timer timer; - - const lexer& l = lexer::instance(); - - parse_file_common(path, l, filename, file_contents, first, it); + parse_file_common(path, lexer, filename, file_contents, first, last, it); //TraceLogger() << "Parse: parsed contents for " << path.string() << " : \n" << file_contents; boost::spirit::qi::in_state_type in_state; - static Rules rules; + Grammar grammar(lexer, filename, first, last); - bool success = boost::spirit::qi::phrase_parse(it, l.end(), rules.start(boost::phoenix::ref(arg1)), in_state("WS")[l.self]); + bool success = boost::spirit::qi::phrase_parse( + it, lexer.end(), grammar(boost::phoenix::ref(arg1)), in_state("WS")[lexer.self]); - TraceLogger() << "Parse: Elapsed time to parse " << path.string() << " = " << (timer.elapsed() * 1000.0); + return parse_file_end_of_file_warnings(path, success, file_contents, first, last); + } - // s_end is global and static. It is wrong when multiple files are concurrently or - // recursively parsed. This check is meaningless and was removed May 2017. - /* std::ptrdiff_t length_of_unparsed_file = std::distance(first, parse::detail::s_end); */ - /* bool parse_length_good = ((length_of_unparsed_file == 0) */ - /* || (length_of_unparsed_file == 1 && *first == '\n')); */ + template + bool parse_file(const lexer& lexer, const boost::filesystem::path& path, Arg1& arg1, Arg2& arg2) { + SectionedScopedTimer timer("parse_file \"" + path.filename().string() + "\"", std::chrono::milliseconds(10)); - return success /*&& parse_length_good*/; + std::string filename; + std::string file_contents; + text_iterator first; + text_iterator last; + token_iterator it; + + parse_file_common(path, lexer, filename, file_contents, first, last, it); + + //TraceLogger() << "Parse: parsed contents for " << path.string() << " : \n" << file_contents; + + boost::spirit::qi::in_state_type in_state; + + Grammar grammar(lexer, filename, first, last); + + bool success = boost::spirit::qi::phrase_parse( + it, lexer.end(), grammar(boost::phoenix::ref(arg1), boost::phoenix::ref(arg2)), in_state("WS")[lexer.self]); + + return parse_file_end_of_file_warnings(path, success, file_contents, first, last); } + } } #endif diff --git a/parse/PlanetEnvironmentValueRefParser.cpp b/parse/PlanetEnvironmentValueRefParser.cpp index 6d79aad5d46..298347697b3 100644 --- a/parse/PlanetEnvironmentValueRefParser.cpp +++ b/parse/PlanetEnvironmentValueRefParser.cpp @@ -1,43 +1,30 @@ -#include "ValueRefParserImpl.h" +#include "ValueRefParser.h" #include "EnumParser.h" +#include "EnumValueRefRules.h" #include "../universe/Enums.h" - -namespace { - struct planet_environment_parser_rules : - public parse::detail::enum_value_ref_rules - { - planet_environment_parser_rules() : - enum_value_ref_rules("PlanetEnvironment") - { - boost::spirit::qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - variable_name - %= tok.PlanetEnvironment_ - ; - - enum_expr - = tok.Uninhabitable_ [ _val = PE_UNINHABITABLE ] - | tok.Hostile_ [ _val = PE_HOSTILE ] - | tok.Poor_ [ _val = PE_POOR ] - | tok.Adequate_ [ _val = PE_ADEQUATE ] - | tok.Good_ [ _val = PE_GOOD ] - ; - } - }; -} - - namespace parse { namespace detail { - -enum_value_ref_rules& planet_environment_rules() -{ - static planet_environment_parser_rules retval; - return retval; -} - -} } + planet_environment_parser_rules::planet_environment_parser_rules( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser + ) : + enum_value_ref_rules("PlanetEnvironment", tok, label, condition_parser) + { + boost::spirit::qi::_val_type _val; + + variable_name + %= tok.PlanetEnvironment_ + ; + + enum_expr + = tok.Uninhabitable_ [ _val = PE_UNINHABITABLE ] + | tok.Hostile_ [ _val = PE_HOSTILE ] + | tok.Poor_ [ _val = PE_POOR ] + | tok.Adequate_ [ _val = PE_ADEQUATE ] + | tok.Good_ [ _val = PE_GOOD ] + ; + } +}} diff --git a/parse/PlanetSizeValueRefParser.cpp b/parse/PlanetSizeValueRefParser.cpp index 3a5acb83b2e..92331b155fc 100644 --- a/parse/PlanetSizeValueRefParser.cpp +++ b/parse/PlanetSizeValueRefParser.cpp @@ -1,47 +1,35 @@ -#include "ValueRefParserImpl.h" +#include "ValueRefParser.h" #include "EnumParser.h" +#include "EnumValueRefRules.h" #include "../universe/Enums.h" - - -namespace { - struct planet_size_parser_rules : - public parse::detail::enum_value_ref_rules - { - planet_size_parser_rules() : - enum_value_ref_rules("PlanetSize") - { - boost::spirit::qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - variable_name - %= tok.PlanetSize_ - | tok.NextLargerPlanetSize_ - | tok.NextSmallerPlanetSize_ - ; - - enum_expr - = tok.Tiny_ [ _val = SZ_TINY ] - | tok.Small_ [ _val = SZ_SMALL ] - | tok.Medium_ [ _val = SZ_MEDIUM ] - | tok.Large_ [ _val = SZ_LARGE ] - | tok.Huge_ [ _val = SZ_HUGE ] - | tok.Asteroids_ [ _val = SZ_ASTEROIDS ] - | tok.GasGiant_ [ _val = SZ_GASGIANT ] - ; - } - }; -} - +#include "../universe/ValueRef.h" namespace parse { namespace detail { - -enum_value_ref_rules& planet_size_rules() -{ - static planet_size_parser_rules retval; - return retval; -} - + planet_size_parser_rules::planet_size_parser_rules( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser + ) : + enum_value_ref_rules("PlanetSize", tok, label, condition_parser) + { + boost::spirit::qi::_val_type _val; + + variable_name + %= tok.PlanetSize_ + | tok.NextLargerPlanetSize_ + | tok.NextSmallerPlanetSize_ + ; + + enum_expr + = tok.Tiny_ [ _val = SZ_TINY ] + | tok.Small_ [ _val = SZ_SMALL ] + | tok.Medium_ [ _val = SZ_MEDIUM ] + | tok.Large_ [ _val = SZ_LARGE ] + | tok.Huge_ [ _val = SZ_HUGE ] + | tok.Asteroids_ [ _val = SZ_ASTEROIDS ] + | tok.GasGiant_ [ _val = SZ_GASGIANT ] + ; + } } } diff --git a/parse/PlanetTypeValueRefParser.cpp b/parse/PlanetTypeValueRefParser.cpp index b7aef9d4603..b9059e2cc76 100644 --- a/parse/PlanetTypeValueRefParser.cpp +++ b/parse/PlanetTypeValueRefParser.cpp @@ -1,54 +1,41 @@ -#include "ValueRefParserImpl.h" +#include "ValueRefParser.h" #include "EnumParser.h" +#include "EnumValueRefRules.h" #include "../universe/Enums.h" - -namespace { - struct planet_type_parser_rules : - public parse::detail::enum_value_ref_rules - { - planet_type_parser_rules() : - enum_value_ref_rules("PlanetType") - { - boost::spirit::qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - variable_name - %= tok.PlanetType_ - | tok.OriginalType_ - | tok.NextCloserToOriginalPlanetType_ - | tok.NextBetterPlanetType_ - | tok.ClockwiseNextPlanetType_ - | tok.CounterClockwiseNextPlanetType_ - ; - - enum_expr - = tok.Swamp_ [ _val = PT_SWAMP ] - | tok.Toxic_ [ _val = PT_TOXIC ] - | tok.Inferno_ [ _val = PT_INFERNO ] - | tok.Radiated_ [ _val = PT_RADIATED ] - | tok.Barren_ [ _val = PT_BARREN ] - | tok.Tundra_ [ _val = PT_TUNDRA ] - | tok.Desert_ [ _val = PT_DESERT ] - | tok.Terran_ [ _val = PT_TERRAN ] - | tok.Ocean_ [ _val = PT_OCEAN ] - | tok.Asteroids_ [ _val = PT_ASTEROIDS ] - | tok.GasGiant_ [ _val = PT_GASGIANT ] - ; - } - }; -} - - namespace parse { namespace detail { - -enum_value_ref_rules& planet_type_rules() -{ - static planet_type_parser_rules retval; - return retval; -} - + planet_type_parser_rules::planet_type_parser_rules( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser + ) : + enum_value_ref_rules("PlanetType", tok, label, condition_parser) + { + boost::spirit::qi::_val_type _val; + + variable_name + %= tok.PlanetType_ + | tok.OriginalType_ + | tok.NextCloserToOriginalPlanetType_ + | tok.NextBetterPlanetType_ + | tok.ClockwiseNextPlanetType_ + | tok.CounterClockwiseNextPlanetType_ + ; + + enum_expr + = tok.Swamp_ [ _val = PT_SWAMP ] + | tok.Toxic_ [ _val = PT_TOXIC ] + | tok.Inferno_ [ _val = PT_INFERNO ] + | tok.Radiated_ [ _val = PT_RADIATED ] + | tok.Barren_ [ _val = PT_BARREN ] + | tok.Tundra_ [ _val = PT_TUNDRA ] + | tok.Desert_ [ _val = PT_DESERT ] + | tok.Terran_ [ _val = PT_TERRAN ] + | tok.Ocean_ [ _val = PT_OCEAN ] + | tok.Asteroids_ [ _val = PT_ASTEROIDS ] + | tok.GasGiant_ [ _val = PT_GASGIANT ] + ; + } } } diff --git a/parse/ReportParseError.cpp b/parse/ReportParseError.cpp index 0a83f537598..892e9994a0a 100644 --- a/parse/ReportParseError.cpp +++ b/parse/ReportParseError.cpp @@ -36,11 +36,7 @@ std::string parse::detail::info_visitor::prepare(const string& s) const { void parse::detail::info_visitor::print(const string& str) const { m_os << prepare(str); } -#if BOOST_VERSION < 105600 -void parse::detail::info_visitor::operator()(boost::spirit::info::nil) const { -#else void parse::detail::info_visitor::operator()(boost::spirit::info::nil_) const { -#endif indent(); print(m_tag); } @@ -97,26 +93,21 @@ void parse::detail::default_send_error_string(const std::string& str) { std::cout << str +"\n" << std::flush; } -const char* parse::detail::s_filename = nullptr; -parse::text_iterator* parse::detail::s_text_it = nullptr; -parse::text_iterator parse::detail::s_begin; -parse::text_iterator parse::detail::s_end; - -boost::function parse::report_error_::send_error_string = +std::function parse::report_error_::send_error_string = &detail::default_send_error_string; namespace { - std::vector LineStarts() { + std::vector LineStarts(const parse::text_iterator& begin, const parse::text_iterator& end) { //DebugLogger() << "line starts start"; using namespace parse; std::vector retval; - text_iterator it = detail::s_begin; + text_iterator it = begin; retval.push_back(it); // first line // find subsequent lines - while (it != detail::s_end) { + while (it != end) { bool eol = false; text_iterator temp; @@ -125,21 +116,21 @@ namespace { eol = true; temp = ++it; } - if (it != detail::s_end && *it == '\n') { + if (it != end && *it == '\n') { eol = true; temp = ++it; } - if (eol && temp != detail::s_end) + if (eol && temp != end) retval.push_back(temp); - else if (it != detail::s_end) + else if (it != end) ++it; } //DebugLogger() << "line starts end. num lines: " << retval.size(); //for (unsigned int i = 0; i < retval.size(); ++i) { // text_iterator line_end = retval[i]; - // while (line_end != detail::s_end && *line_end != '\r' && *line_end != '\n') + // while (line_end != end && *line_end != '\r' && *line_end != '\n') // ++line_end; // DebugLogger() << " line " << i+1 << ": " << std::string(retval[i], line_end); //} @@ -147,12 +138,14 @@ namespace { } } -std::pair parse::report_error_::line_start_and_line_number(text_iterator error_position) const { +std::pair parse::report_error_::line_start_and_line_number( + const parse::text_iterator& begin, const parse::text_iterator& end, text_iterator error_position) const +{ //DebugLogger() << "line_start_and_line_number start ... looking for: " << std::string(error_position, error_position + 20); - if (error_position == detail::s_begin) - return std::make_pair(detail::s_begin, 1); + if (error_position == begin) + return std::make_pair(begin, 1); - std::vector line_starts = LineStarts(); + std::vector line_starts = LineStarts(begin, end); // search for the first line where the iterator to the start of the line is // at or past the error position @@ -165,20 +158,22 @@ std::pair parse::report_error_::line_start_a } //DebugLogger() << "line_start_and_line_number end"; - return std::make_pair(detail::s_begin, 1); + return std::make_pair(begin, 1); } -std::string parse::report_error_::get_line(text_iterator line_start) const { +std::string parse::report_error_::get_line(const parse::text_iterator& end, text_iterator line_start) const { text_iterator line_end = line_start; - while (line_end != detail::s_end && *line_end != '\r' && *line_end != '\n') + while (line_end != end && *line_end != '\r' && *line_end != '\n') ++line_end; return std::string(line_start, line_end); } -std::string parse::report_error_::get_lines_before(text_iterator line_start) const { +std::string parse::report_error_::get_lines_before( + const parse::text_iterator& begin, const parse::text_iterator& end, text_iterator line_start) const +{ //DebugLogger() << "get_lines_before start"; - std::vector all_line_starts = LineStarts(); + std::vector all_line_starts = LineStarts(begin, end); unsigned int target_line = 1; for (unsigned int line_minus_one = 0; line_minus_one < all_line_starts.size(); ++line_minus_one) { if (std::distance(all_line_starts[line_minus_one], line_start) < 0) { @@ -202,10 +197,12 @@ std::string parse::report_error_::get_lines_before(text_iterator line_start) con return std::string(all_line_starts[retval_first_line-1], all_line_starts[retval_last_line]); // start of first line to start of line after first line } -std::string parse::report_error_::get_lines_after(text_iterator line_start) const { +std::string parse::report_error_::get_lines_after( + const parse::text_iterator& begin, const parse::text_iterator& end, text_iterator line_start) const +{ //DebugLogger() << "get_lines_after start"; - std::vector all_line_starts = LineStarts(); + std::vector all_line_starts = LineStarts(begin, end); unsigned int target_line = 1; for (unsigned int line_minus_one = 0; line_minus_one < all_line_starts.size(); ++line_minus_one) { if (std::distance(all_line_starts[line_minus_one], line_start) < 0) { @@ -225,7 +222,7 @@ std::string parse::report_error_::get_lines_after(text_iterator line_start) cons if (retval_first_line + NUM_LINES < all_line_starts.size()) retval_last_line = retval_first_line + NUM_LINES - 1; - text_iterator last_it = detail::s_end; + text_iterator last_it = end; if (retval_last_line < all_line_starts.size()) last_it = all_line_starts[retval_last_line]; @@ -233,7 +230,9 @@ std::string parse::report_error_::get_lines_after(text_iterator line_start) cons return std::string(all_line_starts[retval_first_line-1], last_it); } -void parse::report_error_::generate_error_string(const token_iterator& first, +void parse::report_error_::generate_error_string(const std::string& filename, + const text_iterator& begin, const text_iterator& end, + const token_iterator& first, const token_iterator& it, const boost::spirit::info& rule_name, std::string& str) const @@ -245,25 +244,25 @@ void parse::report_error_::generate_error_string(const token_iterator& first, unsigned int line_number; text_iterator text_it = it->matched().begin(); if (it->matched().begin() == it->matched().end()) { - text_it = *detail::s_text_it; - if (text_it != detail::s_end) + text_it = begin; + if (text_it != end) ++text_it; } { text_iterator text_it_copy = text_it; - while (text_it_copy != detail::s_end && boost::algorithm::is_space()(*text_it_copy)) { + while (text_it_copy != end && boost::algorithm::is_space()(*text_it_copy)) { ++text_it_copy; } - if (text_it_copy != detail::s_end) + if (text_it_copy != end) text_it = text_it_copy; } - std::tie(line_start, line_number) = line_start_and_line_number(text_it); + std::tie(line_start, line_number) = line_start_and_line_number(begin, end, text_it); std::size_t column_number = std::distance(line_start, text_it); //DebugLogger() << "generate_error_string found line number: " << line_number << " column number: " << column_number; - is << detail::s_filename << ":" << line_number << ":" << column_number << ": " + is << filename << ":" << line_number << ":" << column_number << ": " << "Parse error. Expected"; { @@ -274,14 +273,14 @@ void parse::report_error_::generate_error_string(const token_iterator& first, is << regex_replace(os.str(), regex, "$&, ..."); } - if (text_it == detail::s_end) { + if (text_it == end) { is << " before end of input.\n"; } else { is << " here:\n"; - is << get_lines_before(line_start); - is << get_line(line_start) << "\n"; + is << get_lines_before(begin, end, line_start); + is << get_line(end, line_start) << "\n"; is << std::string(column_number, ' ') << '^' << std::endl; - is << get_lines_after(line_start); + is << get_lines_after(begin, end, line_start); } str = is.str(); diff --git a/parse/ReportParseError.h b/parse/ReportParseError.h index 3df1973df70..e4b617d46c6 100644 --- a/parse/ReportParseError.h +++ b/parse/ReportParseError.h @@ -15,11 +15,7 @@ namespace parse { void indent() const; std::string prepare(const string& s) const; void print(const string& str) const; -#if BOOST_VERSION < 105600 - void operator()(boost::spirit::info::nil) const; -#else void operator()(boost::spirit::info::nil_) const; -#endif void operator()(const string& str) const; void operator()(const boost::spirit::info& what) const; void operator()(const std::pair& pair) const; @@ -35,34 +31,31 @@ namespace parse { void pretty_print(std::ostream& os, boost::spirit::info const& what); void default_send_error_string(const std::string& str); - - // WARNING: These static global values assume that only one file is parsed at a time. - // That assumption is incorrect, and these values will be unreliable. - extern const char* s_filename; - extern text_iterator* s_text_it; - extern text_iterator s_begin; - extern text_iterator s_end; } struct report_error_ { typedef void result_type; template - void operator()(Arg1 first, Arg2, Arg3 it, Arg4 rule_name) const + void operator()(const std::string& filename, const text_iterator& begin, const text_iterator& end, + Arg1 first, Arg2, Arg3 it, Arg4 rule_name) const { std::string error_string; - generate_error_string(first, it, rule_name, error_string); + generate_error_string(filename, begin, end, first, it, rule_name, error_string); send_error_string(error_string); } - static boost::function send_error_string; + static std::function send_error_string; private: - std::pair line_start_and_line_number(text_iterator error_position) const; - std::string get_line(text_iterator line_start) const; - std::string get_lines_before(text_iterator line_start) const; - std::string get_lines_after(text_iterator line_start) const; - void generate_error_string(const token_iterator& first, + std::pair line_start_and_line_number( + const text_iterator& begin, const text_iterator& end, text_iterator error_position) const; + std::string get_line(const text_iterator& end, text_iterator line_start) const; + std::string get_lines_before(const text_iterator& begin, const text_iterator& end, text_iterator line_start) const; + std::string get_lines_after(const text_iterator& begin, const text_iterator& end, text_iterator line_start) const; + void generate_error_string(const std::string& filename, + const text_iterator& begin, const text_iterator& end, + const token_iterator& first, const token_iterator& it, const boost::spirit::info& rule_name, std::string& str) const; diff --git a/parse/ShipDesignsParser.cpp b/parse/ShipDesignsParser.cpp index c7a17ca7a59..93bf11e6686 100644 --- a/parse/ShipDesignsParser.cpp +++ b/parse/ShipDesignsParser.cpp @@ -10,29 +10,29 @@ #include #include + FO_COMMON_API extern const int ALL_EMPIRES; #define DEBUG_PARSERS 0 #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::map>&) { return os; } - inline ostream& operator<<(ostream& os, const std::pair>&) { return os; } + inline ostream& operator<<(ostream& os, const std::map>&) { return os; } + inline ostream& operator<<(ostream& os, const std::pair>&) { return os; } inline ostream& operator<<(ostream& os, const std::vector&) { return os; } } #endif namespace { - void insert_ship_design(boost::optional>& maybe_design, + void insert_ship_design(boost::optional>& maybe_design, const std::string& name, const std::string& description, const std::string& hull, const std::vector& parts, const std::string& icon, const std::string& model, bool name_desc_in_stringtable, const boost::uuids::uuid& uuid) { - // TODO use make_unique when converting to C++14 - auto design = std::unique_ptr( - new ShipDesign(name, description, 0, ALL_EMPIRES, hull, parts, icon, model, - name_desc_in_stringtable, false, uuid)); + auto design = std::make_unique( + name, description, 0, ALL_EMPIRES, hull, parts, icon, model, + name_desc_in_stringtable, false, uuid); maybe_design = std::move(design); }; @@ -65,8 +65,15 @@ namespace { // A lazy UUID parser BOOST_PHOENIX_ADAPT_FUNCTION(boost::uuids::uuid, parse_uuid_, parse_uuid, 1) - struct rules { - rules() { + using start_rule_signature = void (boost::optional>&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + one_or_more_string_tokens(tok) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; @@ -92,34 +99,29 @@ namespace { qi::_r5_type _r5; qi::eps_type eps; - const parse::lexer& tok = parse::lexer::instance(); - design_prefix = tok.ShipDesign_ - > parse::detail::label(Name_token) > tok.string [ _r1 = _1 ] - > ((parse::detail::label(UUID_token) + > label(tok.Name_) > tok.string [ _r1 = _1 ] + > ((label(tok.UUID_) > tok.string [_pass = is_valid_uuid_(_1), _r5 = parse_uuid_(_1) ]) | eps [ _r5 = boost::uuids::nil_generator()() ] ) - > parse::detail::label(Description_token) > tok.string [ _r2 = _1 ] + > label(tok.Description_) > tok.string [ _r2 = _1 ] > ( tok.NoStringtableLookup_ [ _r4 = false ] | eps [ _r4 = true ] ) - > parse::detail::label(Hull_token) > tok.string [ _r3 = _1 ] + > label(tok.Hull_) > tok.string [ _r3 = _1 ] ; design = design_prefix(_a, _b, _c, _f, _g) - > parse::detail::label(Parts_token) - > ( - ('[' > +tok.string [ push_back(_d, _1) ] > ']') - | tok.string [ push_back(_d, _1) ] - ) + > label(tok.Parts_) + > one_or_more_string_tokens [ _d = _1 ] > -( - parse::detail::label(Icon_token) > tok.string [ _e = _1 ] + label(tok.Icon_) > tok.string [ _e = _1 ] ) - > parse::detail::label(Model_token) > tok.string + > label(tok.Model_) > tok.string [ insert_ship_design_(_r1, _a, _b, _c, _d, _e, _1, _f, _g) ] ; @@ -128,21 +130,21 @@ namespace { ; design_prefix.name("Name, UUID, Description, Lookup Flag, Hull"); - design.name("ShipDesign"); + design.name("ParsedShipDesign"); #if DEBUG_PARSERS debug(design_prefix); debug(design); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } using design_prefix_rule = parse::detail::rule< void (std::string&, std::string&, std::string&, bool&, boost::uuids::uuid&)>; using design_rule = parse::detail::rule< - void (boost::optional>&), + void (boost::optional>&), boost::spirit::qi::locals< std::string, std::string, @@ -154,17 +156,23 @@ namespace { > >; - using start_rule = parse::detail::rule< - void (boost::optional>&) - >; + using start_rule = parse::detail::rule; + parse::detail::Labeller label; + parse::detail::single_or_repeated_string<> one_or_more_string_tokens; design_prefix_rule design_prefix; design_rule design; start_rule start; }; - struct manifest_rules { - manifest_rules() { + using manifest_start_rule_signature = void (std::vector&); + + struct manifest_grammar : public parse::detail::grammar { + manifest_grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + manifest_grammar::base_type(start) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; @@ -176,98 +184,85 @@ namespace { qi::_4_type _4; qi::_r1_type _r1; - const parse::lexer& tok = parse::lexer::instance(); - design_manifest = tok.ShipDesignOrdering_ - > *(parse::detail::label(UUID_token) > tok.string [ push_back(_r1, parse_uuid_(_1)) ]) + > *(label(tok.UUID_) > tok.string [ push_back(_r1, parse_uuid_(_1)) ]) ; start = +design_manifest(_r1) ; - design_manifest.name("ShipDesignOrdering"); + design_manifest.name("ParsedShipDesignOrdering"); #if DEBUG_PARSERS debug(design_manifest); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } using manifest_rule = parse::detail::rule&)>; - using start_rule = parse::detail::rule&)>; + using start_rule = parse::detail::rule; + parse::detail::Labeller label; manifest_rule design_manifest; start_rule start; }; - } namespace parse { - bool ship_designs(const boost::filesystem::path& path, - std::vector, boost::filesystem::path>>& designs_and_paths, - std::vector& ordering) - { + std::pair< + std::vector, boost::filesystem::path>>, // designs_and_paths + std::vector //ordering + > + ship_designs(const boost::filesystem::path& path) { + /* Note: Parsing to ParsedShipDesign instead of ShipDesign means that + checks of parts, hulls etc are done elsewhere. A successful parse + depends only on the parsed string and not other potentially + concurrent parses of hulls and parts.*/ + const lexer lexer; + std::vector, boost::filesystem::path>> designs_and_paths; + std::vector ordering; + boost::filesystem::path manifest_file; - // Allow files with any suffix in order to convert legacy ShipDesign files. + // Allow files with any suffix in order to convert legacy ParsedShipDesign files. bool permissive_mode = true; const auto& scripts = ListScripts(path, permissive_mode); - bool result = true; - for(const boost::filesystem::path& file : scripts) { + for (const auto& file : scripts) { + TraceLogger() << "Parse ship design file " << file.filename(); if (file.filename() == "ShipDesignOrdering.focs.txt" ) { manifest_file = file; continue; } try { - boost::optional> maybe_design; - auto partial_result = detail::parse_file>>(file, maybe_design); - result &= partial_result; + boost::optional> maybe_design; + auto partial_result = detail::parse_file>>( + lexer, file, maybe_design); + if (!partial_result || !maybe_design) continue; designs_and_paths.push_back({std::move(*maybe_design), file}); } catch (const std::runtime_error& e) { - result = false; ErrorLogger() << "Failed to parse ship design in " << file << " from " << path << " because " << e.what();; } } if (!manifest_file.empty()) { try { - result &= detail::parse_file>(manifest_file, ordering); + /*auto success =*/ detail::parse_file>( + lexer, manifest_file, ordering); + } catch (const std::runtime_error& e) { - result = false; ErrorLogger() << "Failed to parse ship design manifest in " << manifest_file << " from " << path << " because " << e.what();; } } - return result; - } -} -namespace { - /** Remove the file paths from the returned ShipDesigns.*/ - bool only_ship_designs(const boost::filesystem::path& path, - std::vector>& designs, - std::vector& ordering) - { - std::vector, boost::filesystem::path>> designs_and_paths; - auto result = parse::ship_designs(path, designs_and_paths, ordering); - for (auto& design_and_path : designs_and_paths) - designs.push_back(std::move(design_and_path.first)); - return result; + return {std::move(designs_and_paths), ordering}; } } - -namespace parse { - bool ship_designs(std::vector>& designs, std::vector& ordering) - { return only_ship_designs("scripting/ship_designs", designs, ordering); } - - bool monster_designs(std::vector>& designs, std::vector& ordering) - { return only_ship_designs("scripting/monster_designs", designs, ordering); } -} diff --git a/parse/ShipHullsParser.cpp b/parse/ShipHullsParser.cpp index 095f729dbe6..17c760f90c4 100644 --- a/parse/ShipHullsParser.cpp +++ b/parse/ShipHullsParser.cpp @@ -7,10 +7,11 @@ #include "EnumParser.h" #include "ConditionParserImpl.h" #include "ValueRefParser.h" -#include "CommonParams.h" +#include "CommonParamsParser.h" #include "../universe/Condition.h" -#include "../universe/ShipDesign.h" +#include "../universe/ShipHull.h" +#include "../universe/ValueRef.h" #include @@ -19,85 +20,104 @@ #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector&) { return os; } - inline ostream& operator<<(ostream& os, const std::vector>&) { return os; } - inline ostream& operator<<(ostream& os, const std::map&) { return os; } - inline ostream& operator<<(ostream& os, const std::pair&) { return os; } - inline ostream& operator<<(ostream& os, const HullType::Slot&) { return os; } - inline ostream& operator<<(ostream& os, const HullTypeStats&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector&) { return os; } + inline ostream& operator<<(ostream& os, const parse::effects_group_payload&) { return os; } + inline ostream& operator<<(ostream& os, const std::map>&) { return os; } + inline ostream& operator<<(ostream& os, const std::pair>&) { return os; } + inline ostream& operator<<(ostream& os, const ShipHull::Slot&) { return os; } + inline ostream& operator<<(ostream& os, const ShipHullStats&) { return os; } } #endif namespace { const boost::phoenix::function is_unique_; - const boost::phoenix::function insert_; + void insert_shiphull(std::map>& shiphulls, + const ShipHullStats& stats, + const std::unique_ptr& common_params, + const MoreCommonParams& more_common_params, + const boost::optional>& slots, + const std::string& icon, const std::string& graphic) + { + auto shiphull = std::make_unique( + stats, std::move(*common_params), more_common_params, + (slots ? *slots : std::vector()), + icon, graphic); + shiphulls.emplace(shiphull->Name(), std::move(shiphull)); + } - struct rules { - rules() { + BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_shiphull_, insert_shiphull, 7) + + using start_rule_payload = std::map>; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + condition_parser(tok, label), + string_grammar(tok, label, condition_parser), + tags_parser(tok, label), + common_rules(tok, label, condition_parser, string_grammar, tags_parser), + ship_slot_type_enum(tok), + double_rule(tok), + one_or_more_slots(slot) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; using phoenix::construct; - using phoenix::new_; - using phoenix::push_back; - using phoenix::insert; qi::_1_type _1; qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; + qi::_5_type _5; + qi::_6_type _6; + qi::_7_type _7; + qi::_8_type _8; qi::_r1_type _r1; + qi::matches_type matches_; qi::_pass_type _pass; qi::_val_type _val; qi::eps_type eps; qi::lit_type lit; - - const parse::lexer& tok = parse::lexer::instance(); + qi::omit_type omit_; + const boost::phoenix::function deconstruct_movable_; hull_stats - = parse::detail::label(Speed_token) > parse::detail::double_ [ _a = _1 ] - > parse::detail::label(Fuel_token) > parse::detail::double_ [ _c = _1 ] - > parse::detail::label(Stealth_token) > parse::detail::double_ [ _d = _1 ] - > parse::detail::label(Structure_token) > parse::detail::double_ - [ _val = construct(_c, _a, _d, _1) ] + = (label(tok.Speed_) > double_rule // _1 + > matches_[tok.NoDefaultSpeedEffect_] // _2 + > label(tok.Fuel_) > double_rule // _3 + > matches_[tok.NoDefaultFuelEffect_] // _4 + > label(tok.Stealth_) > double_rule // _5 + > matches_[tok.NoDefaultStealthEffect_] // _6 + > label(tok.Structure_) > double_rule // _7 + > matches_[tok.NoDefaultStructureEffect_])// _8 + [ _val = construct(_3, _1, _5, _7, _4, _2, _6, _8) ] ; slot - = tok.Slot_ - > parse::detail::label(Type_token) > parse::ship_slot_type_enum() [ _a = _1 ] - > parse::detail::label(Position_token) - > '(' > parse::detail::double_ [ _b = _1 ] > ',' > parse::detail::double_ [ _c = _1 ] > lit(')') - [ _val = construct(_a, _b, _c) ] - ; - - slots - = -( - parse::detail::label(Slots_token) - > ( - ('[' > +slot [ push_back(_r1, _1) ] > ']') - | slot [ push_back(_r1, _1) ] - ) - ) + = (omit_[tok.Slot_] + > label(tok.Type_) > ship_slot_type_enum + > label(tok.Position_) + > '(' > double_rule > ',' > double_rule > lit(')')) + [ _val = construct(_1, _2, _3) ] ; hull - = tok.Hull_ - > parse::detail::more_common_params_parser() - [_pass = is_unique_(_r1, HullType_token, phoenix::bind(&MoreCommonParams::name, _1)), _a = _1 ] - > hull_stats [ _c = _1 ] - > -slots(_e) - > parse::detail::common_params_parser() [ _d = _1 ] - > parse::detail::label(Icon_token) > tok.string [ _f = _1 ] - > parse::detail::label(Graphic_token) > tok.string - [ insert_(_r1, phoenix::bind(&MoreCommonParams::name, _a), - new_(_c, _d, _a, _e, _f, _1)) ] + = (tok.Hull_ + > common_rules.more_common + > hull_stats + > -(label(tok.Slots_) > one_or_more_slots) + > common_rules.common + > label(tok.Icon_) > tok.string + > label(tok.Graphic_) > tok.string) + [ _pass = is_unique_(_r1, _1, phoenix::bind(&MoreCommonParams::name, _2)), + insert_shiphull_(_r1, _3, + deconstruct_movable_(_5, _pass), + _2, _4, _6, _7) ] ; start @@ -106,75 +126,52 @@ namespace { hull_stats.name("Hull stats"); slot.name("Slot"); - slots.name("Slots"); hull.name("Hull"); #if DEBUG_PARSERS debug(cost); debug(hull_stats); debug(slot); - debug(slots); debug(hull); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - HullTypeStats (), - boost::spirit::qi::locals< - double, - double, - double, - double - > - > hull_stats_rule; - - typedef parse::detail::rule< - HullType::Slot (), - boost::spirit::qi::locals< - ShipSlotType, - double, - double - > - > slot_rule; - - typedef parse::detail::rule< - void (std::vector&) - > slots_rule; - - typedef parse::detail::rule< - void (std::map&), - boost::spirit::qi::locals< - MoreCommonParams, - std::string, // dummy - HullTypeStats, - CommonParams, - std::vector, - std::string - > - > hull_rule; - - typedef parse::detail::rule< - void (std::map&) - > start_rule; + using hull_stats_rule = parse::detail::rule; + + using slot_rule = parse::detail::rule; + + using hull_rule = parse::detail::rule< + void (std::map>&) + >; + + using start_rule = parse::detail::rule; + parse::detail::Labeller label; + parse::conditions_parser_grammar condition_parser; + const parse::string_parser_grammar string_grammar; + parse::detail::tags_grammar tags_parser; + parse::detail::common_params_rules common_rules; + parse::ship_slot_enum_grammar ship_slot_type_enum; + parse::detail::double_grammar double_rule; hull_stats_rule hull_stats; slot_rule slot; - slots_rule slots; + parse::detail::single_or_bracketed_repeat one_or_more_slots; hull_rule hull; start_rule start; }; } namespace parse { - bool ship_hulls(std::map& hulls) { - bool result = true; + start_rule_payload ship_hulls(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload hulls; - for (const boost::filesystem::path& file : ListScripts("scripting/ship_hulls")) { - result &= detail::parse_file>(file, hulls); + for (const boost::filesystem::path& file : ListScripts(path)) { + /*auto success =*/ detail::parse_file(lexer, file, hulls); } - return result; + return hulls; } } diff --git a/parse/ShipPartsParser.cpp b/parse/ShipPartsParser.cpp index 1f9304b1e46..ebcc7b6002a 100644 --- a/parse/ShipPartsParser.cpp +++ b/parse/ShipPartsParser.cpp @@ -1,4 +1,5 @@ -#define PHOENIX_LIMIT 11 +#define FUSION_MAX_VECTOR_SIZE 15 +#define PHOENIX_LIMIT 12 #define BOOST_RESULT_OF_NUM_ARGS PHOENIX_LIMIT #include "Parse.h" @@ -7,10 +8,11 @@ #include "EnumParser.h" #include "ValueRefParser.h" #include "ConditionParserImpl.h" -#include "CommonParams.h" +#include "CommonParamsParser.h" -#include "../universe/ShipDesign.h" #include "../universe/Condition.h" +#include "../universe/ShipPart.h" +#include "../universe/ValueRef.h" #include @@ -20,129 +22,152 @@ #if DEBUG_PARSERS namespace std { inline ostream& operator<<(ostream& os, const std::vector&) { return os; } - inline ostream& operator<<(ostream& os, const std::vector>&) { return os; } - inline ostream& operator<<(ostream& os, const std::map&) { return os; } - inline ostream& operator<<(ostream& os, const std::pair&) { return os; } + inline ostream& operator<<(ostream& os, const parse::effects_group_payload&) { return os; } + inline ostream& operator<<(ostream& os, const std::map>&) { return os; } + inline ostream& operator<<(ostream& os, const std::pair>&) { return os; } } #endif +namespace parse { + namespace detail { + typedef std::tuple< + boost::optional, + boost::optional, + boost::optional> + > OptCap_OptStat2_OptMoveableTargets; + } +} + namespace { const boost::phoenix::function is_unique_; - const boost::phoenix::function insert_; + void insert_shippart(std::map>& ship_parts, + ShipPartClass part_class, + const parse::detail::OptCap_OptStat2_OptMoveableTargets capacity_and_stat2_and_targets, + const parse::detail::MovableEnvelope& common_params, + const MoreCommonParams& more_common_params, + boost::optional> mountable_slot_types, + const std::string& icon, + bool no_default_capacity_effect, + bool& pass) + { + boost::optional capacity, stat2; + boost::optional> combat_targets; + std::tie(capacity, stat2, combat_targets) = capacity_and_stat2_and_targets; + + + auto ship_part = std::make_unique( + part_class, + (capacity ? *capacity : 0.0), + (stat2 ? *stat2 : 1.0), + *common_params.OpenEnvelope(pass), more_common_params, + (mountable_slot_types ? *mountable_slot_types : std::vector()), + icon, + !no_default_capacity_effect, + (combat_targets ? (*combat_targets).OpenEnvelope(pass) : nullptr)); + + ship_parts.insert(std::make_pair(ship_part->Name(), std::move(ship_part))); + } - struct rules { - rules() { + BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_shippart_, insert_shippart, 9) + + using start_rule_payload = std::map>; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + condition_parser(tok, label), + string_grammar(tok, label, condition_parser), + tags_parser(tok, label), + common_rules(tok, label, condition_parser, string_grammar, tags_parser), + ship_slot_type_enum(tok), + ship_part_class_enum(tok), + double_rule(tok), + one_or_more_slots(ship_slot_type_enum) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; using phoenix::construct; - using phoenix::new_; - using phoenix::push_back; qi::_1_type _1; qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_g_type _g; - qi::_h_type _h; + qi::_5_type _5; + qi::_6_type _6; + qi::_7_type _7; + qi::_8_type _8; + qi::_9_type _9; + phoenix::actor> _10; // qi::_10_type is not predefined qi::_pass_type _pass; qi::_r1_type _r1; - qi::eps_type eps; - - const parse::lexer& tok = parse::lexer::instance(); - - slots - = -( - parse::detail::label(MountableSlotTypes_token) - > ( - ('[' > +parse::ship_slot_type_enum() [ push_back(_r1, _1) ] > ']') - | parse::ship_slot_type_enum() [ push_back(_r1, _1) ] - ) - ) - ; - - part_type - = ( tok.Part_ - > parse::detail::more_common_params_parser() - [_pass = is_unique_(_r1, PartType_token, phoenix::bind(&MoreCommonParams::name, _1)), _a = _1 ] - > parse::detail::label(Class_token) > parse::ship_part_class_enum() [ _c = _1 ] - > ( (parse::detail::label(Capacity_token) > parse::detail::double_ [ _d = _1 ]) - | (parse::detail::label(Damage_token) > parse::detail::double_ [ _d = _1 ]) - | eps [ _d = 0.0 ] - ) - > ( (parse::detail::label(Damage_token) > parse::detail::double_ [ _h = _1 ]) // damage is secondary for fighters - | (parse::detail::label(Shots_token) > parse::detail::double_ [ _h = _1 ]) // shots is secondary for direct fire weapons - | eps [ _h = 1.0 ] - ) - > ( tok.NoDefaultCapacityEffect_ [ _g = false ] - | eps [ _g = true ] - ) - > slots(_f) - > parse::detail::common_params_parser() [ _e = _1 ] - > parse::detail::label(Icon_token) > tok.string [ _b = _1 ] - ) [ insert_(_r1, phoenix::bind(&MoreCommonParams::name, _a), - new_(_c, _d, _h, _e, _a, _f, _b, _g)) ] + qi::matches_type matches_; + + ship_part + = ( tok.Part_ // _1 + > common_rules.more_common // _2 + > label(tok.Class_) > ship_part_class_enum // _3 + > -( (label(tok.Capacity_) > double_rule) // _4 + | (label(tok.Damage_) > double_rule) // _4 + ) + > -( (label(tok.Damage_) > double_rule ) // _5 : damage is secondary stat for fighters + | (label(tok.Shots_) > double_rule ) // _5 : shots is secondary stat for direct fire weapons + ) + > matches_[tok.NoDefaultCapacityEffect_] // _6 + > -(label(tok.CombatTargets_) > condition_parser) // _7 + > -(label(tok.MountableSlotTypes_) > one_or_more_slots)// _8 + > common_rules.common // _9 + > label(tok.Icon_) > tok.string // _10 + ) [ _pass = is_unique_(_r1, _1, phoenix::bind(&MoreCommonParams::name, _2)), + insert_shippart_(_r1, _3, + construct(_4, _5, _7) + , _9, _2, _8, _10, _6, _pass) ] ; start - = +part_type(_r1) + = +ship_part(_r1) ; - slots.name("mountable slot types"); - part_type.name("Part"); + ship_part.name("Part"); #if DEBUG_PARSERS - debug(slots); - debug(part_type); + debug(ship_part); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - void (std::vector&) - > slots_rule; - - typedef parse::detail::rule< - void (std::map&), - boost::spirit::qi::locals< - MoreCommonParams, - std::string, - ShipPartClass, - double, - CommonParams, - std::vector, - bool, - double - > - > part_type_rule; - - typedef parse::detail::rule< - void (std::map&) - > start_rule; - - slots_rule slots; - part_type_rule part_type; - start_rule start; + using ship_part_rule = parse::detail::rule; + using start_rule = parse::detail::rule; + + parse::detail::Labeller label; + const parse::conditions_parser_grammar condition_parser; + const parse::string_parser_grammar string_grammar; + parse::detail::tags_grammar tags_parser; + parse::detail::common_params_rules common_rules; + parse::ship_slot_enum_grammar ship_slot_type_enum; + parse::ship_part_class_enum_grammar ship_part_class_enum; + parse::detail::double_grammar double_rule; + parse::detail::single_or_bracketed_repeat + one_or_more_slots; + ship_part_rule ship_part; + start_rule start; }; - } namespace parse { - bool ship_parts(std::map& parts) { - bool result = true; + start_rule_payload ship_parts(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload parts; - for(const boost::filesystem::path& file : ListScripts("scripting/ship_parts")) { - result &= detail::parse_file>(file, parts); + for (const auto& file : ListScripts(path)) { + /*auto success =*/ detail::parse_file(lexer, file, parts); } - return result; + return parts; } } diff --git a/parse/SpecialsParser.cpp b/parse/SpecialsParser.cpp index 9762910c043..ed8bdd271d1 100644 --- a/parse/SpecialsParser.cpp +++ b/parse/SpecialsParser.cpp @@ -1,10 +1,11 @@ #include "Parse.h" -#include "ParseImpl.h" -#include "ConditionParserImpl.h" -#include "ValueRefParser.h" +#include "EffectParser.h" +#include "../universe/Condition.h" +#include "../universe/Effect.h" #include "../universe/Special.h" +#include "../universe/ValueRef.h" #include @@ -13,130 +14,148 @@ #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector>&) { return os; } - inline ostream& operator<<(ostream& os, const std::map&) { return os; } - inline ostream& operator<<(ostream& os, const std::pair&) { return os; } + inline ostream& operator<<(ostream& os, const parse::effects_group_payload&) { return os; } + inline ostream& operator<<(ostream& os, const std::map&) { return os; } + inline ostream& operator<<(ostream& os, const std::pair&) { return os; } } #endif namespace { const boost::phoenix::function is_unique_; - const boost::phoenix::function insert_; + struct special_pod { + special_pod(std::string name_, + std::string description_, + const boost::optional>& stealth_, + const boost::optional& effects_, + boost::optional spawn_rate_, + boost::optional spawn_limit_, + const boost::optional>& initial_capacity_, + const boost::optional& location_, + const std::string& graphic_) : + name(name_), + description(description_), + stealth(stealth_), + effects(effects_), + spawn_rate(spawn_rate_), + spawn_limit(spawn_limit_), + initial_capacity(initial_capacity_), + location(location_), + graphic(graphic_) + {} + + std::string name; + std::string description; + const boost::optional> stealth; + const boost::optional& effects; + boost::optional spawn_rate; + boost::optional spawn_limit; + boost::optional> initial_capacity; + boost::optional location; + const std::string& graphic; + }; + + void insert_special(std::map>& specials, special_pod special_, bool& pass) { + auto special_ptr = std::make_unique( + special_.name, special_.description, + (special_.stealth ? special_.stealth->OpenEnvelope(pass) : nullptr), + (special_.effects ? OpenEnvelopes(*special_.effects, pass) : std::vector>()), + (special_.spawn_rate ? *special_.spawn_rate : 1.0), + (special_.spawn_limit ? *special_.spawn_limit : 9999), + (special_.initial_capacity ? special_.initial_capacity->OpenEnvelope(pass) : nullptr), + (special_.location ? special_.location->OpenEnvelope(pass) : nullptr), + special_.graphic); + + specials.insert(std::make_pair(special_ptr->Name(), std::move(special_ptr))); + } - struct rules { - rules() { + BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_special_, insert_special, 3) + + using start_rule_payload = SpecialsManager::SpecialsTypeMap; + using start_rule_signature = void(start_rule_payload&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + condition_parser(tok, label), + string_grammar(tok, label, condition_parser), + double_rules(tok, label, condition_parser, string_grammar), + effects_group_grammar(tok, label, condition_parser, string_grammar), + double_rule(tok), + int_rule(tok) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; - using phoenix::new_; - qi::_1_type _1; qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_g_type _g; - qi::_h_type _h; + qi::_5_type _5; + qi::_6_type _6; + qi::_7_type _7; + qi::_8_type _8; + qi::_9_type _9; + phoenix::actor> _10; // qi::_10_type is not predefined qi::_pass_type _pass; qi::_r1_type _r1; - qi::_r2_type _r2; - qi::_r3_type _r3; qi::eps_type eps; - - const parse::lexer& tok = parse::lexer::instance(); - - special_prefix - = tok.Special_ - > parse::detail::label(Name_token) - > tok.string [ _pass = is_unique_(_r1, Special_token, _1), _r2 = _1 ] - > parse::detail::label(Description_token) > tok.string [ _r3 = _1 ] - ; - - spawn - = ( (parse::detail::label(SpawnRate_token) > parse::detail::double_ [ _r1 = _1 ]) - | eps [ _r1 = 1.0 ] - ) - > ( (parse::detail::label(SpawnLimit_token) > parse::detail::int_ [ _r2 = _1 ]) - | eps [ _r2 = 9999 ] - ) - ; + const boost::phoenix::function deconstruct_movable_; special - = special_prefix(_r1, _a, _b) - > -(parse::detail::label(Stealth_token) > parse::double_value_ref() [ _g = _1 ]) - > spawn(_c, _d) - > -(parse::detail::label(Capacity_token) > parse::double_value_ref() [ _h = _1 ]) - > -(parse::detail::label(Location_token) > parse::detail::condition_parser [ _e = _1 ]) - > -(parse::detail::label(EffectsGroups_token) > parse::detail::effects_group_parser() [ _f = _1 ]) - > parse::detail::label(Graphic_token) > tok.string - [ insert_(_r1, _a, new_(_a, _b, _g, _f, _c, _d, _h, _e, _1)) ] + = ( tok.Special_ + > label(tok.Name_) > tok.string + > label(tok.Description_) > tok.string + > -(label(tok.Stealth_) > double_rules.expr) + > -(label(tok.SpawnRate_) > double_rule) + > -(label(tok.SpawnLimit_) > int_rule) + > -(label(tok.Capacity_) > double_rules.expr) + > -(label(tok.Location_) > condition_parser) + > -(label(tok.EffectsGroups_) > effects_group_grammar) + > label(tok.Graphic_) > tok.string) + [ _pass = is_unique_(_r1, _1, _2), + insert_special_(_r1, phoenix::construct(_2, _3, _4, _9, _5, _6, _7, _8, _10), _pass) ] ; start = +special(_r1) ; - special_prefix.name("Special"); special.name("Special"); - spawn.name("SpawnRate and SpawnLimit"); #if DEBUG_PARSERS - debug(special_prefix); - debug(spawn); debug(special); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - void (const std::map&, std::string&, std::string&) - > special_prefix_rule; - - typedef parse::detail::rule< - void (double&, int&) - > spawn_rule; - - typedef parse::detail::rule< - void (std::map&), - boost::spirit::qi::locals< - std::string, - std::string, - double, - int, - Condition::ConditionBase*, - std::vector>, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > - > special_rule; - - typedef parse::detail::rule< - void (std::map&) - > start_rule; - - - special_prefix_rule special_prefix; - spawn_rule spawn; - special_rule special; - start_rule start; + using special_rule = parse::detail::rule; + using start_rule = parse::detail::rule; + + parse::detail::Labeller label; + parse::conditions_parser_grammar condition_parser; + const parse::string_parser_grammar string_grammar; + parse::double_parser_rules double_rules; + parse::effects_group_grammar effects_group_grammar; + parse::detail::double_grammar double_rule; + parse::detail::int_grammar int_rule; + special_rule special; + start_rule start; }; } namespace parse { - bool specials(std::map& specials_) { - bool result = true; + start_rule_payload specials(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload specials_; - for (const boost::filesystem::path& file : ListScripts("scripting/specials")) { - result &= detail::parse_file>(file, specials_); + for (const boost::filesystem::path& file : ListScripts(path)) { + /*auto success =*/ detail::parse_file(lexer, file, specials_); } - return result; + return specials_; } } diff --git a/parse/SpeciesParser.cpp b/parse/SpeciesParser.cpp index 8f3230e77d9..acc6cc1af46 100644 --- a/parse/SpeciesParser.cpp +++ b/parse/SpeciesParser.cpp @@ -2,9 +2,14 @@ #include "ParseImpl.h" #include "EnumParser.h" -#include "ValueRefParserImpl.h" +#include "ValueRefParser.h" +#include "EnumValueRefRules.h" +#include "EffectParser.h" #include "ConditionParserImpl.h" +#include "MovableEnvelope.h" +#include "../universe/Condition.h" +#include "../universe/Effect.h" #include "../universe/Species.h" #include @@ -16,73 +21,125 @@ namespace std { inline ostream& operator<<(ostream& os, const FocusType&) { return os; } inline ostream& operator<<(ostream& os, const std::vector&) { return os; } - inline ostream& operator<<(ostream& os, const std::vector>&) { return os; } + inline ostream& operator<<(ostream& os, const parse::effects_group_payload&) { return os; } inline ostream& operator<<(ostream& os, const std::pair&) { return os; } inline ostream& operator<<(ostream& os, const std::pair&) { return os; } inline ostream& operator<<(ostream& os, const std::map&) { return os; } - inline ostream& operator<<(ostream& os, const std::pair&) { return os; } - inline ostream& operator<<(ostream& os, const std::map&) { return os; } + inline ostream& operator<<(ostream& os, const std::pair>&) { return os; } + inline ostream& operator<<(ostream& os, const std::map>&) { return os; } } #endif namespace { const boost::phoenix::function is_unique_; - const boost::phoenix::function insert_; + struct SpeciesStuff { + SpeciesStuff(const boost::optional>& foci_, + const boost::optional& preferred_focus_, + const std::set& tags_, + const std::string& graphic_) : + foci(foci_), + preferred_focus(preferred_focus_), + tags(tags_), + graphic(graphic_) + {} + + boost::optional> foci; + boost::optional preferred_focus; + std::set tags; + std::string graphic; + }; + - struct rules { - rules() { + void insert_species( + std::map>& species, + const SpeciesStrings& strings, + const boost::optional>& planet_environments, + const boost::optional& effects, + boost::optional>& combat_targets, + const SpeciesParams& params, + const SpeciesStuff& foci_preferred_tags_graphic, + bool& pass) + { + auto species_ptr = std::make_unique( + strings, + (foci_preferred_tags_graphic.foci ? *foci_preferred_tags_graphic.foci : std::vector()), + (foci_preferred_tags_graphic.preferred_focus ? *foci_preferred_tags_graphic.preferred_focus : std::string()), + (planet_environments ? *planet_environments : std::map()), + (effects ? OpenEnvelopes(*effects, pass) : std::vector>()), + (combat_targets ? (*combat_targets).OpenEnvelope(pass) : nullptr), + params, + foci_preferred_tags_graphic.tags, + foci_preferred_tags_graphic.graphic); + + species.insert(std::make_pair(species_ptr->Name(), std::move(species_ptr))); + } + + BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_species_, insert_species, 8) + + + using start_rule_payload = std::pair< + std::map>, // species_by_name + std::vector // census ordering + >; + using start_rule_signature = void(start_rule_payload::first_type&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + condition_parser(tok, label), + string_grammar(tok, label, condition_parser), + tags_parser(tok, label), + effects_group_grammar(tok, label, condition_parser, string_grammar), + one_or_more_foci(focus_type), + planet_type_rules(tok, label, condition_parser), + planet_environment_rules(tok, label, condition_parser) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; using phoenix::construct; using phoenix::insert; - using phoenix::new_; using phoenix::push_back; qi::_1_type _1; qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_g_type _g; + qi::_5_type _5; + qi::_6_type _6; + qi::_7_type _7; + qi::_8_type _8; + qi::_9_type _9; qi::_pass_type _pass; qi::_r1_type _r1; qi::_val_type _val; qi::eps_type eps; - - const parse::lexer& tok = parse::lexer::instance(); + qi::matches_type matches_; + qi::omit_type omit_; + qi::as_string_type as_string_; + const boost::phoenix::function deconstruct_movable_; focus_type - = tok.Focus_ - > parse::detail::label(Name_token) > tok.string [ _a = _1 ] - > parse::detail::label(Description_token) > tok.string [ _b = _1 ] - > parse::detail::label(Location_token) > parse::detail::condition_parser [ _c = _1 ] - > parse::detail::label(Graphic_token) > tok.string - [ _val = construct(_a, _b, _c, _1) ] + = ( omit_[tok.Focus_] + > label(tok.Name_) > tok.string + > label(tok.Description_) > tok.string + > label(tok.Location_) > condition_parser + > label(tok.Graphic_) > tok.string + ) [ _val = construct(_1, _2, deconstruct_movable_(_3, _pass), _4) ] ; foci - = parse::detail::label(Foci_token) - > ( - ('[' > +focus_type [ push_back(_r1, _1) ] > ']') - | focus_type [ push_back(_r1, _1) ] - ) - ; - - effects - = parse::detail::label(EffectsGroups_token) > parse::detail::effects_group_parser() [ _r1 = _1 ] + = label(tok.Foci_) + > one_or_more_foci ; environment_map_element - = parse::detail::label(Type_token) > parse::detail::planet_type_rules().enum_expr [ _a = _1 ] - > parse::detail::label(Environment_token) > parse::detail::planet_environment_rules().enum_expr - [ _val = construct>(_a, _1) ] + = ( label(tok.Type_) > planet_type_rules.enum_expr + > label(tok.Environment_) > planet_environment_rules.enum_expr + ) [ _val = construct>(_1, _2) ] ; environment_map @@ -90,50 +147,46 @@ namespace { | environment_map_element [ insert(_val, _1) ] ; - environments - = parse::detail::label(Environments_token) > environment_map [ _r1 = _1 ] - ; - species_params - = ((tok.Playable_ [ _a = true ]) | eps) - > ((tok.Native_ [ _b = true ]) | eps) - > ((tok.CanProduceShips_ [ _c = true ]) | eps) - > ((tok.CanColonize_ [ _d = true ]) | eps) - [ _val = construct(_a, _b, _d, _c) ] + = (matches_[tok.Playable_] + > matches_[tok.Native_] + > matches_[tok.CanProduceShips_] + > matches_[tok.CanColonize_] + ) [ _val = construct(_1, _2, _4, _3) ] ; species_strings - = parse::detail::label(Name_token) > tok.string - [ _pass = is_unique_(_r1, Species_token, _1), _a = _1 ] - > parse::detail::label(Description_token) > tok.string [ _b = _1 ] - > parse::detail::label(Gameplay_Description_token) > tok.string [ _c = _1 ] - [ _val = construct(_a, _b, _c) ] + = ( tok.Species_ + > label(tok.Name_) > tok.string + > label(tok.Description_) > tok.string + > label(tok.Gameplay_Description_) > tok.string + ) [ _pass = is_unique_(_r1, _1, _2), + _val = construct(_2, _3, _4) ] ; species - = tok.Species_ - > species_strings(_r1) [ _a = _1 ] - > species_params [ _b = _1] - > parse::detail::tags_parser()(_c) - > -foci(_d) - > -(parse::detail::label(PreferredFocus_token) >> tok.string [ _g = _1 ]) - > -effects(_e) - > -environments(_f) - > parse::detail::label(Graphic_token) > tok.string - [ insert_(_r1, phoenix::bind(&SpeciesStrings::name, _a), - new_(_a, _d, _g, _f, _e, _b, _c, _1)) ] + = ( species_strings(_r1)// _1 + > species_params // _2 + > tags_parser // _3 + > -foci // _4 + > -as_string_[(label(tok.PreferredFocus_) > tok.string )] // _5 + > -(label(tok.EffectsGroups_) > effects_group_grammar) // _6 + > -(label(tok.CombatTargets_) > condition_parser) // _7 + > -(label(tok.Environments_) > environment_map) // _8 + > label(tok.Graphic_) > tok.string // _9 + ) [ insert_species_(_r1, _1, _8, _6, _7, _2, + construct(_4, _5, _3, _9), + _pass) ] ; start - = +species(_r1) + = +species(_r1) ; focus_type.name("Focus"); foci.name("Foci"); - effects.name("EffectsGroups"); environment_map_element.name("Type = Environment = "); environment_map.name("Environments"); - environments.name("Environments"); species_params.name("Species Flags"); species_strings.name("Species Strings"); species.name("Species"); @@ -142,106 +195,119 @@ namespace { #if DEBUG_PARSERS debug(focus_type); debug(foci); - debug(effects); debug(environment_map_element); debug(environment_map); - debug(environments); debug(species_params); debug(species_strings); debug(species); debug(start); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); + } + + using focus_type_rule = parse::detail::rule; + using foci_rule = parse::detail::rule ()>; + using environment_map_element_rule = parse::detail::rule ()>; + using environment_map_rule = parse::detail::rule ()>; + using species_params_rule = parse::detail::rule; + using species_strings_rule = parse::detail::rule; + using species_rule = parse::detail::rule; + using start_rule = parse::detail::rule; + + parse::detail::Labeller label; + const parse::conditions_parser_grammar condition_parser; + const parse::string_parser_grammar string_grammar; + parse::detail::tags_grammar tags_parser; + parse::effects_group_grammar effects_group_grammar; + foci_rule foci; + focus_type_rule focus_type; + parse::detail::single_or_bracketed_repeat one_or_more_foci; + environment_map_element_rule environment_map_element; + environment_map_rule environment_map; + species_params_rule species_params; + species_strings_rule species_strings; + species_rule species; + start_rule start; + parse::detail::planet_type_parser_rules planet_type_rules; + parse::detail::planet_environment_parser_rules planet_environment_rules; + }; + + using manifest_start_rule_signature = void (std::vector&); + + struct manifest_grammar : public parse::detail::grammar { + manifest_grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + manifest_grammar::base_type(start) + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::push_back; + + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_r1_type _r1; + qi::omit_type omit_; + + species_manifest + = omit_[tok.SpeciesCensusOrdering_] + > *(label(tok.Tag_) > tok.string [ push_back(_r1, _1) ]) + ; + + start + = +species_manifest(_r1) + ; + + species_manifest.name("ParsedSpeciesCensusOrdering"); + +#if DEBUG_PARSERS + debug(species_manifest); +#endif + + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - FocusType (), - boost::spirit::qi::locals< - std::string, - std::string, - Condition::ConditionBase* - > - > focus_type_rule; - - typedef parse::detail::rule< - void (std::vector&) - > foci_rule; - - typedef parse::detail::rule< - void (std::vector>&) - > effects_rule; - - typedef parse::detail::rule< - std::pair (), - boost::spirit::qi::locals - > environment_map_element_rule; - - typedef parse::detail::rule< - std::map () - > environment_map_rule; - - typedef parse::detail::rule< - void (std::map&) - > environments_rule; - - typedef parse::detail::rule< - SpeciesParams (), - boost::spirit::qi::locals< - bool, - bool, - bool, - bool - > - > species_params_rule; - - typedef parse::detail::rule< - SpeciesStrings (const std::map&), - boost::spirit::qi::locals< - std::string, - std::string, - std::string - > - > species_strings_rule; - - typedef parse::detail::rule< - void (std::map&), - boost::spirit::qi::locals< - SpeciesStrings, - SpeciesParams, - std::set, // tags - std::vector, - std::vector>, - std::map, - std::string // graphic - > - > species_rule; - - typedef parse::detail::rule< - void (std::map&) - > start_rule; - - foci_rule foci; - focus_type_rule focus_type; - effects_rule effects; - environment_map_element_rule environment_map_element; - environment_map_rule environment_map; - environments_rule environments; - species_params_rule species_params; - species_strings_rule species_strings; - species_rule species; - start_rule start; + using manifest_rule = parse::detail::rule&)>; + using start_rule = parse::detail::rule; + + parse::detail::Labeller label; + manifest_rule species_manifest; + start_rule start; }; } namespace parse { - bool species(std::map& species_) { - bool result = true; + start_rule_payload species(const boost::filesystem::path& path) { + const lexer lexer; + start_rule_payload::first_type species_; + start_rule_payload::second_type ordering; + + boost::filesystem::path manifest_file; + + for (const boost::filesystem::path& file : ListScripts(path)) { + if (file.filename() == "SpeciesCensusOrdering.focs.txt" ) { + manifest_file = file; + continue; + } + + /*auto success =*/ detail::parse_file(lexer, file, species_); + } + + if (!manifest_file.empty()) { + try { + /*auto success =*/ detail::parse_file( + lexer, manifest_file, ordering); - for (const boost::filesystem::path& file : ListScripts("scripting/species")) { - result &= detail::parse_file>(file, species_); + } catch (const std::runtime_error& e) { + ErrorLogger() << "Failed to species census manifest in " << manifest_file << " from " << path + << " because " << e.what(); + } } - return result; + return {std::move(species_), ordering}; } } diff --git a/parse/StarTypeValueRefParser.cpp b/parse/StarTypeValueRefParser.cpp index 414db162e9f..1e67b990dad 100644 --- a/parse/StarTypeValueRefParser.cpp +++ b/parse/StarTypeValueRefParser.cpp @@ -1,47 +1,38 @@ -#include "ValueRefParserImpl.h" +#include "ValueRefParser.h" #include "EnumParser.h" +#include "EnumValueRefRules.h" #include "../universe/Enums.h" - -namespace { - struct star_type_parser_rules : - public parse::detail::enum_value_ref_rules - { - star_type_parser_rules() : - enum_value_ref_rules("StarType") - { - boost::spirit::qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - variable_name - %= tok.StarType_ - | tok.NextOlderStarType_ - | tok.NextYoungerStarType_ - ; - - enum_expr - = tok.Blue_ [ _val = STAR_BLUE ] - | tok.White_ [ _val = STAR_WHITE ] - | tok.Yellow_ [ _val = STAR_YELLOW ] - | tok.Orange_ [ _val = STAR_ORANGE ] - | tok.Red_ [ _val = STAR_RED ] - | tok.Neutron_ [ _val = STAR_NEUTRON ] - | tok.BlackHole_ [ _val = STAR_BLACK ] - | tok.NoStar_ [ _val = STAR_NONE ] - ; - } - }; -} - namespace parse { namespace detail { - -enum_value_ref_rules& star_type_rules() -{ - static star_type_parser_rules retval; - return retval; -} - + star_type_parser_rules::star_type_parser_rules( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser + ) : + enum_value_ref_rules("StarType", tok, label, condition_parser) + { + boost::spirit::qi::_val_type _val; + + variable_name + %= tok.StarType_ + | tok.NextOlderStarType_ + | tok.NextYoungerStarType_ + ; + + enum_expr + = tok.Blue_ [ _val = STAR_BLUE ] + | tok.White_ [ _val = STAR_WHITE ] + | tok.Yellow_ [ _val = STAR_YELLOW ] + | tok.Orange_ [ _val = STAR_ORANGE ] + | tok.Red_ [ _val = STAR_RED ] + | tok.Neutron_ [ _val = STAR_NEUTRON ] + | tok.BlackHole_ [ _val = STAR_BLACK ] + | tok.NoStar_ [ _val = STAR_NONE ] + ; + + // complex_expr left empty, as no direct complex variable + // expressions are available available that return a StarType + } } } diff --git a/parse/StringComplexValueRefParser.cpp b/parse/StringComplexValueRefParser.cpp index de90ae4c6b3..9446ce3f6b4 100644 --- a/parse/StringComplexValueRefParser.cpp +++ b/parse/StringComplexValueRefParser.cpp @@ -1,289 +1,100 @@ -#include "ValueRefParserImpl.h" - - -namespace parse { - struct string_complex_parser_rules { - string_complex_parser_rules() { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::construct; - using phoenix::new_; - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - const parse::value_ref_rule& simple_int = int_simple(); - - - game_rule - = tok.GameRule_ [ _a = construct(_1) ] - > detail::label(Name_token) > parse::string_value_ref() [ _d = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - lowest_cost_enqueued_tech - = tok.LowestCostEnqueuedTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - highest_cost_enqueued_tech - = tok.HighestCostEnqueuedTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - top_priority_enqueued_tech - = tok.TopPriorityEnqueuedTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - most_spent_enqueued_tech - = tok.MostSpentEnqueuedTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - random_enqueued_tech - = tok.RandomEnqueuedTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - lowest_cost_researchable_tech - = tok.LowestCostResearchableTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - highest_cost_researchable_tech - = tok.HighestCostResearchableTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - top_priority_researchable_tech - = tok.TopPriorityResearchableTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - most_spent_researchable_tech - = tok.MostSpentResearchableTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - random_researchable_tech - = tok.RandomResearchableTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - random_complete_tech - = tok.RandomCompleteTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - lowest_cost_transferrable_tech - = tok.LowestCostTransferrableTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - > detail::label(Empire_token) > simple_int [ _c = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - highest_cost_transferrable_tech - = tok.HighestCostTransferrableTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - > detail::label(Empire_token) > simple_int [ _c = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - top_priority_transferrable_tech - = tok.TopPriorityTransferrableTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - > detail::label(Empire_token) > simple_int [ _c = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - most_spent_transferrable_tech - = tok.MostSpentTransferrableTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - > detail::label(Empire_token) > simple_int [ _c = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - random_transferrable_tech - = tok.RandomTransferrableTech_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - > detail::label(Empire_token) > simple_int [ _c = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - most_populous_species - = tok.MostPopulousSpecies_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - most_happy_species - = tok.MostHappySpecies_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - least_happy_species - = tok.LeastHappySpecies_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - ramdom_colonizable_species - = tok.RandomColonizableSpecies_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - random_controlled_species - = tok.RandomControlledSpecies_ [ _a = construct(_1) ] - > detail::label(Empire_token) > simple_int [ _b = _1 ] - [ _val = new_>(_a, _b, _c, _f, _d, _e) ] - ; - - start - = game_rule - - | lowest_cost_enqueued_tech - | highest_cost_enqueued_tech - | top_priority_enqueued_tech - | most_spent_enqueued_tech - | random_enqueued_tech - - | lowest_cost_researchable_tech - | highest_cost_researchable_tech - | top_priority_researchable_tech - | most_spent_researchable_tech - | random_researchable_tech - - | random_complete_tech - - | lowest_cost_transferrable_tech - | highest_cost_transferrable_tech - | top_priority_transferrable_tech - | most_spent_transferrable_tech - | random_transferrable_tech - - | most_populous_species - | most_happy_species - | least_happy_species - | ramdom_colonizable_species - | random_controlled_species - ; - - game_rule.name("GameRule"); - - lowest_cost_enqueued_tech.name("LowestCostEnqueuedTech"); - highest_cost_enqueued_tech.name("HighestCostEnqueuedTech"); - top_priority_enqueued_tech.name("TopPriorityEnqueuedTech"); - most_spent_enqueued_tech.name("MostSpentEnqueuedTech"); - random_enqueued_tech.name("RandomEnqueuedTech"); - - lowest_cost_researchable_tech.name("LowestCostResearchableTech"); - highest_cost_researchable_tech.name("HighestCostesearchableTech"); - top_priority_researchable_tech.name("TopPriorityResearchableTech"); - most_spent_researchable_tech.name("MostSpentResearchableTech"); - random_researchable_tech.name("RandomResearchableTech"); - - random_complete_tech.name("RandomCompleteTech"); - - lowest_cost_transferrable_tech.name("LowestCostTransferrableTech"); - highest_cost_transferrable_tech.name("HighestCostTransferrableTech"); - top_priority_transferrable_tech.name("TopPriorityTransferrableTech"); - most_spent_transferrable_tech.name("MostSpentTransferrableTech"); - random_transferrable_tech.name("RandomTransferrableTech"); - - most_populous_species.name("MostPopulousSpecies"); - most_happy_species.name("MostHappySpecies"); - least_happy_species.name("LeastHappySpecies"); - ramdom_colonizable_species.name("RandomColonizableSpecies"); - random_controlled_species.name("RandomControlledSpecies"); +#include "ValueRefParser.h" + +#include "MovableEnvelope.h" +#include "../universe/ValueRefs.h" + +#include + +namespace parse { namespace detail { + string_complex_parser_grammar::string_complex_parser_grammar( + const parse::lexer& tok, + Labeller& label, + const value_ref_grammar& string_grammar + ) : + string_complex_parser_grammar::base_type(start, "string_complex_parser_grammar"), + simple_int_rules(tok) + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::construct; + using phoenix::new_; + + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_val_type _val; + qi::_pass_type _pass; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + const value_ref_rule& simple_int = simple_int_rules.simple; + + game_rule + = ( tok.GameRule_ + > label(tok.Name_) > string_grammar + ) [ _val = construct_movable_(new_>(_1, nullptr, nullptr, nullptr, deconstruct_movable_(_2, _pass), nullptr)) ] + ; + + empire_ref = + ( + ( tok.LowestCostEnqueuedTech_ + | tok.HighestCostEnqueuedTech_ + | tok.TopPriorityEnqueuedTech_ + | tok.MostSpentEnqueuedTech_ + | tok.RandomEnqueuedTech_ + | tok.LowestCostResearchableTech_ + | tok.HighestCostResearchableTech_ + | tok.TopPriorityResearchableTech_ + | tok.MostSpentResearchableTech_ + | tok.RandomResearchableTech_ + | tok.RandomCompleteTech_ + | tok.MostPopulousSpecies_ + | tok.MostHappySpecies_ + | tok.LeastHappySpecies_ + | tok.RandomColonizableSpecies_ + | tok.RandomControlledSpecies_ + ) + > label(tok.Empire_) > simple_int + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_2, _pass), nullptr, nullptr, nullptr, nullptr)) ] + ; + + empire_empire_ref = + ( + ( tok.LowestCostTransferrableTech_ + | tok.HighestCostTransferrableTech_ + | tok.TopPriorityTransferrableTech_ + | tok.MostSpentTransferrableTech_ + | tok.RandomTransferrableTech_ + ) + > label(tok.Empire_) > simple_int + > label(tok.Empire_) > simple_int + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_2, _pass), deconstruct_movable_(_3, _pass), nullptr, nullptr, nullptr)) ] + ; + + start + %= game_rule + | empire_ref + | empire_empire_ref + ; + + game_rule.name("GameRule"); + + empire_ref.name("LowestCostEnqueuedTech, HighestCostEnqueuedTech, TopPriorityEnqueuedTech, " + "MostSpentEnqueuedTech, RandomEnqueuedTech, LowestCostResearchableTech, " + "HighestCostesearchableTech, TopPriorityResearchableTech, MostSpentResearchableTech, " + "RandomResearchableTech, MostPopulousSpecies, MostHappySpecies, " + "LeastHappySpecies, RandomColonizableSpecies, RandomControlledSpecies"); + empire_empire_ref.name("LowestCostTransferrableTech, HighestCostTransferrableTech, " + "TopPriorityTransferrableTech, MostSpentTransferrableTech, " + "RandomTransferrableTech"); #if DEBUG_DOUBLE_COMPLEX_PARSERS - debug(game_rule); - - debug(lowest_cost_enqueued_tech); - debug(highest_cost_enqueued_tech); - debug(top_priority_enqueued_tech); - debug(most_spent_enqueued_tech); - debug(random_enqueued_tech); - - debug(lowest_cost_researchable_tech); - debug(highest_cost_researchable_tech); - debug(top_priority_researchable_tech); - debug(most_spent_researchable_tech); - debug(random_researchable_tech); - - debug(random_complete_tech); + debug(game_rule); - debug(lowest_cost_transferrable_tech); - debug(highest_cost_transferrable_tech); - debug(top_priority_transferrable_tech); - debug(most_spent_transferrable_tech); - debug(random_transferrable_tech); - - debug(most_populous_species); - debug(most_happy_species); - debug(least_happy_species); - debug(ramdom_colonizable_species); - debug(random_controlled_species); + debug(empire_ref); + debug(empire_empire_ref); #endif - } - - complex_variable_rule game_rule; - - complex_variable_rule lowest_cost_enqueued_tech; - complex_variable_rule highest_cost_enqueued_tech; - complex_variable_rule top_priority_enqueued_tech; - complex_variable_rule most_spent_enqueued_tech; - complex_variable_rule random_enqueued_tech; - - complex_variable_rule lowest_cost_researchable_tech; - complex_variable_rule highest_cost_researchable_tech; - complex_variable_rule top_priority_researchable_tech; - complex_variable_rule most_spent_researchable_tech; - complex_variable_rule random_researchable_tech; - - complex_variable_rule random_complete_tech; - - complex_variable_rule lowest_cost_transferrable_tech; - complex_variable_rule highest_cost_transferrable_tech; - complex_variable_rule top_priority_transferrable_tech; - complex_variable_rule most_spent_transferrable_tech; - complex_variable_rule random_transferrable_tech; - - complex_variable_rule most_populous_species; - complex_variable_rule most_happy_species; - complex_variable_rule least_happy_species; - complex_variable_rule ramdom_colonizable_species; - complex_variable_rule random_controlled_species; - - complex_variable_rule start; - }; - - namespace detail { - string_complex_parser_rules string_complex_parser; } -} -const complex_variable_rule& string_var_complex() -{ return parse::detail::string_complex_parser.start; } + }} diff --git a/parse/StringValueRefParser.cpp b/parse/StringValueRefParser.cpp index e8db11bba2e..db17a828ae5 100644 --- a/parse/StringValueRefParser.cpp +++ b/parse/StringValueRefParser.cpp @@ -1,152 +1,183 @@ -#include "ValueRefParserImpl.h" - - -namespace { - struct string_parser_rules { - string_parser_rules() { - namespace phoenix = boost::phoenix; - namespace qi = boost::spirit::qi; - - using phoenix::new_; - using phoenix::push_back; - - qi::_1_type _1; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_val_type _val; - qi::lit_type lit; - - static const std::string TOK_CURRENT_CONTENT{"CurrentContent"}; - - const parse::lexer& tok = parse::lexer::instance(); - - bound_variable_name - %= tok.Name_ - | tok.Species_ - | tok.BuildingType_ - | tok.Focus_ - | tok.PreferredFocus_ - ; - - constant - = tok.string [ _val = new_>(_1) ] - | ( tok.CurrentContent_ - | tok.ThisBuilding_ - | tok.ThisField_ - | tok.ThisHull_ - | tok.ThisPart_ // various aliases for this reference in scripts, allowing scripter to use their preference - | tok.ThisTech_ - | tok.ThisSpecies_ - | tok.ThisSpecial_ - ) [ _val = new_>(TOK_CURRENT_CONTENT) ] - ; - - free_variable - = tok.Value_ [ _val = new_>(ValueRef::EFFECT_TARGET_VALUE_REFERENCE) ] - | tok.GalaxySeed_ [ _val = new_>(ValueRef::NON_OBJECT_REFERENCE, _1) ] - ; - - initialize_bound_variable_parser(bound_variable, bound_variable_name); - - statistic_sub_value_ref - = constant - | bound_variable - | free_variable - | string_var_complex() - ; - - function_expr - = ( - ( - ( - tok.OneOf_ [ _c = ValueRef::RANDOM_PICK ] - | tok.Min_ [ _c = ValueRef::MINIMUM ] - | tok.Max_ [ _c = ValueRef::MAXIMUM ] - ) - > '(' > expr [ push_back(_d, _1) ] - >*(',' > expr [ push_back(_d, _1) ] ) - [ _val = new_>(_c, _d) ] > ')' - ) - | ( - tok.UserString_ > '(' > expr[ _val = new_>(_1) ] > ')' - ) - | ( - primary_expr [ _val = _1 ] - ) - ) - ; +#include "ValueRefParser.h" + +#include "Parse.h" +#include "ConditionParserImpl.h" +#include "EnumValueRefRules.h" +#include "MovableEnvelope.h" +#include "../universe/ValueRefs.h" - operated_expr - = +#include + +namespace parse { + string_parser_grammar::string_parser_grammar( + const parse::lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser + ) : + string_parser_grammar::base_type(expr, "string_parser_grammar"), + string_var_complex(tok, label, *this) + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::new_; + using phoenix::push_back; + + qi::_1_type _1; + qi::_a_type _a; + qi::_b_type _b; + qi::_c_type _c; + qi::_d_type _d; + qi::_val_type _val; + qi::lit_type lit; + qi::_pass_type _pass; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + const boost::phoenix::function deconstruct_movable_vector_; + + const std::string TOK_CURRENT_CONTENT{"CurrentContent"}; + + bound_variable_name + %= tok.Name_ + | tok.Species_ + | tok.BuildingType_ + | tok.FieldType_ + | tok.Focus_ + | tok.PreferredFocus_ + | tok.Hull_ + ; + + constant + = tok.string [ _val = construct_movable_(new_>(_1)) ] + | ( tok.CurrentContent_ + | tok.ThisBuilding_ + | tok.ThisField_ + | tok.ThisHull_ + | tok.ThisPart_ // various aliases for this reference in scripts, allowing scripter to use their preference + | tok.ThisTech_ + | tok.ThisSpecies_ + | tok.ThisSpecial_ + ) [ _val = construct_movable_(new_>(TOK_CURRENT_CONTENT)) ] + ; + + free_variable + = tok.Value_ [ _val = construct_movable_(new_>(ValueRef::EFFECT_TARGET_VALUE_REFERENCE)) ] + | tok.GalaxySeed_ [ _val = construct_movable_(new_>(ValueRef::NON_OBJECT_REFERENCE, _1)) ] + ; + + variable_scope_rule = detail::variable_scope(tok); + container_type_rule = detail::container_type(tok); + detail::initialize_bound_variable_parser( + bound_variable, unwrapped_bound_variable, value_wrapped_bound_variable, + bound_variable_name, variable_scope_rule, container_type_rule, + tok); + + statistic_sub_value_ref + = constant + | bound_variable + | free_variable + | string_var_complex + ; + + function_expr + = ( + ( ( - ( - function_expr [ _a = _1 ] - >> lit('+') [ _c = ValueRef::PLUS ] - >> function_expr [ _b = new_>(_c, _a, _1) ] - [ _val = _b ] - ) - | ( - function_expr [ push_back(_d, _1) ] // template string - >>+('%' > function_expr [ push_back(_d, _1) ] ) // must have at least one sub-string - [ _val = new_>(ValueRef::SUBSTITUTION, _d) ] - ) - | ( - function_expr [ _val = _1 ] - ) + tok.OneOf_ [ _c = ValueRef::RANDOM_PICK ] + | tok.Min_ [ _c = ValueRef::MINIMUM ] + | tok.Max_ [ _c = ValueRef::MAXIMUM ] ) - ; + > ( '(' > expr [ push_back(_d, _1) ] + > *(',' > expr [ push_back(_d, _1) ] ) > ')' ) + [ _val = construct_movable_(new_>(_c, deconstruct_movable_vector_(_d, _pass))) ] + ) + | ( + tok.UserString_ > ('(' > expr > ')') + [ _val = construct_movable_(new_>(deconstruct_movable_(_1, _pass))) ] + ) + | ( + primary_expr [ _val = _1 ] + ) + ) + ; + + operated_expr + = + ( + ( + function_expr [ _a = _1 ] + >> lit('+') [ _c = ValueRef::PLUS ] + >> function_expr [ _b = construct_movable_(new_>(_c, deconstruct_movable_(_a, _pass), deconstruct_movable_(_1, _pass))) ] + [ _val = _b ] + ) + | ( + function_expr [ push_back(_d, _1) ] // template string + >>+('%' > function_expr [ push_back(_d, _1) ] ) // must have at least one sub-string + [ _val = construct_movable_(new_>(ValueRef::SUBSTITUTION, deconstruct_movable_vector_(_d, _pass))) ] + ) + | ( + function_expr [ _val = _1 ] + ) + ) + ; + + expr + = operated_expr + ; + + detail::initialize_nonnumeric_statistic_parser( + statistic, tok, label, condition_parser, statistic_sub_value_ref); + + primary_expr + = constant + | free_variable + | bound_variable + | statistic + | string_var_complex + ; + + bound_variable_name.name("string bound_variable name (e.g., Name)"); + constant.name("quoted string or CurrentContent"); + free_variable.name("free string variable"); + bound_variable.name("string bound_variable"); + statistic.name("string statistic"); + expr.name("string expression"); - expr - = operated_expr - ; +#if DEBUG_VALUEREF_PARSERS + debug(bound_variable_name); + debug(constant); + debug(free_variable); + debug(bound_variable); + debug(statistic); + debug(expr); +#endif + } - initialize_nonnumeric_statistic_parser(statistic, statistic_sub_value_ref); + detail::name_token_rule bound_variable_name; + detail::value_ref_rule constant; + detail::value_ref_rule free_variable; + detail::variable_rule bound_variable; + detail::value_ref_rule statistic_sub_value_ref; + detail::statistic_rule statistic; + detail::expression_rule function_expr; + detail::expression_rule operated_expr; + detail::value_ref_rule expr; + detail::value_ref_rule primary_expr; + detail::reference_token_rule variable_scope_rule; + detail::name_token_rule container_type_rule; - primary_expr - = constant - | free_variable - | bound_variable - | statistic - | string_var_complex() - ; - bound_variable_name.name("string bound_variable name (e.g., Name)"); - constant.name("quoted string or CurrentContent"); - free_variable.name("free string variable"); - bound_variable.name("string bound_variable"); - statistic.name("string statistic"); - expr.name("string expression"); + bool string_free_variable(std::string& text) { + const lexer tok; + boost::spirit::qi::in_state_type in_state; -#if DEBUG_VALUEREF_PARSERS - debug(bound_variable_name); - debug(constant); - debug(free_variable); - debug(bound_variable); - debug(statistic); - debug(expr); -#endif - } - - name_token_rule bound_variable_name; - parse::value_ref_rule constant; - parse::value_ref_rule free_variable; - variable_rule bound_variable; - parse::value_ref_rule statistic_sub_value_ref; - statistic_rule statistic; - expression_rule function_expr; - expression_rule operated_expr; - parse::value_ref_rule expr; - parse::value_ref_rule primary_expr; - }; -} + text_iterator first = text.begin(); + text_iterator last = text.end(); + token_iterator it = tok.begin(first, last); + bool success = boost::spirit::qi::phrase_parse( + it, tok.end(), tok.GalaxySeed_, in_state("WS")[tok.self]); -namespace parse { - value_ref_rule& string_value_ref() - { - static string_parser_rules retval; - return retval.expr; + return success; } } diff --git a/parse/TechsParser.cpp b/parse/TechsParser.cpp index 245139c37e1..74ddb0f79e6 100644 --- a/parse/TechsParser.cpp +++ b/parse/TechsParser.cpp @@ -2,23 +2,30 @@ #include "ParseImpl.h" #include "EnumParser.h" +#include "EffectParser.h" #include "ValueRefParser.h" +#include "MovableEnvelope.h" +#include "../universe/Effect.h" #include "../universe/Species.h" +#include "../universe/Tech.h" +#include "../universe/UnlockableItem.h" +#include "../universe/ValueRef.h" #include "../util/Directories.h" #include +#include #define DEBUG_PARSERS 0 #if DEBUG_PARSERS namespace std { - inline ostream& operator<<(ostream& os, const std::vector&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector&) { return os; } inline ostream& operator<<(ostream& os, const std::set&) { return os; } - inline ostream& operator<<(ostream& os, const std::vector>&) { return os; } + inline ostream& operator<<(ostream& os, const parse::effects_group_payload&) { return os; } inline ostream& operator<<(ostream& os, const Tech::TechInfo&) { return os; } - inline ostream& operator<<(ostream& os, const std::pair&) { return os; } + inline ostream& operator<<(ostream& os, const std::pair>&) { return os; } } #endif @@ -26,128 +33,144 @@ namespace { const boost::phoenix::function is_unique_; std::set* g_categories_seen = nullptr; - std::map* g_categories = nullptr; + std::map>* g_categories = nullptr; /// Check if the tech will be unique. - struct check_tech { - typedef bool result_type; - - result_type operator()(TechManager::TechContainer& techs, Tech* tech) const { - auto retval = true; - if (techs.get().find(tech->Name()) != techs.get().end()) { - ErrorLogger() << "More than one tech has the name " << tech->Name(); - retval = false; - } - if (tech->Prerequisites().find(tech->Name()) != tech->Prerequisites().end()) { - ErrorLogger() << "Tech " << tech->Name() << " depends on itself!"; - retval = false; - } - return retval; + bool check_tech(TechManager::TechContainer& techs, const std::unique_ptr& tech) { + auto retval = true; + if (techs.get().count(tech->Name())) { + ErrorLogger() << "More than one tech has the name " << tech->Name(); + retval = false; } - }; - - struct insert_tech { - typedef void result_type; - - result_type operator()(TechManager::TechContainer& techs, Tech* tech) const { - g_categories_seen->insert(tech->Category()); - techs.insert(tech); + if (tech->Prerequisites().count(tech->Name())) { + ErrorLogger() << "Tech " << tech->Name() << " depends on itself!"; + retval = false; } - }; - - const boost::phoenix::function check_tech_; - const boost::phoenix::function insert_tech_; - - struct insert_category { - typedef void result_type; + return retval; + } - void operator()(std::map& categories, TechCategory* category) const { - categories.insert(std::make_pair(category->name, category)); + void insert_tech(TechManager::TechContainer& techs, + const parse::detail::MovableEnvelope& tech_info, + const boost::optional& effects, + const boost::optional>& prerequisites, + const boost::optional>& unlocked_items, + const boost::optional& graphic, + bool& pass) + { + auto tech_ptr = std::make_unique( + *tech_info.OpenEnvelope(pass), + (effects ? parse::detail::OpenEnvelopes(*effects, pass) : std::vector>()), + (prerequisites ? *prerequisites : std::set()), + (unlocked_items ? *unlocked_items : std::vector()), + (graphic ? *graphic : std::string())); + + if (check_tech(techs, tech_ptr)) { + g_categories_seen->insert(tech_ptr->Category()); + techs.insert(std::move(tech_ptr)); } - }; - const boost::phoenix::function insert_category_; + } + BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_tech_, insert_tech, 7) - struct rules { - rules() { + void insert_category(std::map>& categories, + const std::string& name, const std::string& graphic, const GG::Clr& color) + { + auto category_ptr = std::make_unique(name, graphic, color); + categories.insert(std::make_pair(category_ptr->name, std::move(category_ptr))); + } + + BOOST_PHOENIX_ADAPT_FUNCTION(void, insert_category_, insert_category, 4) + + + using start_rule_signature = void(TechManager::TechContainer&); + + struct grammar : public parse::detail::grammar { + grammar(const parse::lexer& tok, + const std::string& filename, + const parse::text_iterator& first, const parse::text_iterator& last) : + grammar::base_type(start), + one_or_more_string_tokens(tok), + condition_parser(tok, label), + string_grammar(tok, label, condition_parser), + castable_int_rules(tok, label, condition_parser, string_grammar), + double_rules(tok, label, condition_parser, string_grammar), + effects_group_grammar(tok, label, condition_parser, string_grammar), + tags_parser(tok, label), + unlockable_item_parser(tok, label), + one_or_more_unlockable_items(unlockable_item_parser), + color_parser(tok) + { namespace phoenix = boost::phoenix; namespace qi = boost::spirit::qi; + using phoenix::new_; using phoenix::construct; using phoenix::insert; - using phoenix::new_; using phoenix::push_back; qi::_1_type _1; qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; - qi::_a_type _a; - qi::_b_type _b; - qi::_c_type _c; - qi::_d_type _d; - qi::_e_type _e; - qi::_f_type _f; - qi::_g_type _g; - qi::_h_type _h; + qi::_5_type _5; + qi::_6_type _6; + qi::_7_type _7; + qi::_8_type _8; qi::_pass_type _pass; qi::_r1_type _r1; - qi::_r2_type _r2; - qi::_r3_type _r3; qi::_val_type _val; qi::eps_type eps; - - const parse::lexer& tok = parse::lexer::instance(); - - tech_info_name_desc - = parse::detail::label(Name_token) > tok.string [ _r1 = _1 ] - > parse::detail::label(Description_token) > tok.string [ _r2 = _1 ] - > parse::detail::label(Short_Description_token) > tok.string [ _r3 = _1 ] // TODO: Get rid of underscore. + qi::omit_type omit_; + qi::as_string_type as_string_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + researchable = + tok.Unresearchable_ [ _val = false ] + | tok.Researchable_ [ _val = true ] + | eps [ _val = true ] ; tech_info - = tech_info_name_desc(_a, _b, _c) - > parse::detail::label(Category_token) > tok.string [ _e = _1 ] - > parse::detail::label(ResearchCost_token) > parse::double_value_ref() [ _f = _1 ] - > parse::detail::label(ResearchTurns_token) > parse::flexible_int_value_ref() [ _g = _1 ] - > ( tok.Unresearchable_ [ _h = false ] - | tok.Researchable_ [ _h = true ] - | eps [ _h = true ] - ) - > parse::detail::tags_parser()(_d) - [ _val = construct(_a, _b, _c, _e, _f, _g, _h, _d) ] + = ( label(tok.Name_) > tok.string + > label(tok.Description_) > tok.string + > label(tok.Short_Description_) > tok.string // TODO: Get rid of underscore. + > label(tok.Category_) > tok.string + > label(tok.ResearchCost_) > double_rules.expr + > label(tok.ResearchTurns_) > castable_int_rules.flexible_int + > researchable + > tags_parser + ) [ _val = construct_movable_(new_(_1, _2, _3, _4, deconstruct_movable_(_5, _pass), + deconstruct_movable_(_6, _pass), _7, _8)) ] ; prerequisites - = parse::detail::label(Prerequisites_token) - > ( ('[' > +tok.string [ insert(_r1, _1) ] > ']') - | tok.string [ insert(_r1, _1) ] - ) + %= label(tok.Prerequisites_) + > one_or_more_string_tokens ; unlocks - = parse::detail::label(Unlock_token) - > ( ('[' > +parse::detail::item_spec_parser() [ push_back(_r1, _1) ] > ']') - | parse::detail::item_spec_parser() [ push_back(_r1, _1) ] - ) + %= label(tok.Unlock_) + > one_or_more_unlockable_items ; tech - = (tok.Tech_ - > tech_info [ _a = _1 ] - > -prerequisites(_b) - > -unlocks(_c) - > -(parse::detail::label(EffectsGroups_token) > parse::detail::effects_group_parser() [ _d = _1 ]) - > -(parse::detail::label(Graphic_token) > tok.string [ _e = _1 ]) - ) - [ _f = new_(_a, _d, _b, _c, _e), _pass = check_tech_(_r1, _f), insert_tech_(_r1, _f) ] + = ( omit_[tok.Tech_] + > tech_info + > -prerequisites + > -unlocks + > -(label(tok.EffectsGroups_) > effects_group_grammar) + > -as_string_[(label(tok.Graphic_) > tok.string)] + ) [ insert_tech_(_r1, _1, _4, _2, _3, _5, _pass) ] ; category - = tok.Category_ - > parse::detail::label(Name_token) > tok.string [ _pass = is_unique_(_r1, Category_token, _1), _a = _1 ] - > parse::detail::label(Graphic_token) > tok.string [ _b = _1 ] - > parse::detail::label(Colour_token) > parse::detail::color_parser() [ insert_category_(_r1, new_(_a, _b, _1)) ] + = ( tok.Category_ + > label(tok.Name_) > tok.string + > label(tok.Graphic_) > tok.string + > label(tok.Colour_) > color_parser + ) [ _pass = is_unique_(_r1, _1, _2), + insert_category_(_r1, _2, _3, _4) ] ; start @@ -156,7 +179,7 @@ namespace { ) ; - tech_info_name_desc.name("tech name"); + researchable.name("Researchable"); tech_info.name("Tech info"); prerequisites.name("Prerequisites"); unlocks.name("Unlock"); @@ -173,85 +196,61 @@ namespace { debug(category); #endif - qi::on_error(start, parse::report_error(_1, _2, _3, _4)); + qi::on_error(start, parse::report_error(filename, first, last, _1, _2, _3, _4)); } - typedef parse::detail::rule< - Tech::TechInfo (std::string&, std::string&, std::string&) - > tech_info_name_desc_rule; - - typedef parse::detail::rule< - Tech::TechInfo (), - boost::spirit::qi::locals< - std::string, - std::string, - std::string, - std::set, - std::string, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - bool - > - > tech_info_rule; - - typedef parse::detail::rule< - Tech::TechInfo (std::set&) - > prerequisites_rule; - - typedef parse::detail::rule< - Tech::TechInfo (std::vector&) - > unlocks_rule; - - typedef parse::detail::rule< - void (TechManager::TechContainer&), - boost::spirit::qi::locals< - Tech::TechInfo, - std::set, - std::vector, - std::vector>, - std::string, - Tech* - > - > tech_rule; - - typedef parse::detail::rule< - void (std::map&), - boost::spirit::qi::locals< - std::string, - std::string - > - > category_rule; - - typedef parse::detail::rule< - void (TechManager::TechContainer&) - > start_rule; - - tech_info_name_desc_rule tech_info_name_desc; - tech_info_rule tech_info; - prerequisites_rule prerequisites; - unlocks_rule unlocks; - tech_rule tech; - category_rule category; - start_rule start; + using tech_info_rule = parse::detail::rule ()>; + using prerequisites_rule = parse::detail::rule ()>; + using unlocks_rule = parse::detail::rule ()>; + using tech_rule = parse::detail::rule; + using category_rule = parse::detail::rule>&)>; + using start_rule = parse::detail::rule; + + parse::detail::Labeller label; + parse::detail::single_or_repeated_string> + one_or_more_string_tokens; + parse::conditions_parser_grammar condition_parser; + const parse::string_parser_grammar string_grammar; + parse::castable_as_int_parser_rules castable_int_rules; + parse::double_parser_rules double_rules; + parse::effects_group_grammar effects_group_grammar; + parse::detail::tags_grammar tags_parser; + parse::detail::unlockable_item_grammar unlockable_item_parser; + parse::detail::single_or_bracketed_repeat + one_or_more_unlockable_items; + parse::detail::color_parser_grammar color_parser; + parse::detail::rule researchable; + tech_info_rule tech_info; + prerequisites_rule prerequisites; + unlocks_rule unlocks; + tech_rule tech; + category_rule category; + start_rule start; }; } namespace parse { - bool techs(TechManager::TechContainer& techs_, - std::map& categories, - std::set& categories_seen) - { - bool result = true; + template + T techs(const boost::filesystem::path& path) { + const lexer lexer; + TechManager::TechContainer techs_; + std::map> categories; + std::set categories_seen; g_categories_seen = &categories_seen; g_categories = &categories; - result &= detail::parse_file(GetResourceDir() / "scripting/techs/Categories.inf", techs_); + /*auto success =*/ detail::parse_file(lexer, path / "Categories.inf", techs_); - for (const boost::filesystem::path& file : ListScripts("scripting/techs")) { - result &= detail::parse_file(file, techs_); + for (const boost::filesystem::path& file : ListScripts(path)) { + /*auto success =*/ detail::parse_file(lexer, file, techs_); } - return result; + return std::make_tuple(std::move(techs_), std::move(categories), categories_seen); } } + +// explicitly instantiate techs. +// This allows Tech.h to only be included in this .cpp file and not Parse.h +// which recompiles all parsers if Tech.h changes. +template FO_PARSE_API TechManager::TechParseTuple parse::techs(const boost::filesystem::path& path); diff --git a/parse/Tokens.cpp b/parse/Tokens.cpp deleted file mode 100644 index 0f373168847..00000000000 --- a/parse/Tokens.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "Tokens.h" - -#include - - -#define DEFINE_TOKEN(r, _, name) const char* const BOOST_PP_CAT(name, _token) = BOOST_PP_STRINGIZE(name); -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_1) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_2) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_3) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_4) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_5) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_6) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_7) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_8) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_9) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_10) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_11) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_12) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_13) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_14) -BOOST_PP_SEQ_FOR_EACH(DEFINE_TOKEN, _, TOKEN_SEQ_15) -#undef DEFINE_TOKEN diff --git a/parse/Tokens.h b/parse/Tokens.h index 26dc914d279..5d1c9352abe 100644 --- a/parse/Tokens.h +++ b/parse/Tokens.h @@ -52,10 +52,13 @@ (Capture) \ (CaptureResult) \ (Category) \ + (Ceil) \ (Class) \ (ClockwiseNextPlanetType) \ (Colony) \ (Colour) \ + (CombatBout) \ + (CombatTargets) \ (Condition) \ (Construction) \ (Consumption) \ @@ -102,9 +105,15 @@ (EffectsGroups) \ (Else) \ (Empire) \ + (EmpireHasBuildingAvailable) \ + (EmpireHasShipDesignAvailable) \ + (EmpireHasShipPartAvailable) \ + (EmpireHasTechResearched) \ (EmpireMeter) \ (EmpireMeterValue) \ + (EmpireObjectVisibility) \ (EmpireShipsDestroyed) \ + (EmpireStockpile) \ (Enabled) \ (Endpoint) \ (EnemyOf) \ @@ -120,6 +129,7 @@ #define TOKEN_SEQ_4 \ (Field) \ (FieldType) \ + (Fighter) \ (FighterBay) \ (FighterHangar) \ (FighterWeapon) \ @@ -127,6 +137,7 @@ (Fleet) \ (FleetID) \ (FleetSupplyableByEmpire) \ + (Floor) \ (Foci) \ (Focus) \ (FocusType) \ @@ -152,6 +163,7 @@ (GiveEmpireTech) \ (Good) \ (Graphic) \ + (HabitableSize) \ (Happiness) \ (HasSpecial) \ (HasSpecialCapacity) \ @@ -169,7 +181,6 @@ (HullSpeed) \ (HullStealth) \ (HullStructure) \ - (HullType) \ (Human) \ (Icon) \ (ID) \ @@ -190,8 +201,13 @@ #define TOKEN_SEQ_6 \ (Label) \ (Large) \ - (LastTurnBattleHere) \ (LastTurnActiveInBattle) \ + (LastTurnAttackedByShip) \ + (LastTurnBattleHere) \ + (LastTurnColonized) \ + (LastTurnConquered) \ + (LastTurnResupplied) \ + (LaunchedFrom) \ (LeastHappySpecies) \ (LocalCandidate) \ (Location) \ @@ -206,7 +222,9 @@ (MaxDefense) \ (MaxFuel) \ (MaximumNumberOf) \ + (MaxSecondaryStat) \ (MaxShield) \ + (MaxStockpile) \ (MaxStructure) \ (MaxSupply) \ (MaxTroops) \ @@ -246,6 +264,10 @@ (NextTurnPopGrowth) \ (NextYoungerStarType) \ (NoDefaultCapacityEffect) \ + (NoDefaultFuelEffect) \ + (NoDefaultSpeedEffect) \ + (NoDefaultStealthEffect) \ + (NoDefaultStructureEffect) \ (None) \ (NoOp) \ (NoStar) \ @@ -274,7 +296,6 @@ (Owner) \ (OwnerHasShipPartAvailable) \ (OwnerHasTech) \ - (OwnerTradeStockpile) \ (Parameters) \ (Part) \ (PartCapacity) \ @@ -288,8 +309,8 @@ (Parts) \ (PartOfClassInShipDesign) \ (PartsInShipDesign) \ - (PartType) \ (Passive) \ + (PeaceWith) \ (Planet) \ (Planetbound) \ (PlanetEnvironment) \ @@ -345,7 +366,9 @@ (Retain) \ (RMS) \ (RootCandidate) \ - (Scope) + (Round) \ + (Scope) \ + (SecondaryStat) #define TOKEN_SEQ_11 \ (SetAggressive) \ @@ -358,7 +381,7 @@ (SetEmpireCapital) \ (SetEmpireMeter) \ (SetEmpireTechProgress) \ - (SetEmpireTradeStockpile) \ + (SetEmpireStockpile) \ (SetFuel) \ (SetHappiness) \ (SetIndustry) \ @@ -368,6 +391,7 @@ (SetMaxFuel) \ (SetMaxSecondaryStat) \ (SetMaxShield) \ + (SetMaxStockpile) \ (SetMaxStructure) \ (SetMaxSupply) \ (SetMaxTroops) \ @@ -391,6 +415,7 @@ (SetSpeed) \ (SetStarType) \ (SetStealth) \ + (SetStockpile) \ (SetStructure) \ (SetSupply) \ (SetTargetConstruction) \ @@ -402,11 +427,15 @@ (SetTexture) \ (SetTrade) \ (SetTroops) \ - (SetVisibility) \ + (SetVisibility) + +#define TOKEN_SEQ_13 \ (Shield) \ (ShipDesign) \ + (ShipDesignCost) \ (ShipDesignOrdering) \ (ShipDesignsDestroyed) \ + (ShipDesignsInProduction) \ (ShipDesignsLost) \ (ShipDesignsOwned) \ (ShipDesignsProduced) \ @@ -425,7 +454,7 @@ (Size) \ (SizeAsDouble) -#define TOKEN_SEQ_13 \ +#define TOKEN_SEQ_14 \ (Slot) \ (Slots) \ (SlotsInHull) \ @@ -439,11 +468,16 @@ (SpawnLimit) \ (SpawnRate) \ (Special) \ + (SpecialAddedOnTurn) \ + (SpecialCapacity) \ (Species) \ (SpeciesID) \ + (SpeciesCensusOrdering) \ (SpeciesOpinion) \ (SpeciesPlanetsBombed) \ - (SpeciesColoniesOwned) \ + (SpeciesColoniesOwned) + +#define TOKEN_SEQ_15 \ (SpeciesPlanetsDepoped) \ (SpeciesPlanetsInvaded) \ (SpeciesShipsDestroyed) \ @@ -460,6 +494,7 @@ (Statistic) \ (StDev) \ (Stealth) \ + (Stockpile) \ (String) \ (StringList) \ (Structure) \ @@ -470,7 +505,7 @@ (System) \ (SystemID) -#define TOKEN_SEQ_14 \ +#define TOKEN_SEQ_16 \ (Tag) \ (Tags) \ (Target) \ @@ -497,15 +532,16 @@ (TopPriorityEnqueuedTech) \ (TopPriorityResearchableTech) \ (TopPriorityTransferrableTech) \ + (OrderedAlternativesOf) \ (Toxic) \ - (Trade) \ - (TradeStockpile) + (Trade) -#define TOKEN_SEQ_15 \ +#define TOKEN_SEQ_17 \ (Troops) \ (Tundra) \ (Turn) \ (TurnsSinceFocusChange) \ + (TurnTechResearched) \ (Type) \ (Uninhabitable) \ (UniverseCentreX) \ @@ -516,6 +552,7 @@ (Unproducible) \ (Unresearchable) \ (UpgradeVisibility) \ + (UsedInDesignID) \ (UserString) \ (UUID) \ (Value) \ @@ -529,24 +566,4 @@ (Y) \ (Yellow) -#define DECLARE_TOKEN(r, _, elem) extern const char* const BOOST_PP_CAT(elem, _token); -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_1) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_2) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_3) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_4) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_5) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_6) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_7) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_8) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_9) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_10) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_11) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_12) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_13) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_14) -BOOST_PP_SEQ_FOR_EACH(DECLARE_TOKEN, _, TOKEN_SEQ_15) - -#undef DECLARE_TOKEN - - #endif diff --git a/parse/UniverseObjectTypeValueRefParser.cpp b/parse/UniverseObjectTypeValueRefParser.cpp index 393e1314da1..2fe6a68b897 100644 --- a/parse/UniverseObjectTypeValueRefParser.cpp +++ b/parse/UniverseObjectTypeValueRefParser.cpp @@ -1,46 +1,35 @@ -#include "ValueRefParserImpl.h" +#include "ValueRefParser.h" #include "EnumParser.h" +#include "EnumValueRefRules.h" #include "../universe/Enums.h" - -namespace { - struct universe_object_type_parser_rules : - public parse::detail::enum_value_ref_rules +namespace parse { namespace detail { + universe_object_type_parser_rules::universe_object_type_parser_rules( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser +) : + enum_value_ref_rules("ObjectType", tok, label, condition_parser) { - universe_object_type_parser_rules() : - enum_value_ref_rules("ObjectType") - { - boost::spirit::qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - variable_name - %= tok.ObjectType_ - ; - - enum_expr - = tok.Building_ [ _val = OBJ_BUILDING ] - | tok.Ship_ [ _val = OBJ_SHIP ] - | tok.Fleet_ [ _val = OBJ_FLEET ] - | tok.Planet_ [ _val = OBJ_PLANET ] - | tok.PopulationCenter_ [ _val = OBJ_POP_CENTER ] - | tok.ProductionCenter_ [ _val = OBJ_PROD_CENTER ] - | tok.System_ [ _val = OBJ_SYSTEM ] - | tok.Field_ [ _val = OBJ_FIELD ] - ; - } - }; + boost::spirit::qi::_val_type _val; + + variable_name + %= tok.ObjectType_ + ; + + enum_expr + = tok.Building_ [ _val = OBJ_BUILDING ] + | tok.Ship_ [ _val = OBJ_SHIP ] + | tok.Fleet_ [ _val = OBJ_FLEET ] + | tok.Planet_ [ _val = OBJ_PLANET ] + | tok.PopulationCenter_ [ _val = OBJ_POP_CENTER ] + | tok.ProductionCenter_ [ _val = OBJ_PROD_CENTER ] + | tok.System_ [ _val = OBJ_SYSTEM ] + | tok.Field_ [ _val = OBJ_FIELD ] + | tok.Fighter_ [ _val = OBJ_FIGHTER ] + ; + } } - - -namespace parse { namespace detail { - -enum_value_ref_rules& universe_object_type_rules() -{ - static universe_object_type_parser_rules retval; - return retval; } - -} } diff --git a/parse/ValueRefParser.cpp b/parse/ValueRefParser.cpp new file mode 100644 index 00000000000..58286d2ee43 --- /dev/null +++ b/parse/ValueRefParser.cpp @@ -0,0 +1,112 @@ +#include "ValueRefParser.h" + +#include "ConditionParserImpl.h" +#include "MovableEnvelope.h" +#include "../universe/ValueRefs.h" + +#include + + +#define DEBUG_VALUEREF_PARSERS 0 + +// These are just here to satisfy the requirements of boost::spirit::qi::debug(). +#if DEBUG_VALUEREF_PARSERS +namespace std { + inline ostream& operator<<(ostream& os, const std::vector>>&) { return os; } + inline ostream& operator<<(ostream& os, const std::vector>>&) { return os; } +} +#endif + +namespace qi = boost::spirit::qi; +namespace phoenix = boost::phoenix; + +namespace parse { namespace detail { + + const reference_token_rule variable_scope(const parse::lexer& tok) { + qi::_val_type _val; + + reference_token_rule variable_scope; + variable_scope + = tok.Source_ [ _val = ValueRef::SOURCE_REFERENCE ] + | tok.Target_ [ _val = ValueRef::EFFECT_TARGET_REFERENCE ] + | tok.LocalCandidate_ [ _val = ValueRef::CONDITION_LOCAL_CANDIDATE_REFERENCE ] + | tok.RootCandidate_ [ _val = ValueRef::CONDITION_ROOT_CANDIDATE_REFERENCE ] + ; + + variable_scope.name("Source, Target, LocalCandidate, or RootCandidate"); + + return variable_scope; + } + + const name_token_rule container_type(const parse::lexer& tok) { + name_token_rule container_type; + container_type + = tok.Planet_ + | tok.System_ + | tok.Fleet_ + ; + + container_type.name("Planet, System, or Fleet"); + + return container_type; + } + + template + simple_variable_rules::simple_variable_rules( + const std::string& type_name, const parse::lexer& tok) + { + using phoenix::new_; + + qi::_1_type _1; + qi::_val_type _val; + qi::lit_type lit; + const phoenix::function construct_movable_; + + free_variable + = (tok.Value_ >> !lit('(')) + [ _val = construct_movable_(new_>( + ValueRef::EFFECT_TARGET_VALUE_REFERENCE)) ] + | free_variable_name + [ _val = construct_movable_(new_>( + ValueRef::NON_OBJECT_REFERENCE, _1)) ] + ; + + simple + = constant + | bound_variable + | free_variable + ; + + variable_scope_rule = variable_scope(tok); + container_type_rule = container_type(tok); + initialize_bound_variable_parser( + bound_variable, unwrapped_bound_variable, + value_wrapped_bound_variable, bound_variable_name, + variable_scope_rule, container_type_rule, tok); + +#if DEBUG_VALUEREF_PARSERS + debug(bound_variable_name); + debug(free_variable_name); + debug(constant); + debug(free_variable); + debug(bound_variable); + debug(simple); +#endif + + unwrapped_bound_variable.name(type_name + " unwrapped bound variable name"); + value_wrapped_bound_variable.name(type_name + " value-wrapped bound variable name"); + bound_variable_name.name(type_name + " bound variable name"); + free_variable_name.name(type_name + " free variable name"); + constant.name(type_name + " constant"); + free_variable.name(type_name + " free variable"); + bound_variable.name(type_name + " bound variable"); + simple.name(type_name + " simple variable expression"); + } + + // Explicit instantiation to prevent costly recompilation in multiple units + template simple_variable_rules::simple_variable_rules( + const std::string& type_name, const parse::lexer& tok); + template simple_variable_rules::simple_variable_rules( + const std::string& type_name, const parse::lexer& tok); + +} } diff --git a/parse/ValueRefParser.h b/parse/ValueRefParser.h index 6b5cad41d40..a3a391602ff 100644 --- a/parse/ValueRefParser.h +++ b/parse/ValueRefParser.h @@ -1,48 +1,307 @@ #ifndef _ValueRefParser_h_ #define _ValueRefParser_h_ -#include "Lexer.h" -#include "ParseImpl.h" +#include "EnumParser.h" +#include "MovableEnvelope.h" -#include "../universe/ValueRefFwd.h" +#include "../universe/ValueRefs.h" #include "../universe/EnumsFwd.h" #include +#include +namespace Condition { + struct Condition; +} -namespace parse { +namespace parse { namespace detail { + // TODO: Investigate refactoring ValueRef to use variant, + // for increased locality of reference. + template + using value_ref_payload = MovableEnvelope>; + template + using value_ref_signature = value_ref_payload (); + template + using value_ref_rule = detail::rule>; template - using value_ref_rule = detail::rule< - // TODO: Investigate refactoring ValueRef to use variant, - // for increased locality of reference. - ValueRef::ValueRefBase* () - >; + using value_ref_grammar = detail::grammar>; - value_ref_rule& int_value_ref(); + using condition_payload = MovableEnvelope; + using condition_signature = condition_payload (); + using condition_parser_grammar = grammar; - value_ref_rule& flexible_int_value_ref(); + using name_token_rule = rule; + using reference_token_rule = rule; + const parse::detail::reference_token_rule variable_scope(const parse::lexer& tok); + const parse::detail::name_token_rule container_type(const parse::lexer& tok); - value_ref_rule& double_value_ref(); - value_ref_rule& string_value_ref(); + template + using variable_payload = MovableEnvelope>; + template + using variable_signature = variable_payload (); + template + using variable_rule = rule< + variable_signature, + boost::spirit::qi::locals< + std::vector, + ValueRef::ReferenceType + > + >; + + template + struct simple_variable_rules + { + simple_variable_rules(const std::string& type_name, const parse::lexer& tok); + name_token_rule bound_variable_name; + name_token_rule free_variable_name; + detail::value_ref_rule constant; + variable_rule free_variable; + variable_rule bound_variable; + variable_rule unwrapped_bound_variable; + variable_rule value_wrapped_bound_variable; + detail::value_ref_rule simple; + reference_token_rule variable_scope_rule; + name_token_rule container_type_rule; + }; -namespace detail { + template + using statistic_payload = MovableEnvelope>; + template + using statistic_signature = statistic_payload (); + template + using statistic_rule = rule< + statistic_signature, + boost::spirit::qi::locals< + value_ref_payload, + ValueRef::StatisticType + > + >; -template -struct enum_value_ref_rules; + template + using expression_rule = rule< + value_ref_payload (), + boost::spirit::qi::locals< + value_ref_payload, + value_ref_payload, + ValueRef::OpType, + std::vector> + > + >; -enum_value_ref_rules& planet_environment_rules(); + template + struct arithmetic_rules { + arithmetic_rules(const std::string& type_name, + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser); -enum_value_ref_rules& planet_size_rules(); + parse::statistic_enum_grammar statistic_type_enum; + expression_rule functional_expr; + expression_rule exponential_expr; + expression_rule multiplicative_expr; + expression_rule additive_expr; + detail::value_ref_rule primary_expr; + detail::value_ref_rule statistic_value_ref_expr; + statistic_rule statistic_collection_expr; + statistic_rule statistic_value_expr; + statistic_rule statistic_expr; + detail::value_ref_rule expr; + }; -enum_value_ref_rules& planet_type_rules(); + struct simple_int_parser_rules : public simple_variable_rules { + simple_int_parser_rules(const parse::lexer& tok); + }; -enum_value_ref_rules& star_type_rules(); + struct simple_double_parser_rules : public simple_variable_rules { + simple_double_parser_rules(const parse::lexer& tok); + }; -enum_value_ref_rules& universe_object_type_rules(); + template + using complex_variable_payload = MovableEnvelope>; + template + using complex_variable_signature = complex_variable_payload (); + template + using complex_variable_rule = rule>; + template + using complex_variable_grammar = grammar>; -} + struct string_complex_parser_grammar : public complex_variable_grammar { + string_complex_parser_grammar(const parse::lexer& tok, + Labeller& label, + const value_ref_grammar& string_grammar); + + simple_int_parser_rules simple_int_rules; + complex_variable_rule game_rule; + complex_variable_rule empire_ref; + complex_variable_rule empire_empire_ref; + complex_variable_rule start; + }; + + template + void initialize_bound_variable_parser( + variable_rule& bound_variable, + variable_rule& unwrapped_bound_variable, + variable_rule& value_wrapped_bound_variable, + const name_token_rule& variable_name, + const reference_token_rule& variable_scope_rule, + const name_token_rule& container_type_rule, + const parse::lexer& tok) + { + using boost::phoenix::construct; + using boost::phoenix::new_; + using boost::phoenix::push_back; + + boost::spirit::qi::_1_type _1; + boost::spirit::qi::_2_type _2; + boost::spirit::qi::_3_type _3; + boost::spirit::qi::lit_type lit; + boost::spirit::qi::_val_type _val; + boost::spirit::qi::omit_type omit_; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + unwrapped_bound_variable + = ( + variable_scope_rule >> '.' + >> -(container_type_rule > '.') + >> variable_name + ) [ _val = construct_movable_(new_>( + _1, construct>(_2), + construct(_3), false)) ]; + + value_wrapped_bound_variable + = ( + omit_[tok.Value_] >> '(' + >> variable_scope_rule >> '.' + >> -(container_type_rule > '.') + >> variable_name + >> ')' + ) [ _val = construct_movable_(new_>( + _1, construct>(_2), + construct(_3), true)) ]; + + bound_variable + = + value_wrapped_bound_variable [ _val = _1 ] + | unwrapped_bound_variable [ _val = _1 ] + ; + } +}} + +namespace parse { + struct string_parser_grammar : public detail::value_ref_grammar { + string_parser_grammar(const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser); + + detail::string_complex_parser_grammar string_var_complex; + detail::name_token_rule bound_variable_name; + detail::value_ref_rule constant; + detail::value_ref_rule free_variable; + detail::variable_rule bound_variable; + detail::variable_rule unwrapped_bound_variable; + detail::variable_rule value_wrapped_bound_variable; + detail::value_ref_rule statistic_sub_value_ref; + detail::statistic_rule statistic; + detail::expression_rule function_expr; + detail::expression_rule operated_expr; + detail::value_ref_rule expr; + detail::value_ref_rule primary_expr; + detail::reference_token_rule variable_scope_rule; + detail::name_token_rule container_type_rule; + }; + + struct int_arithmetic_rules; + + struct int_complex_parser_grammar : public detail::complex_variable_grammar { + int_complex_parser_grammar(const lexer& tok, + detail::Labeller& label, + const int_arithmetic_rules& _int_arith_rules, + const detail::value_ref_grammar& string_grammar); + + const int_arithmetic_rules& int_rules; + ship_part_class_enum_grammar ship_part_class_enum; + detail::complex_variable_rule game_rule; + detail::complex_variable_rule empire_name_ref; + detail::complex_variable_rule empire_ships_destroyed; + detail::complex_variable_rule jumps_between; + //complex_variable_rule jumps_between_by_empire_supply; + detail::complex_variable_rule outposts_owned; + detail::complex_variable_rule parts_in_ship_design; + detail::complex_variable_rule part_class_in_ship_design; + detail::value_ref_rule part_class_as_int; + detail::complex_variable_rule ship_parts_owned; + detail::complex_variable_rule empire_design_ref; + detail::complex_variable_rule slots_in_hull; + detail::complex_variable_rule slots_in_ship_design; + detail::complex_variable_rule special_added_on_turn; + detail::complex_variable_rule start; + }; + + struct int_arithmetic_rules : public detail::arithmetic_rules { + int_arithmetic_rules( + const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser, + const detail::value_ref_grammar& string_grammar); + detail::simple_int_parser_rules simple_int_rules; + int_complex_parser_grammar int_complex_grammar; + }; + + struct double_complex_parser_grammar : public detail::complex_variable_grammar { + double_complex_parser_grammar(const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser, + const detail::value_ref_grammar& string_grammar); + + detail::simple_int_parser_rules simple_int_rules; + detail::complex_variable_rule name_property_rule; + detail::complex_variable_rule id_empire_location_rule; + detail::complex_variable_rule empire_meter_value; + detail::complex_variable_rule direct_distance; + detail::complex_variable_rule shortest_path; + detail::rule()> species_opinion; + detail::complex_variable_rule species_empire_opinion; + detail::complex_variable_rule species_species_opinion; + detail::complex_variable_rule special_capacity; + detail::complex_variable_rule unwrapped_part_meter; + detail::complex_variable_rule value_wrapped_part_meter; + detail::complex_variable_rule start; + parse::ship_part_meter_enum_grammar ship_part_meter_type_enum; + }; + + struct double_parser_rules : public detail::arithmetic_rules { + double_parser_rules( + const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser, + const detail::value_ref_grammar& string_grammar); + + int_arithmetic_rules int_rules; + detail::simple_int_parser_rules simple_int_rules; + detail::simple_double_parser_rules simple_double_rules; + int_complex_parser_grammar int_complex_grammar; + double_complex_parser_grammar double_complex_grammar; + detail::value_ref_rule int_constant_cast; + detail::value_ref_rule int_bound_variable_cast; + detail::value_ref_rule int_free_variable_cast; + detail::value_ref_rule int_statistic_cast; + detail::value_ref_rule int_complex_variable_cast; + }; + + struct castable_as_int_parser_rules { + castable_as_int_parser_rules(const lexer& tok, + detail::Labeller& label, + const detail::condition_parser_grammar& condition_parser, + const detail::value_ref_grammar& string_grammar); + + int_arithmetic_rules int_rules; + double_parser_rules double_rules; + detail::value_ref_rule castable_expr; + detail::value_ref_rule flexible_int; + }; } -#endif +#endif // _ValueRefParser_h_ diff --git a/parse/ValueRefParserImpl.cpp b/parse/ValueRefParserImpl.cpp deleted file mode 100644 index b6b1b144255..00000000000 --- a/parse/ValueRefParserImpl.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "ValueRefParserImpl.h" - -namespace { - struct variable_parser_rules { - variable_parser_rules() { - boost::spirit::qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - variable_scope - = tok.Source_ [ _val = ValueRef::SOURCE_REFERENCE ] - | tok.Target_ [ _val = ValueRef::EFFECT_TARGET_REFERENCE ] - | tok.LocalCandidate_ [ _val = ValueRef::CONDITION_LOCAL_CANDIDATE_REFERENCE ] - | tok.RootCandidate_ [ _val = ValueRef::CONDITION_ROOT_CANDIDATE_REFERENCE ] - ; - - container_type - = tok.Planet_ - | tok.System_ - | tok.Fleet_ - ; - - variable_scope.name("Source, Target, LocalCandidate, or RootCandidate"); - container_type.name("Planet, System, or Fleet"); - -#if DEBUG_VALUEREF_PARSERS - debug(variable_scope); - debug(container_type); -#endif - } - - reference_token_rule variable_scope; - name_token_rule container_type; - }; - - variable_parser_rules& get_variable_parser_rules() { - static variable_parser_rules retval; - return retval; - } -} - -const reference_token_rule& variable_scope() -{ return get_variable_parser_rules().variable_scope; } - -const name_token_rule& container_type() -{ return get_variable_parser_rules().container_type; } diff --git a/parse/ValueRefParserImpl.h b/parse/ValueRefParserImpl.h deleted file mode 100644 index 1515a6220be..00000000000 --- a/parse/ValueRefParserImpl.h +++ /dev/null @@ -1,472 +0,0 @@ -#include "ValueRefParser.h" - -#include "ParseImpl.h" -#include "ConditionParserImpl.h" -#include "EnumParser.h" -#include "../universe/ValueRef.h" - -#include - - -#define DEBUG_VALUEREF_PARSERS 0 - -// These are just here to satisfy the requirements of boost::spirit::qi::debug(). -#if DEBUG_VALUEREF_PARSERS -namespace std { - inline ostream& operator<<(ostream& os, const std::vector&) { return os; } - inline ostream& operator<<(ostream& os, const std::vector*>>&) { return os; } - inline ostream& operator<<(ostream& os, const std::vector*>>&) { return os; } -} -#endif - -typedef parse::detail::rule< - ValueRef::ReferenceType () -> reference_token_rule; - -typedef parse::detail::rule< - const char* () -> name_token_rule; - -template -using variable_rule = parse::detail::rule< - ValueRef::Variable* (), - boost::spirit::qi::locals< - std::vector, - ValueRef::ReferenceType - > ->; - -template -using statistic_rule = parse::detail::rule< - ValueRef::Statistic* (), - boost::spirit::qi::locals< - ValueRef::ValueRefBase*, - ValueRef::StatisticType - > ->; - -template -using complex_variable_rule = parse::detail::rule< - ValueRef::ComplexVariable* (), - boost::spirit::qi::locals< - std::string, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase* - > ->; - -template -using expression_rule = parse::detail::rule< - ValueRef::ValueRefBase* (), - boost::spirit::qi::locals< - ValueRef::ValueRefBase*, - ValueRef::ValueRefBase*, - ValueRef::OpType, - std::vector*> - > ->; - - -const reference_token_rule& variable_scope(); -const name_token_rule& container_type(); - -const variable_rule& int_bound_variable(); -const variable_rule& int_free_variable(); -const statistic_rule& int_var_statistic(); -const complex_variable_rule& int_var_complex(); -const parse::value_ref_rule& int_simple(); - -const complex_variable_rule& double_var_complex(); -const parse::value_ref_rule& double_simple(); - -const complex_variable_rule& string_var_complex(); - - -template -void initialize_nonnumeric_statistic_parser( - statistic_rule& statistic, - const typename parse::value_ref_rule& value_ref) -{ - using boost::phoenix::construct; - using boost::phoenix::new_; - using boost::phoenix::push_back; - - boost::spirit::qi::_1_type _1; - boost::spirit::qi::_a_type _a; - boost::spirit::qi::_b_type _b; - boost::spirit::qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - statistic - = (tok.Statistic_ >> tok.Mode_ [ _b = ValueRef::MODE ]) - > parse::detail::label(Value_token) > value_ref [ _a = _1 ] - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_>(_a, _b, _1) ] - ; -} - - -template -void initialize_bound_variable_parser( - variable_rule& bound_variable, - const name_token_rule& variable_name) -{ - using boost::phoenix::construct; - using boost::phoenix::new_; - using boost::phoenix::push_back; - - boost::spirit::qi::_1_type _1; - boost::spirit::qi::_a_type _a; - boost::spirit::qi::_b_type _b; - boost::spirit::qi::_val_type _val; - - bound_variable - = variable_scope() [ _b = _1 ] >> '.' - >>-(container_type() [ push_back(_a, construct(_1)) ] > '.') - >> variable_name [ push_back(_a, construct(_1)), _val = new_>(_b, _a) ] - ; -} - - -namespace parse { namespace detail { - -template -struct enum_value_ref_rules { - enum_value_ref_rules(const std::string& type_name) { - using boost::phoenix::new_; - using boost::phoenix::push_back; - - boost::spirit::qi::_1_type _1; - boost::spirit::qi::_c_type _c; - boost::spirit::qi::_d_type _d; - boost::spirit::qi::_val_type _val; - boost::spirit::qi::lit_type lit; - - const parse::lexer& tok = parse::lexer::instance(); - - constant_expr - = enum_expr [ _val = new_>(_1) ] - ; - - initialize_bound_variable_parser(bound_variable_expr, variable_name); - - statistic_value_ref_expr - = constant_expr - | bound_variable_expr - ; - - functional_expr - = ( - ( - ( - tok.OneOf_ [ _c = ValueRef::RANDOM_PICK ] - | tok.Min_ [ _c = ValueRef::MINIMUM ] - | tok.Max_ [ _c = ValueRef::MAXIMUM ] - ) - > '(' > expr [ push_back(_d, _1) ] - >*(',' > expr [ push_back(_d, _1) ] ) - [ _val = new_>(_c, _d) ] > ')' - ) - | ( - primary_expr [ _val = _1 ] - ) - ) - ; - - expr - = functional_expr - ; - - initialize_nonnumeric_statistic_parser(statistic_expr, statistic_value_ref_expr); - - primary_expr - = constant_expr - | bound_variable_expr - | statistic_expr - ; - -#if DEBUG_VALUEREF_PARSERS - debug(variable_name); - debug(enum_expr); - debug(constant_expr); - debug(bound_variable_expr); - debug(statistic_value_ref_expr); - debug(statistic_expr); - debug(functional_expr); - debug(primary_expr); - debug(expr); -#endif - - variable_name.name(type_name + " variable name"); - enum_expr.name(type_name); - constant_expr.name(type_name); - bound_variable_expr.name(type_name + " variable"); - statistic_value_ref_expr.name(type_name + " statistic value reference"); - statistic_expr.name(type_name + " statistic"); - primary_expr.name(type_name + " expression"); - expr.name(type_name + " expression"); - } - - name_token_rule variable_name; - parse::enum_rule enum_expr; - parse::value_ref_rule constant_expr; - variable_rule bound_variable_expr; - expression_rule functional_expr; - parse::value_ref_rule primary_expr; - parse::value_ref_rule statistic_value_ref_expr; - statistic_rule statistic_expr; - parse::value_ref_rule expr; -}; - - -template -struct simple_variable_rules -{ - simple_variable_rules(const std::string& type_name) { - using boost::phoenix::new_; - - boost::spirit::qi::_1_type _1; - boost::spirit::qi::_val_type _val; - - const parse::lexer& tok = parse::lexer::instance(); - - free_variable - = tok.Value_ - [ _val = new_>(ValueRef::EFFECT_TARGET_VALUE_REFERENCE) ] - | free_variable_name - [ _val = new_>(ValueRef::NON_OBJECT_REFERENCE, _1) ] - ; - - simple - = constant - | free_variable - | bound_variable - ; - - initialize_bound_variable_parser(bound_variable, bound_variable_name); - -#if DEBUG_VALUEREF_PARSERS - debug(bound_variable_name); - debug(free_variable_name); - debug(constant); - debug(free_variable); - debug(bound_variable); - debug(simple); -#endif - - bound_variable_name.name(type_name + " bound variable name"); - free_variable_name.name(type_name + " free variable name"); - constant.name(type_name + " constant"); - free_variable.name(type_name + " free variable"); - bound_variable.name(type_name + " bound variable"); - simple.name(type_name + " simple variable expression"); - } - - name_token_rule bound_variable_name; - name_token_rule free_variable_name; - parse::value_ref_rule constant; - variable_rule free_variable; - variable_rule bound_variable; - parse::value_ref_rule simple; -}; - -} } - - -template -struct arithmetic_rules -{ - arithmetic_rules(const std::string& type_name) { - using boost::phoenix::construct; - using boost::phoenix::new_; - using boost::phoenix::push_back; - - boost::spirit::qi::_1_type _1; - boost::spirit::qi::_a_type _a; - boost::spirit::qi::_b_type _b; - boost::spirit::qi::_c_type _c; - boost::spirit::qi::_d_type _d; - boost::spirit::qi::_val_type _val; - boost::spirit::qi::lit_type lit; - - const parse::lexer& tok = parse::lexer::instance(); - - functional_expr - = ( - ( - ( - tok.Sin_ [ _c = ValueRef::SINE ] // single-parameter math functions - | tok.Cos_ [ _c = ValueRef::COSINE ] - | tok.Log_ [ _c = ValueRef::LOGARITHM ] - | tok.Abs_ [ _c = ValueRef::ABS ] - ) - >> '(' >> expr [ _val = new_>(_c, _1) ] >> ')' - ) - | ( - tok.RandomNumber_ [ _c = ValueRef::RANDOM_UNIFORM ] // random number requires a min and max value - > '(' > expr [ _a = _1 ] - > ',' > expr [ _val = new_>(_c, _a, _1) ] >> ')' - ) - | ( - ( - tok.OneOf_ [ _c = ValueRef::RANDOM_PICK ] // oneof, min, or max can take any number or operands - | tok.Min_ [ _c = ValueRef::MINIMUM ] - | tok.Max_ [ _c = ValueRef::MAXIMUM ] - ) - >> '(' >> expr [ push_back(_d, _1) ] - >>(*(',' > expr [ push_back(_d, _1) ] )) - [ _val = new_>(_c, _d) ] >> ')' - ) - | ( - lit('(') >> expr [ push_back(_d, _1) ] - >> ( - ( lit("==") [ _c = ValueRef::COMPARE_EQUAL ] - | lit('=') [ _c = ValueRef::COMPARE_EQUAL ] - | lit(">=") [ _c = ValueRef::COMPARE_GREATER_THAN_OR_EQUAL ] - | lit('>') [ _c = ValueRef::COMPARE_GREATER_THAN ] - | lit("<=") [ _c = ValueRef::COMPARE_LESS_THAN_OR_EQUAL ] - | lit('<') [ _c = ValueRef::COMPARE_LESS_THAN ] - | lit("!=") [ _c = ValueRef::COMPARE_NOT_EQUAL ] - ) - > expr [ push_back(_d, _1) ] - ) - > ( - lit(')') - | ( - (lit('?') > expr [ push_back(_d, _1) ]) - > ( - lit(')') - | lit(':') > expr [ push_back(_d, _1) ] > ')' - ) - ) - ) [ _val = new_>(_c, _d) ] - ) - | ( - lit('-') >> functional_expr // single parameter math function with a function expression rather than any arbitrary expression as parameter, because negating more general expressions can be ambiguous - [ _val = new_>(ValueRef::NEGATE, _1) ] - ) - | ( - primary_expr [ _val = _1 ] - ) - ) - ; - - exponential_expr - = ( - functional_expr [ _a = _1 ] - >> '^' >> functional_expr - [ _val = new_>( ValueRef::EXPONENTIATE, _a, _1 ) ] - ) - | functional_expr [ _val = _1 ] - ; - - multiplicative_expr - = ( - exponential_expr [ _a = _1 ] - >> ( - *( - ( - ( - lit('*') [ _c = ValueRef::TIMES ] - | lit('/') [ _c = ValueRef::DIVIDE ] - ) - >> exponential_expr [ _b = new_>(_c, _a, _1) ] - ) [ _a = _b ] - ) - ) - ) - [ _val = _a ] - ; - - additive_expr - = - ( - ( - ( - multiplicative_expr [ _a = _1 ] - >> ( - *( - ( - ( - lit('+') [ _c = ValueRef::PLUS ] - | lit('-') [ _c = ValueRef::MINUS ] - ) - >> multiplicative_expr [ _b = new_>(_c, _a, _1) ] - ) [ _a = _b ] - ) - ) - ) - [ _val = _a ] - ) - | ( - multiplicative_expr [ _val = _1 ] - ) - ) - ; - - statistic_collection_expr - = (tok.Statistic_ - >> ( tok.Count_ [ _b = ValueRef::COUNT ] - | tok.If_ [ _b = ValueRef::IF ] - ) - ) - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_>(_a, _b, _1) ] - ; - - statistic_value_expr - = (tok.Statistic_ >> parse::statistic_type_enum() [ _b = _1 ]) - > parse::detail::label(Value_token) > statistic_value_ref_expr [ _a = _1 ] - > parse::detail::label(Condition_token) > parse::detail::condition_parser - [ _val = new_>(_a, _b, _1) ] - ; - - statistic_expr - = statistic_collection_expr - | statistic_value_expr - ; - - expr - = additive_expr - ; - -#if DEBUG_VALUEREF_PARSERS - debug(functional_expr); - debug(exponential_expr); - debug(multiplicative_expr); - debug(additive_expr); - debug(primary_expr); - debug(statistic_value_ref_expr); - debug(statistic_collection_expr); - debug(statistic_value_expr); - debug(statistic_expr); - debug(expr); -#endif - - functional_expr.name(type_name + " function expression"); - exponential_expr.name(type_name + " exponential expression"); - multiplicative_expr.name(type_name + " multiplication expression"); - additive_expr.name(type_name + " additive expression"); - statistic_value_ref_expr.name(type_name + " statistic value reference"); - statistic_collection_expr.name(type_name + " collection statistic"); - statistic_value_expr.name(type_name + " value statistic"); - statistic_expr.name("real number statistic"); - primary_expr.name(type_name + " expression"); - expr.name(type_name + " expression"); - } - - expression_rule functional_expr; - expression_rule exponential_expr; - expression_rule multiplicative_expr; - expression_rule additive_expr; - parse::value_ref_rule primary_expr; - parse::value_ref_rule statistic_value_ref_expr; - statistic_rule statistic_collection_expr; - statistic_rule statistic_value_expr; - statistic_rule statistic_expr; - parse::value_ref_rule expr; -}; diff --git a/parse/VisibilityValueRefParser.cpp b/parse/VisibilityValueRefParser.cpp new file mode 100644 index 00000000000..e6fdf946a36 --- /dev/null +++ b/parse/VisibilityValueRefParser.cpp @@ -0,0 +1,88 @@ +#include "ValueRefParser.h" + +#include "EnumParser.h" +#include "EnumValueRefRules.h" + +#include "../universe/Enums.h" +#include "../universe/ValueRefs.h" + +#include + +namespace parse { namespace detail { + visibility_complex_parser_grammar::visibility_complex_parser_grammar( + const parse::lexer& tok, Labeller& label + ) : + visibility_complex_parser_grammar::base_type(start, "EmpireObjectVisibility"), + simple_int_rules(tok) + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::construct; + using phoenix::new_; + + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_val_type _val; + qi::_pass_type _pass; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + const value_ref_rule& simple_int = simple_int_rules.simple; + + empire_object_visibility + = ( tok.EmpireObjectVisibility_ + > label(tok.Empire_) > simple_int + > label(tok.Object_) > simple_int + ) [ _val = construct_movable_(new_>(_1, deconstruct_movable_(_2, _pass), deconstruct_movable_(_3, _pass), nullptr, nullptr, nullptr)) ] + ; + + start + = empire_object_visibility + ; + + empire_object_visibility.name("EmpireObjectVisibility"); + +#if DEBUG_DOUBLE_COMPLEX_PARSERS + debug(empire_object_visibility); + +#endif + } + + visibility_parser_rules::visibility_parser_rules( + const parse::lexer& tok, + Labeller& label, + const condition_parser_grammar& condition_parser + ) : + enum_value_ref_rules("Visibility", tok, label, condition_parser), + visibility_var_complex_grammar(tok, label) + { + namespace phoenix = boost::phoenix; + namespace qi = boost::spirit::qi; + + using phoenix::new_; + + qi::_val_type _val; + const boost::phoenix::function construct_movable_; + const boost::phoenix::function deconstruct_movable_; + + // variable_name left empty, as no direct object properties are + // available that return a visibility + + enum_expr + = tok.Invisible_ [ _val = VIS_NO_VISIBILITY ] + | tok.Basic_ [ _val = VIS_BASIC_VISIBILITY ] + | tok.Partial_ [ _val = VIS_PARTIAL_VISIBILITY ] + | tok.Full_ [ _val = VIS_FULL_VISIBILITY ] + ; + + free_variable_expr + = tok.Value_ [ _val = construct_movable_(new_>(ValueRef::EFFECT_TARGET_VALUE_REFERENCE)) ] + ; + + complex_expr = visibility_var_complex_grammar; + } + + } +} diff --git a/python/AI/AIFramework.h b/python/AI/AIFramework.h deleted file mode 100644 index 71c2915873b..00000000000 --- a/python/AI/AIFramework.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef __FreeOrion__Python__AIFramework__ -#define __FreeOrion__Python__AIFramework__ - -#include "../CommonFramework.h" -#include "../../AI/AIInterface.h" - -#include - -class PythonAI : public PythonBase, public AIBase { -public: - bool Initialize(); - - /** Initializes AI Python modules. */ - bool InitModules() override; - - void GenerateOrders() override; - - void HandleChatMessage(int sender_id, const std::string& msg) override; - - void HandleDiplomaticMessage(const DiplomaticMessage& msg) override; - - void HandleDiplomaticStatusUpdate(const DiplomaticStatusUpdateInfo& u) override; - - void StartNewGame() override; - - void ResumeLoadedGame(const std::string& save_state_string) override; - - const std::string& GetSaveStateString() override; - -private: - // reference to imported Python AI module - boost::python::object m_python_module_ai; -}; - -#endif // __FreeOrion__Python__AIFramework__ diff --git a/python/AI/AIWrapper.cpp b/python/AI/AIWrapper.cpp deleted file mode 100644 index 34c58fb73a5..00000000000 --- a/python/AI/AIWrapper.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include "AIWrapper.h" - -#include "../../AI/AIInterface.h" -#include "../../universe/Universe.h" -#include "../../util/Directories.h" -#include "../../util/Logger.h" -#include "../../util/i18n.h" -#include "../../util/MultiplayerCommon.h" -#include "../../util/OptionsDB.h" -#include "../../Empire/Empire.h" -#include "../../Empire/EmpireManager.h" -#include "../../Empire/Diplomacy.h" -#include "../SetWrapper.h" -#include "../CommonWrappers.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -using boost::python::class_; -using boost::python::def; -using boost::python::return_value_policy; -using boost::python::copy_const_reference; -using boost::python::reference_existing_object; -using boost::python::return_by_value; -using boost::python::return_internal_reference; - -using boost::python::vector_indexing_suite; -using boost::python::map_indexing_suite; - -using boost::python::object; -using boost::python::import; -using boost::python::error_already_set; -using boost::python::exec; -using boost::python::dict; -using boost::python::list; -using boost::python::extract; - - -//////////////////////// -// Python AIInterface // -//////////////////////// -// disambiguation of overloaded functions -const std::string& (*AIIntPlayerNameVoid)(void) = &AIInterface::PlayerName; -const std::string& (*AIIntPlayerNameInt)(int) = &AIInterface::PlayerName; - -const Empire* (*AIIntGetEmpireVoid)(void) = &AIInterface::GetEmpire; -const Empire* (*AIIntGetEmpireInt)(int) = &AIInterface::GetEmpire; - -int (*AIIntNewFleet)(const std::string&, int) = &AIInterface::IssueNewFleetOrder; -int (*AIIntScrap)(int) = &AIInterface::IssueScrapOrder; - - - -namespace { - // static string to save AI state - static std::string s_save_state_string(""); - - int IssueCreateShipDesignOrderWrapper(const std::string& name, const std::string& description, - const std::string& hull, boost::python::list partsList, - const std::string& icon, const std::string& model, bool nameDescInStringTable) - { - std::vector parts; - int const numParts = boost::python::len(partsList); - for (int i = 0; i < numParts; i++) - parts.push_back(boost::python::extract(partsList[i])); - int result = AIInterface::IssueCreateShipDesignOrder(name, description, hull, parts, icon, model, nameDescInStringTable); - return result; - } - - boost::python::list GetUserStringList(const std::string& list_key) { - boost::python::list ret_list; - for (const std::string& string : UserStringList(list_key)) - ret_list.append(string); - return ret_list; - } - - boost::python::str GetUserConfigDirWrapper() - { return boost::python::str(PathString(GetUserConfigDir())); } - - boost::python::str GetUserDataDirWrapper() - { return boost::python::str(PathString(GetUserDataDir())); } - -} - -namespace FreeOrionPython { - const std::string& GetStaticSaveStateString() - { return s_save_state_string; } - - void SetStaticSaveStateString(const std::string& new_state_string) - { s_save_state_string = new_state_string; } - - void ClearStaticSaveStateString() - { s_save_state_string = ""; } - - /** Expose AIInterface to Python. - * - * CallPolicies: - * - * return_value_policy when returning a relatively small object, such as a string, - * that is returned by const reference or pointer - * - * return_value_policy when returning either a simple data type or a temporary object - * in a function that will go out of scope after being returned - * - * return_value_policy when returning an object from a non-member function - */ - void WrapAI() - { - def("playerName", AIIntPlayerNameVoid, return_value_policy(), "Returns the name (string) of this AI player."); - def("playerName", AIIntPlayerNameInt, return_value_policy(), "Returns the name (string) of the player with the indicated playerID (int)."); - - def("playerID", AIInterface::PlayerID, "Returns the integer id of this AI player."); - def("empirePlayerID", AIInterface::EmpirePlayerID, "Returns the player ID (int) of the player who is controlling the empire with the indicated empireID (int)."); - def("allPlayerIDs", AIInterface::AllPlayerIDs, return_value_policy(), "Returns an object (intVec) that contains the player IDs of all players in the game."); - - def("playerIsAI", AIInterface::PlayerIsAI, "Returns True (boolean) if the player with the indicated playerID (int) is controlled by an AI and false (boolean) otherwise."); - def("playerIsHost", AIInterface::PlayerIsHost, "Returns True (boolean) if the player with the indicated playerID (int) is the host player for the game and false (boolean) otherwise."); - - def("empireID", AIInterface::EmpireID, "Returns the empire ID (int) of this AI player's empire."); - def("playerEmpireID", AIInterface::PlayerEmpireID, "Returns the empire ID (int) of the player with the specified player ID (int)."); - def("allEmpireIDs", AIInterface::AllEmpireIDs, return_value_policy(), "Returns an object (intVec) that contains the empire IDs of all empires in the game."); - - def("getEmpire", AIIntGetEmpireVoid, return_value_policy(), "Returns the empire object (Empire) of this AI player"); - def("getEmpire", AIIntGetEmpireInt, return_value_policy(), "Returns the empire object (Empire) with the specified empire ID (int)"); - - def("getUniverse", AIInterface::GetUniverse, return_value_policy(), "Returns the universe object (Universe)"); - - def("currentTurn", AIInterface::CurrentTurn, "Returns the current game turn (int)."); - - def("getOptionsDBOptionStr", AIInterface::GetOptionsDBOptionStr, return_value_policy(), "Returns the string value of option in OptionsDB or None if the option does not exist."); - def("getOptionsDBOptionInt", AIInterface::GetOptionsDBOptionInt, return_value_policy(), "Returns the integer value of option in OptionsDB or None if the option does not exist."); - def("getOptionsDBOptionBool", AIInterface::GetOptionsDBOptionBool, return_value_policy(), "Returns the bool value of option in OptionsDB or None if the option does not exist."); - def("getOptionsDBOptionDouble", AIInterface::GetOptionsDBOptionDouble, return_value_policy(), "Returns the double value of option in OptionsDB or None if the option does not exist."); - - def("getAIDir", AIInterface::GetAIDir, return_value_policy()); - def("getUserConfigDir", GetUserConfigDirWrapper, /* no return value policy, */ "Returns path to directory where FreeOrion stores user specific configuration."); - def("getUserDataDir", GetUserDataDirWrapper, /* no return value policy, */ "Returns path to directory where FreeOrion stores user specific data (saves, etc.)."); - - def("initMeterEstimatesDiscrepancies", AIInterface::InitMeterEstimatesAndDiscrepancies); - def("updateMeterEstimates", AIInterface::UpdateMeterEstimates); - def("updateResourcePools", AIInterface::UpdateResourcePools); - def("updateResearchQueue", AIInterface::UpdateResearchQueue); - def("updateProductionQueue", AIInterface::UpdateProductionQueue); - - def("issueFleetMoveOrder", AIInterface::IssueFleetMoveOrder, "Orders the fleet with indicated fleetID (int) to move to the system with the indicated destinationID (int). Returns 1 (int) on success or 0 (int) on failure due to not finding the indicated fleet or system."); - def("issueRenameOrder", AIInterface::IssueRenameOrder, "Orders the renaming of the object with indicated objectID (int) to the new indicated name (string). Returns 1 (int) on success or 0 (int) on failure due to this AI player not being able to rename the indicated object (which this player must fully own, and which must be a fleet, ship or planet)."); - def("issueScrapOrder", AIIntScrap, "Orders the ship or building with the indicated objectID (int) to be scrapped. Returns 1 (int) on success or 0 (int) on failure due to not finding a ship or building with the indicated ID, or if the indicated ship or building is not owned by this AI client's empire."); - def("issueNewFleetOrder", AIIntNewFleet, "Orders a new fleet to be created with the indicated name (string) and containing the indicated shipIDs (IntVec). The ships must be located in the same system and must all be owned by this player. Returns 1 (int) on success or 0 (int) on failure due to one of the noted conditions not being met."); - def("issueFleetTransferOrder", AIInterface::IssueFleetTransferOrder, "Orders the ship with ID shipID (int) to be transferred to the fleet with ID newFleetID. Returns 1 (int) on success, or 0 (int) on failure due to not finding the fleet or ship, or the client's empire not owning either, or the two not being in the same system (or either not being in a system) or the ship already being in the fleet."); - def("issueColonizeOrder", AIInterface::IssueColonizeOrder, "Orders the ship with ID shipID (int) to colonize the planet with ID planetID (int). Returns 1 (int) on success or 0 (int) on failure due to not finding the indicated ship or planet, this client's player not owning the indicated ship, the planet already being colonized, or the planet and ship not being in the same system."); - def("issueInvadeOrder", AIInterface::IssueInvadeOrder, ""); - def("issueBombardOrder", AIInterface::IssueBombardOrder, ""); - def("issueAggressionOrder", AIInterface::IssueAggressionOrder); - def("issueGiveObjectToEmpireOrder", AIInterface::IssueGiveObjectToEmpireOrder); - def("issueChangeFocusOrder", AIInterface::IssueChangeFocusOrder, "Orders the planet with ID planetID (int) to use focus setting focus (string). Returns 1 (int) on success or 0 (int) on failure if the planet can't be found or isn't owned by this player, or if the specified focus is not valid on the planet."); - def("issueEnqueueTechOrder", AIInterface::IssueEnqueueTechOrder, "Orders the tech with name techName (string) to be added to the tech queue at position (int) on the queue. Returns 1 (int) on success or 0 (int) on failure if the indicated tech can't be found. Will return 1 (int) but do nothing if the indicated tech can't be enqueued by this player's empire."); - def("issueDequeueTechOrder", AIInterface::IssueDequeueTechOrder, "Orders the tech with name techName (string) to be removed from the queue. Returns 1 (int) on success or 0 (int) on failure if the indicated tech can't be found. Will return 1 (int) but do nothing if the indicated tech isn't on this player's empire's tech queue."); - def("issueEnqueueBuildingProductionOrder", AIInterface::IssueEnqueueBuildingProductionOrder, "Orders the building with name (string) to be added to the production queue at the location of the planet with id locationID. Returns 1 (int) on success or 0 (int) on failure if there is no such building or it is not available to this player's empire, or if the building can't be produced at the specified location."); - def("issueEnqueueShipProductionOrder", AIInterface::IssueEnqueueShipProductionOrder, "Orders the ship design with ID designID (int) to be added to the production queue at the location of the planet with id locationID (int). Returns 1 (int) on success or 0 (int) on failure there is no such ship design or it not available to this player's empire, or if the design can't be produced at the specified location."); - def("issueChangeProductionQuantityOrder", AIInterface::IssueChangeProductionQuantityOrder); - def("issueRequeueProductionOrder", AIInterface::IssueRequeueProductionOrder, "Orders the item on the production queue at index oldQueueIndex (int) to be moved to index newQueueIndex (int). Returns 1 (int) on success or 0 (int) on failure if the old and new queue indices are equal, if either queue index is less than 0 or greater than the largest indexed item on the queue."); - def("issueDequeueProductionOrder", AIInterface::IssueDequeueProductionOrder, "Orders the item on the production queue at index queueIndex (int) to be removed form the production queue. Returns 1 (int) on success or 0 (int) on failure if the queue index is less than 0 or greater than the largest indexed item on the queue."); - def("issueCreateShipDesignOrder", IssueCreateShipDesignOrderWrapper, "Orders the creation of a new ship design with the name (string), description (string), hull (string), parts vector partsVec (StringVec), graphic (string) and model (string). model should be left as an empty string as of this writing. There is currently no easy way to find the id of the new design, though the client's empire should have the new design after this order is issued successfully. Returns 1 (int) on success or 0 (int) on failure if any of the name, description, hull or graphic are empty strings, if the design is invalid (due to not following number and type of slot requirements for the hull) or if creating the design fails for some reason."); - - def("sendChatMessage", AIInterface::SendPlayerChatMessage, "Sends the indicated message (string) to the player with the indicated recipientID (int) or to all players if recipientID is -1."); - def("sendDiplomaticMessage", AIInterface::SendDiplomaticMessage); - - def("setSaveStateString", SetStaticSaveStateString, "Sets the save state string (string). This is a persistant storage space for the AI script to retain state information when the game is saved and reloaded. Any AI state information to be saved should be stored in a single string (likely using Python's pickle module) and stored using this function when the prepareForSave() Python function is called."); - def("getSaveStateString", GetStaticSaveStateString, return_value_policy(), "Returns the previously-saved state string (string). Can be used to retrieve the last-set save state string at any time, although this string is also passed to the resumeLoadedGame(savedStateString) Python function when a game is loaded, so this function isn't necessary to use if resumeLoadedGame stores the passed string. "); - - def("doneTurn", AIInterface::DoneTurn, "Ends the AI player's turn, indicating to the server that all orders have been issued and turn processing may commence."); - def("userString", make_function(&UserString, return_value_policy())); - def("userStringExists", make_function(&UserStringExists, return_value_policy())); - def("userStringList", &GetUserStringList); - - def("getGalaxySetupData", AIInterface::GetGalaxySetupData, return_value_policy()); - - boost::python::scope().attr("INVALID_GAME_TURN") = INVALID_GAME_TURN; - } -} diff --git a/python/AI/CMakeLists.txt b/python/AI/CMakeLists.txt deleted file mode 100644 index fd5612f6141..00000000000 --- a/python/AI/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -target_sources(freeorionca - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/AIFramework.h - ${CMAKE_CURRENT_LIST_DIR}/AIWrapper.h - PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/AIFramework.cpp - ${CMAKE_CURRENT_LIST_DIR}/AIWrapper.cpp -) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 57e68082a64..f08054a4cbe 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,6 +1,3 @@ -add_subdirectory(AI) -add_subdirectory(server) - target_sources(freeoriond PUBLIC ${CMAKE_CURRENT_LIST_DIR}/CommonFramework.h @@ -8,6 +5,7 @@ target_sources(freeoriond ${CMAKE_CURRENT_LIST_DIR}/SetWrapper.h PRIVATE ${CMAKE_CURRENT_LIST_DIR}/CommonFramework.cpp + ${CMAKE_CURRENT_LIST_DIR}/ConfigWrapper.cpp ${CMAKE_CURRENT_LIST_DIR}/EmpireWrapper.cpp ${CMAKE_CURRENT_LIST_DIR}/EnumWrapper.cpp ${CMAKE_CURRENT_LIST_DIR}/LoggingWrapper.cpp @@ -21,6 +19,7 @@ target_sources(freeorionca ${CMAKE_CURRENT_LIST_DIR}/SetWrapper.h PRIVATE ${CMAKE_CURRENT_LIST_DIR}/CommonFramework.cpp + ${CMAKE_CURRENT_LIST_DIR}/ConfigWrapper.cpp ${CMAKE_CURRENT_LIST_DIR}/EmpireWrapper.cpp ${CMAKE_CURRENT_LIST_DIR}/EnumWrapper.cpp ${CMAKE_CURRENT_LIST_DIR}/LoggingWrapper.cpp diff --git a/python/CommonFramework.cpp b/python/CommonFramework.cpp index b3f82a87e9f..50a8c4d4e69 100644 --- a/python/CommonFramework.cpp +++ b/python/CommonFramework.cpp @@ -4,6 +4,8 @@ #include "../util/Logger.h" #include "CommonWrappers.h" +#include +#include #include #include #include @@ -16,7 +18,10 @@ using boost::python::exec; using boost::python::dict; using boost::python::list; using boost::python::extract; +using boost::python::handle; +using boost::python::borrowed; +namespace fs = boost::filesystem; // Python module for logging functions BOOST_PYTHON_MODULE(freeorion_logger) { @@ -24,20 +29,13 @@ BOOST_PYTHON_MODULE(freeorion_logger) { FreeOrionPython::WrapLogger(); } -PythonBase::PythonBase() : -#if defined(FREEORION_MACOSX) - m_home_dir(""), - m_program_name(""), -#endif - m_python_module_error(nullptr) +PythonBase::PythonBase() {} -PythonBase::~PythonBase() { - Finalize(); -} +PythonBase::~PythonBase() +{ Finalize(); } -bool PythonBase::Initialize() -{ +bool PythonBase::Initialize() { DebugLogger() << "Initializing FreeOrion Python interface"; try { @@ -47,13 +45,22 @@ bool PythonBase::Initialize() // provided by the system). These API calls have been added in an attempt to // solve the problems. Not sure if they are really required, but better save // than sorry... ;) - strcpy(m_home_dir, GetPythonHome().string().c_str()); + m_home_dir = Py_DecodeLocale(GetPythonHome().string().c_str(), nullptr); Py_SetPythonHome(m_home_dir); DebugLogger() << "Python home set to " << Py_GetPythonHome(); - strcpy(m_program_name, (GetPythonHome() / "Python").string().c_str()); + m_program_name = Py_DecodeLocale((GetPythonHome() / "Python").string().c_str(), nullptr); Py_SetProgramName(m_program_name); DebugLogger() << "Python program name set to " << Py_GetProgramFullPath(); #endif + // allow the "freeorion_logger" C++ module to be imported within Python code + if (PyImport_AppendInittab("freeorion_logger", &PyInit_freeorion_logger) == -1) { + ErrorLogger() << "Unable to initialize freeorion_logger import"; + return false; + } + if (!InitImports()) { + ErrorLogger() << "Unable to initialize imports"; + return false; + } // initializes Python interpreter, allowing Python functions to be called from C++ Py_Initialize(); DebugLogger() << "Python initialized"; @@ -68,32 +75,37 @@ bool PythonBase::Initialize() DebugLogger() << "Initializing C++ interfaces for Python"; - m_system_exit = import("exceptions").attr("SystemExit"); try { + m_system_exit = import("builtins").attr("SystemExit"); // get main namespace, needed to run other interpreted code object py_main = import("__main__"); - m_namespace = extract(py_main.attr("__dict__")); + dict py_namespace = extract(py_main.attr("__dict__")); + m_namespace = py_namespace; // add the directory containing common Python modules used by all Python scripts to Python sys.path AddToSysPath(GetPythonCommonDir()); + } catch (const error_already_set& err) { + HandleErrorAlreadySet(); + ErrorLogger() << "Unable to initialize FreeOrion Python namespace and set path"; + return false; + } catch (...) { + ErrorLogger() << "Unable to initialize FreeOrion Python namespace and set path"; + return false; + } - // allow the "freeorion_logger" C++ module to be imported within Python code - try { - initfreeorion_logger(); - } catch (...) { - ErrorLogger() << "Unable to initialize FreeOrion Python logging module"; - return false; - } - + try { // Allow C++ modules implemented by derived classes to be imported // within Python code - if (!InitModules()) { ErrorLogger() << "Unable to initialize FreeOrion Python modules"; return false; } - } catch (error_already_set &err) { + } catch (const error_already_set& err) { HandleErrorAlreadySet(); + ErrorLogger() << "Unable to initialize FreeOrion Python modules (exception caught)"; + return false; + } catch (...) { + ErrorLogger() << "Unable to initialize FreeOrion Python modules (exception caught)"; return false; } @@ -118,29 +130,77 @@ void PythonBase::HandleErrorAlreadySet() { return; } - PyErr_Print(); + PyObject *extype, *value, *traceback; + PyErr_Fetch(&extype, &value, &traceback); + PyErr_NormalizeException(&extype, &value, &traceback); + if (extype == nullptr) { + ErrorLogger() << "Missing python exception type"; + return; + } + + object o_extype(handle<>(borrowed(extype))); + object o_value(handle<>(borrowed(value))); + object o_traceback = traceback != nullptr ? object(handle<>(borrowed(traceback))) : object(); + + object mod_traceback = import("traceback"); + object lines = mod_traceback.attr("format_exception")(o_extype, o_value, o_traceback); + for (int i = 0; i < len(lines); ++i) { + std::string line = extract(lines[i])(); + boost::algorithm::trim_right(line); + ErrorLogger() << line; + } + return; } - void PythonBase::Finalize() { if (Py_IsInitialized()) { - Py_Finalize(); + // cleanup python objects before interpterer shutdown + m_system_exit = object(); + m_namespace = boost::none; + if (m_python_module_error != nullptr) { + (*m_python_module_error) = object(); + m_python_module_error = nullptr; + } + try { + Py_Finalize(); +#if defined(FREEORION_MACOSX) || defined(FREEORION_WIN32) + if (m_home_dir != nullptr) { + PyMem_RawFree(m_home_dir); + m_home_dir = nullptr; + } + if (m_program_name != nullptr) { + PyMem_RawFree(m_program_name); + m_program_name = nullptr; + } +#endif + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception when cleaning up FreeOrion Python interface: " << e.what(); + return; + } DebugLogger() << "Cleaned up FreeOrion Python interface"; } } void PythonBase::SetCurrentDir(const std::string dir) { + if (!fs::exists(dir)) { + ErrorLogger() << "Tried setting current dir to non-existing dir: " << dir; + return; + } std::string script = "import os\n" "os.chdir(r'" + dir + "')\n" - "print 'Python current directory set to', os.getcwd()"; - exec(script.c_str(), m_namespace, m_namespace); + "print ('Python current directory set to', os.getcwd())"; + exec(script.c_str(), *m_namespace, *m_namespace); } void PythonBase::AddToSysPath(const std::string dir) { + if (!fs::exists(dir)) { + ErrorLogger() << "Tried adding non-existing dir to sys.path: " << dir; + return; + } std::string script = "import sys\n" "sys.path.append(r'" + dir + "')"; - exec(script.c_str(), m_namespace, m_namespace); + exec(script.c_str(), *m_namespace, *m_namespace); } void PythonBase::SetErrorModule(object& module) @@ -158,12 +218,12 @@ std::vector PythonBase::ErrorReport() { list py_err_list; try { py_err_list = extract(f()); } - catch (error_already_set err) { + catch (const error_already_set& err) { HandleErrorAlreadySet(); return err_list; } - for(int i = 0; i < len(py_err_list); i++) { + for (int i = 0; i < len(py_err_list); i++) { err_list.push_back(extract(py_err_list[i])); } } diff --git a/python/CommonFramework.h b/python/CommonFramework.h index cad85cd9665..9165edf208e 100644 --- a/python/CommonFramework.h +++ b/python/CommonFramework.h @@ -1,6 +1,7 @@ #ifndef __FreeOrion__Python__CommonFramework__ #define __FreeOrion__Python__CommonFramework__ +#include #include #include @@ -25,37 +26,38 @@ class PythonBase { HandleErrorAlreadySet is idempotent, calling it multiple times won't crash or hang the process. */ - void HandleErrorAlreadySet(); + void HandleErrorAlreadySet(); + /** IsPythonRunning returns true is the python interpreter is initialized. It is typically called after HandleErrorAlreadySet() to determine if the error caused the interpreter to shutdown. */ - bool IsPythonRunning(); + bool IsPythonRunning(); bool Initialize(); // initializes and runs the Python interpreter, prepares the Python environment - void Finalize(); // stops Python interpreter and releases its resources + virtual bool InitImports() = 0; // initializes Python imports, must be implemented by derived classes virtual bool InitModules() = 0; // initializes Python modules, must be implemented by derived classes void SetCurrentDir(const std::string dir); // sets Python current work directory or throws error_already_set void AddToSysPath(const std::string dir); // adds directory to Python sys.path or throws error_already_set void SetErrorModule(boost::python::object& module); // sets Python module that contains error report function defined on the Python side + std::vector ErrorReport(); // wraps call to error report function defined on the Python side private: + void Finalize(); // stops Python interpreter and releases its resources // A copy of the systemExit exception to compare with returned // exceptions. It can't be created in the exception handler. - boost::python::object m_system_exit; + boost::python::object m_system_exit; // some helper objects needed to initialize and run the Python interface #if defined(FREEORION_MACOSX) || defined(FREEORION_WIN32) - char m_home_dir[1024]; - char m_program_name[1024]; + wchar_t* m_home_dir = nullptr; + wchar_t* m_program_name = nullptr; #endif - boost::python::dict m_namespace; - - // used to track if and which Python module contains the "error_report" function ErrorReport should call - boost::python::object* m_python_module_error; + boost::optional m_namespace; // stores main namespace in optional to be finalized before Python interpreter + boost::python::object* m_python_module_error = nullptr; // used to track if and which Python module contains the "error_report" function ErrorReport should call }; // returns root folder containing all the Python scripts diff --git a/python/CommonWrappers.h b/python/CommonWrappers.h index 8113aed6035..f04e3bdd549 100644 --- a/python/CommonWrappers.h +++ b/python/CommonWrappers.h @@ -7,8 +7,8 @@ namespace FreeOrionPython { void WrapGalaxySetupData(); void WrapGameStateEnums(); void WrapEmpire(); - void WrapLogger(); + void WrapConfig(); } #endif diff --git a/python/ConfigWrapper.cpp b/python/ConfigWrapper.cpp new file mode 100644 index 00000000000..259fb30fcc8 --- /dev/null +++ b/python/ConfigWrapper.cpp @@ -0,0 +1,58 @@ +#include "../util/Directories.h" +#include "../util/OptionsDB.h" + +#include + +#include + +using boost::python::def; +using boost::python::return_value_policy; +using boost::python::return_by_value; + +namespace { + boost::python::object GetOptionsDBOptionStr(std::string const &option) + { return GetOptionsDB().OptionExists(option) ? boost::python::str(GetOptionsDB().Get(option)) : boost::python::str(); } + + boost::python::object GetOptionsDBOptionInt(std::string const &option) + { return GetOptionsDB().OptionExists(option) ? boost::python::object(GetOptionsDB().Get(option)) : boost::python::object(); } + + boost::python::object GetOptionsDBOptionBool(std::string const &option) + { return GetOptionsDB().OptionExists(option) ? boost::python::object(GetOptionsDB().Get(option)) : boost::python::object(); } + + boost::python::object GetOptionsDBOptionDouble(std::string const &option) + { return GetOptionsDB().OptionExists(option) ? boost::python::object(GetOptionsDB().Get(option)) : boost::python::object(); } + + boost::python::str GetUserConfigDirWrapper() + { return boost::python::str(PathToString(GetUserConfigDir())); } + + boost::python::str GetUserDataDirWrapper() + { return boost::python::str(PathToString(GetUserDataDir())); } +} + +namespace FreeOrionPython { + void WrapConfig() + { +#ifdef FREEORION_BUILD_AI + // For the AI client provide function names in camelCase, + // as that's still the preferred style there (for the time being) + def("getOptionsDBOptionStr", GetOptionsDBOptionStr, return_value_policy(), "Returns the string value of option in OptionsDB or None if the option does not exist."); + def("getOptionsDBOptionInt", GetOptionsDBOptionInt, return_value_policy(), "Returns the integer value of option in OptionsDB or None if the option does not exist."); + def("getOptionsDBOptionBool", GetOptionsDBOptionBool, return_value_policy(), "Returns the bool value of option in OptionsDB or None if the option does not exist."); + def("getOptionsDBOptionDouble", GetOptionsDBOptionDouble, return_value_policy(), "Returns the double value of option in OptionsDB or None if the option does not exist."); + + def("getUserConfigDir", GetUserConfigDirWrapper, /* no return value policy, */ "Returns path to directory where FreeOrion stores user specific configuration."); + def("getUserDataDir", GetUserDataDirWrapper, /* no return value policy, */ "Returns path to directory where FreeOrion stores user specific data (saves, etc.)."); +#endif + +#ifdef FREEORION_BUILD_SERVER + // For the server, provide the function names already in snake_case + def("get_options_db_option_str", GetOptionsDBOptionStr, return_value_policy(), "Returns the string value of option in OptionsDB or None if the option does not exist."); + def("get_options_db_option_int", GetOptionsDBOptionInt, return_value_policy(), "Returns the integer value of option in OptionsDB or None if the option does not exist."); + def("get_options_db_option_bool", GetOptionsDBOptionBool, return_value_policy(), "Returns the bool value of option in OptionsDB or None if the option does not exist."); + def("get_options_db_option_double", GetOptionsDBOptionDouble, return_value_policy(), "Returns the double value of option in OptionsDB or None if the option does not exist."); + + def("get_user_config_dir", GetUserConfigDirWrapper, /* no return value policy, */ "Returns path to directory where FreeOrion stores user specific configuration."); + def("get_user_data_dir", GetUserDataDirWrapper, /* no return value policy, */ "Returns path to directory where FreeOrion stores user specific data (saves, etc.)."); +#endif + } +} diff --git a/python/EmpireWrapper.cpp b/python/EmpireWrapper.cpp index 347c79fe952..57dccb4f4da 100644 --- a/python/EmpireWrapper.cpp +++ b/python/EmpireWrapper.cpp @@ -4,17 +4,17 @@ #include "../Empire/Diplomacy.h" #include "../universe/Predicates.h" #include "../universe/UniverseObject.h" +#include "../universe/UnlockableItem.h" #include "../universe/Planet.h" #include "../universe/Tech.h" #include "../universe/Enums.h" -#include "../util/SitRepEntry.h" #include "../util/AppInterface.h" #include "../util/Logger.h" +#include "../util/SitRepEntry.h" #include "SetWrapper.h" #include -#include #include #include #include @@ -30,24 +30,24 @@ namespace { // Research queue tests whether it contains a Tech by name, but Python needs // a __contains__ function that takes a *Queue::Element. This helper // functions take an Element and returns the associated Tech name string. - const std::string& TechFromResearchQueueElement(const ResearchQueue::Element& element) { return element.name; } + const std::string& TechFromResearchQueueElement(const ResearchQueue::Element& element) + { return element.name; } - std::vector (TechManager::*TechNamesVoid)(void) const = &TechManager::TechNames; - boost::function(const TechManager*)> TechNamesMemberFunc = TechNamesVoid; + std::vector (TechManager::*TechNamesVoid)(void) const = &TechManager::TechNames; + auto TechNamesMemberFunc = TechNamesVoid; - std::vector (TechManager::*TechNamesCategory)(const std::string&) const = &TechManager::TechNames; - boost::function(const TechManager*, const std::string&)> - TechNamesCategoryMemberFunc = TechNamesCategory; + std::vector (TechManager::*TechNamesCategory)(const std::string&) const = &TechManager::TechNames; + auto TechNamesCategoryMemberFunc = TechNamesCategory; std::vector TechRecursivePrereqs(const Tech& tech, int empire_id) { return GetTechManager().RecursivePrereqs(tech.Name(), empire_id); } - boost::function(const Tech& tech, int)> TechRecursivePrereqsFunc = TechRecursivePrereqs; + auto TechRecursivePrereqsFunc = TechRecursivePrereqs; // Concatenate functions to create one that takes two parameters. The first parameter is a ResearchQueue*, which // is passed directly to ResearchQueue::InQueue as the this pointer. The second parameter is a // ResearchQueue::Element which is passed into TechFromResearchQueueElement, which returns a Tech*, which is // passed into ResearchQueue::InQueue as the second parameter. - boost::function InQueueFromResearchQueueElementFunc = + auto InQueueFromResearchQueueElementFunc = boost::bind(&ResearchQueue::InQueue, _1, boost::bind(TechFromResearchQueueElement, _2)); // ProductionQueue::Element contains a ProductionItem which contains details of the item on the queue. Need helper @@ -69,9 +69,11 @@ namespace { return EMPTY_ENTRY; return *std::next(empire.SitRepBegin(), index); } - boost::function GetEmpireSitRepFunc = GetSitRep; + auto GetEmpireSitRepFunc = GetSitRep; + + const Meter* (Empire::*EmpireGetMeter)(const std::string&) const = &Empire::GetMeter; - template + template struct PairToTupleConverter { static PyObject* convert(const std::pair& pair) { return boost::python::incref(boost::python::make_tuple(pair.first, pair.second).ptr()); @@ -96,7 +98,7 @@ namespace { } return retval; } - boost::function(const Empire&)> obstructedStarlanesFunc = &obstructedStarlanesP; + auto obstructedStarlanesFunc = &obstructedStarlanesP; std::map jumpsToSuppliedSystemP(const Empire& empire) { std::map retval; @@ -117,14 +119,14 @@ namespace { int from_sys_dist = retval[from_sys_id]; // get lanes connected to this system - std::map>::const_iterator lane_set_it = empire_starlanes.find(from_sys_id); + auto lane_set_it = empire_starlanes.find(from_sys_id); if (lane_set_it == empire_starlanes.end()) continue; // no lanes to propagate from for this supply source - const std::set& lane_ends = lane_set_it->second; + auto& lane_ends = lane_set_it->second; // propagate to any not-already-counted adjacent system for (int lane_end_system_id : lane_ends) { - if (retval.find(lane_end_system_id) != retval.end()) + if (retval.count(lane_end_system_id)) continue; // system already processed // system not yet processed; add it to list to propagate from, and set its range to one more than this system propagating_list.push_back(lane_end_system_id); @@ -134,7 +136,7 @@ namespace { //// DEBUG //DebugLogger() << "jumpsToSuppliedSystemP results for empire, " << empire.Name() << " (" << empire.EmpireID() << ") :"; - //for (const std::map::value_type& system_jumps : retval) { + //for (const auto& system_jumps : retval) { // DebugLogger() << "sys " << system_jumps.first << " range: " << system_jumps.second; //} //// END DEBUG @@ -142,76 +144,87 @@ namespace { return retval; } - boost::function(const Empire&)> jumpsToSuppliedSystemFunc = &jumpsToSuppliedSystemP; + auto jumpsToSuppliedSystemFunc = &jumpsToSuppliedSystemP; const std::set& EmpireFleetSupplyableSystemIDsP(const Empire& empire) { return GetSupplyManager().FleetSupplyableSystemIDs(empire.EmpireID()); } - boost::function& (const Empire&)> empireFleetSupplyableSystemIDsFunc = &EmpireFleetSupplyableSystemIDsP; + auto empireFleetSupplyableSystemIDsFunc = &EmpireFleetSupplyableSystemIDsP; typedef std::pair FloatIntPair; typedef PairToTupleConverter FloatIntPairConverter; //boost::mpl::vector() - FloatIntPair ProductionCostAndTimeP(const Empire& empire, const ProductionQueue::Element& element) + FloatIntPair ProductionCostAndTimeP(const Empire& empire, + const ProductionQueue::Element& element) { return empire.ProductionCostAndTime(element); } - boost::function ProductionCostAndTimeFunc = &ProductionCostAndTimeP; + auto ProductionCostAndTimeFunc = &ProductionCostAndTimeP; //.def("availablePP", make_function(&ProductionQueue::AvailablePP, return_value_policy())) //.add_property("allocatedPP", make_function(&ProductionQueue::AllocatedPP, return_internal_reference<>())) //.def("objectsWithWastedPP", make_function(&ProductionQueue::ObjectsWithWastedPP, return_value_policy())) std::map, float> PlanetsWithAvailablePP_P(const Empire& empire) { - const std::shared_ptr& industry_pool = empire.GetResourcePool(RE_INDUSTRY); - const ProductionQueue& prodQueue = empire.GetProductionQueue(); - std::map, float> planetsWithAvailablePP; - for (const std::map, float>::value_type& objects_pp : prodQueue.AvailablePP(industry_pool)) { - std::set planetSet; - for (int object_id : objects_pp.first) { - if (/* std::shared_ptr planet = */ GetPlanet(object_id)) - planetSet.insert(object_id); + const auto& industry_pool = empire.GetResourcePool(RE_INDUSTRY); + const ProductionQueue& prod_queue = empire.GetProductionQueue(); + std::map, float> planets_with_available_pp; + for (const auto& objects_pp : prod_queue.AvailablePP(industry_pool)) { + std::set planets; + for (const auto& planet : Objects().find(objects_pp.first)) { + if (!planet) + continue; + planets.insert(planet->ID()); } - if (!planetSet.empty()) - planetsWithAvailablePP[planetSet] = objects_pp.second; + if (!planets.empty()) + planets_with_available_pp[planets] = objects_pp.second; } - return planetsWithAvailablePP; + return planets_with_available_pp; } - boost::function, float>(const Empire& )> PlanetsWithAvailablePP_Func = &PlanetsWithAvailablePP_P; + auto PlanetsWithAvailablePP_Func = &PlanetsWithAvailablePP_P; std::map, float> PlanetsWithAllocatedPP_P(const Empire& empire) { - const ProductionQueue& prodQueue = empire.GetProductionQueue(); - std::map, float> planetsWithAllocatedPP; - std::map, float> objectsWithAllocatedPP = prodQueue.AllocatedPP(); - for (const std::map, float>::value_type& objects_pp : objectsWithAllocatedPP) { - std::set planetSet; - for (int object_id : objects_pp.first) { - if (/* std::shared_ptr planet = */ GetPlanet(object_id)) - planetSet.insert(object_id); + const auto& prod_queue = empire.GetProductionQueue(); + std::map, float> planets_with_allocated_pp; + for (const auto& objects_pp : prod_queue.AllocatedPP()) { + std::set planets; + for (const auto& planet : Objects().find(objects_pp.first)) { + if (!planet) + continue; + planets.insert(planet->ID()); } - if (!planetSet.empty()) - planetsWithAllocatedPP[planetSet] = objects_pp.second; + if (!planets.empty()) + planets_with_allocated_pp[planets] = objects_pp.second; } - return planetsWithAllocatedPP; + return planets_with_allocated_pp; } - boost::function, float>(const Empire& )> PlanetsWithAllocatedPP_Func = &PlanetsWithAllocatedPP_P; + auto PlanetsWithAllocatedPP_Func = &PlanetsWithAllocatedPP_P; std::set> PlanetsWithWastedPP_P(const Empire& empire) { - const std::shared_ptr& industry_pool = empire.GetResourcePool(RE_INDUSTRY); - const ProductionQueue& prodQueue = empire.GetProductionQueue(); - std::set> planetsWithWastedPP; - std::set> objectsWithWastedPP = prodQueue.ObjectsWithWastedPP(industry_pool); - for (const std::set& objects : objectsWithWastedPP) { - std::set planetSet; - for (int object_id : objects) { - if (/* std::shared_ptr planet = */ GetPlanet(object_id)) - planetSet.insert(object_id); + const auto& industry_pool = empire.GetResourcePool(RE_INDUSTRY); + const ProductionQueue& prod_queue = empire.GetProductionQueue(); + std::set> planets_with_wasted_pp; + for (const auto& objects : prod_queue.ObjectsWithWastedPP(industry_pool)) { + std::set planets; + for (const auto& planet : Objects().find(objects)) { + if (!planet) + continue; + planets.insert(planet->ID()); } - if (!planetSet.empty()) - planetsWithWastedPP.insert(planetSet); + if (!planets.empty()) + planets_with_wasted_pp.insert(planets); } - return planetsWithWastedPP; + return planets_with_wasted_pp; + } + auto PlanetsWithWastedPP_Func = &PlanetsWithWastedPP_P; + + std::set ResearchedTechNames(const Empire& empire) { + std::set retval; + for (const auto& entry : empire.ResearchedTechs()) + retval.insert(entry.first); + return retval; } - boost::function>(const Empire&)> PlanetsWithWastedPP_Func = &PlanetsWithWastedPP_P; + auto ResearchTechNamesFunc = &ResearchedTechNames; + } namespace FreeOrionPython { @@ -253,15 +266,15 @@ namespace FreeOrionPython { class_>("IntPairVec") .def(boost::python::vector_indexing_suite, true>()) ; - class_>("ItemSpecVec") - .def(boost::python::vector_indexing_suite, true>()) + class_>("UnlockableItemVec") + .def(boost::python::vector_indexing_suite, true>()) ; boost::python::to_python_converter(); class_, boost::noncopyable>("resPool", boost::python::no_init); //FreeOrionPython::SetWrapper::Wrap("IntSet"); FreeOrionPython::SetWrapper::Wrap("IntSetSet"); - class_, float>> ("resPoolMap") + class_, float>>("resPoolMap") .def(boost::python::map_indexing_suite, float>, true>()) ; @@ -307,7 +320,11 @@ namespace FreeOrionPython { )) .def("techResearched", &Empire::TechResearched) - .add_property("availableTechs", make_function(&Empire::AvailableTechs, return_internal_reference<>())) + .add_property("availableTechs", make_function( + ResearchTechNamesFunc, + return_value_policy(), + boost::mpl::vector&, const Empire& >() + )) .def("getTechStatus", &Empire::GetTechStatus) .def("researchProgress", &Empire::ResearchProgress) .add_property("researchQueue", make_function(&Empire::GetResearchQueue, return_internal_reference<>())) @@ -329,6 +346,7 @@ namespace FreeOrionPython { .def("population", &Empire::Population) + .def("preservedLaneTravel", &Empire::PreservedLaneTravel) .add_property("fleetSupplyableSystemIDs", make_function( empireFleetSupplyableSystemIDsFunc, return_value_policy(), @@ -354,6 +372,10 @@ namespace FreeOrionPython { return_value_policy(), boost::mpl::vector, const Empire&>() )) + .def("getMeter", make_function( + EmpireGetMeter, + return_internal_reference<>()), + "Returns the empire meter with the indicated name (string).") ; //////////////////// @@ -405,21 +427,22 @@ namespace FreeOrionPython { .add_property("turnsLeft", &ProductionQueue::Element::turns_left_to_completion) .add_property("remaining", &ProductionQueue::Element::remaining) .add_property("blocksize", &ProductionQueue::Element::blocksize) + .add_property("paused", &ProductionQueue::Element::paused) + .add_property("allowedStockpile", &ProductionQueue::Element::allowed_imperial_stockpile_use) ; class_("productionQueue", no_init) .def("__iter__", iterator()) // ProductionQueue provides STL container-like interface to contained queue - .def("__getitem__", ProductionQueueOperatorSquareBrackets, return_internal_reference<>()) + .def("__getitem__", ProductionQueueOperatorSquareBrackets, return_internal_reference<>()) .def("__len__", &ProductionQueue::size) .add_property("size", &ProductionQueue::size) .add_property("empty", &ProductionQueue::empty) .add_property("totalSpent", &ProductionQueue::TotalPPsSpent) .add_property("empireID", &ProductionQueue::EmpireID) - .def("availablePP", make_function(&ProductionQueue::AvailablePP, return_value_policy())) - .add_property("allocatedPP", make_function(&ProductionQueue::AllocatedPP, return_internal_reference<>())) - .def("objectsWithWastedPP", make_function(&ProductionQueue::ObjectsWithWastedPP, return_value_policy())) + .def("availablePP", make_function(&ProductionQueue::AvailablePP, return_value_policy())) + .add_property("allocatedPP", make_function(&ProductionQueue::AllocatedPP, return_internal_reference<>())) + .def("objectsWithWastedPP", make_function(&ProductionQueue::ObjectsWithWastedPP,return_value_policy())) ; - ////////////////// // Tech // ////////////////// @@ -456,9 +479,9 @@ namespace FreeOrionPython { boost::python::setattr(techsInCategoryFunc, "__doc__", boost::python::str("Returns the names of all techs (StringVec) in the indicated tech category name (string).")); def("techsInCategory", techsInCategoryFunc); - class_("ItemSpec", init()) - .add_property("type", &ItemSpec::type) - .add_property("name", &ItemSpec::name) + class_("UnlockableItem", init()) + .add_property("type", &UnlockableItem::type) + .add_property("name", &UnlockableItem::name) ; /////////////////// @@ -495,7 +518,7 @@ namespace FreeOrionPython { /////////// // Color // /////////// - class_("GGColor", no_init) + class_("GGColor", init()) .add_property("r", &GG::Clr::r) .add_property("g", &GG::Clr::g) .add_property("b", &GG::Clr::b) diff --git a/python/EnumWrapper.cpp b/python/EnumWrapper.cpp index 2c98a404761..cf74c2d7d84 100644 --- a/python/EnumWrapper.cpp +++ b/python/EnumWrapper.cpp @@ -1,6 +1,9 @@ #include "../universe/Enums.h" +#include "../universe/ShipPart.h" +#include "../universe/UnlockableItem.h" #include "../Empire/Diplomacy.h" #include "../util/MultiplayerCommon.h" +#include "../util/GameRules.h" #include @@ -66,11 +69,13 @@ namespace FreeOrionPython { enum_("buildType") .value("building", BT_BUILDING) .value("ship", BT_SHIP) + .value("stockpile", BT_STOCKPILE) ; enum_("resourceType") .value("industry", RE_INDUSTRY) .value("trade", RE_TRADE) .value("research", RE_RESEARCH) + .value("stockpile", RE_STOCKPILE) ; enum_("meterType") .value("targetPopulation", METER_TARGET_POPULATION) @@ -89,6 +94,7 @@ namespace FreeOrionPython { .value("maxStructure", METER_MAX_STRUCTURE) .value("maxDefense", METER_MAX_DEFENSE) .value("maxSupply", METER_MAX_SUPPLY) + .value("maxStockpile", METER_MAX_STOCKPILE) .value("maxTroops", METER_MAX_TROOPS) .value("population", METER_POPULATION) @@ -107,6 +113,7 @@ namespace FreeOrionPython { .value("structure", METER_STRUCTURE) .value("defense", METER_DEFENSE) .value("supply", METER_SUPPLY) + .value("stockpile", METER_STOCKPILE) .value("troops", METER_TROOPS) .value("rebels", METER_REBEL_TROOPS) @@ -136,6 +143,18 @@ namespace FreeOrionPython { .value("destroy", CR_DESTROY) .value("retain", CR_RETAIN) ; + enum_("effectsCauseType") + .value("invalid", INVALID_EFFECTS_GROUP_CAUSE_TYPE) + .value("unknown", ECT_UNKNOWN_CAUSE) + .value("inherent", ECT_INHERENT) + .value("tech", ECT_TECH) + .value("building", ECT_BUILDING) + .value("field", ECT_FIELD) + .value("special", ECT_SPECIAL) + .value("species", ECT_SPECIES) + .value("shipPart", ECT_SHIP_PART) + .value("shipHull", ECT_SHIP_HULL) + ; enum_("shipSlotType") .value("external", SL_EXTERNAL) .value("internal", SL_INTERNAL) @@ -198,12 +217,19 @@ namespace FreeOrionPython { .value("ring", RING) .value("random", RANDOM) ; - enum_("ruleType") - .value("toggle", GameRules::TOGGLE) - .value("int", GameRules::INT) - .value("double", GameRules::DOUBLE) - .value("string", GameRules::STRING) - .value("int", GameRules::INVALID_RULE_TYPE); + enum_("ruleType") + .value("invalid", GameRules::Type::INVALID) + .value("toggle", GameRules::Type::TOGGLE) + .value("int", GameRules::Type::INT) + .value("double", GameRules::Type::DOUBLE) + .value("string", GameRules::Type::STRING) + ; + enum_("roleType") + .value("host", Networking::ROLE_HOST) + .value("clientTypeModerator", Networking::ROLE_CLIENT_TYPE_MODERATOR) + .value("clientTypePlayer", Networking::ROLE_CLIENT_TYPE_PLAYER) + .value("clientTypeObserver", Networking::ROLE_CLIENT_TYPE_OBSERVER) + .value("galaxySetup", Networking::ROLE_GALAXY_SETUP) ; } } diff --git a/python/LoggingWrapper.cpp b/python/LoggingWrapper.cpp index dc416d888b1..65e93c35487 100644 --- a/python/LoggingWrapper.cpp +++ b/python/LoggingWrapper.cpp @@ -47,46 +47,7 @@ namespace { } } - void ConfigurePythonFileSinkFrontEnd(LoggerTextFileSinkFrontend& sink_frontend, const std::string& channel_name) { - // Create the format - sink_frontend.set_formatter( - expr::stream - << expr::format_date_time("TimeStamp", "%H:%M:%S.%f") - << " [" << log_severity << "] " - << expr::message - ); - - // Set a filter to only format this channel - sink_frontend.set_filter(log_channel == channel_name); - } - - // Setup file sink, formatting, and \p name channel filter for \p logger. - void ConfigurePythonLogger(NamedThreadedLogger& logger, const std::string& name) { - if (name.empty()) - return; - - SetLoggerThreshold(name, default_log_level_threshold); - - ApplyConfigurationToFileSinkFrontEnd( - name, - std::bind(ConfigurePythonFileSinkFrontEnd, std::placeholders::_1, name)); - - LoggerCreatedSignal(name); - } - - // Place in source file to create the previously defined global logger \p name -#define DeclareThreadSafePythonLogger(name) \ - BOOST_LOG_INLINE_GLOBAL_LOGGER_INIT( \ - FO_GLOBAL_LOGGER_NAME(name), NamedThreadedLogger) \ - { \ - auto lg = NamedThreadedLogger( \ - (boost::log::keywords::severity = LogLevel::debug), \ - (boost::log::keywords::channel = #name)); \ - ConfigurePythonLogger(lg, #name); \ - return lg; \ - } - - DeclareThreadSafePythonLogger(python); + DeclareThreadSafeLogger(python); // Assemble a python message that is the same as the C++ message format. void PythonLogger(const std::string& msg, @@ -94,26 +55,33 @@ namespace { const std::string& python_logger, const std::string& filename, // const std::string& function_name, - const std::string& lineno) + const std::string& linenostr) { + int lineno{0}; + + try { + lineno = std::stoi(linenostr); + } catch(...) + {} + // Assembling the log in the stream input to the logger means that the // string assembly is gated by the log level. logs are not assembled // if that log level is disabled. switch (log_level) { case LogLevel::trace: - TraceLogger(python) << python_logger << " : " << filename << ":" /*<< function_name << ":"*/ << lineno << " - " << msg; + FO_LOGGER(LogLevel::trace, python) << python_logger << boost::log::add_value("SrcFilename", filename) << boost::log::add_value("SrcLinenum", lineno) << " : " << msg; break; case LogLevel::debug: - DebugLogger(python) << python_logger << " : " << filename << ":" /*<< function_name << ":"*/ << lineno << " - " << msg; + FO_LOGGER(LogLevel::debug, python) << python_logger << boost::log::add_value("SrcFilename", filename) << boost::log::add_value("SrcLinenum", lineno) << " : " << msg; break; case LogLevel::info: - InfoLogger(python) << python_logger << " : " << filename << ":" /*<< function_name << ":"*/ << lineno << " - " << msg; + FO_LOGGER(LogLevel::info, python) << python_logger << boost::log::add_value("SrcFilename", filename) << boost::log::add_value("SrcLinenum", lineno) << " : " << msg; break; case LogLevel::warn: - WarnLogger(python) << python_logger << " : " << filename << ":" /*<< function_name << ":"*/ << lineno << " - " << msg; + FO_LOGGER(LogLevel::warn, python) << python_logger << boost::log::add_value("SrcFilename", filename) << boost::log::add_value("SrcLinenum", lineno) << " : " << msg; break; case LogLevel::error: - ErrorLogger(python) << python_logger << " : " << filename << ":" /*<< function_name << ":"*/ << lineno << " - " << msg; + FO_LOGGER(LogLevel::error, python) << python_logger << boost::log::add_value("SrcFilename", filename) << boost::log::add_value("SrcLinenum", lineno) << " : " << msg; break; } } diff --git a/python/SetWrapper.h b/python/SetWrapper.h index 98c2854ee1f..29231fd402c 100644 --- a/python/SetWrapper.h +++ b/python/SetWrapper.h @@ -25,11 +25,11 @@ namespace FreeOrionPython { static unsigned int size(const Set& self) { return static_cast(self.size()); // ignore warning http://lists.boost.org/Archives/boost/2007/04/120377.php } - static bool empty(const Set& self) { return self.empty(); } - static bool contains(const Set& self, const ElementType& item) { return self.find(item) != self.end(); } - static unsigned int count(const Set& self, const ElementType& item) { return self.find(item) == self.end() ? 0u : 1u; } - static SetIterator begin(const Set& self) { return self.begin(); } - static SetIterator end(const Set& self) { return self.end(); } + static bool empty(const Set& self) { return self.empty(); } + static bool contains(const Set& self, const ElementType& item) { return self.count(item); } + static unsigned int count(const Set& self, const ElementType& item) { return self.count(item); } + static SetIterator begin(const Set& self) { return self.begin(); } + static SetIterator end(const Set& self) { return self.end(); } static void Wrap(const std::string& python_name) { class_(python_name.c_str(), no_init) diff --git a/python/UniverseWrapper.cpp b/python/UniverseWrapper.cpp index 36fa9e0555c..600fe754501 100644 --- a/python/UniverseWrapper.cpp +++ b/python/UniverseWrapper.cpp @@ -4,17 +4,27 @@ #include "../universe/Fleet.h" #include "../universe/Ship.h" #include "../universe/ShipDesign.h" +#include "../universe/ShipPart.h" +#include "../universe/ShipHull.h" #include "../universe/Building.h" +#include "../universe/BuildingType.h" #include "../universe/ResourceCenter.h" #include "../universe/PopCenter.h" #include "../universe/Planet.h" #include "../universe/System.h" #include "../universe/Field.h" +#include "../universe/FieldType.h" #include "../universe/Special.h" #include "../universe/Species.h" #include "../universe/Enums.h" +#include "../universe/Effect.h" +#include "../universe/Predicates.h" +#include "../universe/ScriptingContext.h" +#include "../universe/Condition.h" #include "../util/Logger.h" #include "../util/MultiplayerCommon.h" +#include "../util/GameRules.h" +#include "../util/AppInterface.h" #include #include @@ -48,118 +58,122 @@ namespace boost { namespace { void DumpObjects(const Universe& universe) { DebugLogger() << universe.Objects().Dump(); } + std::string ObjectDump(const UniverseObject& obj) + { return obj.Dump(0); } // We're returning the result of operator-> here so that python doesn't // need to deal with std::shared_ptr class. // Please don't use this trick elsewhere to grab a raw UniverseObject*! const UniverseObject* GetUniverseObjectP(const Universe& universe, int id) - { return ::GetUniverseObject(id).operator->(); } + { return ::Objects().get(id).operator->(); } const Ship* GetShipP(const Universe& universe, int id) - { return ::GetShip(id).operator->(); } + { return ::Objects().get(id).operator->(); } const Fleet* GetFleetP(const Universe& universe, int id) - { return ::GetFleet(id).operator->(); } + { return ::Objects().get(id).operator->(); } const Planet* GetPlanetP(const Universe& universe, int id) - { return ::GetPlanet(id).operator->(); } + { return ::Objects().get(id).operator->(); } const System* GetSystemP(const Universe& universe, int id) - { return ::GetSystem(id).operator->(); } + { return ::Objects().get(id).operator->(); } const Field* GetFieldP(const Universe& universe, int id) - { return ::GetField(id).operator->(); } + { return ::Objects().get(id).operator->(); } const Building* GetBuildingP(const Universe& universe, int id) - { return ::GetBuilding(id).operator->(); } - - std::vector ObjectIDs(const Universe& universe) - { return Objects().FindObjectIDs(); } - std::vector FleetIDs(const Universe& universe) - { return Objects().FindObjectIDs(); } - std::vector SystemIDs(const Universe& universe) - { return Objects().FindObjectIDs(); } - std::vector FieldIDs(const Universe& universe) - { return Objects().FindObjectIDs(); } - std::vector PlanetIDs(const Universe& universe) - { return Objects().FindObjectIDs(); } - std::vector ShipIDs(const Universe& universe) - { return Objects().FindObjectIDs(); } - std::vector BuildingIDs(const Universe& universe) - { return Objects().FindObjectIDs(); } + { return ::Objects().get(id).operator->(); } + + template + std::vector ObjectIDs(const Universe& universe) + { + std::vector result; + result.reserve(universe.Objects().size()); + for (const auto& obj : universe.Objects().all()) + result.push_back(obj->ID()); + return result; + } std::vectorSpeciesFoci(const Species& species) { std::vector retval; + retval.reserve(species.Foci().size()); for (const FocusType& focus : species.Foci()) retval.push_back(focus.Name()); return retval; } - void UpdateMetersWrapper(const Universe& universe, boost::python::list objList) { - std::vector objvec; - int const numObjects = boost::python::len(objList); - for (int i = 0; i < numObjects; i++) - objvec.push_back(boost::python::extract(objList[i])); + void UpdateMetersWrapper(const Universe& universe, const boost::python::object& objIter) { + boost::python::stl_input_iterator begin(objIter), end; + std::vector objvec(begin, end); GetUniverse().UpdateMeterEstimates(objvec); } - //void (Universe::*UpdateMeterEstimatesVoidFunc)(void) = &Universe::UpdateMeterEstimates; + //void (Universe::*UpdateMeterEstimatesVoidFunc)(void) = &Universe::UpdateMeterEstimates; - double LinearDistance(const Universe& universe, int system1_id, int system2_id) { - double retval = 9999999.9; // arbitrary large value - retval = universe.GetPathfinder()->LinearDistance(system1_id, system2_id); + double LinearDistance(const Universe& universe, int system1_id, int system2_id) { + double retval = universe.GetPathfinder()->LinearDistance(system1_id, system2_id); return retval; } - boost::function LinearDistanceFunc = &LinearDistance; + std::function LinearDistanceFunc = &LinearDistance; - int JumpDistanceBetweenObjects(const Universe& universe, int object1_id, int object2_id) { + int JumpDistanceBetweenObjects(const Universe& universe, int object1_id, int object2_id) { return universe.GetPathfinder()->JumpDistanceBetweenObjects(object1_id, object2_id); } - boost::function JumpDistanceFunc = &JumpDistanceBetweenObjects; + std::function JumpDistanceFunc = &JumpDistanceBetweenObjects; - std::vector ShortestPath(const Universe& universe, int start_sys, int end_sys, int empire_id) { + std::vector ShortestPath(const Universe& universe, int start_sys, int end_sys, int empire_id) { std::vector retval; std::pair, int> path = universe.GetPathfinder()->ShortestPath(start_sys, end_sys, empire_id); std::copy(path.first.begin(), path.first.end(), std::back_inserter(retval)); return retval; } - boost::function(const Universe&, int, int, int)> ShortestPathFunc = &ShortestPath; + std::function (const Universe&, int, int, int)> ShortestPathFunc = &ShortestPath; - double ShortestPathDistance(const Universe& universe, int object1_id, int object2_id) { + std::vector ShortestNonHostilePath(const Universe& universe, int start_sys, int end_sys, int empire_id) { + std::vector retval; + auto fleet_pred = std::make_shared(empire_id); + std::pair, int> path = universe.GetPathfinder()->ShortestPath(start_sys, end_sys, empire_id, fleet_pred); + std::copy(path.first.begin(), path.first.end(), std::back_inserter(retval)); + return retval; + } + std::function (const Universe&, int, int, int)> ShortestNonHostilePathFunc = &ShortestNonHostilePath; + + double ShortestPathDistance(const Universe& universe, int object1_id, int object2_id) { return universe.GetPathfinder()->ShortestPathDistance(object1_id, object2_id); } - boost::function ShortestPathDistanceFunc = &ShortestPathDistance; + std::function ShortestPathDistanceFunc = &ShortestPathDistance; - std::vector LeastJumpsPath(const Universe& universe, int start_sys, int end_sys, int empire_id) { + std::vector LeastJumpsPath(const Universe& universe, int start_sys, int end_sys, int empire_id) { std::vector retval; std::pair, int> path = universe.GetPathfinder()->LeastJumpsPath(start_sys, end_sys, empire_id); std::copy(path.first.begin(), path.first.end(), std::back_inserter(retval)); return retval; } - boost::function(const Universe&, int, int, int)> LeastJumpsFunc = &LeastJumpsPath; + std::function (const Universe&, int, int, int)> LeastJumpsFunc = &LeastJumpsPath; - bool SystemsConnectedP(const Universe& universe, int system1_id, int system2_id, int empire_id=ALL_EMPIRES) { + bool SystemsConnectedP(const Universe& universe, int system1_id, int system2_id, int empire_id=ALL_EMPIRES) { //DebugLogger() << "SystemsConnected!(" << system1_id << ", " << system2_id << ")"; bool retval = universe.GetPathfinder()->SystemsConnected(system1_id, system2_id, empire_id); //DebugLogger() << "SystemsConnected! retval: " << retval; return retval; } - boost::function SystemsConnectedFunc = &SystemsConnectedP; + std::function SystemsConnectedFunc = &SystemsConnectedP; - bool SystemHasVisibleStarlanesP(const Universe& universe, int system_id, int empire_id = ALL_EMPIRES) { + bool SystemHasVisibleStarlanesP(const Universe& universe, int system_id, int empire_id = ALL_EMPIRES) { return universe.GetPathfinder()->SystemHasVisibleStarlanes(system_id, empire_id); } - boost::function SystemHasVisibleStarlanesFunc = &SystemHasVisibleStarlanesP; + std::function SystemHasVisibleStarlanesFunc = &SystemHasVisibleStarlanesP; - std::vector ImmediateNeighborsP(const Universe& universe, int system1_id, int empire_id = ALL_EMPIRES) { + std::vector ImmediateNeighborsP(const Universe& universe, int system1_id, int empire_id = ALL_EMPIRES) { std::vector retval; - for (const std::multimap::value_type& entry : universe.GetPathfinder()->ImmediateNeighbors(system1_id, empire_id)) + for (const auto& entry : universe.GetPathfinder()->ImmediateNeighbors(system1_id, empire_id)) { retval.push_back(entry.second); } return retval; } - boost::function (const Universe&, int, int)> ImmediateNeighborsFunc = &ImmediateNeighborsP; + std::function (const Universe&, int, int)> ImmediateNeighborsFunc = &ImmediateNeighborsP; - std::map SystemNeighborsMapP(const Universe& universe, int system1_id, int empire_id = ALL_EMPIRES) { - std::map retval; - for (const std::multimap::value_type& entry : universe.GetPathfinder()->ImmediateNeighbors(system1_id, empire_id)) + std::map SystemNeighborsMapP(const Universe& universe, int system1_id, int empire_id = ALL_EMPIRES) { + std::map retval; + for (const auto& entry : universe.GetPathfinder()->ImmediateNeighbors(system1_id, empire_id)) { retval[entry.second] = entry.first; } return retval; } - boost::function (const Universe&, int, int)> SystemNeighborsMapFunc = &SystemNeighborsMapP; + std::function (const Universe&, int, int)> SystemNeighborsMapFunc = &SystemNeighborsMapP; const Meter* (UniverseObject::*ObjectGetMeter)(MeterType) const = &UniverseObject::GetMeter; const std::map& @@ -167,7 +181,8 @@ namespace { std::vector ObjectSpecials(const UniverseObject& object) { std::vector retval; - for (const std::map>::value_type& special : object.Specials()) + retval.reserve(object.Specials().size()); + for (const auto& special : object.Specials()) { retval.push_back(special.first); } return retval; } @@ -176,13 +191,17 @@ namespace { const Ship::PartMeterMap& (Ship::*ShipPartMeters)(void) const = &Ship::PartMeters; - const std::string& ShipDesignName(const ShipDesign& ship_design) + const ShipHull* ShipDesignHullP(const ShipDesign& design) + { return GetShipHull(design.Hull()); } + std::function ShipDesignHullFunc = &ShipDesignHullP; + + const std::string& ShipDesignName(const ShipDesign& ship_design) { return ship_design.Name(false); } - boost::function ShipDesignNameFunc = &ShipDesignName; + std::function ShipDesignNameFunc = &ShipDesignName; - const std::string& ShipDesignDescription(const ShipDesign& ship_design) + const std::string& ShipDesignDescription(const ShipDesign& ship_design) { return ship_design.Description(false); } - boost::function ShipDesignDescriptionFunc = &ShipDesignDescription; + std::function ShipDesignDescriptionFunc = &ShipDesignDescription; bool (*ValidDesignHullAndParts)(const std::string& hull, const std::vector& parts) = &ShipDesign::ValidDesign; @@ -191,30 +210,30 @@ namespace { // The following (PartsSlotType) is not currently used, but left as an example for this kind of wrapper //std::vector (ShipDesign::*PartsSlotType)(ShipSlotType) const = &ShipDesign::Parts; - std::vector AttackStatsP(const ShipDesign& ship_design) { + std::vector AttackStatsP(const ShipDesign& ship_design) { std::vector results; + results.reserve(ship_design.Parts().size()); for (const std::string& part_name : ship_design.Parts()) { - const PartType* part = GetPartType(part_name); - if (part && part->Class() == PC_DIRECT_WEAPON) { // TODO: handle other weapon classes when they are implemented + const ShipPart* part = GetShipPart(part_name); + if (part && part->Class() == PC_DIRECT_WEAPON) // TODO: handle other weapon classes when they are implemented results.push_back(part->Capacity()); - } } return results; } - boost::function (const ShipDesign&)> AttackStatsFunc = &AttackStatsP; + std::function (const ShipDesign&)> AttackStatsFunc = &AttackStatsP; - std::vector HullSlots(const HullType& hull) { + std::vector HullSlots(const ShipHull& hull) { std::vector retval; - for (const HullType::Slot& slot : hull.Slots()) + for (const ShipHull::Slot& slot : hull.Slots()) retval.push_back(slot.type); return retval; } - boost::function (const HullType&)> HullSlotsFunc = &HullSlots; + std::function (const ShipHull&)> HullSlotsFunc = &HullSlots; - unsigned int (HullType::*NumSlotsTotal)(void) const = &HullType::NumSlots; - unsigned int (HullType::*NumSlotsOfSlotType)(ShipSlotType) const = &HullType::NumSlots; + unsigned int (ShipHull::*NumSlotsTotal)(void) const = &ShipHull::NumSlots; + unsigned int (ShipHull::*NumSlotsOfSlotType)(ShipSlotType) const = &ShipHull::NumSlots; - bool ObjectInField(const Field& field, const UniverseObject& obj) + bool ObjectInField(const Field& field, const UniverseObject& obj) { return field.InField(obj.X(), obj.Y()); } bool (Field::*LocationInField)(double x, double y) const = &Field::InField; @@ -223,10 +242,30 @@ namespace { bool EnqueueLocationTest(const BuildingType& building_type, int empire_id, int loc_id) { return building_type.EnqueueLocation(empire_id, loc_id);} - bool RuleExistsAnyType(const GameRules& rules, const std::string& name) + bool HullProductionLocation(const ShipHull& hull, int location_id) { + auto location = Objects().get(location_id); + if (!location) { + ErrorLogger() << "UniverseWrapper::HullProductionLocation Could not find location with id " << location_id; + return false; + } + ScriptingContext location_as_source_context(location, location); + return hull.Location()->Eval(location_as_source_context, location); + } + + bool ShipPartProductionLocation(const ShipPart& part_type, int location_id) { + auto location = Objects().get(location_id); + if (!location) { + ErrorLogger() << "UniverseWrapper::PartTypeProductionLocation Could not find location with id " << location_id; + return false; + } + ScriptingContext location_as_source_context(location, location); + return part_type.Location()->Eval(location_as_source_context, location); + } + + bool RuleExistsAnyType(const GameRules& rules, const std::string& name) { return rules.RuleExists(name); } - bool RuleExistsWithType(const GameRules& rules, const std::string& name, GameRules::RuleType rule_type) - { return rules.RuleExists(name, rule_type); } + bool RuleExistsWithType(const GameRules& rules, const std::string& name, GameRules::Type type) + { return rules.RuleExists(name, type); } } namespace FreeOrionPython { @@ -272,12 +311,16 @@ namespace FreeOrionPython { class_>("IntFltMap") .def(boost::python::map_indexing_suite, true>()) ; - class_>("VisibilityIntMap") + class_>("VisibilityIntMap") .def(boost::python::map_indexing_suite, true>()) ; class_>("ShipSlotVec") .def(boost::python::vector_indexing_suite, true>()) ; + class_>("StringsMap") + .def(boost::python::map_indexing_suite, true>()) + ; + class_>("MeterTypeMeterMap") .def(boost::python::map_indexing_suite, true>()) ; @@ -290,13 +333,48 @@ namespace FreeOrionPython { .def(boost::python::map_indexing_suite()) ; + class_>>>("StatRecordsMap") + .def(boost::python::map_indexing_suite< + std::map>>, true>()) + ; + class_>>("IntIntDblMapMap") + .def(boost::python::map_indexing_suite< + std::map>, true>()) + ; + + /////////////////////////// + // Effect Accounting // + /////////////////////////// + class_("EffectCause") + .add_property("causeType", &Effect::AccountingInfo::cause_type) + .def_readonly("specificCause", &Effect::AccountingInfo::specific_cause) + .def_readonly("customLabel", &Effect::AccountingInfo::custom_label) + ; + class_>("AccountingInfo") + .add_property("sourceID", &Effect::AccountingInfo::source_id) + .add_property("meterChange", &Effect::AccountingInfo::meter_change) + .add_property("meterRunningTotal", &Effect::AccountingInfo::running_meter_total) + ; + class_>("AccountingInfoVec") + .def(boost::python::vector_indexing_suite< + std::vector, true>()) + ; + class_>>("MeterTypeAccountingInfoVecMap") + .def(boost::python::map_indexing_suite< + std::map>, true>()) + ; + class_("TargetIDAccountingMapMap") + .def(boost::python::map_indexing_suite< + Effect::AccountingMap, true>()) + ; + /////////////// // Meter // /////////////// class_("meter", no_init) .add_property("current", &Meter::Current) .add_property("initial", &Meter::Initial) - .add_property("dump", &Meter::Dump) + .def("dump", &Meter::Dump, return_value_policy(), "Returns string with debug information, use '0' as argument.") ; //////////////////// @@ -312,15 +390,15 @@ namespace FreeOrionPython { .def("getBuilding", make_function(GetBuildingP, return_value_policy())) .def("getGenericShipDesign", &Universe::GetGenericShipDesign, return_value_policy(), "Returns the ship design (ShipDesign) with the indicated name (string).") - .add_property("allObjectIDs", make_function(ObjectIDs, return_value_policy())) - .add_property("fleetIDs", make_function(FleetIDs, return_value_policy())) - .add_property("systemIDs", make_function(SystemIDs, return_value_policy())) - .add_property("fieldIDs", make_function(FieldIDs, return_value_policy())) - .add_property("planetIDs", make_function(PlanetIDs, return_value_policy())) - .add_property("shipIDs", make_function(ShipIDs, return_value_policy())) - .add_property("buildingIDs", make_function(BuildingIDs, return_value_policy())) + .add_property("allObjectIDs", make_function(ObjectIDs,return_value_policy())) + .add_property("fleetIDs", make_function(ObjectIDs, return_value_policy())) + .add_property("systemIDs", make_function(ObjectIDs, return_value_policy())) + .add_property("fieldIDs", make_function(ObjectIDs, return_value_policy())) + .add_property("planetIDs", make_function(ObjectIDs, return_value_policy())) + .add_property("shipIDs", make_function(ObjectIDs, return_value_policy())) + .add_property("buildingIDs", make_function(ObjectIDs, return_value_policy())) .def("destroyedObjectIDs", make_function(&Universe::EmpireKnownDestroyedObjectIDs, - return_value_policy())) + return_value_policy())) .def("systemHasStarlane", make_function( SystemHasVisibleStarlanesFunc, @@ -329,6 +407,8 @@ namespace FreeOrionPython { )) .def("updateMeterEstimates", &UpdateMetersWrapper) + .add_property("effectAccounting", make_function(&Universe::GetEffectAccountingMap, + return_value_policy())) .def("linearDistance", make_function( LinearDistanceFunc, @@ -354,6 +434,16 @@ namespace FreeOrionPython { boost::mpl::vector, const Universe&, int, int, int>() )) + .def("shortestNonHostilePath", make_function( + ShortestNonHostilePathFunc, + return_value_policy(), + boost::mpl::vector, const Universe&, int, int, int>() + ), + "Shortest sequence of System ids and distance from System (number1) to " + "System (number2) with no hostile Fleets as determined by visibility " + "of Empire (number3). (number3) must be a valid empire." + ) + .def("shortestPathDistance", make_function( ShortestPathDistanceFunc, return_value_policy(), @@ -394,6 +484,16 @@ namespace FreeOrionPython { return_value_policy() )) + // Indexed by stat name (string), contains a map indexed by empire id, + // contains a map from turn number (int) to stat value (double). + .def("statRecords", make_function( + &Universe::GetStatRecords, + return_value_policy()), + "Empire statistics recorded by the server each turn. Indexed first by " + "staistic name (string), then by empire id (int), then by turn " + "number (int), pointing to the statisic value (double)." + ) + .def("dump", &DumpObjects) ; @@ -420,12 +520,11 @@ namespace FreeOrionPython { .add_property("containerObject", &UniverseObject::ContainerObjectID) .def("currentMeterValue", &UniverseObject::CurrentMeterValue) .def("initialMeterValue", &UniverseObject::InitialMeterValue) - .def("nextTurnCurrentMeterValue", &UniverseObject::NextTurnCurrentMeterValue) .add_property("tags", make_function(&UniverseObject::Tags, return_value_policy())) .def("hasTag", &UniverseObject::HasTag) .add_property("meters", make_function(ObjectMeters, return_internal_reference<>())) .def("getMeter", make_function(ObjectGetMeter, return_internal_reference<>())) - .add_property("dump", &UniverseObject::Dump) + .def("dump", make_function(&ObjectDump, return_value_policy()), "Returns string with debug information.") ; /////////////////// @@ -459,6 +558,9 @@ namespace FreeOrionPython { .add_property("designID", &Ship::DesignID) .add_property("fleetID", &Ship::FleetID) .add_property("producedByEmpireID", &Ship::ProducedByEmpireID) + .add_property("arrivedOnTurn", &Ship::ArrivedOnTurn) + .add_property("lastResuppliedOnTurn", &Ship::LastResuppliedOnTurn) + .add_property("lastTurnActiveInCombat", &Ship::LastTurnActiveInCombat) .add_property("isMonster", &Ship::IsMonster) .add_property("isArmed", &Ship::IsArmed) .add_property("hasFighters", &Ship::HasFighters) @@ -511,6 +613,7 @@ namespace FreeOrionPython { .add_property("canInvade", make_function(&ShipDesign::HasTroops, return_value_policy())) .add_property("isArmed", make_function(&ShipDesign::IsArmed, return_value_policy())) .add_property("hasFighters", make_function(&ShipDesign::HasFighters, return_value_policy())) + .add_property("hasDirectWeapons", make_function(&ShipDesign::HasDirectWeapons,return_value_policy())) .add_property("isMonster", make_function(&ShipDesign::IsMonster, return_value_policy())) .def("productionCost", &ShipDesign::ProductionCost) .def("productionTime", &ShipDesign::ProductionTime) @@ -518,7 +621,11 @@ namespace FreeOrionPython { .add_property("costTimeLocationInvariant", &ShipDesign::ProductionCostTimeLocationInvariant) .add_property("hull", make_function(&ShipDesign::Hull, return_value_policy())) - .add_property("hull_type", make_function(&ShipDesign::GetHull, return_value_policy())) + .add_property("ship_hull", make_function( + ShipDesignHullFunc, + return_value_policy(), + boost::mpl::vector() + )) .add_property("parts", make_function(PartsVoid, return_internal_reference<>())) .add_property("attackStats", make_function( AttackStatsFunc, @@ -527,46 +634,50 @@ namespace FreeOrionPython { )) .def("productionLocationForEmpire", &ShipDesign::ProductionLocation) - .add_property("dump", &ShipDesign::Dump) + .def("dump", &ShipDesign::Dump, return_value_policy(), "Returns string with debug information, use '0' as argument.") ; def("validShipDesign", ValidDesignHullAndParts, "Returns true (boolean) if the passed hull (string) and parts (StringVec) make up a valid ship design, and false (boolean) otherwise. Valid ship designs don't have any parts in slots that can't accept that type of part, and contain only hulls and parts that exist (and may also need to contain the correct number of parts - this needs to be verified)."); def("getShipDesign", &GetShipDesign, return_value_policy(), "Returns the ship design (ShipDesign) with the indicated id number (int)."); - - class_("partType", no_init) - .add_property("name", make_function(&PartType::Name, return_value_policy())) - .add_property("partClass", &PartType::Class) - .add_property("capacity", &PartType::Capacity) - .add_property("secondaryStat", &PartType::SecondaryStat) - .add_property("mountableSlotTypes", make_function(&PartType::MountableSlotTypes,return_value_policy())) - .def("productionCost", &PartType::ProductionCost) - .def("productionTime", &PartType::ProductionTime) - .def("canMountInSlotType", &PartType::CanMountInSlotType) + def("getPredefinedShipDesign", &GetPredefinedShipDesign, return_value_policy(), "Returns the ship design (ShipDesign) with the indicated name (string)."); + + + class_("shipPart", no_init) + .add_property("name", make_function(&ShipPart::Name, return_value_policy())) + .add_property("partClass", &ShipPart::Class) + .add_property("capacity", &ShipPart::Capacity) + .add_property("secondaryStat", &ShipPart::SecondaryStat) + .add_property("mountableSlotTypes", make_function(&ShipPart::MountableSlotTypes,return_value_policy())) + .def("productionCost", &ShipPart::ProductionCost) + .def("productionTime", &ShipPart::ProductionTime) + .def("canMountInSlotType", &ShipPart::CanMountInSlotType) .add_property("costTimeLocationInvariant", - &PartType::ProductionCostTimeLocationInvariant) + &ShipPart::ProductionCostTimeLocationInvariant) + .def("productionLocation", &ShipPartProductionLocation, "Returns the result of Location condition (bool) in passed location_id (int)") ; - def("getPartType", &GetPartType, return_value_policy(), "Returns the ship part (PartType) with the indicated name (string)."); + def("getShipPart", &GetShipPart, return_value_policy(), "Returns the ShipPart with the indicated name (string)."); - class_("hullType", no_init) - .add_property("name", make_function(&HullType::Name, return_value_policy())) + class_("shipHull", no_init) + .add_property("name", make_function(&ShipHull::Name, return_value_policy())) .add_property("numSlots", make_function(NumSlotsTotal, return_value_policy())) - .add_property("structure", &HullType::Structure) - .add_property("stealth", &HullType::Stealth) - .add_property("fuel", &HullType::Fuel) - .add_property("starlaneSpeed", &HullType::Speed) // TODO: Remove this after transition period - .add_property("speed", &HullType::Speed) + .add_property("structure", &ShipHull::Structure) + .add_property("stealth", &ShipHull::Stealth) + .add_property("fuel", &ShipHull::Fuel) + .add_property("starlaneSpeed", &ShipHull::Speed) // TODO: Remove this after transition period + .add_property("speed", &ShipHull::Speed) .def("numSlotsOfSlotType", NumSlotsOfSlotType) .add_property("slots", make_function( HullSlotsFunc, return_value_policy(), - boost::mpl::vector, const HullType&>() + boost::mpl::vector, const ShipHull&>() )) - .def("productionCost", &HullType::ProductionCost) - .def("productionTime", &HullType::ProductionTime) + .def("productionCost", &ShipHull::ProductionCost) + .def("productionTime", &ShipHull::ProductionTime) .add_property("costTimeLocationInvariant", - &HullType::ProductionCostTimeLocationInvariant) - .def("hasTag", &HullType::HasTag) + &ShipHull::ProductionCostTimeLocationInvariant) + .def("hasTag", &ShipHull::HasTag) + .def("productionLocation", &HullProductionLocation, "Returns the result of Location condition (bool) in passed location_id (int)") ; - def("getHullType", &GetHullType, return_value_policy(), "Returns the ship hull (HullType) with the indicated name (string)."); + def("getShipHull", &GetShipHull, return_value_policy(), "Returns the ship hull with the indicated name (string)."); ////////////////// // Building // @@ -592,14 +703,14 @@ namespace FreeOrionPython { .def("canBeEnqueued", &EnqueueLocationTest) //(int empire_id, int location_id) .add_property("costTimeLocationInvariant", &BuildingType::ProductionCostTimeLocationInvariant) - .add_property("dump", &BuildingType::Dump) + .def("dump", &BuildingType::Dump, return_value_policy(), "Returns string with debug information, use '0' as argument.") ; def("getBuildingType", &GetBuildingType, return_value_policy(), "Returns the building type (BuildingType) with the indicated name (string)."); //////////////////// // ResourceCenter // //////////////////// class_("resourceCenter", no_init) - .add_property("focus", make_function(&ResourceCenter::Focus, return_value_policy())) + .add_property("focus", make_function(&ResourceCenter::Focus, return_value_policy())) .add_property("turnsSinceFocusChange" , &ResourceCenter::TurnsSinceFocusChange) .add_property("availableFoci", &ResourceCenter::AvailableFoci) ; @@ -609,7 +720,6 @@ namespace FreeOrionPython { /////////////////// class_("popCenter", no_init) .add_property("speciesName", make_function(&PopCenter::SpeciesName, return_value_policy())) - .add_property("nextTurnPopGrowth", &PopCenter::NextTurnPopGrowth) ; ////////////////// @@ -630,8 +740,10 @@ namespace FreeOrionPython { .add_property("InitialOrbitalPosition", &Planet::InitialOrbitalPosition) .def("OrbitalPositionOnTurn", &Planet::OrbitalPositionOnTurn) .add_property("RotationalPeriod", &Planet::RotationalPeriod) - //.add_property("AxialTilt", &Planet::AxialTilt) + .add_property("LastTurnAttackedByShip", &Planet::LastTurnAttackedByShip) + .add_property("LastTurnConquered", &Planet::LastTurnConquered) .add_property("buildingIDs", make_function(&Planet::BuildingIDs, return_internal_reference<>())) + .add_property("habitableSize", &Planet::HabitableSize) ; ////////////////// @@ -667,7 +779,7 @@ namespace FreeOrionPython { class_("fieldType", no_init) .add_property("name", make_function(&FieldType::Name, return_value_policy())) .add_property("description", make_function(&FieldType::Description, return_value_policy())) - .add_property("dump", &FieldType::Dump) + .def("dump", &FieldType::Dump, return_value_policy(), "Returns string with debug information, use '0' as argument.") ; def("getFieldType", &GetFieldType, return_value_policy()); @@ -680,7 +792,7 @@ namespace FreeOrionPython { .add_property("description", &Special::Description) .add_property("spawnrate", make_function(&Special::SpawnRate, return_value_policy())) .add_property("spawnlimit", make_function(&Special::SpawnLimit, return_value_policy())) - .add_property("dump", &Special::Dump) + .def("dump", &Special::Dump, return_value_policy(), "Returns string with debug information, use '0' as argument.") .def("initialCapacity", SpecialInitialCapacityOnObject) ; def("getSpecial", &GetSpecial, return_value_policy(), "Returns the special (Special) with the indicated name (string)."); @@ -699,7 +811,7 @@ namespace FreeOrionPython { .add_property("tags", make_function(&Species::Tags, return_value_policy())) // TODO: const std::vector& Species::Foci() .def("getPlanetEnvironment", &Species::GetPlanetEnvironment) - .add_property("dump", &Species::Dump) + .def("dump", &Species::Dump, return_value_policy(), "Returns string with debug information, use '0' as argument.") ; def("getSpecies", &GetSpecies, return_value_policy(), "Returns the species (Species) with the indicated name (string)."); } @@ -715,20 +827,13 @@ namespace FreeOrionPython { .add_property("specialsFrequency", make_function(&GalaxySetupData::GetSpecialsFreq, return_value_policy())) .add_property("monsterFrequency", make_function(&GalaxySetupData::GetMonsterFreq, return_value_policy())) .add_property("nativeFrequency", make_function(&GalaxySetupData::GetNativeFreq, return_value_policy())) - .add_property("maxAIAggression", make_function(&GalaxySetupData::GetAggression, return_value_policy())); - - //std::vector> - class_>("RuleValueStringStringPair") - .add_property("name", &std::pair::first) - .add_property("value", &std::pair::second) - ; - class_>>("RuleValueStringsVec") - .def(boost::python::vector_indexing_suite>, true>()) - ; + .add_property("maxAIAggression", make_function(&GalaxySetupData::GetAggression, return_value_policy())) + .add_property("gameUID", make_function(&GalaxySetupData::GetGameUID, return_value_policy()), + &GalaxySetupData::SetGameUID); class_("GameRules", no_init) .add_property("empty", make_function(&GameRules::Empty, return_value_policy())) - .add_property("getRulesAsStrings", make_function(&GameRules::GetRulesAsStrings, return_value_policy())) + .def("getRulesAsStrings", make_function(&GameRules::GetRulesAsStrings, return_value_policy())) .def("ruleExists", RuleExistsAnyType) .def("ruleExistsWithType", RuleExistsWithType) .def("getDescription", make_function(&GameRules::GetDescription, return_value_policy())) diff --git a/python/server/CMakeLists.txt b/python/server/CMakeLists.txt deleted file mode 100644 index aee54c73396..00000000000 --- a/python/server/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -target_sources(freeoriond - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/ServerWrapper.h - ${CMAKE_CURRENT_LIST_DIR}/ServerFramework.h - PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/ServerWrapper.cpp - ${CMAKE_CURRENT_LIST_DIR}/ServerFramework.cpp -) diff --git a/python/server/ServerFramework.cpp b/python/server/ServerFramework.cpp deleted file mode 100644 index 4a6f2d16746..00000000000 --- a/python/server/ServerFramework.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "ServerFramework.h" - -#include "ServerWrapper.h" -#include "../SetWrapper.h" -#include "../CommonWrappers.h" - -#include "../../util/Logger.h" -#include "../../universe/Universe.h" -#include "../../universe/UniverseGenerator.h" - -#include -#include -#include -#include -#include - - -#include - -using boost::python::object; -using boost::python::class_; -using boost::python::import; -using boost::python::error_already_set; -using boost::python::dict; -using boost::python::vector_indexing_suite; -using boost::python::map_indexing_suite; - -namespace fs = boost::filesystem; - -BOOST_PYTHON_MODULE(freeorion) { - boost::python::docstring_options doc_options(true, true, false); - - FreeOrionPython::WrapGameStateEnums(); - FreeOrionPython::WrapGalaxySetupData(); - FreeOrionPython::WrapEmpire(); - FreeOrionPython::WrapUniverseClasses(); - FreeOrionPython::WrapServer(); - - // STL Containers - class_>("IntVec") - .def(vector_indexing_suite>()) - ; - class_>("StringVec") - .def(vector_indexing_suite>()) - ; - - class_>("IntBoolMap") - .def(map_indexing_suite>()) - ; - - FreeOrionPython::SetWrapper::Wrap("IntSet"); - FreeOrionPython::SetWrapper::Wrap("StringSet"); -} - -namespace { -} - -bool PythonServer::InitModules() { - DebugLogger() << "Initializing server Python modules"; - - // Allow the "freeorion" C++ module to be imported within Python code - try { - initfreeorion(); - } catch (...) { - ErrorLogger() << "Unable to initialize 'freeorion' server Python module"; - return false; - } - - // Confirm existence of the directory containing the universe generation - // Python scripts and add it to Pythons sys.path to make sure Python will - // find our scripts - if (!fs::exists(GetPythonUniverseGeneratorDir())) { - ErrorLogger() << "Can't find folder containing universe generation scripts"; - return false; - } - AddToSysPath(GetPythonUniverseGeneratorDir()); - - // import universe generator script file - m_python_module_universe_generator = import("universe_generator"); - - // Confirm existence of the directory containing the turn event Python - // scripts and add it to Pythons sys.path to make sure Python will find - // our scripts - if (!fs::exists(GetPythonTurnEventsDir())) { - ErrorLogger() << "Can't find folder containing turn events scripts"; - return false; - } - AddToSysPath(GetPythonTurnEventsDir()); - - // import universe generator script file - m_python_module_turn_events = import("turn_events"); - - DebugLogger() << "Server Python modules successfully initialized!"; - return true; -} - -bool PythonServer::CreateUniverse(std::map& player_setup_data) { - dict py_player_setup_data; - - // the universe generator module should contain an "error_report" function, - // so set the ErrorReport member function to use it - SetErrorModule(m_python_module_universe_generator); - - for (std::map::value_type& psd : player_setup_data) { - py_player_setup_data[psd.first] = object(psd.second); - } - - object f = m_python_module_universe_generator.attr("create_universe"); - if (!f) { - ErrorLogger() << "Unable to call Python function create_universe "; - return false; - } - - return f(py_player_setup_data); -} - -bool PythonServer::ExecuteTurnEvents() { - object f = m_python_module_turn_events.attr("execute_turn_events"); - if (!f) { - ErrorLogger() << "Unable to call Python function execute_turn_events "; - return false; - } - return f(); -} - -const std::string GetPythonUniverseGeneratorDir() -{ return GetPythonDir() + "/universe_generation"; } - -const std::string GetPythonTurnEventsDir() -{ return GetPythonDir() + "/turn_events"; } diff --git a/python/server/ServerFramework.h b/python/server/ServerFramework.h deleted file mode 100644 index a8b22fc86b3..00000000000 --- a/python/server/ServerFramework.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef __FreeOrion__Python__ServerFramework__ -#define __FreeOrion__Python__ServerFramework__ - -#include "../CommonFramework.h" -#include "../../util/MultiplayerCommon.h" - -#include - - -class PythonServer : public PythonBase { -public: - /** Initializes server Python modules. */ - bool InitModules() override; - - bool CreateUniverse(std::map& player_setup_data); // Wraps call to the main Python universe generator function - bool ExecuteTurnEvents(); // Wraps call to the main Python turn events function - -private: - // reference to imported Python universe generator module - boost::python::object m_python_module_universe_generator; - - // reference to imported Python turn events module - boost::python::object m_python_module_turn_events; -}; - -// Returns folder containing the Python universe generator scripts -const std::string GetPythonUniverseGeneratorDir(); - -// Returns folder containing the Python turn events scripts -const std::string GetPythonTurnEventsDir(); - - -#endif /* defined(__FreeOrion__Python__ServerFramework__) */ diff --git a/python/server/ServerWrapper.h b/python/server/ServerWrapper.h deleted file mode 100644 index d9fef63ee87..00000000000 --- a/python/server/ServerWrapper.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef __FreeOrion__Python__ServerWrapper__ -#define __FreeOrion__Python__ServerWrapper__ - -namespace FreeOrionPython { - void WrapServer(); -} - -#endif /* defined(__FreeOrion__Python__ServerWrapper__) */ diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 8c8f532e0f3..7b43bf43503 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -2,10 +2,18 @@ target_sources(freeoriond PUBLIC ${CMAKE_CURRENT_LIST_DIR}/SaveLoad.h ${CMAKE_CURRENT_LIST_DIR}/ServerApp.h + ${CMAKE_CURRENT_LIST_DIR}/ServerFramework.h ${CMAKE_CURRENT_LIST_DIR}/ServerFSM.h + ${CMAKE_CURRENT_LIST_DIR}/ServerNetworking.h + ${CMAKE_CURRENT_LIST_DIR}/ServerWrapper.h + ${CMAKE_CURRENT_LIST_DIR}/UniverseGenerator.h PRIVATE ${CMAKE_CURRENT_LIST_DIR}/dmain.cpp ${CMAKE_CURRENT_LIST_DIR}/SaveLoad.cpp ${CMAKE_CURRENT_LIST_DIR}/ServerApp.cpp + ${CMAKE_CURRENT_LIST_DIR}/ServerFramework.cpp ${CMAKE_CURRENT_LIST_DIR}/ServerFSM.cpp + ${CMAKE_CURRENT_LIST_DIR}/ServerNetworking.cpp + ${CMAKE_CURRENT_LIST_DIR}/ServerWrapper.cpp + ${CMAKE_CURRENT_LIST_DIR}/UniverseGenerator.cpp ) diff --git a/server/SaveLoad.cpp b/server/SaveLoad.cpp index d4e40a649ec..f0ee83cfa1c 100644 --- a/server/SaveLoad.cpp +++ b/server/SaveLoad.cpp @@ -3,17 +3,11 @@ #include "ServerApp.h" #include "../Empire/Empire.h" #include "../Empire/EmpireManager.h" -#include "../universe/Building.h" -#include "../universe/Fleet.h" -#include "../universe/Ship.h" -#include "../universe/Planet.h" -#include "../universe/ShipDesign.h" -#include "../universe/System.h" #include "../universe/Species.h" #include "../util/Directories.h" +#include "../util/base64_filter.h" #include "../util/i18n.h" #include "../util/Logger.h" -#include "../util/MultiplayerCommon.h" #include "../util/OptionsDB.h" #include "../util/Order.h" #include "../util/OrderSet.h" @@ -36,37 +30,12 @@ #include #include -// HACK: The following two includes work around a bug in boost 1.56, -// which uses them without including. -#include -#if BOOST_VERSION == 105600 -#include // This -#include //This -#endif -// HACK: For a similar boost 1.57 bug -#if BOOST_VERSION == 105700 -#include // This -#endif - -#if BOOST_VERSION == 105800 -// HACK: The following two includes work around a bug in boost 1.58 -#include -#include -#endif - #include namespace fs = boost::filesystem; namespace { - std::map CompileSaveGameEmpireData(const EmpireManager& empire_manager) { - std::map retval; - for (const std::map::value_type& entry : Empires()) - retval[entry.first] = SaveGameEmpireData(entry.first, entry.second->Name(), entry.second->PlayerName(), entry.second->Color()); - return retval; - } - void CompileSaveGamePreviewData(const ServerSaveGameData& server_save_game_data, const std::vector& player_save_game_data, const std::map& empire_save_game_data, @@ -77,6 +46,8 @@ namespace { preview.number_of_empires = empire_save_game_data.size(); preview.save_time = boost::posix_time::to_iso_extended_string(boost::posix_time::second_clock::local_time()); + DebugLogger() << "CompileSaveGamePreviewData(...) player_save_game_data size: " << player_save_game_data.size(); + if (player_save_game_data.empty()) { preview.main_player_name = UserString("NO_PLAYERS"); preview.main_player_empire_name = UserString("NO_EMPIRE"); @@ -103,7 +74,7 @@ namespace { preview.number_of_human_players = humans; // Find the empire of the player, if it has one - std::map::const_iterator empire = empire_save_game_data.find(player->m_empire_id); + auto empire = empire_save_game_data.find(player->m_empire_id); if (empire != empire_save_game_data.end()) { preview.main_player_empire_name = empire->second.m_empire_name; preview.main_player_empire_colour = empire->second.m_color; @@ -114,25 +85,34 @@ namespace { const std::string UNABLE_TO_OPEN_FILE("Unable to open file"); const std::string XML_COMPRESSED_MARKER("zlib-xml"); + const std::string XML_COMPRESSED_BASE64_MARKER("zb64-xml"); const std::string XML_DIRECT_MARKER("raw-xml"); const std::string BINARY_MARKER("binary"); } +std::map CompileSaveGameEmpireData() { + std::map retval; + for (const auto& entry : Empires()) + retval[entry.first] = SaveGameEmpireData(entry.first, entry.second->Name(), entry.second->PlayerName(), entry.second->Color(), entry.second->IsAuthenticated(), entry.second->Eliminated(), entry.second->Won()); + return retval; +} + int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_game_data, const std::vector& player_save_game_data, const Universe& universe, const EmpireManager& empire_manager, const SpeciesManager& species_manager, - const CombatLogManager& combat_log_manager, const GalaxySetupData& galaxy_setup_data, + const CombatLogManager& combat_log_manager, GalaxySetupData galaxy_setup_data, bool multiplayer) { - ScopedTimer timer("SaveGame: " + filename, true); + SectionedScopedTimer timer("SaveGame"); - bool use_binary = GetOptionsDB().Get("binary-serialization"); - bool use_zlib_for_zml = GetOptionsDB().Get("xml-zlib-serialization"); + bool use_binary = GetOptionsDB().Get("save.format.binary.enabled"); + bool use_zlib_for_zml = GetOptionsDB().Get("save.format.xml.zlib.enabled"); DebugLogger() << "SaveGame(" << (use_binary ? "binary" : (use_zlib_for_zml ? "zlib-xml" : "raw-xml")) << ") filename: " << filename; GetUniverse().EncodingEmpire() = ALL_EMPIRES; DebugLogger() << "Compiling save empire and preview data"; - std::map empire_save_game_data = CompileSaveGameEmpireData(empire_manager); + timer.EnterSection("compiling data"); + std::map empire_save_game_data = CompileSaveGameEmpireData(); SaveGamePreviewData save_preview_data; CompileSaveGamePreviewData(server_save_game_data, player_save_game_data, empire_save_game_data, save_preview_data); @@ -149,19 +129,35 @@ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_ try { + timer.EnterSection("path management"); fs::path path = FilenameToPath(filename); + // A relative path should be relative to the save directory. if (path.is_relative()) { - path = GetSaveDir() / path; + path = (multiplayer ? GetServerSaveDir() : GetSaveDir()) / path; DebugLogger() << "Made save path relative to save dir. Is now: " << path; } if (multiplayer) { // Make sure the path points into our save directory - if (!IsInside(path, GetSaveDir())) { - path = GetSaveDir() / path.filename(); + if (!IsInDir(GetServerSaveDir(), path.parent_path())) { + WarnLogger() << "Path \"" << path << "\" is not in server save directory."; + path = GetServerSaveDir() / path.filename(); + WarnLogger() << "Path changed to \"" << path << "\""; + } else { + try { + // ensure save directory exists + if (!exists(path.parent_path())) { + WarnLogger() << "Creating save directories " << path.parent_path().string(); + boost::filesystem::create_directories(path.parent_path()); + } + } catch (const std::exception& e) { + ErrorLogger() << "Server unable to check / create save directory: " << e.what(); + } } } + timer.EnterSection(""); + // set up output archive / stream for saving fs::ofstream ofs(path, std::ios_base::binary); @@ -170,17 +166,19 @@ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_ pos_before_writing = ofs.tellp(); bool save_completed_as_xml = false; + galaxy_setup_data.m_encoding_empire = ALL_EMPIRES; if (!use_binary) { if (use_zlib_for_zml) { // Attempt compressed XML serialization try { + timer.EnterSection("xml prep / allocation"); // Two-tier serialization: // main archive is uncompressed serialized header data first // then contains a string for compressed second archive // that contains the main gamestate info save_preview_data.SetBinary(false); - save_preview_data.save_format_marker = XML_COMPRESSED_MARKER; + save_preview_data.save_format_marker = XML_COMPRESSED_BASE64_MARKER; // allocate buffers for serialized gamestate DebugLogger() << "Allocating buffers for XML serialization..."; @@ -202,16 +200,27 @@ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_ InsertDevice serial_inserter(serial_str); boost::iostreams::stream s_sink(serial_inserter); - // create archive with (preallocated) buffer... - freeorion_xml_oarchive xoa(s_sink); - // serialize main gamestate info - xoa << BOOST_SERIALIZATION_NVP(player_save_game_data); - xoa << BOOST_SERIALIZATION_NVP(empire_manager); - xoa << BOOST_SERIALIZATION_NVP(species_manager); - xoa << BOOST_SERIALIZATION_NVP(combat_log_manager); - Serialize(xoa, universe); + timer.EnterSection(""); + { + // create archive with (preallocated) buffer... + freeorion_xml_oarchive xoa(s_sink); + // serialize main gamestate info + timer.EnterSection("player data to xml"); + xoa << BOOST_SERIALIZATION_NVP(player_save_game_data); + timer.EnterSection("empires to xml"); + xoa << BOOST_SERIALIZATION_NVP(empire_manager); + timer.EnterSection("species to xml"); + xoa << BOOST_SERIALIZATION_NVP(species_manager); + timer.EnterSection("combat logs to xml"); + xoa << BOOST_SERIALIZATION_NVP(combat_log_manager); + timer.EnterSection("universe to xml"); + Serialize(xoa, universe); + timer.EnterSection(""); + } + s_sink.flush(); + timer.EnterSection("compression"); // wrap gamestate string in iostream::stream to extract serialized data typedef boost::iostreams::basic_array_source SourceDevice; SourceDevice source(serial_str.data(), serial_str.size()); @@ -224,6 +233,7 @@ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_ // compression-filter gamestate into compressed string boost::iostreams::filtering_ostreambuf o; o.push(boost::iostreams::zlib_compressor()); + o.push(boost::iostreams::base64_encoder()); o.push(c_sink); boost::iostreams::copy(s_source, o); c_sink.flush(); @@ -231,6 +241,7 @@ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_ save_preview_data.uncompressed_text_size = serial_str.size(); save_preview_data.compressed_text_size = compressed_str.size(); + timer.EnterSection("headers to xml"); // write to save file: uncompressed header serialized data, with compressed main archive string at end... freeorion_xml_oarchive xoa2(ofs); // serialize uncompressed save header info @@ -242,6 +253,7 @@ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_ // append compressed gamestate info xoa2 << BOOST_SERIALIZATION_NVP(compressed_str); + timer.EnterSection(""); save_completed_as_xml = true; } catch (...) { save_completed_as_xml = false; // redundant, but here for clarity @@ -252,6 +264,7 @@ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_ save_preview_data.SetBinary(false); save_preview_data.save_format_marker = XML_DIRECT_MARKER; + timer.EnterSection("headers to xml"); // create archive with (preallocated) buffer... freeorion_xml_oarchive xoa(ofs); xoa << BOOST_SERIALIZATION_NVP(save_preview_data); @@ -260,12 +273,14 @@ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_ xoa << BOOST_SERIALIZATION_NVP(player_save_header_data); xoa << BOOST_SERIALIZATION_NVP(empire_save_game_data); + timer.EnterSection("gamestate to xml"); xoa << BOOST_SERIALIZATION_NVP(player_save_game_data); xoa << BOOST_SERIALIZATION_NVP(empire_manager); xoa << BOOST_SERIALIZATION_NVP(species_manager); xoa << BOOST_SERIALIZATION_NVP(combat_log_manager); Serialize(xoa, universe); + timer.EnterSection(""); save_completed_as_xml = true; } catch (...) { save_completed_as_xml = false; // redundant, but here for clarity @@ -278,6 +293,7 @@ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_ save_preview_data.SetBinary(true); save_preview_data.save_format_marker = BINARY_MARKER; + timer.EnterSection("headers to binary"); freeorion_bin_oarchive boa(ofs); boa << BOOST_SERIALIZATION_NVP(save_preview_data); boa << BOOST_SERIALIZATION_NVP(galaxy_setup_data); @@ -285,12 +301,14 @@ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_ boa << BOOST_SERIALIZATION_NVP(player_save_header_data); boa << BOOST_SERIALIZATION_NVP(empire_save_game_data); + timer.EnterSection("gamestate to binary"); boa << BOOST_SERIALIZATION_NVP(player_save_game_data); boa << BOOST_SERIALIZATION_NVP(empire_manager); boa << BOOST_SERIALIZATION_NVP(species_manager); boa << BOOST_SERIALIZATION_NVP(combat_log_manager); Serialize(boa, universe); + timer.EnterSection(""); DebugLogger() << "Done serializing"; } @@ -311,7 +329,7 @@ void LoadGame(const std::string& filename, ServerSaveGameData& server_save_game_ EmpireManager& empire_manager, SpeciesManager& species_manager, CombatLogManager& combat_log_manager, GalaxySetupData& galaxy_setup_data) { - ScopedTimer timer("LoadGame: " + filename, true); + SectionedScopedTimer timer("LoadGame"); // player notifications if (ServerApp* server = ServerApp::GetApp()) @@ -333,33 +351,40 @@ void LoadGame(const std::string& filename, ServerSaveGameData& server_save_game_ if (!ifs) throw std::runtime_error(UNABLE_TO_OPEN_FILE); - try { - // first attempt binary deserialziation + std::string signature(5, '\0'); + if (!ifs.read(&signature[0], 5)) + throw std::runtime_error(UNABLE_TO_OPEN_FILE); + boost::iostreams::seek(ifs, 0, std::ios_base::beg); + + if (strncmp(signature.c_str(), "> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data); ia >> BOOST_SERIALIZATION_NVP(server_save_game_data); ia >> BOOST_SERIALIZATION_NVP(ignored_player_save_header_data); ia >> BOOST_SERIALIZATION_NVP(ignored_save_game_empire_data); - + timer.EnterSection("binary player data"); ia >> BOOST_SERIALIZATION_NVP(player_save_game_data); + timer.EnterSection("binary empires"); ia >> BOOST_SERIALIZATION_NVP(empire_manager); + timer.EnterSection("binary species"); ia >> BOOST_SERIALIZATION_NVP(species_manager); + timer.EnterSection("binary combat log"); ia >> BOOST_SERIALIZATION_NVP(combat_log_manager); + timer.EnterSection("binary universe"); Deserialize(ia, universe); DebugLogger() << "Done deserializing"; - } catch (...) { - // if binary deserialization failed, try more-portable XML deserialization - - // reset to start of stream (attempted binary serialization will have consumed some input...) - boost::iostreams::seek(ifs, 0, std::ios_base::beg); - + } else { // create archive with (preallocated) buffer... freeorion_xml_iarchive xia(ifs); + DebugLogger() << "Reading XML iarchive"; // read from save file: uncompressed header serialized data, with compressed main archive string at end... // deserialize uncompressed save header info + timer.EnterSection("xml headers"); xia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); xia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data); xia >> BOOST_SERIALIZATION_NVP(server_save_game_data); @@ -370,15 +395,23 @@ void LoadGame(const std::string& filename, ServerSaveGameData& server_save_game_ if (ignored_save_preview_data.save_format_marker == XML_DIRECT_MARKER) { // deserialize directly from file / disk to gamestate + timer.EnterSection("xml player data"); xia >> BOOST_SERIALIZATION_NVP(player_save_game_data); + timer.EnterSection("xml empires"); xia >> BOOST_SERIALIZATION_NVP(empire_manager); + timer.EnterSection("xml species"); xia >> BOOST_SERIALIZATION_NVP(species_manager); + timer.EnterSection("xml combat log"); xia >> BOOST_SERIALIZATION_NVP(combat_log_manager); + timer.EnterSection("xml universe"); Deserialize(xia, universe); } else { // assume compressed XML + if (BOOST_VERSION >= 106600 && ignored_save_preview_data.save_format_marker == XML_COMPRESSED_MARKER) + throw std::invalid_argument("Save Format Not Compatible with Boost Version " BOOST_LIB_VERSION); + timer.EnterSection("decompression"); // allocate buffers for compressed and deceompressed serialized gamestate DebugLogger() << "Allocating buffers for XML deserialization..."; std::string serial_str, compressed_str; @@ -387,13 +420,13 @@ void LoadGame(const std::string& filename, ServerSaveGameData& server_save_game_ DebugLogger() << "Based on header info for uncompressed state string, attempting to reserve: " << ignored_save_preview_data.uncompressed_text_size << " bytes"; serial_str.reserve(ignored_save_preview_data.uncompressed_text_size); } else { - serial_str.reserve( std::pow(2.0, 29.0)); + serial_str.reserve(std::pow(2.0, 29.0)); } if (ignored_save_preview_data.compressed_text_size > 0) { DebugLogger() << "Based on header info for compressed state string, attempting to reserve: " << ignored_save_preview_data.compressed_text_size << " bytes"; compressed_str.reserve(ignored_save_preview_data.compressed_text_size); } else { - compressed_str.reserve( std::pow(2.0, 26.0)); + compressed_str.reserve(std::pow(2.0, 26.0)); } } catch (...) { DebugLogger() << "Unable to preallocate full deserialization buffers. Attempting deserialization with dynamic buffer allocation."; @@ -415,6 +448,8 @@ void LoadGame(const std::string& filename, ServerSaveGameData& server_save_game_ // set up filter to decompress data boost::iostreams::filtering_istreambuf i; i.push(boost::iostreams::zlib_decompressor()); + if (ignored_save_preview_data.save_format_marker == XML_COMPRESSED_BASE64_MARKER) + i.push(boost::iostreams::base64_decoder()); i.push(c_source); boost::iostreams::copy(i, s_sink); // The following line has been commented out because it caused an assertion in boost iostreams to fail @@ -427,10 +462,15 @@ void LoadGame(const std::string& filename, ServerSaveGameData& server_save_game_ // create archive with (preallocated) buffer... freeorion_xml_iarchive xia2(s_source); // deserialize main gamestate info + timer.EnterSection("xml player data"); xia2 >> BOOST_SERIALIZATION_NVP(player_save_game_data); + timer.EnterSection("xml empires"); xia2 >> BOOST_SERIALIZATION_NVP(empire_manager); + timer.EnterSection("xml species"); xia2 >> BOOST_SERIALIZATION_NVP(species_manager); + timer.EnterSection("xml combat logs"); xia2 >> BOOST_SERIALIZATION_NVP(combat_log_manager); + timer.EnterSection("xml universe"); Deserialize(xia2, universe); } } @@ -439,13 +479,13 @@ void LoadGame(const std::string& filename, ServerSaveGameData& server_save_game_ ErrorLogger() << "LoadGame(...) failed! Error: " << err.what(); return; } - DebugLogger() << "LoadGame : Successfully loaded save file"; + DebugLogger() << "LoadGame : Successfully loaded save file: " + filename; } void LoadGalaxySetupData(const std::string& filename, GalaxySetupData& galaxy_setup_data) { SaveGamePreviewData ignored_save_preview_data; - ScopedTimer timer("LoadGalaxySetupData: " + filename, true); + ScopedTimer timer("LoadGalaxySetupData"); try { fs::path path = FilenameToPath(filename); @@ -454,18 +494,21 @@ void LoadGalaxySetupData(const std::string& filename, GalaxySetupData& galaxy_se if (!ifs) throw std::runtime_error(UNABLE_TO_OPEN_FILE); - try { - // first attempt binary deserialziation + std::string signature(5, '\0'); + if (!ifs.read(&signature[0], 5)) + throw std::runtime_error(UNABLE_TO_OPEN_FILE); + boost::iostreams::seek(ifs, 0, std::ios_base::beg); + + if (strncmp(signature.c_str(), "> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data); - } catch(...) { - // if binary deserialization failed, try more-portable XML deserialization - - // reset to start of stream (attempted binary serialization will have consumed some input...) - boost::iostreams::seek(ifs, 0, std::ios_base::beg); + } else { + DebugLogger() << "Attempting XML deserialization..."; freeorion_xml_iarchive ia(ifs); ia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); @@ -494,8 +537,13 @@ void LoadPlayerSaveHeaderData(const std::string& filename, std::vector> BOOST_SERIALIZATION_NVP(ignored_galaxy_setup_data); ia >> BOOST_SERIALIZATION_NVP(ignored_server_save_game_data); ia >> BOOST_SERIALIZATION_NVP(player_save_header_data); - - } catch (...) { - // if binary deserialization failed, try more-portable XML deserialization - DebugLogger() << "Trying again with XML deserialization..."; - - // reset to start of stream (attempted binary serialization will have consumed some input...) - boost::iostreams::seek(ifs, 0, std::ios_base::beg); + } else { + DebugLogger() << "Attempting XML deserialization..."; freeorion_xml_iarchive ia(ifs); ia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); @@ -517,6 +560,7 @@ void LoadPlayerSaveHeaderData(const std::string& filename, std::vector> BOOST_SERIALIZATION_NVP(ignored_server_save_game_data); ia >> BOOST_SERIALIZATION_NVP(player_save_header_data); } + // skipping additional deserialization which is not needed for this function DebugLogger() << "Done reading player save game data..."; } catch (const std::exception& e) { @@ -525,11 +569,15 @@ void LoadPlayerSaveHeaderData(const std::string& filename, std::vector& empire_save_game_data) { +void LoadEmpireSaveGameData(const std::string& filename, + std::map& empire_save_game_data, + std::vector& player_save_header_data, + GalaxySetupData& galaxy_setup_data, + int& current_turn) +{ SaveGamePreviewData ignored_save_preview_data; - ServerSaveGameData ignored_server_save_game_data; - std::vector ignored_player_save_header_data; - GalaxySetupData ignored_galaxy_setup_data; + ServerSaveGameData saved_server_save_game_data; + GalaxySetupData saved_galaxy_setup_data; ScopedTimer timer("LoadEmpireSaveGameData: " + filename, true); @@ -541,27 +589,30 @@ void LoadEmpireSaveGameData(const std::string& filename, std::map> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); - ia >> BOOST_SERIALIZATION_NVP(ignored_galaxy_setup_data); - ia >> BOOST_SERIALIZATION_NVP(ignored_server_save_game_data); - ia >> BOOST_SERIALIZATION_NVP(ignored_player_save_header_data); + ia >> BOOST_SERIALIZATION_NVP(saved_galaxy_setup_data); + ia >> BOOST_SERIALIZATION_NVP(saved_server_save_game_data); + ia >> BOOST_SERIALIZATION_NVP(player_save_header_data); ia >> BOOST_SERIALIZATION_NVP(empire_save_game_data); - } catch (...) { - // if binary deserialization failed, try more-portable XML deserialization - - // reset to start of stream (attempted binary serialization will have consumed some input...) - boost::iostreams::seek(ifs, 0, std::ios_base::beg); + } else { + DebugLogger() << "Attempting XML deserialization..."; freeorion_xml_iarchive ia(ifs); ia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); - ia >> BOOST_SERIALIZATION_NVP(ignored_galaxy_setup_data); - ia >> BOOST_SERIALIZATION_NVP(ignored_server_save_game_data); - ia >> BOOST_SERIALIZATION_NVP(ignored_player_save_header_data); + ia >> BOOST_SERIALIZATION_NVP(saved_galaxy_setup_data); + ia >> BOOST_SERIALIZATION_NVP(saved_server_save_game_data); + ia >> BOOST_SERIALIZATION_NVP(player_save_header_data); ia >> BOOST_SERIALIZATION_NVP(empire_save_game_data); } // skipping additional deserialization which is not needed for this function @@ -570,4 +621,18 @@ void LoadEmpireSaveGameData(const std::string& filename, std::map CompileSaveGameEmpireData(); + /** Saves the provided data to savefile \a filename. */ int SaveGame(const std::string& filename, const ServerSaveGameData& server_save_game_data, @@ -23,7 +26,7 @@ int SaveGame(const std::string& filename, const EmpireManager& empire_manager, const SpeciesManager& species_manager, const CombatLogManager& combat_log_manager, - const GalaxySetupData& galaxy_setup_data, + GalaxySetupData galaxy_setup_data, bool multiplayer); /** Loads the indicated data from savefile \a filename. */ @@ -44,8 +47,12 @@ void LoadPlayerSaveHeaderData(const std::string& filename, /** Loads from a savefile \a filename some basic empire information that is * useful when selecting which player will control which empire when reloading - * a saved game: player name, empire name, and empire colour (and empire id). */ + * a saved game: player name, empire name, and empire colour (and empire id). + * Also loads galaxy setup data to show it in lobby window. */ void LoadEmpireSaveGameData(const std::string& filename, - std::map& empire_save_game_data); + std::map& empire_save_game_data, + std::vector& player_save_header_data, + GalaxySetupData& galaxy_setup_data, + int ¤t_turn); #endif diff --git a/server/ServerApp.cpp b/server/ServerApp.cpp index 6e617569583..d7f1238f8f6 100644 --- a/server/ServerApp.cpp +++ b/server/ServerApp.cpp @@ -2,12 +2,15 @@ #include "SaveLoad.h" #include "ServerFSM.h" +#include "UniverseGenerator.h" #include "../combat/CombatSystem.h" #include "../combat/CombatEvents.h" #include "../combat/CombatLogManager.h" +#include "../parse/Parse.h" #include "../universe/Building.h" -#include "../universe/Effect.h" +#include "../universe/Condition.h" #include "../universe/Fleet.h" +#include "../universe/FleetPlan.h" #include "../universe/Ship.h" #include "../universe/ShipDesign.h" #include "../universe/Planet.h" @@ -15,17 +18,21 @@ #include "../universe/Special.h" #include "../universe/System.h" #include "../universe/Species.h" -#include "../universe/UniverseGenerator.h" +#include "../universe/Tech.h" +#include "../universe/UnlockableItem.h" #include "../universe/Enums.h" +#include "../universe/ValueRef.h" #include "../Empire/Empire.h" #include "../util/Directories.h" #include "../util/i18n.h" #include "../util/Logger.h" #include "../util/LoggerWithOptionsDB.h" -#include "../util/MultiplayerCommon.h" +#include "../util/GameRules.h" #include "../util/OptionsDB.h" #include "../util/Order.h" #include "../util/OrderSet.h" +#include "../util/Pending.h" +#include "../util/Random.h" #include "../util/SaveGamePreviewUtils.h" #include "../util/SitRepEntry.h" #include "../util/ScopedTimer.h" @@ -37,16 +44,18 @@ #include #include #include - +#include +#include #include #include namespace fs = boost::filesystem; -void Seed(unsigned int seed); - namespace { + DeclareThreadSafeLogger(effects); + DeclareThreadSafeLogger(combat); + //If there's only one other empire, return their ID: int EnemyId(int empire_id, const std::set &empire_ids) { if (empire_ids.size() == 2) { @@ -57,70 +66,9 @@ namespace { } return ALL_EMPIRES; } - - /// Generates information on the subdirectories of the save directory - void ListSaveSubdirectories(std::vector& list) { - fs::recursive_directory_iterator end; - std::string savedir = fs::canonical(GetSaveDir()).generic_string(); - for (fs::recursive_directory_iterator it(GetSaveDir()); it != end; ++it) { - if (fs::is_directory(it->path())) { - std::string subdirectory = it->path().generic_string(); - if (subdirectory.find(savedir) == 0){ - list.push_back(subdirectory.substr(savedir.length())); - } else { - ErrorLogger() << "ListSaveSubfolders Expected a subdirectory of " << GetSaveDir() << " got " << subdirectory; - } - } - } - } }; -//////////////////////////////////////////////// -// PlayerSaveHeaderData -//////////////////////////////////////////////// -PlayerSaveHeaderData::PlayerSaveHeaderData() : - m_name(), - m_empire_id(ALL_EMPIRES), - m_client_type(Networking::INVALID_CLIENT_TYPE) -{} - -PlayerSaveHeaderData::PlayerSaveHeaderData(const std::string& name, int empire_id, Networking::ClientType client_type) : - m_name(name), - m_empire_id(empire_id), - m_client_type(client_type) -{} - - -//////////////////////////////////////////////// -// PlayerSaveGameData -//////////////////////////////////////////////// -PlayerSaveGameData::PlayerSaveGameData() : - PlayerSaveHeaderData(), - m_orders(), - m_ui_data(), - m_save_state_string() -{} - -PlayerSaveGameData::PlayerSaveGameData(const std::string& name, int empire_id, const std::shared_ptr& orders, - const std::shared_ptr& ui_data, const std::string& save_state_string, - Networking::ClientType client_type) : - PlayerSaveHeaderData(name, empire_id, client_type), - m_orders(orders), - m_ui_data(ui_data), - m_save_state_string(save_state_string) -{} - - -//////////////////////////////////////////////// -// ServerSaveGameData -//////////////////////////////////////////////// -ServerSaveGameData::ServerSaveGameData() : - m_current_turn(INVALID_GAME_TURN) -{} - -ServerSaveGameData::ServerSaveGameData(int current_turn) : - m_current_turn(current_turn) -{} +void Seed(unsigned int seed); //////////////////////////////////////////////// @@ -128,28 +76,46 @@ ServerSaveGameData::ServerSaveGameData(int current_turn) : //////////////////////////////////////////////// ServerApp::ServerApp() : IApp(), - m_signals(m_io_service, SIGINT, SIGTERM), - m_networking(m_io_service, + m_signals(m_io_context, SIGINT, SIGTERM), + m_networking(m_io_context, boost::bind(&ServerApp::HandleNonPlayerMessage, this, _1, _2), boost::bind(&ServerApp::HandleMessage, this, _1, _2), boost::bind(&ServerApp::PlayerDisconnected, this, _1)), m_fsm(new ServerFSM(*this)), - m_current_turn(INVALID_GAME_TURN), - m_single_player_game(false) + m_chat_history(1000) { - const std::string SERVER_LOG_FILENAME((GetUserDataDir() / "freeoriond.log").string()); - + // Force the log file if requested. + if (GetOptionsDB().Get("log-file").empty()) { + const std::string SERVER_LOG_FILENAME((GetUserDataDir() / "freeoriond.log").string()); + GetOptionsDB().Set("log-file", SERVER_LOG_FILENAME); + } // Force the log threshold if requested. auto force_log_level = GetOptionsDB().Get("log-level"); if (!force_log_level.empty()) OverrideAllLoggersThresholds(to_LogLevel(force_log_level)); - InitLoggingSystem(SERVER_LOG_FILENAME, "Server"); + InitLoggingSystem(GetOptionsDB().Get("log-file"), "Server"); InitLoggingOptionsDBSystem(); InfoLogger() << FreeOrionVersionString(); LogDependencyVersions(); + m_galaxy_setup_data.m_seed = GetOptionsDB().Get("setup.seed"); + m_galaxy_setup_data.m_size = GetOptionsDB().Get("setup.star.count"); + m_galaxy_setup_data.m_shape = GetOptionsDB().Get("setup.galaxy.shape"); + m_galaxy_setup_data.m_age = GetOptionsDB().Get("setup.galaxy.age"); + m_galaxy_setup_data.m_starlane_freq = GetOptionsDB().Get("setup.starlane.frequency"); + m_galaxy_setup_data.m_planet_density = GetOptionsDB().Get("setup.planet.density"); + m_galaxy_setup_data.m_specials_freq = GetOptionsDB().Get("setup.specials.frequency"); + m_galaxy_setup_data.m_monster_freq = GetOptionsDB().Get("setup.monster.frequency"); + m_galaxy_setup_data.m_native_freq = GetOptionsDB().Get("setup.native.frequency"); + m_galaxy_setup_data.m_ai_aggr = GetOptionsDB().Get("setup.ai.aggression"); + m_galaxy_setup_data.m_game_uid = GetOptionsDB().Get("setup.game.uid"); + + // Start parsing content before FSM initialization + // to have data initialized before autostart execution + StartBackgroundParsing(); + m_fsm->initiate(); Empires().DiplomaticStatusChangedSignal.connect( @@ -162,7 +128,10 @@ ServerApp::ServerApp() : ServerApp::~ServerApp() { DebugLogger() << "ServerApp::~ServerApp"; - m_python_server.Finalize(); + + // Calling Py_Finalize here causes segfault when m_python_server destructing with its python + // object fields + CleanupAIs(); delete m_fsm; DebugLogger() << "Server exited cleanly."; @@ -192,6 +161,36 @@ namespace { #include #endif +void ServerApp::StartBackgroundParsing() { + IApp::StartBackgroundParsing(); + const auto& rdir = GetResourceDir(); + + if (fs::exists(rdir / "scripting/starting_unlocks/items.inf")) + m_universe.SetInitiallyUnlockedItems(Pending::StartParsing(parse::items, rdir / "scripting/starting_unlocks/items.inf")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/starting_unlocks/items.inf").string(); + + if (fs::exists(rdir / "scripting/starting_unlocks/buildings.inf")) + m_universe.SetInitiallyUnlockedBuildings(Pending::StartParsing(parse::starting_buildings, rdir / "scripting/starting_unlocks/buildings.inf")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/starting_unlocks/buildings.inf").string(); + + if (fs::exists(rdir / "scripting/starting_unlocks/fleets.inf")) + m_universe.SetInitiallyUnlockedFleetPlans(Pending::StartParsing(parse::fleet_plans, rdir / "scripting/starting_unlocks/fleets.inf")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/starting_unlocks/fleets.inf").string(); + + if (fs::exists(rdir / "scripting/monster_fleets.inf")) + m_universe.SetMonsterFleetPlans(Pending::StartParsing(parse::monster_fleet_plans, rdir / "scripting/monster_fleets.inf")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/monster_fleets.inf").string(); + + if (fs::exists(rdir / "scripting/empire_statistics")) + m_universe.SetEmpireStats(Pending::StartParsing(parse::statistics, rdir / "scripting/empire_statistics")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/empire_statistics").string(); +} + void ServerApp::CreateAIClients(const std::vector& player_setup_data, int max_aggression) { DebugLogger() << "ServerApp::CreateAIClients: " << player_setup_data.size() << " player (maybe not all AIs) at max aggression: " << max_aggression; // check if AI clients are needed for given setup data @@ -233,8 +232,8 @@ void ServerApp::CreateAIClients(const std::vector& player_setup std::stringstream max_aggr_str; max_aggr_str << max_aggression; args.push_back(max_aggr_str.str()); - args.push_back("--resource-dir"); - args.push_back("\"" + GetOptionsDB().Get("resource-dir") + "\""); + args.push_back("--resource.path"); + args.push_back("\"" + GetOptionsDB().Get("resource.path") + "\""); auto force_log_level = GetOptionsDB().Get("log-level"); if (!force_log_level.empty()) { @@ -242,6 +241,15 @@ void ServerApp::CreateAIClients(const std::vector& player_setup args.push_back(GetOptionsDB().Get("log-level")); } + if (GetOptionsDB().Get("testing")) { + args.push_back("--testing"); +#ifdef FREEORION_LINUX + // Dirty hack to output log to console. + args.push_back("--log-file"); + args.push_back("/proc/self/fd/1"); +#endif + } + args.push_back("--ai-path"); args.push_back(GetOptionsDB().Get("ai-path")); DebugLogger() << "starting AIs with " << AI_CLIENT_EXE ; @@ -294,13 +302,13 @@ SupplyManager& ServerApp::GetSupplyManager() { return m_supply_manager; } std::shared_ptr ServerApp::GetUniverseObject(int object_id) -{ return m_universe.Objects().Object(object_id); } +{ return m_universe.Objects().get(object_id); } ObjectMap& ServerApp::EmpireKnownObjects(int empire_id) { return m_universe.EmpireKnownObjects(empire_id); } std::shared_ptr ServerApp::EmpireKnownObject(int object_id, int empire_id) -{ return m_universe.EmpireKnownObjects(empire_id).Object(object_id); } +{ return m_universe.EmpireKnownObjects(empire_id).get(object_id); } ServerNetworking& ServerApp::Networking() { return m_networking; } @@ -318,7 +326,7 @@ void ServerApp::Run() { DebugLogger() << "FreeOrion server waiting for network events"; try { while (1) { - if (m_io_service.run_one()) + if (m_io_context.run_one()) m_networking.HandleNextEvent(); else break; @@ -369,11 +377,7 @@ void ServerApp::CleanupAIs() { ErrorLogger() << "ServerApp::CleanupAIs() exception while killing processes"; } - try { - m_ai_client_processes.clear(); - } catch (...) { - ErrorLogger() << "ServerApp::CleanupAIs() exception while clearing client processes"; - } + m_ai_client_processes.clear(); } void ServerApp::SetAIsProcessPriorityToLow(bool set_to_low) { @@ -393,7 +397,8 @@ void ServerApp::SetAIsProcessPriorityToLow(bool set_to_low) { void ServerApp::HandleMessage(const Message& msg, PlayerConnectionPtr player_connection) { - //DebugLogger() << "ServerApp::HandleMessage type " << boost::lexical_cast(msg.Type()); + //DebugLogger() << "ServerApp::HandleMessage type " << msg.Type(); + m_networking.UpdateCookie(player_connection->Cookie()); // update cookie expire date switch (msg.Type()) { case Message::HOST_SP_GAME: m_fsm->process_event(HostSPGame(msg, player_connection)); break; @@ -401,10 +406,12 @@ void ServerApp::HandleMessage(const Message& msg, PlayerConnectionPtr player_con case Message::LOBBY_UPDATE: m_fsm->process_event(LobbyUpdate(msg, player_connection)); break; case Message::SAVE_GAME_INITIATE: m_fsm->process_event(SaveGameRequest(msg, player_connection)); break; case Message::TURN_ORDERS: m_fsm->process_event(TurnOrders(msg, player_connection)); break; - case Message::CLIENT_SAVE_DATA: m_fsm->process_event(ClientSaveData(msg, player_connection)); break; + case Message::TURN_PARTIAL_ORDERS: m_fsm->process_event(TurnPartialOrders(msg, player_connection));break; + case Message::UNREADY: m_fsm->process_event(RevokeReadiness(msg, player_connection)); break; case Message::PLAYER_CHAT: m_fsm->process_event(PlayerChat(msg, player_connection)); break; case Message::DIPLOMACY: m_fsm->process_event(Diplomacy(msg, player_connection)); break; case Message::MODERATOR_ACTION: m_fsm->process_event(ModeratorAct(msg, player_connection)); break; + case Message::ELIMINATE_SELF: m_fsm->process_event(EliminateSelf(msg, player_connection)); break; case Message::ERROR_MSG: case Message::DEBUG: break; @@ -450,7 +457,7 @@ void ServerApp::HandleLoggerConfig(const Message& msg, PlayerConnectionPtr playe // Forward the message to all the AIs const auto relay_options_message = LoggerConfigMessage(Networking::INVALID_PLAYER_ID, options); - for (ServerNetworking::established_iterator players_it = m_networking.established_begin(); + for (auto players_it = m_networking.established_begin(); players_it != m_networking.established_end(); ++players_it) { if ((*players_it)->GetClientType() == Networking::CLIENT_TYPE_AI_PLAYER) { @@ -460,14 +467,14 @@ void ServerApp::HandleLoggerConfig(const Message& msg, PlayerConnectionPtr playe } } - void ServerApp::HandleNonPlayerMessage(const Message& msg, PlayerConnectionPtr player_connection) { switch (msg.Type()) { - case Message::HOST_SP_GAME: m_fsm->process_event(HostSPGame(msg, player_connection)); break; - case Message::HOST_MP_GAME: m_fsm->process_event(HostMPGame(msg, player_connection)); break; - case Message::JOIN_GAME: m_fsm->process_event(JoinGame(msg, player_connection)); break; - case Message::ERROR_MSG: m_fsm->process_event(Error(msg, player_connection)); break; - case Message::DEBUG: break; + case Message::HOST_SP_GAME: m_fsm->process_event(HostSPGame(msg, player_connection)); break; + case Message::HOST_MP_GAME: m_fsm->process_event(HostMPGame(msg, player_connection)); break; + case Message::JOIN_GAME: m_fsm->process_event(JoinGame(msg, player_connection)); break; + case Message::AUTH_RESPONSE: m_fsm->process_event(AuthResponse(msg, player_connection)); break; + case Message::ERROR_MSG: m_fsm->process_event(Error(msg, player_connection)); break; + case Message::DEBUG: break; default: if ((m_networking.size() == 1) && (player_connection->IsLocalConnection()) && (msg.Type() == Message::SHUT_DOWN_SERVER)) { DebugLogger() << "ServerApp::HandleNonPlayerMessage received Message::SHUT_DOWN_SERVER from the sole " @@ -500,7 +507,7 @@ void ServerApp::SelectNewHost() { DebugLogger() << "ServerApp::SelectNewHost old host id: " << old_host_id; // scan through players for a human to host - for (ServerNetworking::established_iterator players_it = m_networking.established_begin(); + for (auto players_it = m_networking.established_begin(); players_it != m_networking.established_end(); ++players_it) { PlayerConnectionPtr player_connection = *players_it; @@ -531,16 +538,13 @@ void ServerApp::NewSPGameInit(const SinglePlayerSetupData& single_player_setup_d // id == m_networking.HostPlayerID() should be the human player in // PlayerSetupData. AI player connections are assigned one of the remaining // PlayerSetupData entries that is for an AI player. - std::map player_id_setup_data; - const std::vector& player_setup_data = single_player_setup_data.m_players; - + const auto& player_setup_data = single_player_setup_data.m_players; NewGameInitConcurrentWithJoiners(single_player_setup_data, player_setup_data); } bool ServerApp::VerifySPGameAIs(const SinglePlayerSetupData& single_player_setup_data) { - const std::vector& player_setup_data = single_player_setup_data.m_players; - - return NewGameInitVerifyJoiners(single_player_setup_data, player_setup_data); + const auto& player_setup_data = single_player_setup_data.m_players; + return NewGameInitVerifyJoiners(player_setup_data); } void ServerApp::NewMPGameInit(const MultiplayerLobbyData& multiplayer_lobby_data) { @@ -548,10 +552,10 @@ void ServerApp::NewMPGameInit(const MultiplayerLobbyData& multiplayer_lobby_data // available (human) and names (for AI clients which didn't have an ID // before now because the lobby data was set up without connected/established // clients for the AIs. - std::map player_id_setup_data; - const std::list>& player_setup_data = multiplayer_lobby_data.m_players; + const auto& player_setup_data = multiplayer_lobby_data.m_players; + std::vector psds; - for (const std::pair& entry : player_setup_data) { + for (const auto& entry : player_setup_data) { const PlayerSetupData& psd = entry.second; if (psd.m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER || psd.m_client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER || @@ -563,19 +567,29 @@ void ServerApp::NewMPGameInit(const MultiplayerLobbyData& multiplayer_lobby_data // find player connection with same ID as this player setup data bool found_matched_id_connection = false; int player_id = entry.first; - for (ServerNetworking::const_established_iterator established_player_it = m_networking.established_begin(); + for (auto established_player_it = m_networking.established_begin(); established_player_it != m_networking.established_end(); ++established_player_it) { const PlayerConnectionPtr player_connection = *established_player_it; if (player_connection->PlayerID() == player_id) { - player_id_setup_data[player_id] = psd; + PlayerSetupData new_psd = psd; + new_psd.m_player_id = player_id; + psds.emplace_back(std::move(new_psd)); found_matched_id_connection = true; break; } } - if (!found_matched_id_connection) - ErrorLogger() << "ServerApp::NewMPGameInit couldn't find player setup data for human player with id: " << player_id; + if (!found_matched_id_connection) { + if (player_id != Networking::INVALID_PLAYER_ID) { + ErrorLogger() << "ServerApp::NewMPGameInit couldn't find player setup data for human player with id: " << player_id << " player name: " << psd.m_player_name; + } else { + // There is no player currently connected for the current setup data. A player + // may connect later, at which time they may be assigned to this data or the + // corresponding empire. + psds.push_back(psd); + } + } } else if (psd.m_client_type == Networking::CLIENT_TYPE_AI_PLAYER) { // All AI player setup data, as determined from their client type, is @@ -584,7 +598,7 @@ void ServerApp::NewMPGameInit(const MultiplayerLobbyData& multiplayer_lobby_data // find player connection with same name as this player setup data bool found_matched_name_connection = false; const std::string& player_name = psd.m_player_name; - for (ServerNetworking::const_established_iterator established_player_it = m_networking.established_begin(); + for (auto established_player_it = m_networking.established_begin(); established_player_it != m_networking.established_end(); ++established_player_it) { const PlayerConnectionPtr player_connection = *established_player_it; @@ -593,7 +607,9 @@ void ServerApp::NewMPGameInit(const MultiplayerLobbyData& multiplayer_lobby_data { // assign name-matched AI client's player setup data to appropriate AI connection int player_id = player_connection->PlayerID(); - player_id_setup_data[player_id] = psd; + PlayerSetupData new_psd = psd; + new_psd.m_player_id = player_id; + psds.emplace_back(std::move(new_psd)); found_matched_name_connection = true; break; } @@ -610,17 +626,36 @@ void ServerApp::NewMPGameInit(const MultiplayerLobbyData& multiplayer_lobby_data } } - std::vector psds; - for (auto& id_and_psd : player_id_setup_data) { - id_and_psd.second.m_player_id = id_and_psd.first; - psds.push_back(id_and_psd.second); - } - NewGameInitConcurrentWithJoiners(multiplayer_lobby_data, psds); - if (NewGameInitVerifyJoiners(multiplayer_lobby_data, psds)) + if (NewGameInitVerifyJoiners(psds)) SendNewGameStartMessages(); } +void UpdateEmpireSupply(bool precombat=false) { + EmpireManager& empires = Empires(); + + // Determine initial supply distribution and exchanging and resource pools for empires + for (auto& entry : empires) { + Empire* empire = entry.second; + if (empire->Eliminated()) + continue; // skip eliminated empires. presumably this shouldn't be an issue when initializing a new game, but apparently I thought this was worth checking for... + + empire->UpdateSupplyUnobstructedSystems(precombat); // determines which systems can propagate fleet and resource (same for both) + empire->UpdateSystemSupplyRanges(); // sets range systems can propagate fleet and resourse supply (separately) + } + + GetSupplyManager().Update(); + + for (auto& entry : empires) { + Empire* empire = entry.second; + if (empire->Eliminated()) + continue; + + empire->InitResourcePools(); // determines population centers and resource centers of empire, tells resource pools the centers and groups of systems that can share resources (note that being able to share resources doesn't mean a system produces resources) + empire->UpdateResourcePools(); // determines how much of each resources is available in each resource sharing group + } +} + void ServerApp::NewGameInitConcurrentWithJoiners( const GalaxySetupData& galaxy_setup_data, const std::vector& player_setup_data) @@ -628,20 +663,23 @@ void ServerApp::NewGameInitConcurrentWithJoiners( DebugLogger() << "ServerApp::NewGameInitConcurrentWithJoiners"; m_galaxy_setup_data = galaxy_setup_data; + + // set game rules for server based on those specified in setup data GetGameRules().SetFromStrings(m_galaxy_setup_data.GetGameRules()); // validate some connection info / determine which players need empires created - std::map active_players_id_setup_data; + std::map active_empire_id_setup_data; + int next_empire_id = 1; for (const auto& psd : player_setup_data) { if (!psd.m_player_name.empty() && (psd.m_client_type == Networking::CLIENT_TYPE_AI_PLAYER || psd.m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER)) { - active_players_id_setup_data[psd.m_player_id] = psd; + active_empire_id_setup_data[next_empire_id++] = psd; } } - if (active_players_id_setup_data.empty()) { + if (active_empire_id_setup_data.empty()) { ErrorLogger() << "ServerApp::NewGameInitConcurrentWithJoiners found no active players!"; m_networking.SendMessageAll(ErrorMessage(UserStringNop("SERVER_FOUND_NO_ACTIVE_PLAYERS"), true)); return; @@ -650,10 +688,11 @@ void ServerApp::NewGameInitConcurrentWithJoiners( // clear previous game player state info m_turn_sequence.clear(); m_player_empire_ids.clear(); - + m_empires.Clear(); // set server state info for new game m_current_turn = BEFORE_FIRST_TURN; + m_turn_expired = false; // create universe and empires for players DebugLogger() << "ServerApp::NewGameInitConcurrentWithJoiners: Creating Universe"; @@ -662,7 +701,8 @@ void ServerApp::NewGameInitConcurrentWithJoiners( // m_current_turn set above so that every UniverseObject created before game // starts will have m_created_on_turn BEFORE_FIRST_TURN - GenerateUniverse(active_players_id_setup_data); + GenerateUniverse(active_empire_id_setup_data); + // after all game initialization stuff has been created, set current turn to 0 and apply only GenerateSitRep Effects // so that a set of SitReps intended as the player's initial greeting will be segregated @@ -672,19 +712,21 @@ void ServerApp::NewGameInitConcurrentWithJoiners( //can set current turn to 1 for start of game m_current_turn = 1; - // record empires for each active player: ID of empire and player should - // be the same when creating a new game. Note: active_players_id_setup_data - // contains only ids of players who control an empire; observers and + // record empires for each active player. Note: active_empire_id_setup_data + // contains only data of players who control an empire; observers and // moderators are not included. - for (const auto& player_id_and_setup : active_players_id_setup_data) { - int player_id = player_id_and_setup.first; - m_player_empire_ids[player_id] = player_id; - + for (const auto& player_id_and_setup : active_empire_id_setup_data) { + int empire_id = player_id_and_setup.first; + if (player_id_and_setup.second.m_player_id != Networking::INVALID_PLAYER_ID) + m_player_empire_ids[player_id_and_setup.second.m_player_id] = empire_id; // add empires to turn processing - int empire_id = PlayerEmpireID(player_id); - if (GetEmpire(empire_id)) - AddEmpireTurn(empire_id); + if (auto *empire = GetEmpire(empire_id)) { + AddEmpireTurn(empire_id, PlayerSaveGameData(player_id_and_setup.second.m_player_name, empire_id, + nullptr, nullptr, std::string(), + player_id_and_setup.second.m_client_type)); + empire->SetReady(false); + } } // update visibility information to ensure data sent out is up-to-date @@ -693,43 +735,16 @@ void ServerApp::NewGameInitConcurrentWithJoiners( // initialize empire owned object counters EmpireManager& empires = Empires(); - for (std::map::value_type& entry : empires) + for (auto& entry : empires) entry.second->UpdateOwnedObjectCounters(); - - // Determine initial supply distribution and exchanging and resource pools for empires - for (std::map::value_type& entry : empires) { - Empire* empire = entry.second; - if (empire->Eliminated()) - continue; // skip eliminated empires. presumably this shouldn't be an issue when initializing a new game, but apparently I thought this was worth checking for... - - empire->UpdateSupplyUnobstructedSystems(); // determines which systems can propagate fleet and resource (same for both) - empire->UpdateSystemSupplyRanges(); // sets range systems can propagate fleet and resourse supply (separately) - } - - GetSupplyManager().Update(); - - for (std::map::value_type& entry : empires) { - Empire* empire = entry.second; - if (empire->Eliminated()) - continue; - - empire->InitResourcePools(); // determines population centers and resource centers of empire, tells resource pools the centers and groups of systems that can share resources (note that being able to share resources doesn't mean a system produces resources) - empire->UpdateResourcePools(); // determines how much of each resources is available in each resource sharing group - } - + UpdateEmpireSupply(); m_universe.UpdateStatRecords(); - } -bool ServerApp::NewGameInitVerifyJoiners( - const GalaxySetupData& galaxy_setup_data, - const std::vector& player_setup_data) -{ +bool ServerApp::NewGameInitVerifyJoiners(const std::vector& player_setup_data) { DebugLogger() << "ServerApp::NewGameInitVerifyJoiners"; - m_galaxy_setup_data = galaxy_setup_data; - // associate player IDs with player setup data. the player connection with // id == m_networking.HostPlayerID() should be the human player in // PlayerSetupData. AI player connections are assigned one of the remaining @@ -757,28 +772,28 @@ bool ServerApp::NewGameInitVerifyJoiners( return false; } - if (!host_in_player_id_setup_data) { + if (!host_in_player_id_setup_data && !IsHostless()) { ErrorLogger() << "NewGameInitVerifyJoiners : Host id " << m_networking.HostPlayerID() << " is not a valid player id."; return false; } // ensure number of players connected and for which data are provided are consistent - if (m_networking.NumEstablishedPlayers() != player_id_setup_data.size()) { + if (m_networking.NumEstablishedPlayers() != player_id_setup_data.size() && !IsHostless()) { ErrorLogger() << "ServerApp::NewGameInitVerifyJoiners has " << m_networking.NumEstablishedPlayers() << " established players but " << player_id_setup_data.size() << " players in setup data."; return false; } // validate some connection info / determine which players need empires created - for (ServerNetworking::const_established_iterator player_connection_it = m_networking.established_begin(); + for (auto player_connection_it = m_networking.established_begin(); player_connection_it != m_networking.established_end(); ++player_connection_it) { const PlayerConnectionPtr player_connection = *player_connection_it; Networking::ClientType client_type = player_connection->GetClientType(); int player_id = player_connection->PlayerID(); - std::map::const_iterator player_id_setup_data_it = player_id_setup_data.find(player_id); + auto player_id_setup_data_it = player_id_setup_data.find(player_id); if (player_id_setup_data_it == player_id_setup_data.end()) { ErrorLogger() << "ServerApp::NewGameInitVerifyJoiners couldn't find player setup data for player with ID " << player_id; return false; @@ -813,13 +828,13 @@ void ServerApp::SendNewGameStartMessages() { // send new game start messages DebugLogger() << "SendGameStartMessages: Sending GameStartMessages to players"; - for (ServerNetworking::const_established_iterator player_connection_it = m_networking.established_begin(); + for (auto player_connection_it = m_networking.established_begin(); player_connection_it != m_networking.established_end(); ++player_connection_it) { const PlayerConnectionPtr player_connection = *player_connection_it; int player_id = player_connection->PlayerID(); int empire_id = PlayerEmpireID(player_id); - bool use_binary_serialization = player_connection->ClientVersionStringMatchesThisServer(); + bool use_binary_serialization = player_connection->IsBinarySerializationUsed(); player_connection->SendMessage(GameStartMessage(m_single_player_game, empire_id, m_current_turn, m_empires, m_universe, GetSpeciesManager(), @@ -835,7 +850,7 @@ void ServerApp::LoadSPGameInit(const std::vector& player_sav // Need to determine which data in player_save_game_data should be assigned to which established player std::vector> player_id_to_save_game_data_index; - ServerNetworking::const_established_iterator established_player_it = m_networking.established_begin(); + auto established_player_it = m_networking.established_begin(); // assign all saved game data to a player ID for (int i = 0; i < static_cast(player_save_game_data.size()); ++i) { @@ -844,7 +859,7 @@ void ServerApp::LoadSPGameInit(const std::vector& player_sav // In a single player game, the host player is always the human player, so // this is just a matter of finding which entry in player_save_game_data was // a human player, and assigning that saved player data to the host player ID - player_id_to_save_game_data_index.push_back(std::make_pair(m_networking.HostPlayerID(), i)); + player_id_to_save_game_data_index.push_back({m_networking.HostPlayerID(), i}); } else if (psgd.m_client_type == Networking::CLIENT_TYPE_AI_PLAYER) { // All saved AI player data, as determined from their client type, is @@ -857,7 +872,7 @@ void ServerApp::LoadSPGameInit(const std::vector& player_sav // if player is an AI, assign it to this if (player_connection->GetClientType() == Networking::CLIENT_TYPE_AI_PLAYER) { int player_id = player_connection->PlayerID(); - player_id_to_save_game_data_index.push_back(std::make_pair(player_id, i)); + player_id_to_save_game_data_index.push_back({player_id, i}); break; } } @@ -870,53 +885,198 @@ void ServerApp::LoadSPGameInit(const std::vector& player_sav LoadGameInit(player_save_game_data, player_id_to_save_game_data_index, server_save_game_data); } -void ServerApp::UpdateSavePreviews(const Message& msg, PlayerConnectionPtr player_connection){ - DebugLogger() << "ServerApp::UpdateSavePreviews: ServerApp UpdateSavePreviews"; +namespace { + /** Check that \p path is a file or directory in the server save + directory. */ + bool IsInServerSaveDir(const fs::path& path) { + if (!fs::exists(path)) + return false; - std::string directory_name; - ExtractRequestSavePreviewsMessageData(msg, directory_name); + return IsInDir(GetServerSaveDir(), + (fs::is_regular_file(path) ? path.parent_path() : path)); + } - DebugLogger() << "ServerApp::UpdateSavePreviews: Got preview request for directory: " << directory_name; + /// Generates information on the subdirectories of \p directory + std::vector ListSaveSubdirectories(const fs::path& directory) { + std::vector list; + if (!fs::is_directory(directory)) + return list; - fs::path directory = GetSaveDir() / directory_name; - // Do not allow a relative path to lead outside the save directory. - if(!IsInside(directory, GetSaveDir())) { + auto server_dir_str = PathToString(fs::canonical(GetServerSaveDir())); + + // Adds \p subdir to the list + auto add_to_list = [&list, &server_dir_str](const fs::path& subdir) { + auto subdir_str = PathToString(fs::canonical(subdir)); + auto rel_path = subdir_str.substr(server_dir_str.length()); + list.push_back(rel_path); + TraceLogger() << "Added relative path " << rel_path << " in " << subdir + << " to save preview directories"; + }; + + // Add parent dir if still within server_dir_str + auto parent = directory / ".."; + if (IsInServerSaveDir(parent)) + add_to_list(parent); + + // Add all directories to list + fs::directory_iterator end; + for (fs::directory_iterator it(fs::canonical(directory)); it != end; ++it) { + if (!fs::is_directory(it->path()) || !IsInServerSaveDir(it->path())) + continue; + add_to_list(it->path()); + } + return list; + } +} + +void ServerApp::UpdateSavePreviews(const Message& msg, + PlayerConnectionPtr player_connection) +{ + // Only relative paths are allowed to prevent client from list arbitrary + // directories, or knowing the absolute path of the server save directory. + std::string relative_directory_name; + ExtractRequestSavePreviewsMessageData(msg, relative_directory_name); + + DebugLogger() << "ServerApp::UpdateSavePreviews: Preview request for sub directory: " << relative_directory_name; + + fs::path directory = GetServerSaveDir() / FilenameToPath(relative_directory_name); + // Do not allow a relative path to explore outside the save directory. + bool contains_dot_dot = relative_directory_name.find("..") != std::string::npos; + if (contains_dot_dot || !IsInServerSaveDir(directory)) { + directory = GetServerSaveDir(); ErrorLogger() << "ServerApp::UpdateSavePreviews: Tried to load previews from " - << directory_name + << relative_directory_name << " which is outside the allowed save directory. Defaulted to the save directory, " << directory; - directory = GetSaveDir(); - directory_name = "."; + relative_directory_name = "."; } PreviewInformation preview_information; - preview_information.folder = directory_name; - ListSaveSubdirectories( preview_information.subdirectories); - LoadSaveGamePreviews(directory_name, m_single_player_game? SP_SAVE_FILE_EXTENSION : MP_SAVE_FILE_EXTENSION, preview_information.previews); - DebugLogger() << "ServerApp::UpdateSavePreviews: Sending " << preview_information.previews.size() << " previews in response."; + preview_information.folder = std::move(relative_directory_name); + preview_information.subdirectories = ListSaveSubdirectories(directory); + LoadSaveGamePreviews( + directory, + m_single_player_game? SP_SAVE_FILE_EXTENSION : MP_SAVE_FILE_EXTENSION, + preview_information.previews); + + DebugLogger() << "ServerApp::UpdateSavePreviews: Sending " << preview_information.previews.size() + << " previews in response."; + player_connection->SendMessage(DispatchSavePreviewsMessage(preview_information)); - DebugLogger() << "ServerApp::UpdateSavePreviews: Previews sent."; } -void ServerApp::UpdateCombatLogs(const Message& msg, PlayerConnectionPtr player_connection){ +void ServerApp::UpdateCombatLogs(const Message& msg, PlayerConnectionPtr player_connection) { std::vector ids; ExtractRequestCombatLogsMessageData(msg, ids); // Compose a vector of the requested ids and logs std::vector> logs; - for (std::vector::iterator it = ids.begin(); it != ids.end(); ++it) { + for (auto it = ids.begin(); it != ids.end(); ++it) { boost::optional log = GetCombatLogManager().GetLog(*it); if (!log) { ErrorLogger() << "UpdateCombatLogs can't fetch log with id = "<< *it << " ... skipping."; continue; } - logs.push_back(std::make_pair(*it, *log)); + logs.push_back({*it, *log}); } // Return them to the client DebugLogger() << "UpdateCombatLogs returning " << logs.size() << " logs to player " << player_connection->PlayerID(); - player_connection->SendMessage(DispatchCombatLogsMessage(logs)); + + try { + bool use_binary_serialization = player_connection->IsBinarySerializationUsed(); + player_connection->SendMessage(DispatchCombatLogsMessage(logs, use_binary_serialization)); + } catch (const std::exception& e) { + ErrorLogger() << "caught exception sending combat logs message: " << e.what(); + std::vector> empty_logs; + player_connection->SendMessage(DispatchCombatLogsMessage(empty_logs, false)); + } +} + +void ServerApp::LoadChatHistory() { + // don't load history if it was already loaded + if (!m_chat_history.empty()) + return; + + bool success = false; + try { + m_python_server.SetCurrentDir(GetPythonChatDir()); + // Call the Python load_history function + success = m_python_server.LoadChatHistory(m_chat_history); + } catch (const boost::python::error_already_set& err) { + success = false; + m_python_server.HandleErrorAlreadySet(); + if (!m_python_server.IsPythonRunning()) { + ErrorLogger() << "Python interpreter is no longer running. Attempting to restart."; + if (m_python_server.Initialize()) { + ErrorLogger() << "Python interpreter successfully restarted."; + } else { + ErrorLogger() << "Python interpreter failed to restart. Exiting."; + m_fsm->process_event(ShutdownServer()); + } + } + } + + if (!success) { + ErrorLogger() << "Python scripted chat failed."; + ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"), + false)); + } +} + +void ServerApp::PushChatMessage(const std::string& text, + const std::string& player_name, + GG::Clr text_color, + const boost::posix_time::ptime& timestamp) +{ + ChatHistoryEntity chat; + chat.m_timestamp = timestamp; + chat.m_player_name = player_name; + chat.m_text_color = text_color; + chat.m_text = text; + m_chat_history.push_back(chat); + + bool success = false; + try { + m_python_server.SetCurrentDir(GetPythonChatDir()); + // Call the Python load_history function + success = m_python_server.PutChatHistoryEntity(chat); + } catch (const boost::python::error_already_set& err) { + success = false; + m_python_server.HandleErrorAlreadySet(); + if (!m_python_server.IsPythonRunning()) { + ErrorLogger() << "Python interpreter is no longer running. Attempting to restart."; + if (m_python_server.Initialize()) { + ErrorLogger() << "Python interpreter successfully restarted."; + } else { + ErrorLogger() << "Python interpreter failed to restart. Exiting."; + m_fsm->process_event(ShutdownServer()); + } + } + } + + if (!success) { + ErrorLogger() << "Python scripted chat failed."; + ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"), + false)); + } +} + +void ServerApp::ExpireTurn() { + InfoLogger() << "Turn was set to expired"; + m_turn_expired = true; +} + +bool ServerApp::IsTurnExpired() const +{ return m_turn_expired; } + +bool ServerApp::IsHaveWinner() const { + for (const auto& empire : m_empires) { + if (empire.second->Won()) + return true; + } + return false; } namespace { @@ -924,16 +1084,16 @@ namespace { bool HumanPlayerWithIdConnected(const ServerNetworking& sn, int id) { // make sure there is a human player connected with the player id // matching what this PlayerSetupData say - ServerNetworking::const_established_iterator established_player_it = sn.GetPlayer(id); + auto established_player_it = sn.GetPlayer(id); if (established_player_it == sn.established_end()) { ErrorLogger() << "ServerApp::LoadMPGameInit couldn't find player connection for " - << "human player setup data with player id: " << id; + << "human player setup data with player id: " << id; return false; } const PlayerConnectionPtr player_connection = *established_player_it; if (player_connection->GetClientType() != Networking::CLIENT_TYPE_HUMAN_PLAYER) { ErrorLogger() << "ServerApp::LoadMPGameInit found player connection of wrong type " - << "for human player setup data with player id: " << id; + << "for human player setup data with player id: " << id; return false; } return true; @@ -975,7 +1135,7 @@ namespace { // determine and store save game data index for this player int index = VectorIndexForPlayerSaveGameDataForEmpireID(player_save_game_data, psd.m_save_game_empire_id); if (index != -1) { - player_id_to_save_game_data_index.push_back(std::make_pair(setup_data_player_id, index)); + player_id_to_save_game_data_index.push_back({setup_data_player_id, index}); } else { ErrorLogger() << "ServerApp::LoadMPGameInit couldn't find save game data for " << "human player with assigned empire id: " << psd.m_save_game_empire_id; @@ -987,7 +1147,7 @@ namespace { if (player_name.empty()) return Networking::INVALID_PLAYER_ID; - for (ServerNetworking::const_established_iterator established_player_it = sn.established_begin(); + for (auto established_player_it = sn.established_begin(); established_player_it != sn.established_end(); ++established_player_it) { const PlayerConnectionPtr player_connection = *established_player_it; @@ -1038,7 +1198,7 @@ namespace { // determine and store save game data index for this player int index = VectorIndexForPlayerSaveGameDataForEmpireID(player_save_game_data, psd.m_save_game_empire_id); if (index != -1) { - player_id_to_save_game_data_index.push_back(std::make_pair(player_id, index)); + player_id_to_save_game_data_index.push_back({player_id, index}); } else { ErrorLogger() << "ServerApp::LoadMPGameInit couldn't find save game data for " << "human player with assigned empire id: " << psd.m_save_game_empire_id; @@ -1053,7 +1213,7 @@ void ServerApp::LoadMPGameInit(const MultiplayerLobbyData& lobby_data, // Need to determine which data in player_save_game_data should be assigned to which established player std::vector> player_id_to_save_game_data_index; - const std::list>& player_setup_data = lobby_data.m_players; + const auto& player_setup_data = lobby_data.m_players; // * Multiplayer lobby data has a map from player ID to PlayerSetupData. // * PlayerSetupData contains an empire ID that the player will be controlling. @@ -1066,7 +1226,7 @@ void ServerApp::LoadMPGameInit(const MultiplayerLobbyData& lobby_data, // for every player setup data entry that represents an empire in the game, // assign saved game data to the player ID of an established human or AI player - for (const std::pair& entry : player_setup_data) { + for (const auto& entry : player_setup_data) { const PlayerSetupData& psd = entry.second; if (psd.m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) { @@ -1104,15 +1264,21 @@ void ServerApp::LoadGameInit(const std::vector& player_save_ } // ensure number of players connected and for which data are provided are consistent - if (player_id_to_save_game_data_index.size() != player_save_game_data.size()) { + if (player_id_to_save_game_data_index.size() != player_save_game_data.size()) ErrorLogger() << "ServerApp::LoadGameInit passed index mapping and player save game data are of different sizes..."; - } - if (m_networking.NumEstablishedPlayers() != player_save_game_data.size()) { - ErrorLogger() << "ServerApp::LoadGameInit has " << m_networking.NumEstablishedPlayers() << " established players but " << player_save_game_data.size() << " entries in player save game data. Could be ok... so not aborting, but might crash"; - } + + if (m_networking.NumEstablishedPlayers() != player_save_game_data.size()) + ErrorLogger() << "ServerApp::LoadGameInit has " << m_networking.NumEstablishedPlayers() + << " established players but " << player_save_game_data.size() + << " entries in player save game data. Could be ok... so not aborting, but might crash"; + + + // set game rules for server based on those specified in setup data + GetGameRules().SetFromStrings(m_galaxy_setup_data.GetGameRules()); + // validate some connection info - for (ServerNetworking::const_established_iterator player_connection_it = m_networking.established_begin(); + for (auto player_connection_it = m_networking.established_begin(); player_connection_it != m_networking.established_end(); ++player_connection_it) { const PlayerConnectionPtr player_connection = *player_connection_it; @@ -1139,16 +1305,16 @@ void ServerApp::LoadGameInit(const std::vector& player_save_ std::map player_id_save_game_data; // add empires to turn processing and record empires for each player - for (ServerNetworking::const_established_iterator player_connection_it = m_networking.established_begin(); + for (auto player_connection_it = m_networking.established_begin(); player_connection_it != m_networking.established_end(); ++player_connection_it) { const PlayerConnectionPtr player_connection = *player_connection_it; - int player_id = player_connection->PlayerID(); - if (player_id == Networking::INVALID_PLAYER_ID) { - ErrorLogger() << "LoadGameInit got invalid player id from connection"; + if (!player_connection->IsEstablished()) { + ErrorLogger() << "LoadGameInit got player from connection"; continue; } + int player_id = player_connection->PlayerID(); // get index into save game data for this player id int player_save_game_data_index = -1; // default invalid index @@ -1183,10 +1349,18 @@ void ServerApp::LoadGameInit(const std::vector& player_save_ // player ID than when the game was first created m_player_empire_ids[player_id] = empire_id; + // set actual authentication status + if (Empire* empire = GetEmpire(empire_id)) { + empire->SetAuthenticated(player_connection->IsAuthenticated()); + } + } + for (const auto& psgd : player_save_game_data) { + int empire_id = psgd.m_empire_id; // add empires to turn processing, and restore saved orders and UI data or save state data - if (GetEmpire(empire_id)) { - AddEmpireTurn(empire_id); + if (Empire* empire = GetEmpire(empire_id)) { + if (!empire->Eliminated()) + AddEmpireTurn(empire_id, psgd); } else { ErrorLogger() << "ServerApp::LoadGameInit couldn't find empire with id " << empire_id << " to add to turn processing"; } @@ -1197,33 +1371,14 @@ void ServerApp::LoadGameInit(const std::vector& player_save_ // so need to be reinitialized when loading based on the gamestate m_universe.InitializeSystemGraph(); - - // Determine supply distribution and exchanging and resource pools for empires - EmpireManager& empires = Empires(); - for (std::map::value_type& entry : empires) { - Empire* empire = entry.second; - if (empire->Eliminated()) - continue; // skip eliminated empires. presumably this shouldn't be an issue when initializing a new game, but apparently I thought this was worth checking for... - empire->UpdateSupplyUnobstructedSystems(); // determines which systems can propagate fleet and resource (same for both) - empire->UpdateSystemSupplyRanges(); // sets range systems can propagate fleet and resourse supply (separately) - } - - GetSupplyManager().Update(); - - for (std::map::value_type& entry : empires) { - Empire* empire = entry.second; - if (empire->Eliminated()) - continue; // skip eliminated empires. presumably this shouldn't be an issue when initializing a new game, but apparently I thought this was worth checking for... - empire->InitResourcePools(); // determines population centers and resource centers of empire, tells resource pools the centers and groups of systems that can share resources (note that being able to share resources doesn't mean a system produces resources) - empire->UpdateResourcePools(); // determines how much of each resources is available in each resource sharing group - } + UpdateEmpireSupply(true); // precombat type supply update std::map player_info_map = GetPlayerInfoMap(); // assemble player state information, and send game start messages DebugLogger() << "ServerApp::CommonGameInit: Sending GameStartMessages to players"; - for (ServerNetworking::const_established_iterator player_connection_it = m_networking.established_begin(); + for (auto player_connection_it = m_networking.established_begin(); player_connection_it != m_networking.established_end(); ++player_connection_it) { const PlayerConnectionPtr player_connection = *player_connection_it; @@ -1232,10 +1387,11 @@ void ServerApp::LoadGameInit(const std::vector& player_save_ // attempt to find saved state data for this player. PlayerSaveGameData psgd; - std::map::const_iterator save_data_it = player_id_save_game_data.find(player_id); + auto save_data_it = player_id_save_game_data.find(player_id); if (save_data_it != player_id_save_game_data.end()) { psgd = save_data_it->second; - } else { + } + if (!psgd.m_orders) { psgd.m_orders.reset(new OrderSet()); // need an empty order set pointed to for serialization in case no data is loaded but the game start message wants orders to send } @@ -1245,12 +1401,17 @@ void ServerApp::LoadGameInit(const std::vector& player_save_ ErrorLogger() << "LoadGameInit got inconsistent empire ids between player save game data and result of PlayerEmpireID"; } + // Revoke readiness only for online players so they can redo orders for the current turn. + // Without doing it, server would immediatly advance the turn because saves are made when + // all players sent orders and became ready. + RevokeEmpireTurnReadyness(empire_id); + // restore saved orders. these will be re-executed on client and // re-sent to the server (after possibly modification) by clients // when they end their turn - std::shared_ptr orders = psgd.m_orders; + auto orders = psgd.m_orders; - bool use_binary_serialization = player_connection->ClientVersionStringMatchesThisServer(); + bool use_binary_serialization = player_connection->IsBinarySerializationUsed(); if (client_type == Networking::CLIENT_TYPE_AI_PLAYER) { // get save state string @@ -1290,6 +1451,12 @@ void ServerApp::LoadGameInit(const std::vector& player_save_ void ServerApp::GenerateUniverse(std::map& player_setup_data) { Universe& universe = GetUniverse(); + // Set game UID. Needs to be done first so we can use ClockSeed to + // prevent reproducible UIDs. + ClockSeed(); + if (GetOptionsDB().Get("setup.game.uid").empty()) + GetGalaxySetupData().SetGameUID(boost::uuids::to_string(boost::uuids::random_generator()())); + // Initialize RNG with provided seed to get reproducible universes int seed = 0; try { @@ -1301,7 +1468,7 @@ void ServerApp::GenerateUniverse(std::map& player_setup_da seed = static_cast(h); } catch (...) {} } - if (GetGalaxySetupData().m_seed.empty()) { + if (GetGalaxySetupData().GetSeed().empty() || GetGalaxySetupData().GetSeed() == "RANDOM") { //ClockSeed(); // replicate ClockSeed code here so can log the seed used boost::posix_time::ptime ltime = boost::posix_time::microsec_clock::local_time(); @@ -1311,13 +1478,14 @@ void ServerApp::GenerateUniverse(std::map& player_setup_da DebugLogger() << "GenerateUniverse using clock for seed:" << new_seed; seed = static_cast(h); // store seed in galaxy setup data - ServerApp::GetApp()->GetGalaxySetupData().m_seed = std::to_string(seed); + ServerApp::GetApp()->GetGalaxySetupData().SetSeed(std::to_string(seed)); } Seed(seed); DebugLogger() << "GenerateUniverse with seed: " << seed; // Reset the universe object for a new universe - universe.ResetUniverse(); + universe.Clear(); + GetSpeciesManager().ClearSpeciesHomeworlds(); // Reset the object id manager for the new empires. std::vector empire_ids(player_setup_data.size()); @@ -1337,7 +1505,7 @@ void ServerApp::GenerateUniverse(std::map& player_setup_da m_python_server.SetCurrentDir(GetPythonUniverseGeneratorDir()); // Call the main Python universe generator function success = m_python_server.CreateUniverse(player_setup_data); - } catch (boost::python::error_already_set err) { + } catch (const boost::python::error_already_set& err) { success = false; m_python_server.HandleErrorAlreadySet(); if (!m_python_server.IsPythonRunning()) { @@ -1349,13 +1517,26 @@ void ServerApp::GenerateUniverse(std::map& player_setup_da if (!success) ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_UNIVERSE_GENERATION_ERRORS"), false)); + for (auto& empire : Empires()) { + empire.second->ApplyNewTechs(); + } DebugLogger() << "Applying first turn effects and updating meters"; // Apply effects for 1st turn. universe.ApplyAllEffectsAndUpdateMeters(false); + + TraceLogger(effects) << "After First turn meter effect applying: " << universe.Objects().Dump(); // Set active meters to targets or maxes after first meter effects application SetActiveMetersToTargetMaxCurrentValues(universe.Objects()); + + universe.UpdateMeterEstimates(); + universe.BackPropagateObjectMeters(); + SetActiveMetersToTargetMaxCurrentValues(universe.Objects()); + universe.BackPropagateObjectMeters(); + + TraceLogger(effects) << "After First active set to target/max: " << universe.Objects().Dump(); + universe.BackPropagateObjectMeters(); Empires().BackPropagateMeters(); @@ -1384,7 +1565,7 @@ void ServerApp::ExecuteScriptedTurnEvents() { m_python_server.SetCurrentDir(GetPythonTurnEventsDir()); // Call the main Python turn events function success = m_python_server.ExecuteTurnEvents(); - } catch (boost::python::error_already_set err) { + } catch (const boost::python::error_already_set& err) { success = false; m_python_server.HandleErrorAlreadySet(); if (!m_python_server.IsPythonRunning()) { @@ -1408,7 +1589,7 @@ std::map ServerApp::GetPlayerInfoMap() const { // compile information about players to send out to other players at start of game. DebugLogger() << "ServerApp::GetPlayerInfoMap: Compiling PlayerInfo for each player"; std::map player_info_map; - for (ServerNetworking::const_established_iterator player_connection_it = m_networking.established_begin(); + for (auto player_connection_it = m_networking.established_begin(); player_connection_it != m_networking.established_end(); ++player_connection_it) { const PlayerConnectionPtr player_connection = *player_connection_it; @@ -1438,7 +1619,7 @@ std::map ServerApp::GetPlayerInfoMap() const { } int ServerApp::PlayerEmpireID(int player_id) const { - std::map::const_iterator it = m_player_empire_ids.find(player_id); + auto it = m_player_empire_ids.find(player_id); if (it != m_player_empire_ids.end()) return it->second; else @@ -1446,14 +1627,14 @@ int ServerApp::PlayerEmpireID(int player_id) const { } int ServerApp::EmpirePlayerID(int empire_id) const { - for (const std::map::value_type& entry : m_player_empire_ids) + for (const auto& entry : m_player_empire_ids) if (entry.second == empire_id) return entry.first; return Networking::INVALID_PLAYER_ID; } bool ServerApp::IsLocalHumanPlayer(int player_id) { - ServerNetworking::const_established_iterator it = m_networking.GetPlayer(player_id); + auto it = m_networking.GetPlayer(player_id); if (it == m_networking.established_end()) { ErrorLogger() << "ServerApp::IsLocalHumanPlayer : could not get player connection for player id " << player_id; return false; @@ -1465,15 +1646,336 @@ bool ServerApp::IsLocalHumanPlayer(int player_id) { } bool ServerApp::IsAvailableName(const std::string& player_name) const { - for (ServerNetworking::const_established_iterator it = m_networking.established_begin(); + if (player_name.empty()) + return false; + for (auto it = m_networking.established_begin(); it != m_networking.established_end(); ++it) { if ((*it)->PlayerName() == player_name) return false; } + // check if some name reserved with cookie + return m_networking.IsAvailableNameInCookies(player_name); +} + +bool ServerApp::IsAuthRequiredOrFillRoles(const std::string& player_name, Networking::AuthRoles& roles) { + bool result = false; + bool success = false; + try { + m_python_server.SetCurrentDir(GetPythonAuthDir()); + // Call the main Python turn events function + success = m_python_server.IsRequireAuthOrReturnRoles(player_name, result, roles); + } catch (const boost::python::error_already_set& err) { + success = false; + m_python_server.HandleErrorAlreadySet(); + if (!m_python_server.IsPythonRunning()) { + ErrorLogger() << "Python interpreter is no longer running. Attempting to restart."; + if (m_python_server.Initialize()) { + ErrorLogger() << "Python interpreter successfully restarted."; + } else { + ErrorLogger() << "Python interpreter failed to restart. Exiting."; + m_fsm->process_event(ShutdownServer()); + } + } + } + + if (!success) { + ErrorLogger() << "Python scripted authentication failed."; + ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"), + false)); + } + return result; +} + +bool ServerApp::IsAuthSuccessAndFillRoles(const std::string& player_name, const std::string& auth, Networking::AuthRoles& roles) { + bool result = false; + bool success = false; + try { + m_python_server.SetCurrentDir(GetPythonAuthDir()); + // Call the main Python turn events function + success = m_python_server.IsSuccessAuthAndReturnRoles(player_name, auth, result, roles); + } catch (const boost::python::error_already_set& err) { + success = false; + m_python_server.HandleErrorAlreadySet(); + if (!m_python_server.IsPythonRunning()) { + ErrorLogger() << "Python interpreter is no longer running. Attempting to restart."; + if (m_python_server.Initialize()) { + ErrorLogger() << "Python interpreter successfully restarted."; + } else { + ErrorLogger() << "Python interpreter failed to restart. Exiting."; + m_fsm->process_event(ShutdownServer()); + } + } + } + + if (!success) { + ErrorLogger() << "Python scripted authentication failed."; + ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"), + false)); + } + return result; +} + +std::list ServerApp::FillListPlayers() { + std::list result; + bool success = false; + try { + m_python_server.SetCurrentDir(GetPythonAuthDir()); + success = m_python_server.FillListPlayers(result); + } catch (const boost::python::error_already_set& err) { + success = false; + m_python_server.HandleErrorAlreadySet(); + if (!m_python_server.IsPythonRunning()) { + ErrorLogger() << "Python interpreter is no longer running. Attempting to restart."; + if (m_python_server.Initialize()) { + ErrorLogger() << "Python interpreter successfully restarted."; + } else { + ErrorLogger() << "Python interpreter failed to restart. Exiting."; + m_fsm->process_event(ShutdownServer()); + } + } + } + + if (!success) { + ErrorLogger() << "Python scripted player list failed."; + ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"), + false)); + } + return result; +} + +void ServerApp::AddObserverPlayerIntoGame(const PlayerConnectionPtr& player_connection) { + std::map player_info_map = GetPlayerInfoMap(); + + Networking::ClientType client_type = player_connection->GetClientType(); + bool use_binary_serialization = player_connection->IsBinarySerializationUsed(); + + if (client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER || + client_type == Networking::CLIENT_TYPE_HUMAN_MODERATOR) + { + // simply sends GAME_START message so established player will known he is in the game now + player_connection->SendMessage(GameStartMessage(m_single_player_game, ALL_EMPIRES, + m_current_turn, m_empires, m_universe, + GetSpeciesManager(), GetCombatLogManager(), + GetSupplyManager(), player_info_map, + m_galaxy_setup_data, use_binary_serialization)); + } else { + ErrorLogger() << "ServerApp::CommonGameInit unsupported client type: skipping game start message."; + } +} + +bool ServerApp::EliminatePlayer(const PlayerConnectionPtr& player_connection) { + if (!GetGameRules().Get("RULE_ALLOW_CONCEDE")) { + player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_CONCEDE_DISABLED"), false)); + return false; + } + + int player_id = player_connection->PlayerID(); + int empire_id = PlayerEmpireID(player_id); + if (empire_id == ALL_EMPIRES) { + player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_NONPLAYER_CANNOT_CONCEDE"), false)); + return false; + } + + // test if there other human or disconnected players in the game + bool other_human_player = false; + for (auto& empires : Empires()) { + if (!empires.second->Eliminated() && + empire_id != empires.second->EmpireID()) + { + Networking::ClientType other_client_type = GetEmpireClientType(empires.second->EmpireID()); + if (other_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER || + other_client_type == Networking::INVALID_CLIENT_TYPE) + { + other_human_player = true; + break; + } + } + } + if (!other_human_player) { + player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_CONCEDE_LAST_HUMAN_PLAYER"), false)); + return false; + } + + Empire* empire = GetEmpire(empire_id); + if (!empire) { + player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_NONPLAYER_CANNOT_CONCEDE"), false)); + return false; + } + + // test for colonies count + auto planets = Objects().find(OwnedVisitor(empire_id)); + if (planets.size() > static_cast(GetGameRules().Get("RULE_CONCEDE_COLONIES_THRESHOLD"))) { + player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_CONCEDE_EXCEED_COLONIES"), false)); + return false; + } + + // empire elimination + empire->Eliminate(); + + // destroy owned ships + for (auto& obj : Objects().find(OwnedVisitor(empire_id))) { + obj->SetOwner(ALL_EMPIRES); + GetUniverse().RecursiveDestroy(obj->ID()); + } + // destroy owned buildings + for (auto& obj : Objects().find(OwnedVisitor(empire_id))) { + obj->SetOwner(ALL_EMPIRES); + GetUniverse().RecursiveDestroy(obj->ID()); + } + // unclaim owned planets + for (const auto& planet : planets) { + planet->Reset(); + } + + // Don't wait for turn + RemoveEmpireTurn(empire_id); + + // break link between player and empire + m_player_empire_ids.erase(player_id); + + // notify other player that this empire finished orders + // so them don't think player of eliminated empire is making its turn too long + for (auto player_it = m_networking.established_begin(); + player_it != m_networking.established_end(); ++player_it) + { + PlayerConnectionPtr player_ctn = *player_it; + player_ctn->SendMessage(PlayerStatusMessage(Message::WAITING, + empire_id)); + } + return true; } +void ServerApp::DropPlayerEmpireLink(int player_id) +{ m_player_empire_ids.erase(player_id); } + +int ServerApp::AddPlayerIntoGame(const PlayerConnectionPtr& player_connection, int target_empire_id) { + Empire* empire = nullptr; + int empire_id = ALL_EMPIRES; + std::list delegation = GetPlayerDelegation(player_connection->PlayerName()); + if (target_empire_id == ALL_EMPIRES) { + if (!delegation.empty()) + return ALL_EMPIRES; + // search empire by player name + for (auto e : Empires()) { + if (e.second->PlayerName() == player_connection->PlayerName()) { + empire_id = e.first; + empire = e.second; + break; + } + } + } else { + // use provided empire and test if it's player himself or one of delegated + empire_id = target_empire_id; + empire = Empires().GetEmpire(target_empire_id); + if (empire == nullptr) + return ALL_EMPIRES; + + if (empire->PlayerName() != player_connection->PlayerName()) { + bool matched = false; + for (const auto& delegated : delegation) { + if (empire->PlayerName() == delegated) { + matched = true; + break; + } + } + if (!matched) + return ALL_EMPIRES; + } + } + + if (empire_id == ALL_EMPIRES || empire == nullptr) + return ALL_EMPIRES; + + if (empire->Eliminated()) + return ALL_EMPIRES; + + auto orders_it = m_turn_sequence.find(empire_id); + if (orders_it == m_turn_sequence.end()) { + WarnLogger() << "ServerApp::AddPlayerIntoGame empire " << empire_id + << " for \"" << player_connection->PlayerName() + << "\" doesn't wait for orders"; + return ALL_EMPIRES; + } + + // make a link to new connection + m_player_empire_ids[player_connection->PlayerID()] = empire_id; + empire->SetAuthenticated(player_connection->IsAuthenticated()); + + const OrderSet dummy; + const OrderSet& orders = orders_it->second && orders_it->second->m_orders ? *(orders_it->second->m_orders) : dummy; + const SaveGameUIData* ui_data = orders_it->second ? orders_it->second->m_ui_data.get() : nullptr; + + if (GetOptionsDB().Get("network.server.drop-empire-ready")) { + // drop ready status + empire->SetReady(false); + m_networking.SendMessageAll(PlayerStatusMessage(Message::PLAYING_TURN, + empire_id)); + } + + auto player_info_map = GetPlayerInfoMap(); + bool use_binary_serialization = player_connection->IsBinarySerializationUsed(); + + player_connection->SendMessage(GameStartMessage( + m_single_player_game, empire_id, + m_current_turn, m_empires, m_universe, + GetSpeciesManager(), GetCombatLogManager(), + GetSupplyManager(), player_info_map, orders, + ui_data, + m_galaxy_setup_data, + use_binary_serialization)); + + return empire_id; +} + +std::list ServerApp::GetPlayerDelegation(const std::string& player_name) { + std::list result; + bool success = false; + try { + m_python_server.SetCurrentDir(GetPythonAuthDir()); + // Call the auth provider function get_player_delegation + success = m_python_server.GetPlayerDelegation(player_name, result); + } catch (const boost::python::error_already_set& err) { + success = false; + m_python_server.HandleErrorAlreadySet(); + if (!m_python_server.IsPythonRunning()) { + ErrorLogger() << "Python interpreter is no longer running. Attempting to restart."; + if (m_python_server.Initialize()) { + ErrorLogger() << "Python interpreter successfully restarted."; + } else { + ErrorLogger() << "Python interpreter failed to restart. Exiting."; + m_fsm->process_event(ShutdownServer()); + } + } + } + + if (!success) { + ErrorLogger() << "Python scripted authentication failed."; + ServerApp::GetApp()->Networking().SendMessageAll(ErrorMessage(UserStringNop("SERVER_TURN_EVENTS_ERRORS"), + false)); + } + return result; +} + +bool ServerApp::IsHostless() const +{ return GetOptionsDB().Get("hostless"); } + +const boost::circular_buffer& ServerApp::GetChatHistory() const +{ return m_chat_history; } + +std::vector ServerApp::GetPlayerSaveGameData() const { + std::vector player_save_game_data; + for (const auto& m_save_data : m_turn_sequence) { + DebugLogger() << "ServerApp::GetPlayerSaveGameData() Empire " << m_save_data.first + << " save_game_data " << m_save_data.second.get(); + if (m_save_data.second) { + player_save_game_data.push_back(*m_save_data.second); + } + } + return player_save_game_data; +} + Networking::ClientType ServerApp::GetEmpireClientType(int empire_id) const { return GetPlayerClientType(ServerApp::EmpirePlayerID(empire_id)); } @@ -1481,7 +1983,7 @@ Networking::ClientType ServerApp::GetPlayerClientType(int player_id) const { if (player_id == Networking::INVALID_PLAYER_ID) return Networking::INVALID_CLIENT_TYPE; - ServerNetworking::const_established_iterator it = m_networking.GetPlayer(player_id); + auto it = m_networking.GetPlayer(player_id); if (it == m_networking.established_end()) return Networking::INVALID_CLIENT_TYPE; PlayerConnectionPtr player_connection = *it; @@ -1489,10 +1991,10 @@ Networking::ClientType ServerApp::GetPlayerClientType(int player_id) const { } int ServerApp::EffectsProcessingThreads() const -{ return GetOptionsDB().Get("effects-threads-server"); } +{ return GetOptionsDB().Get("effects.server.threads"); } -void ServerApp::AddEmpireTurn(int empire_id) -{ m_turn_sequence[empire_id].reset(nullptr); } +void ServerApp::AddEmpireTurn(int empire_id, const PlayerSaveGameData& psgd) +{ m_turn_sequence[empire_id] = std::make_unique(psgd); } void ServerApp::RemoveEmpireTurn(int empire_id) { m_turn_sequence.erase(empire_id); } @@ -1500,108 +2002,132 @@ void ServerApp::RemoveEmpireTurn(int empire_id) void ServerApp::ClearEmpireTurnOrders() { for (auto& order : m_turn_sequence) { if (order.second) { - order.second.reset(nullptr); + // reset only orders + // left UI data and AI state intact + order.second->m_orders.reset(); } } + for (auto& empire : Empires()) { + empire.second->SetReady(false); + } } -void ServerApp::SetEmpireTurnOrders(int empire_id, std::unique_ptr&& order_set) -{ m_turn_sequence[empire_id] = std::move(order_set); } +void ServerApp::SetEmpireSaveGameData(int empire_id, std::unique_ptr&& save_game_data) +{ m_turn_sequence[empire_id] = std::move(save_game_data); } + +void ServerApp::UpdatePartialOrders(int empire_id, const OrderSet& added, const std::set& deleted) { + const auto& psgd = m_turn_sequence[empire_id]; + if (psgd) { + if (psgd->m_orders) { + for (int id : deleted) + psgd->m_orders->erase(id); + for (auto it : added) + psgd->m_orders->insert(it); + } else { + psgd->m_orders = std::make_shared(added); + } + } +} + +void ServerApp::RevokeEmpireTurnReadyness(int empire_id) { + if (auto* empire = GetEmpire(empire_id)) + empire->SetReady(false); +} bool ServerApp::AllOrdersReceived() { // debug output - DebugLogger() << "ServerApp::AllOrdersReceived for turn: " << m_current_turn; + DebugLogger() << "ServerApp::AllOrdersReceived for turn: " << m_current_turn + << (m_turn_expired ? " (expired)" : ""); + bool all_orders_received = true; for (const auto& empire_orders : m_turn_sequence) { - if (!empire_orders.second) + bool empire_orders_received = true; + const auto empire = GetEmpire(empire_orders.first); + if (!empire) { + ErrorLogger() << " ... invalid empire id in turn sequence: "<< empire_orders.first; + continue; + } else if (empire->Eliminated()) { + ErrorLogger() << " ... eliminated empire in turn sequence: " << empire_orders.first; + continue; + } else if (!empire->Ready()) { + DebugLogger() << " ... not ready empire id: " << empire_orders.first; + empire_orders_received = false; + } else if (!empire_orders.second) { DebugLogger() << " ... no orders from empire id: " << empire_orders.first; - else + empire_orders_received = false; + } else if (!empire_orders.second->m_orders) { + DebugLogger() << " ... no orders from empire id: " << empire_orders.first; + empire_orders_received = false; + } else { DebugLogger() << " ... have orders from empire id: " << empire_orders.first; + } + if (!empire_orders_received) { + if (GetEmpireClientType(empire_orders.first) != Networking::CLIENT_TYPE_AI_PLAYER + && m_turn_expired) + { + DebugLogger() << " ...... turn expired for empire id: " << empire_orders.first; + } else { + all_orders_received = false; + } + } } - - // Loop through to find empire ID and check for valid orders pointer - for (const auto& empire_orders : m_turn_sequence) { - if (!empire_orders.second) - return false; - } - return true; + return all_orders_received; } namespace { /** Returns true if \a empire has been eliminated by the applicable * definition of elimination. As of this writing, elimination means * having no ships and no planets. */ - bool EmpireEliminated(int empire_id) { - return (Objects().FindObjects(OwnedVisitor(empire_id)).empty() && // no planets - Objects().FindObjects(OwnedVisitor(empire_id)).empty()); // no ship + bool EmpireEliminated(int empire_id) { + return (Objects().find(OwnedVisitor(empire_id)).empty() && // no planets + Objects().find(OwnedVisitor(empire_id)).empty()); // no ship } - /** Compiles and return set of ids of empires that are controlled by a - * human player.*/ - std::set HumanControlledEmpires(const ServerApp* server_app, const ServerNetworking& server_networking) { - std::set retval; - if (!server_app) - return retval; - - for (ServerNetworking::const_established_iterator it = server_networking.established_begin(); - it != server_networking.established_end(); ++it) - { - std::shared_ptr player_connection = *it; - Networking::ClientType client_type = player_connection->GetClientType(); - if (client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) { - int player_id = player_connection->PlayerID(); - int empire_id = server_app->PlayerEmpireID(player_id); - if (empire_id == ALL_EMPIRES || player_id == Networking::INVALID_PLAYER_ID) - ErrorLogger() << "HumanControlledEmpires couldn't get a human player id or empire id"; - else - retval.insert(empire_id); - } - } - return retval; - } - void GetEmpireFleetsAtSystem(std::map>& empire_fleets, int system_id) { empire_fleets.clear(); - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) return; - for (std::shared_ptr fleet : Objects().FindObjects(system->FleetIDs())) { + for (auto& fleet : Objects().find(system->FleetIDs())) { empire_fleets[fleet->Owner()].insert(fleet->ID()); } } void GetEmpirePlanetsAtSystem(std::map>& empire_planets, int system_id) { empire_planets.clear(); - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) return; - for (std::shared_ptr planet : Objects().FindObjects(system->PlanetIDs())) { + for (auto& planet : Objects().find(system->PlanetIDs())) { if (!planet->Unowned()) empire_planets[planet->Owner()].insert(planet->ID()); - else if (planet->CurrentMeterValue(METER_POPULATION) > 0.0) + else if (planet->InitialMeterValue(METER_POPULATION) > 0.0f) empire_planets[ALL_EMPIRES].insert(planet->ID()); } } - void GetFleetsVisibleToEmpireAtSystem(std::set& visible_fleets, int empire_id, int system_id) { + void GetFleetsVisibleToEmpireAtSystem(std::set& visible_fleets, + int empire_id, int system_id) + { visible_fleets.clear(); - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) return; // no such system - const std::set& fleet_ids = system->FleetIDs(); + const auto& fleet_ids = system->FleetIDs(); if (fleet_ids.empty()) return; // no fleets to be seen if (empire_id != ALL_EMPIRES && !GetEmpire(empire_id)) return; // no such empire + TraceLogger(combat) << "\t** GetFleetsVisibleToEmpire " << empire_id << " at system " << system->Name(); // for visible fleets by an empire, check visibility of fleets by that empire if (empire_id != ALL_EMPIRES) { - for (int fleet_id : fleet_ids) { - std::shared_ptr fleet = GetFleet(fleet_id); + for (const auto& fleet : Objects().find(fleet_ids)) { if (!fleet) continue; if (fleet->OwnedBy(empire_id)) continue; // don't care about fleets owned by the same empire for determining combat conditions Visibility fleet_vis = GetUniverse().GetObjectVisibilityByEmpire(fleet->ID(), empire_id); + TraceLogger(combat) << "\t\tfleet (" << fleet->ID() << ") has visibility rank " << fleet_vis; if (fleet_vis >= VIS_BASIC_VISIBILITY) visible_fleets.insert(fleet->ID()); } @@ -1613,18 +2139,16 @@ namespace { // get best monster detection strength here. Use monster detection meters for this... - double monster_detection_strength_here = 0.0; - for (int ship_id : system->ShipIDs()) { - std::shared_ptr ship = GetShip(ship_id); + float monster_detection_strength_here = 0.0f; + for (const auto& ship : Objects().find(system->ShipIDs())) { if (!ship || !ship->Unowned()) // only want unowned / monster ships continue; - if (ship->CurrentMeterValue(METER_DETECTION) > monster_detection_strength_here) - monster_detection_strength_here = ship->CurrentMeterValue(METER_DETECTION); + if (ship->InitialMeterValue(METER_DETECTION) > monster_detection_strength_here) + monster_detection_strength_here = ship->InitialMeterValue(METER_DETECTION); } // test each ship in each fleet for visibility by best monster detection here - for (int fleet_id : fleet_ids) { - std::shared_ptr fleet = GetFleet(fleet_id); + for (const auto& fleet : Objects().find(fleet_ids)) { if (!fleet) continue; if (fleet->Unowned()) { @@ -1632,12 +2156,11 @@ namespace { continue; } - for (int ship_id : fleet->ShipIDs()) { - std::shared_ptr ship = GetShip(ship_id); + for (const auto& ship : Objects().find(fleet->ShipIDs())) { if (!ship) continue; // if a ship is low enough stealth, its fleet can be seen by monsters - if (monster_detection_strength_here >= ship->CurrentMeterValue(METER_STEALTH)) { + if (monster_detection_strength_here >= ship->InitialMeterValue(METER_STEALTH)) { visible_fleets.insert(fleet->ID()); break; // fleet is seen, so don't need to check any more ships in it } @@ -1645,27 +2168,31 @@ namespace { } } - void GetPlanetsVisibleToEmpireAtSystem(std::set& visible_planets, int empire_id, int system_id) { + void GetPlanetsVisibleToEmpireAtSystem(std::set& visible_planets, + int empire_id, int system_id) + { visible_planets.clear(); - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) return; // no such system - const std::set& planet_ids = system->PlanetIDs(); + const auto& planet_ids = system->PlanetIDs(); if (planet_ids.empty()) return; // no planets to be seen if (empire_id != ALL_EMPIRES && !GetEmpire(empire_id)) return; // no such empire + TraceLogger(combat) << "\t** GetPlanetsVisibleToEmpire " << empire_id << " at system " << system->Name(); // for visible planets by an empire, check visibility of planet by that empire if (empire_id != ALL_EMPIRES) { for (int planet_id : planet_ids) { // include planets visible to empire Visibility planet_vis = GetUniverse().GetObjectVisibilityByEmpire(planet_id, empire_id); + TraceLogger(combat) << "\t\tplanet (" << planet_id << ") has visibility rank " << planet_vis; if (planet_vis <= VIS_BASIC_VISIBILITY) continue; // skip planets that have no owner and that are unpopulated; don't matter for combat conditions test - std::shared_ptr planet = GetPlanet(planet_id); - if (planet->Unowned() && planet->CurrentMeterValue(METER_POPULATION) <= 0.0) + auto planet = Objects().get(planet_id); + if (planet->Unowned() && planet->InitialMeterValue(METER_POPULATION) <= 0.0f) continue; visible_planets.insert(planet->ID()); } @@ -1677,20 +2204,20 @@ namespace { // get best monster detection strength here. Use monster detection meters for this... - double monster_detection_strength_here = 0.0; - for (std::shared_ptr ship : Objects().FindObjects(system->ShipIDs())) { + float monster_detection_strength_here = 0.0f; + for (auto& ship : Objects().find(system->ShipIDs())) { if (!ship->Unowned()) // only want unowned / monster ships continue; - if (ship->CurrentMeterValue(METER_DETECTION) > monster_detection_strength_here) - monster_detection_strength_here = ship->CurrentMeterValue(METER_DETECTION); + if (ship->InitialMeterValue(METER_DETECTION) > monster_detection_strength_here) + monster_detection_strength_here = ship->InitialMeterValue(METER_DETECTION); } // test each planet for visibility by best monster detection here - for (std::shared_ptr planet : Objects().FindObjects(system->PlanetIDs())) { + for (auto& planet : Objects().find(system->PlanetIDs())) { if (planet->Unowned()) continue; // only want empire-owned planets; unowned planets visible to monsters don't matter for combat conditions test // if a planet is low enough stealth, it can be seen by monsters - if (monster_detection_strength_here >= planet->CurrentMeterValue(METER_STEALTH)) + if (monster_detection_strength_here >= planet->InitialMeterValue(METER_STEALTH)) visible_planets.insert(planet->ID()); } } @@ -1715,46 +2242,53 @@ namespace { if (empire_fleets_here.empty()) return false; + auto this_system = Objects().get(system_id); + DebugLogger(combat) << "CombatConditionsInSystem() for system (" << system_id << ") " << this_system->Name(); // which empires have aggressive ships here? (including monsters as id ALL_EMPIRES) std::set empires_with_aggressive_fleets_here; - for (std::map>::value_type& empire_fleets : empire_fleets_here) { + for (auto& empire_fleets : empire_fleets_here) { int empire_id = empire_fleets.first; - for (int fleet_id : empire_fleets.second) { - std::shared_ptr fleet = GetFleet(fleet_id); + for (const auto& fleet : Objects().find(empire_fleets.second)) { if (!fleet) continue; // an unarmed Monster will not trigger combat if ( (fleet->Aggressive() || fleet->Unowned()) && - (fleet->HasArmedShips() || fleet->HasFighterShips() || !fleet->Unowned()) ) + (fleet->HasArmedShips() || !fleet->Unowned()) ) { + if (!empires_with_aggressive_fleets_here.count(empire_id)) + DebugLogger(combat) << "\t Empire " << empire_id << " has at least one aggressive fleet present"; empires_with_aggressive_fleets_here.insert(empire_id); break; } } } - if (empires_with_aggressive_fleets_here.empty()) + if (empires_with_aggressive_fleets_here.empty()) { + DebugLogger(combat) << "\t All fleets present are either passive or both unowned and unarmed: no combat."; return false; + } // what empires have planets here? Unowned planets are included for // ALL_EMPIRES if they have population > 0 std::map> empire_planets_here; GetEmpirePlanetsAtSystem(empire_planets_here, system_id); - if (empire_planets_here.empty() && empire_fleets_here.size() <= 1) + if (empire_planets_here.empty() && empire_fleets_here.size() <= 1) { + DebugLogger(combat) << "\t Only one combatant present: no combat."; return false; + } // all empires with something here std::set empires_here; - for (std::map>::value_type& empire_fleets : empire_fleets_here) + for (auto& empire_fleets : empire_fleets_here) { empires_here.insert(empire_fleets.first); } - for (std::map>::value_type& empire_planets : empire_planets_here) + for (auto& empire_planets : empire_planets_here) { empires_here.insert(empire_planets.first); } // what combinations of present empires are at war? std::map> empires_here_at_war; // for each empire, what other empires here is it at war with? - for (std::set::const_iterator emp1_it = empires_here.begin(); + for (auto emp1_it = empires_here.begin(); emp1_it != empires_here.end(); ++emp1_it) { - std::set::const_iterator emp2_it = emp1_it; + auto emp2_it = emp1_it; ++emp2_it; for (; emp2_it != empires_here.end(); ++emp2_it) { if (*emp1_it == ALL_EMPIRES || *emp2_it == ALL_EMPIRES || @@ -1765,29 +2299,33 @@ namespace { } } } - if (empires_here_at_war.empty()) + if (empires_here_at_war.empty()) { + DebugLogger(combat) << "\t No warring combatants present: no combat."; return false; + } // is an empire with an aggressive fleet here able to see a planet of an // empire it is at war with here? for (int aggressive_empire_id : empires_with_aggressive_fleets_here) { // what empires is the aggressive empire at war with? - const std::set& at_war_with_empire_ids = empires_here_at_war[aggressive_empire_id]; + const auto& at_war_with_empire_ids = empires_here_at_war[aggressive_empire_id]; // what planets can the aggressive empire see? std::set aggressive_empire_visible_planets; GetPlanetsVisibleToEmpireAtSystem(aggressive_empire_visible_planets, aggressive_empire_id, system_id); // is any planet owned by an empire at war with aggressive empire? - for (int planet_id : aggressive_empire_visible_planets) { - std::shared_ptr planet = GetPlanet(planet_id); + for (const auto& planet : Objects().find(aggressive_empire_visible_planets)) { if (!planet) continue; int visible_planet_empire_id = planet->Owner(); if (aggressive_empire_id != visible_planet_empire_id && - at_war_with_empire_ids.find(visible_planet_empire_id) != at_war_with_empire_ids.end()) - { return true; } // an aggressive empire can see a planet onwned by an empire it is at war with + at_war_with_empire_ids.count(visible_planet_empire_id)) + { + DebugLogger(combat) << "\t Empire " << aggressive_empire_id << " sees target planet " << planet->Name(); + return true; // an aggressive empire can see a planet onwned by an empire it is at war with + } } } @@ -1795,7 +2333,7 @@ namespace { // planet of an empire it is at war with here? for (int aggressive_empire_id : empires_with_aggressive_fleets_here) { // what empires is the aggressive empire at war with? - const std::set& at_war_with_empire_ids = empires_here_at_war[aggressive_empire_id]; + const auto& at_war_with_empire_ids = empires_here_at_war[aggressive_empire_id]; if (at_war_with_empire_ids.empty()) continue; @@ -1804,29 +2342,33 @@ namespace { GetFleetsVisibleToEmpireAtSystem(aggressive_empire_visible_fleets, aggressive_empire_id, system_id); // is any fleet owned by an empire at war with aggressive empire? - for (int fleet_id : aggressive_empire_visible_fleets) { - std::shared_ptr fleet = GetFleet(fleet_id); + for (const auto& fleet : Objects().find(aggressive_empire_visible_fleets)) { if (!fleet) continue; int visible_fleet_empire_id = fleet->Owner(); if (aggressive_empire_id != visible_fleet_empire_id && - at_war_with_empire_ids.find(visible_fleet_empire_id) != at_war_with_empire_ids.end()) - { return true; } // an aggressive empire can see a fleet onwned by an empire it is at war with + at_war_with_empire_ids.count(visible_fleet_empire_id)) + { + DebugLogger(combat) << "\t Empire " << aggressive_empire_id << " sees target fleet " << fleet->Name(); + return true; // an aggressive empire can see a fleet onwned by an empire it is at war with + } } } + DebugLogger(combat) << "\t No aggressive fleet can see a target: no combat."; return false; // no possible conditions for combat were found } /** Clears and refills \a combats with CombatInfo structs for * every system where a combat should occur this turn. */ void AssembleSystemCombatInfo(std::vector& combats) { + combats.clear(); // for each system, find if a combat will occur in it, and if so, assemble // necessary information about that combat in combats - for (int sys_id : GetUniverse().Objects().FindObjectIDs()) { - if (CombatConditionsInSystem(sys_id)) { - combats.push_back(CombatInfo(sys_id, CurrentTurn())); + for (const auto& sys : GetUniverse().Objects().all()) { + if (CombatConditionsInSystem(sys->ID())) { + combats.push_back(CombatInfo(sys->ID(), CurrentTurn())); } } } @@ -1836,7 +2378,7 @@ namespace { * updating after combat. */ void BackProjectSystemCombatInfoObjectMeters(std::vector& combats) { for (CombatInfo& combat : combats) { - for (std::shared_ptr object : combat.objects) + for (const auto& object : combat.objects.all()) object->BackPropagateMeters(); } } @@ -1846,27 +2388,36 @@ namespace { void DisseminateSystemCombatInfo(const std::vector& combats) { Universe& universe = GetUniverse(); - // loop through resolved combat infos, updating actual main universe - // with changes from combat + // as of this writing, pointers to objects are inserted into combat + // ObjectMap, and these pointers refer to the main gamestate objects + // therefore, copying the combat result state back into the main + // gamestate object map isn't necessary, as these objects have already + // been updated by the combat processing. similarly, standard + // visibility updating will transfer the results to empires' known + // gamestate ObjectMaps. for (const CombatInfo& combat_info : combats) { - // indexed by fleet id, which empire ids to inform that a fleet is destroyed - std::map> empires_to_update_of_fleet_destruction; - - // update visibilities. - for (const Universe::EmpireObjectVisibilityMap::value_type& empire_vis : combat_info.empire_object_visibility) { - for (const Universe::ObjectVisibilityMap::value_type& object_vis : empire_vis.second) { - if (object_vis.second > GetUniverse().GetObjectVisibilityByEmpire(empire_vis.first, object_vis.first)) + // update visibilities from combat, in case anything was revealed + // by shooting during combat + for (const auto& empire_vis : combat_info.empire_object_visibility) { + for (const auto& object_vis : empire_vis.second) { + if (object_vis.first < 0) + continue; // temporary fighter IDs + if (object_vis.second > GetUniverse().GetObjectVisibilityByEmpire(object_vis.first, empire_vis.first)) universe.SetEmpireObjectVisibility(empire_vis.first, object_vis.first, object_vis.second); } } + + // indexed by fleet id, which empire ids to inform that a fleet is destroyed + std::map> empires_to_update_of_fleet_destruction; + // update which empires know objects are destroyed. this may // duplicate the destroyed object knowledge that is set when the // object is destroyed with Universe::Destroy, but there may also // be empires in this battle that otherwise couldn't see the object // as determined for galaxy map purposes, but which do know it has // been destroyed from having observed it during the battle. - for (const std::map>::value_type& dok : combat_info.destroyed_object_knowers) { + for (const auto& dok : combat_info.destroyed_object_knowers) { int empire_id = dok.first; for (int object_id : dok.second) { @@ -1874,9 +2425,9 @@ namespace { // << " for empire " << empire_id; universe.SetEmpireKnowledgeOfDestroyedObject(object_id, empire_id); - // should empire also be informed of potential fleet - // destruction if all ships in the fleet are destroyed? - if (std::shared_ptr ship = GetShip(object_id)) { + // record if empire should be informed of potential fleet + // destruction (which is checked later) + if (auto ship = Objects().get(object_id)) { if (ship->FleetID() != INVALID_OBJECT_ID) empires_to_update_of_fleet_destruction[ship->FleetID()].insert(empire_id); } @@ -1889,16 +2440,16 @@ namespace { // destroyed std::set all_destroyed_object_ids; for (int destroyed_object_id : combat_info.destroyed_object_ids) { - std::set dest_obj_ids = universe.RecursiveDestroy(destroyed_object_id); + auto dest_obj_ids = universe.RecursiveDestroy(destroyed_object_id); all_destroyed_object_ids.insert(dest_obj_ids.begin(), dest_obj_ids.end()); } // after recursive object destruction, fleets might have been // destroyed. If so, need to also update empires knowledge of this - for (const std::map>::value_type& fleet_empires : empires_to_update_of_fleet_destruction) { + for (const auto& fleet_empires : empires_to_update_of_fleet_destruction) { int fleet_id = fleet_empires.first; - if (all_destroyed_object_ids.find(fleet_id) == all_destroyed_object_ids.end()) + if (!all_destroyed_object_ids.count(fleet_id)) continue; // fleet wasn't destroyed // inform empires for (int empire_id : fleet_empires.second) { @@ -1911,7 +2462,7 @@ namespace { // update system ownership after combat. may be necessary if the // combat caused planets to change ownership. - if (std::shared_ptr system = GetSystem(combat_info.system_id)) { + if (auto system = Objects().get(combat_info.system_id)) { // ensure all participants get updates on system. this ensures // that an empire who lose all objects in the system still // knows about a change in system ownership @@ -1937,7 +2488,7 @@ namespace { } // sitreps about destroyed objects - for (const std::map>::value_type& empire_kdos : combat_info.destroyed_object_knowers) { + for (const auto& empire_kdos : combat_info.destroyed_object_knowers) { int empire_id = empire_kdos.first; Empire* empire = GetEmpire(empire_id); if (!empire) @@ -1945,7 +2496,7 @@ namespace { for (int dest_obj_id : empire_kdos.second) { //DebugLogger() << "Creating destroyed object sitrep for empire " << empire_id << " and object " << dest_obj_id; - //if (std::shared_ptr obj = GetEmpireKnownObject(dest_obj_id, empire_id)) { + //if (auto obj = EmpireKnownObjects(empire_id).get(dest_obj_id)) { // DebugLogger() << "Object known to empire: " << obj->Dump(); //} else { // DebugLogger() << "Object not known to empire"; @@ -1959,28 +2510,19 @@ namespace { for (int damaged_object_id : combat_info.damaged_object_ids) { //DebugLogger() << "Checking object " << damaged_object_id << " for damaged sitrep"; // is object destroyed? If so, don't need a damage sitrep - if (combat_info.destroyed_object_ids.find(damaged_object_id) != combat_info.destroyed_object_ids.end()) { + if (combat_info.destroyed_object_ids.count(damaged_object_id)) //DebugLogger() << "Object is destroyed and doesn't need a sitrep."; continue; - } // which empires know about this object? - for (const std::map::value_type& empire_ko : combat_info.empire_known_objects) { - //DebugLogger() << "Checking if empire " << empire_ko.first << " knows about the object."; + for (const auto& empire_ok : combat_info.empire_object_visibility) { // does this empire know about this object? - const ObjectMap& objects = empire_ko.second; - if (!objects.Object(damaged_object_id)) { - //DebugLogger() << "Nope."; - continue; - } - //DebugLogger() << "Yep."; - // empire knows about object, so generate a sitrep about it - int empire_id = empire_ko.first; - Empire* empire = GetEmpire(empire_id); - if (!empire) + const auto& empire_known_objects = empire_ok.second; + if (!empire_known_objects.count(damaged_object_id)) continue; - //DebugLogger() << "Creating sitrep."; - empire->AddSitRepEntry(CreateCombatDamagedObjectSitRep(damaged_object_id, combat_info.system_id, - empire_id)); + int empire_id = empire_ok.first; + if (auto empire = GetEmpire(empire_id)) + empire->AddSitRepEntry(CreateCombatDamagedObjectSitRep( + damaged_object_id, combat_info.system_id, empire_id)); } } } @@ -1989,44 +2531,44 @@ namespace { /** Records info in Empires about what they destroyed or had destroyed during combat. */ void UpdateEmpireCombatDestructionInfo(const std::vector& combats) { for (const CombatInfo& combat_info : combats) { - std::vector events_that_killed; - for (CombatEventPtr event : combat_info.combat_events) { - WeaponsPlatformEvent::ConstWeaponsPlatformEventPtr maybe_attacker - = std::dynamic_pointer_cast(event); - if (maybe_attacker) { - std::vectorsub_events - = maybe_attacker->SubEvents(maybe_attacker->attacker_owner_id); - for (ConstCombatEventPtr weapon_event : sub_events) { - const WeaponFireEvent::ConstWeaponFireEventPtr maybe_fire_event - = std::dynamic_pointer_cast(weapon_event); - if (maybe_fire_event - && combat_info.destroyed_object_ids.find(maybe_fire_event->target_id) - != combat_info.destroyed_object_ids.end()) - events_that_killed.push_back(maybe_fire_event); + std::vector flat_events; + for (auto event : combat_info.combat_events) { + flat_events.push_back(event); + for (auto event2 : event->SubEvents(ALL_EMPIRES)) { + flat_events.push_back(event2); + for (auto event3 : event2->SubEvents(ALL_EMPIRES)) { + flat_events.push_back(event3); + for (auto event4 : event3->SubEvents(ALL_EMPIRES)) + flat_events.push_back(event4); } } + } + - const WeaponFireEvent::ConstWeaponFireEventPtr maybe_fire_event - = std::dynamic_pointer_cast(event); - if (maybe_fire_event - && combat_info.destroyed_object_ids.find(maybe_fire_event->target_id) - != combat_info.destroyed_object_ids.end()) - events_that_killed.push_back(maybe_fire_event); + std::vector events_that_killed; + for (auto event : flat_events) { + auto fire_event = std::dynamic_pointer_cast(event); + if (fire_event && combat_info.destroyed_object_ids.count(fire_event->target_id)) { + events_that_killed.push_back(fire_event); + TraceLogger() << "Kill event: " << event->DebugString(); + } } + DebugLogger() << "Combat combat_info system: " << combat_info.system_id << " Total Kill Events: " << events_that_killed.size(); + // If a ship was attacked multiple times during a combat in which it dies, it will get // processed multiple times here. The below set will keep it from being logged as // multiple destroyed ships for its owner. // TODO: fix similar issue for overlogging on attacker side std::set already_logged__target_ships; - for (WeaponFireEvent::ConstWeaponFireEventPtr attack_event : events_that_killed) { - std::shared_ptr attacker = GetUniverseObject(attack_event->attacker_id); + for (const auto& attack_event : events_that_killed) { + auto attacker = Objects().get(attack_event->attacker_id); if (!attacker) continue; int attacker_empire_id = attacker->Owner(); Empire* attacker_empire = GetEmpire(attacker_empire_id); - std::shared_ptr target_ship = GetShip(attack_event->target_id); + auto target_ship = Objects().get(attack_event->target_id); if (!target_ship) continue; int target_empire_id = target_ship->Owner(); @@ -2034,6 +2576,10 @@ namespace { const std::string& target_species_name = target_ship->SpeciesName(); Empire* target_empire = GetEmpire(target_empire_id); + DebugLogger() << "Attacker " << attacker->Name() << " (id: " << attacker->ID() << " empire: " << attacker_empire_id << ") attacks " + << target_ship->Name() << " (id: " << target_ship->ID() << " empire: " + << (target_empire ? std::to_string(target_empire->EmpireID()) : "(unowned)") << " species: " << target_species_name << ")"; + std::map::iterator map_it; std::map::iterator species_it; @@ -2061,7 +2607,7 @@ namespace { } if (target_empire) { - if (already_logged__target_ships.find(attack_event->target_id) != already_logged__target_ships.end()) + if (already_logged__target_ships.count(attack_event->target_id)) continue; already_logged__target_ships.insert(attack_event->target_id); // record destruction of a ship with a species on it owned by defender empire @@ -2084,22 +2630,21 @@ namespace { /** Records info in Empires about where they invaded. */ void UpdateEmpireInvasionInfo(const std::map>& planet_empire_invasion_troops) { - for (const std::map>::value_type& planet_empire_troops : planet_empire_invasion_troops) { + for (const auto& planet_empire_troops : planet_empire_invasion_troops) { int planet_id = planet_empire_troops.first; - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) continue; - const std::string& planet_species = planet->SpeciesName(); + const auto& planet_species = planet->SpeciesName(); if (planet_species.empty()) continue; - for (const std::map::value_type& empire_troops : planet_empire_troops.second) { + for (const auto& empire_troops : planet_empire_troops.second) { Empire* invader_empire = GetEmpire(empire_troops.first); if (!invader_empire) continue; - std::map::iterator species_it = - invader_empire->SpeciesPlanetsInvaded().find(planet_species); + auto species_it = invader_empire->SpeciesPlanetsInvaded().find(planet_species); if (species_it == invader_empire->SpeciesPlanetsInvaded().end()) invader_empire->SpeciesPlanetsInvaded()[planet_species] = 1; else @@ -2110,19 +2655,19 @@ namespace { /** Does colonization, with safety checks */ bool ColonizePlanet(int ship_id, int planet_id) { - std::shared_ptr ship = GetShip(ship_id); + auto ship = Objects().get(ship_id); if (!ship) { ErrorLogger() << "ColonizePlanet couldn't get ship with id " << ship_id; return false; } - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "ColonizePlanet couldn't get planet with id " << planet_id; return false; } // get species to colonize with: species of ship - const std::string& species_name = ship->SpeciesName(); + const auto& species_name = ship->SpeciesName(); if (species_name.empty()) { ErrorLogger() << "ColonizePlanet ship has no species"; return false; @@ -2151,12 +2696,12 @@ namespace { return false; } - std::shared_ptr system = GetSystem(ship->SystemID()); + auto system = Objects().get(ship->SystemID()); // destroy colonizing ship, and its fleet if now empty - std::shared_ptr fleet = GetFleet(ship->FleetID()); + auto fleet = Objects().get(ship->FleetID()); if (fleet) { - fleet->RemoveShip(ship->ID()); + fleet->RemoveShips({ship->ID()}); if (fleet->Empty()) { if (system) system->Remove(fleet->ID()); @@ -2177,7 +2722,7 @@ namespace { // collect, for each planet, what ships have been ordered to colonize it std::map>> planet_empire_colonization_ship_ids; // map from planet ID to map from empire ID to set of ship IDs - for (std::shared_ptr ship : GetUniverse().Objects().FindObjects()) { + for (auto& ship : GetUniverse().Objects().all()) { if (ship->Unowned()) continue; int owner_empire_id = ship->Owner(); @@ -2191,7 +2736,7 @@ namespace { ship->SetColonizePlanet(INVALID_OBJECT_ID); // reset so failed colonization doesn't leave ship with hanging colonization order set - std::shared_ptr planet = GetPlanet(colonize_planet_id); + auto planet = Objects().get(colonize_planet_id); if (!planet) continue; @@ -2209,26 +2754,26 @@ namespace { // execute colonization except when: // 1) an enemy empire has armed aggressive ships in the system // 2) multiple empires try to colonize a planet on the same turn - for (std::map>>::value_type& planet_colonization : planet_empire_colonization_ship_ids) { + for (auto& planet_colonization : planet_empire_colonization_ship_ids) { // can't colonize if multiple empires attempting to do so on same turn - std::map>& empires_ships_colonizing = planet_colonization.second; + auto& empires_ships_colonizing = planet_colonization.second; if (empires_ships_colonizing.size() != 1) continue; int colonizing_empire_id = empires_ships_colonizing.begin()->first; - const std::set& empire_ships_colonizing = empires_ships_colonizing.begin()->second; + const auto& empire_ships_colonizing = empires_ships_colonizing.begin()->second; if (empire_ships_colonizing.empty()) continue; int colonizing_ship_id = *empire_ships_colonizing.begin(); int planet_id = planet_colonization.first; - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "HandleColonization couldn't get planet with id " << planet_id; continue; } int system_id = planet->SystemID(); - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "HandleColonization couldn't get system with id " << system_id; continue; @@ -2236,8 +2781,8 @@ namespace { // find which empires have aggressive armed ships in system std::set empires_with_armed_ships_in_system; - for (std::shared_ptr fleet : Objects().FindObjects(system->FleetIDs())) { - if (fleet->Aggressive() && (fleet->HasArmedShips() || fleet->HasFighterShips())) + for (auto& fleet : Objects().find(system->FleetIDs())) { + if (fleet->Aggressive() && fleet->HasArmedShips()) empires_with_armed_ships_in_system.insert(fleet->Owner()); // may include ALL_EMPIRES, which is fine; this makes monsters prevent colonization } @@ -2258,10 +2803,10 @@ namespace { continue; // before actual colonization, which deletes the colony ship, store ship info for later use with sitrep generation - std::shared_ptr ship = GetShip(colonizing_ship_id); + auto ship = Objects().get(colonizing_ship_id); if (!ship) ErrorLogger() << "HandleColonization couldn't get ship with id " << colonizing_ship_id; - const std::string& species_name = ship ? ship->SpeciesName() : ""; + const auto& species_name = ship ? ship->SpeciesName() : ""; float colonist_capacity = ship ? ship->ColonyCapacity() : 0.0f; @@ -2292,14 +2837,14 @@ namespace { return; std::multimap inverted_empires_troops; - for (const std::map::value_type& entry : empires_troops) - inverted_empires_troops.insert(std::make_pair(entry.second, entry.first)); + for (const auto& entry : empires_troops) + inverted_empires_troops.insert({entry.second, entry.first}); // everyone but victor loses all troops. victor's troops remaining are // what the victor started with minus what the second-largest troop // amount was - std::multimap::reverse_iterator victor_it = inverted_empires_troops.rbegin(); - std::multimap::reverse_iterator next_it = victor_it; + auto victor_it = inverted_empires_troops.rbegin(); + auto next_it = victor_it; ++next_it; int victor_id = victor_it->second; double victor_troops = victor_it->first - next_it->first; @@ -2312,18 +2857,19 @@ namespace { * ground combat resolution */ void HandleInvasion() { std::map> planet_empire_troops; // map from planet ID to map from empire ID to pair consisting of set of ship IDs and amount of troops empires have at planet + std::vector> invade_ships; - // assemble invasion forces from each invasion ship - for (std::shared_ptr ship : Objects().FindObjects()) { + // collect ships that are invading and the troops they carry + for (auto& ship : Objects().all()) { if (!ship->HasTroops()) // can't invade without troops continue; if (ship->SystemID() == INVALID_OBJECT_ID) continue; - - int invade_planet_id = ship->OrderedInvadePlanet(); - if (invade_planet_id == INVALID_OBJECT_ID) + if (ship->OrderedInvadePlanet() == INVALID_OBJECT_ID) continue; - std::shared_ptr planet = GetPlanet(invade_planet_id); + invade_ships.push_back(ship); + + auto planet = Objects().get(ship->OrderedInvadePlanet()); if (!planet) continue; planet->ResetIsAboutToBeInvaded(); @@ -2334,14 +2880,21 @@ namespace { continue; // how many troops are invading? - planet_empire_troops[invade_planet_id][ship->Owner()] += ship->TroopCapacity(); + planet_empire_troops[ship->OrderedInvadePlanet()][ship->Owner()] += ship->TroopCapacity(); + + DebugLogger() << "HandleInvasion has accounted for "<< ship->TroopCapacity() + << " troops to invade " << planet->Name() + << " and is destroying ship " << ship->ID() + << " named " << ship->Name(); + } - std::shared_ptr system = GetSystem(ship->SystemID()); + // delete ships that invaded something + for (auto& ship : invade_ships) { + auto system = Objects().get(ship->SystemID()); // destroy invading ships and their fleets if now empty - std::shared_ptr fleet = GetFleet(ship->FleetID()); - if (fleet) { - fleet->RemoveShip(ship->ID()); + if (auto fleet = Objects().get(ship->FleetID())) { + fleet->RemoveShips({ship->ID()}); if (fleet->Empty()) { if (system) system->Remove(fleet->ID()); @@ -2351,11 +2904,6 @@ namespace { if (system) system->Remove(ship->ID()); - DebugLogger() << "HandleInvasion has accounted for "<< ship->TroopCapacity() - << " troops to invade " << planet->Name() - << " and is destroying ship " << ship->ID() - << " named " << ship->Name(); - GetUniverse().RecursiveDestroy(ship->ID()); // does not count as ship loss for empire/species } @@ -2363,29 +2911,29 @@ namespace { UpdateEmpireInvasionInfo(planet_empire_troops); // check each planet for other troops, such as due to empire troops, native troops, or rebel troops - for (std::shared_ptr planet : Objects().FindObjects()) { + for (auto& planet : Objects().all()) { if (!planet) { ErrorLogger() << "HandleInvasion couldn't get planet"; continue; } - if (planet->CurrentMeterValue(METER_TROOPS) > 0.0f) { + if (planet->InitialMeterValue(METER_TROOPS) > 0.0f) { // empires may have garrisons on planets - planet_empire_troops[planet->ID()][planet->Owner()] += planet->CurrentMeterValue(METER_TROOPS) + 0.0001; // small bonus to ensure ties are won by initial owner + planet_empire_troops[planet->ID()][planet->Owner()] += planet->InitialMeterValue(METER_TROOPS) + 0.0001; // small bonus to ensure ties are won by initial owner } - if (!planet->Unowned() && planet->CurrentMeterValue(METER_REBEL_TROOPS) > 0.0f) { + if (!planet->Unowned() && planet->InitialMeterValue(METER_REBEL_TROOPS) > 0.0f) { // rebels may be present on empire-owned planets - planet_empire_troops[planet->ID()][ALL_EMPIRES] += planet->CurrentMeterValue(METER_REBEL_TROOPS); + planet_empire_troops[planet->ID()][ALL_EMPIRES] += planet->InitialMeterValue(METER_REBEL_TROOPS); } } // process each planet's ground combats - for (std::map>::value_type& planet_combat : planet_empire_troops) { + for (auto& planet_combat : planet_empire_troops) { int planet_id = planet_combat.first; - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); std::set all_involved_empires; int planet_initial_owner_id = planet->Owner(); - std::map& empires_troops = planet_combat.second; + auto& empires_troops = planet_combat.second; if (empires_troops.empty()) continue; else if (empires_troops.size() == 1) { @@ -2405,11 +2953,11 @@ namespace { } } else { DebugLogger() << "Ground combat troops on " << planet->Name() << " :"; - for (const std::map::value_type& empire_troops : empires_troops) + for (const auto& empire_troops : empires_troops) { DebugLogger() << " ... empire: " << empire_troops.first << " : " << empire_troops.second; } // create sitreps for all empires involved in battle - for (const std::map::value_type& empire_troops : empires_troops) { + for (const auto& empire_troops : empires_troops) { if (empire_troops.first != ALL_EMPIRES) all_involved_empires.insert(empire_troops.first); } @@ -2440,21 +2988,21 @@ namespace { } DebugLogger() << "Empire conquers planet"; - for (const std::map::value_type& empire_troops : empires_troops) + for (const auto& empire_troops : empires_troops) { DebugLogger() << " empire: " << empire_troops.first << ": " << empire_troops.second; } } else if (!planet->Unowned() && victor_id == ALL_EMPIRES) { planet->Conquer(ALL_EMPIRES); DebugLogger() << "Independents conquer planet"; - for (const std::map::value_type& empire_troops : empires_troops) + for (const auto& empire_troops : empires_troops) { DebugLogger() << " empire: " << empire_troops.first << ": " << empire_troops.second; } // TODO: planet lost to rebels sitrep } else { // defender held theh planet DebugLogger() << "Defender holds planet"; - for (const std::map::value_type& empire_troops : empires_troops) + for (const auto& empire_troops : empires_troops) { DebugLogger() << " empire: " << empire_troops.first << ": " << empire_troops.second; } } @@ -2488,47 +3036,47 @@ namespace { std::map>> empire_gifted_objects; // collect fleets ordered to be given - for (std::shared_ptr fleet : GetUniverse().Objects().FindObjects()) { + for (auto& fleet : GetUniverse().Objects().all()) { int ordered_given_to_empire_id = fleet->OrderedGivenToEmpire(); if (ordered_given_to_empire_id == ALL_EMPIRES) continue; fleet->ClearGiveToEmpire(); // in case things fail, to avoid potential inconsistent state - if ( fleet->Unowned() - || fleet->OwnedBy(ordered_given_to_empire_id)) + if (fleet->Unowned() + || fleet->OwnedBy(ordered_given_to_empire_id) + || !fleet->TravelRoute().empty()) { continue; } empire_gifted_objects[ordered_given_to_empire_id].push_back(fleet); } // collect planets ordered to be given - for (std::shared_ptr planet : GetUniverse().Objects().FindObjects()) { + for (auto& planet : GetUniverse().Objects().all()) { int ordered_given_to_empire_id = planet->OrderedGivenToEmpire(); if (ordered_given_to_empire_id == ALL_EMPIRES) continue; planet->ClearGiveToEmpire(); // in case things fail, to avoid potential inconsistent state - if ( planet->Unowned() - || planet->OwnedBy(ordered_given_to_empire_id)) + if (planet->Unowned() || planet->OwnedBy(ordered_given_to_empire_id)) { continue; } empire_gifted_objects[ordered_given_to_empire_id].push_back(planet); } // further filter ordered given objects and do giving if appropriate - for (std::map>>::value_type& gifted_objects : empire_gifted_objects) { + for (auto& gifted_objects : empire_gifted_objects) { int recipient_empire_id = gifted_objects.first; std::map systems_contain_recipient_empire_owned_objects; // for each recipient empire, process objects it is being gifted - for (std::shared_ptr gifted_obj : gifted_objects.second) { + for (auto& gifted_obj : gifted_objects.second) { int initial_owner_empire_id = gifted_obj->Owner(); // gifted object must be in a system if (gifted_obj->SystemID() == INVALID_OBJECT_ID) continue; - std::shared_ptr system = GetSystem(gifted_obj->SystemID()); + auto system = Objects().get(gifted_obj->SystemID()); if (!system) continue; @@ -2536,13 +3084,13 @@ namespace { bool can_receive_here = false; // is reception ability for this location cached? - std::map::iterator sys_it = systems_contain_recipient_empire_owned_objects.find(system->ID()); + auto sys_it = systems_contain_recipient_empire_owned_objects.find(system->ID()); if (sys_it != systems_contain_recipient_empire_owned_objects.end()) { can_receive_here = sys_it->second; } else { // not cached, so scan for objects - for (std::shared_ptr system_obj : Objects().FindObjects(system->ObjectIDs())) { + for (auto& system_obj : Objects().find(system->ObjectIDs())) { if (system_obj->OwnedBy(recipient_empire_id)) { can_receive_here = true; systems_contain_recipient_empire_owned_objects[system->ID()] = true; @@ -2556,38 +3104,43 @@ namespace { continue; // recipient empire can receive objects at this system, so do transfer - for (std::shared_ptr contained_obj : Objects().FindObjects(gifted_obj->ContainedObjectIDs())) { + for (auto& contained_obj : Objects().find(gifted_obj->ContainedObjectIDs())) { if (contained_obj->OwnedBy(initial_owner_empire_id)) contained_obj->SetOwner(recipient_empire_id); } gifted_obj->SetOwner(recipient_empire_id); + + if (Empire* empire = GetEmpire(recipient_empire_id)) { + if (gifted_obj->ObjectType() == OBJ_PLANET) + empire->AddSitRepEntry(CreatePlanetGiftedSitRep(gifted_obj->ID(), initial_owner_empire_id)); + else if (gifted_obj->ObjectType() == OBJ_FLEET) + empire->AddSitRepEntry(CreateFleetGiftedSitRep(gifted_obj->ID(), initial_owner_empire_id)); + } + + Empire::ConquerProductionQueueItemsAtLocation(gifted_obj->ID(), recipient_empire_id); } } } /** Destroys suitable objects that have been ordered scrapped.*/ void HandleScrapping() { - //// debug - //for (std::shared_ptr ship : Objects().FindObjects()) { - // if (!ship->OrderedScrapped()) - // continue; - // DebugLogger() << "... ship: " << ship->ID() << " ordered scrapped"; - //} - //// end debug - - for (std::shared_ptr ship : Objects().FindObjects()) { - if (!ship->OrderedScrapped()) - continue; + std::vector> scrapped_ships; + for (auto& ship : Objects().all()) { + if (ship->OrderedScrapped()) + scrapped_ships.push_back(ship); + } + + for (auto& ship : scrapped_ships) { DebugLogger() << "... ship: " << ship->ID() << " ordered scrapped"; - std::shared_ptr system = GetSystem(ship->SystemID()); + auto system = Objects().get(ship->SystemID()); if (system) system->Remove(ship->ID()); - std::shared_ptr fleet = GetFleet(ship->FleetID()); + auto fleet = Objects().get(ship->FleetID()); if (fleet) { - fleet->RemoveShip(ship->ID()); + fleet->RemoveShips({ship->ID()}); if (fleet->Empty()) { //scrapped_object_ids.push_back(fleet->ID()); system->Remove(fleet->ID()); @@ -2598,14 +3151,14 @@ namespace { // record scrapping in empire stats Empire* scrapping_empire = GetEmpire(ship->Owner()); if (scrapping_empire) { - std::map& designs_scrapped = scrapping_empire->ShipDesignsScrapped(); - if (designs_scrapped.find(ship->DesignID()) != designs_scrapped.end()) + auto& designs_scrapped = scrapping_empire->ShipDesignsScrapped(); + if (designs_scrapped.count(ship->DesignID())) designs_scrapped[ship->DesignID()]++; else designs_scrapped[ship->DesignID()] = 1; - std::map& species_ships_scrapped = scrapping_empire->SpeciesShipsScrapped(); - if (species_ships_scrapped.find(ship->SpeciesName()) != species_ships_scrapped.end()) + auto& species_ships_scrapped = scrapping_empire->SpeciesShipsScrapped(); + if (species_ships_scrapped.count(ship->SpeciesName())) species_ships_scrapped[ship->SpeciesName()]++; else species_ships_scrapped[ship->SpeciesName()] = 1; @@ -2615,21 +3168,25 @@ namespace { GetUniverse().Destroy(ship->ID()); } - for (std::shared_ptr building : Objects().FindObjects()) { - if (!building->OrderedScrapped()) - continue; + std::vector> scrapped_buildings; - if (std::shared_ptr planet = GetPlanet(building->PlanetID())) + for (auto& building : Objects().all()) { + if (building->OrderedScrapped()) + scrapped_buildings.push_back(building); + } + + for (auto& building : scrapped_buildings) { + if (auto planet = Objects().get(building->PlanetID())) planet->RemoveBuilding(building->ID()); - if (std::shared_ptr system = GetSystem(building->SystemID())) + if (auto system = Objects().get(building->SystemID())) system->Remove(building->ID()); // record scrapping in empire stats Empire* scrapping_empire = GetEmpire(building->Owner()); if (scrapping_empire) { - std::map& buildings_scrapped = scrapping_empire->BuildingTypesScrapped(); - if (buildings_scrapped.find(building->BuildingTypeName()) != buildings_scrapped.end()) + auto& buildings_scrapped = scrapping_empire->BuildingTypesScrapped(); + if (buildings_scrapped.count(building->BuildingTypeName())) buildings_scrapped[building->BuildingTypeName()]++; else buildings_scrapped[building->BuildingTypeName()] = 1; @@ -2643,9 +3200,9 @@ namespace { /** Removes bombardment state info from objects. Actual effects of * bombardment are handled during */ void CleanUpBombardmentStateInfo() { - for (std::shared_ptr ship : GetUniverse().Objects().FindObjects()) + for (auto& ship : GetUniverse().Objects().all()) { ship->ClearBombardPlanet(); } - for (std::shared_ptr planet : GetUniverse().Objects().FindObjects()) { + for (auto& planet : GetUniverse().Objects().all()) { if (planet->IsAboutToBeBombarded()) { //DebugLogger() << "CleanUpBombardmentStateInfo: " << planet->Name() << " was about to be bombarded"; planet->ResetIsAboutToBeBombarded(); @@ -2655,18 +3212,21 @@ namespace { /** Causes ResourceCenters (Planets) to update their focus records */ void UpdateResourceCenterFocusHistoryInfo() { - for (std::shared_ptr planet : GetUniverse().Objects().FindObjects()) { + for (auto& planet : GetUniverse().Objects().all()) planet->UpdateFocusHistory(); - } } /** Deletes empty fleets. */ void CleanEmptyFleets() { - for (std::shared_ptr fleet : Objects().FindObjects()) { - if (!fleet->Empty()) - continue; + std::vector> empty_fleets; - std::shared_ptr sys = GetSystem(fleet->SystemID()); + for (auto& fleet : Objects().all()) { + if (fleet->Empty()) + empty_fleets.push_back(fleet); + } + + for (auto& fleet : empty_fleets) { + auto sys = Objects().get(fleet->SystemID()); if (sys) sys->Remove(fleet->ID()); @@ -2677,11 +3237,10 @@ namespace { void ServerApp::PreCombatProcessTurns() { ScopedTimer timer("ServerApp::PreCombatProcessTurns", true); - ObjectMap& objects = m_universe.Objects(); + m_universe.ResetAllObjectMeters(false, true); // revert current meter values to initial values prior to update after incrementing turn number during previous post-combat turn processing. m_universe.UpdateEmpireVisibilityFilteredSystemGraphs(); - DebugLogger() << "ServerApp::ProcessTurns executing orders"; // inform players of order execution @@ -2693,12 +3252,17 @@ void ServerApp::PreCombatProcessTurns() { // execute orders for (const auto& empire_orders : m_turn_sequence) { - auto& order_set = empire_orders.second; - if (!order_set) { + auto& save_game_data = empire_orders.second; + if (!save_game_data) { + DebugLogger() << "No SaveGameData for empire " << empire_orders.first; + continue; + } + if (!save_game_data->m_orders) { DebugLogger() << "No OrderSet for empire " << empire_orders.first; continue; } - for (const auto& id_and_order : *order_set) + DebugLogger() << "<<= Executing Orders for empire " << empire_orders.first << " =>>"; + for (const auto& id_and_order : *save_game_data->m_orders) id_and_order.second->Execute(); } @@ -2712,7 +3276,7 @@ void ServerApp::PreCombatProcessTurns() { CleanEmptyFleets(); // update production queues after order execution - for (std::map::value_type& entry : Empires()) { + for (auto& entry : Empires()) { if (entry.second->Eliminated()) continue; // skip eliminated empires entry.second->UpdateProductionQueue(); @@ -2741,29 +3305,46 @@ void ServerApp::PreCombatProcessTurns() { m_networking.SendMessageAll(TurnProgressMessage(Message::FLEET_MOVEMENT)); + // Update system-obstruction after orders, colonization, invasion, gifting, scrapping + for (auto& entry : Empires()) { + Empire* empire = entry.second; + if (empire->Eliminated()) + continue; + empire->UpdateSupplyUnobstructedSystems(true); + } + + // fleet movement - std::vector> fleets = objects.FindObjects(); - for (std::shared_ptr fleet : fleets) { + auto fleets = Objects().all(); + for (auto& fleet : fleets) { if (fleet) fleet->ClearArrivalFlag(); } - for (std::shared_ptr fleet : fleets) { + // first move unowned fleets, or an empire fleet landing on them could wrongly + // blockade them before they move + for (auto& fleet : fleets) { // save for possible SitRep generation after moving... - if (fleet) + if (fleet && fleet->Unowned()) + fleet->MovementPhase(); + } + for (auto& fleet : fleets) { + // save for possible SitRep generation after moving... + if (fleet && !fleet->Unowned()) fleet->MovementPhase(); } // post-movement visibility update m_universe.UpdateEmpireObjectVisibilities(); m_universe.UpdateEmpireLatestKnownObjectsAndVisibilityTurns(); + m_universe.UpdateEmpireStaleObjectKnowledge(); // SitRep for fleets having arrived at destinations - for (std::shared_ptr fleet : fleets) { + for (auto& fleet : fleets) { // save for possible SitRep generation after moving... if (!fleet || !fleet->ArrivedThisTurn()) continue; // sitreps for all empires that can see fleet at new location - for (std::map::value_type& entry : Empires()) { + for (auto& entry : Empires()) { if (fleet->GetVisibility(entry.first) >= VIS_BASIC_VISIBILITY) entry.second->AddSitRepEntry( CreateFleetArrivedAtDestinationSitRep(fleet->SystemID(), fleet->ID(), @@ -2775,13 +3356,21 @@ void ServerApp::PreCombatProcessTurns() { m_networking.SendMessageAll(TurnProgressMessage(Message::DOWNLOADING)); // send partial turn updates to all players after orders and movement - for (ServerNetworking::const_established_iterator player_it = m_networking.established_begin(); + // exclude those without empire and who are not Observer or Moderator + for (auto player_it = m_networking.established_begin(); player_it != m_networking.established_end(); ++player_it) { PlayerConnectionPtr player = *player_it; - bool use_binary_serialization = player->ClientVersionStringMatchesThisServer(); - player->SendMessage(TurnPartialUpdateMessage(PlayerEmpireID(player->PlayerID()), - m_universe, use_binary_serialization)); + int empire_id = PlayerEmpireID(player->PlayerID()); + const Empire* empire = GetEmpire(empire_id); + if (empire || + player->GetClientType() == Networking::CLIENT_TYPE_HUMAN_MODERATOR || + player->GetClientType() == Networking::CLIENT_TYPE_HUMAN_OBSERVER) + { + bool use_binary_serialization = player->IsBinarySerializationUsed(); + player->SendMessage(TurnPartialUpdateMessage(PlayerEmpireID(player->PlayerID()), + m_universe, use_binary_serialization)); + } } } @@ -2790,7 +3379,6 @@ void ServerApp::ProcessCombats() { DebugLogger() << "ServerApp::ProcessCombats"; m_networking.SendMessageAll(TurnProgressMessage(Message::COMBAT)); - std::set human_controlled_empire_ids = HumanControlledEmpires(this, m_networking); std::vector combats; // map from system ID to CombatInfo for that system @@ -2800,31 +3388,12 @@ void ServerApp::ProcessCombats() { // loop through assembled combat infos, handling each combat to update the // various systems' CombatInfo structs for (CombatInfo& combat_info : combats) { - if (std::shared_ptr system = combat_info.GetSystem()) + if (auto system = combat_info.GetSystem()) system->SetLastTurnBattleHere(CurrentTurn()); - //// DEBUG - //const System* combat_system = combat_info.GetSystem(); - //DebugLogger() << "Processing combat at " << (combat_system ? combat_system->Name() : "(No System)"); - //DebugLogger() << combat_info.objects.Dump(); - //for (const std::map::value_type& eko : combat_info.empire_known_objects) { - // DebugLogger() << "known objects for empire " << eko.first; - // DebugLogger() << eko.second.Dump(); - //} - //// END DEBUG - - // find which human players are involved in this battle - std::set human_empires_involved; - for (int empire_id : combat_info.empire_ids) { - if (human_controlled_empire_ids.find(empire_id) != human_controlled_empire_ids.end()) - human_empires_involved.insert(empire_id); - } - - // if no human players are involved, resolve battle automatically - if (human_empires_involved.empty()) { - AutoResolveCombat(combat_info); - continue; - } + auto combat_system = combat_info.GetSystem(); + DebugLogger(combat) << "Processing combat at " << (combat_system ? combat_system->Name() : "(No System)"); + TraceLogger(combat) << combat_info.objects.Dump(); AutoResolveCombat(combat_info); } @@ -2836,15 +3405,16 @@ void ServerApp::ProcessCombats() { DisseminateSystemCombatInfo(combats); // update visibilities with any new info gleaned during combat m_universe.UpdateEmpireLatestKnownObjectsAndVisibilityTurns(); + // update stale object info based on any mid- combat glimpses + // before visibility is totally recalculated in the post combat processing + m_universe.UpdateEmpireStaleObjectKnowledge(); CreateCombatSitReps(combats); - - //CleanupSystemCombatInfo(combats); - NOTE: No longer needed since ObjectMap.Clear doesn't release any resources that aren't released in the destructor. } void ServerApp::UpdateMonsterTravelRestrictions() { for (auto const &maybe_system : m_universe.Objects().ExistingSystems()) { - std::shared_ptr system = std::dynamic_pointer_cast(maybe_system.second); + auto system = std::dynamic_pointer_cast(maybe_system.second); if (!system) { ErrorLogger() << "Non System object in ExistingSystems with id = " << maybe_system.second->ID(); continue; @@ -2854,20 +3424,14 @@ void ServerApp::UpdateMonsterTravelRestrictions() { bool empires_present = false; bool unrestricted_empires_present = false; std::vector> monsters; - for (auto maybe_fleet : m_universe.Objects().FindObjects(system->FleetIDs())) { - std::shared_ptr fleet = std::dynamic_pointer_cast(maybe_fleet); - if (!fleet) { - ErrorLogger() << "Non Fleet object in system(" << system->ID() - << ") fleets with id = " << maybe_fleet->ID(); - continue; - } + for (const auto& fleet : m_universe.Objects().find(system->FleetIDs())) { // will not require visibility for empires to block clearing of monster travel restrictions // unrestricted lane access (i.e, (fleet->ArrivalStarlane() == system->ID()) ) is used as a proxy for // order of arrival -- if an enemy has unrestricted lane access and you don't, they must have arrived // before you, or be in cahoots with someone who did. bool unrestricted = ((fleet->ArrivalStarlane() == system->ID()) && fleet->Aggressive() - && (fleet->HasArmedShips() || fleet->HasFighterShips())); + && fleet->HasArmedShips()); if (fleet->Unowned()) { monsters.push_back(fleet); if (unrestricted) @@ -2907,9 +3471,9 @@ void ServerApp::PostCombatProcessTurns() { // check for loss of empire capitals - for (std::map::value_type& entry : empires) { + for (auto& entry : empires) { int capital_id = entry.second->CapitalID(); - if (std::shared_ptr capital = GetUniverseObject(capital_id)) { + if (auto capital = Objects().get(capital_id)) { if (!capital->OwnedBy(entry.first)) entry.second->SetCapitalID(INVALID_OBJECT_ID); } else { @@ -2924,8 +3488,8 @@ void ServerApp::PostCombatProcessTurns() { m_networking.SendMessageAll(TurnProgressMessage(Message::EMPIRE_PRODUCTION)); DebugLogger() << "ServerApp::PostCombatProcessTurns effects and meter updates"; - TraceLogger() << "!!!!!!! BEFORE TURN PROCESSING EFFECTS APPLICATION"; - TraceLogger() << objects.Dump(); + TraceLogger(effects) << "!!!!!!! BEFORE TURN PROCESSING EFFECTS APPLICATION"; + TraceLogger(effects) << objects.Dump(); // execute all effects and update meters prior to production, research, etc. if (GetGameRules().Get("RULE_RESEED_PRNG_SERVER")) { @@ -2938,8 +3502,8 @@ void ServerApp::PostCombatProcessTurns() { // have added or removed starlanes. m_universe.InitializeSystemGraph(); - TraceLogger() << "!!!!!!! AFTER TURN PROCESSING EFFECTS APPLICATION"; - TraceLogger() << objects.Dump(); + TraceLogger(effects) << "!!!!!!! AFTER TURN PROCESSING EFFECTS APPLICATION"; + TraceLogger(effects) << objects.Dump(); DebugLogger() << "ServerApp::PostCombatProcessTurns empire resources updates"; @@ -2948,58 +3512,40 @@ void ServerApp::PostCombatProcessTurns() { m_universe.UpdateEmpireObjectVisibilities(); m_universe.UpdateEmpireLatestKnownObjectsAndVisibilityTurns(); - // Determine how much of each resource is available, and determine how to - // distribute it to planets or on queues - for (std::map::value_type& entry : empires) { - Empire* empire = entry.second; - if (empire->Eliminated()) - continue; // skip eliminated empires - - empire->UpdateSupplyUnobstructedSystems(); // determines which systems can propagate fleet and resource (same for both) - empire->UpdateSystemSupplyRanges(); // sets range systems can propagate fleet and resourse supply (separately) - } - - GetSupplyManager().Update(); - - for (std::map::value_type& entry : empires) { - Empire* empire = entry.second; - if (empire->Eliminated()) - continue; // skip eliminated empires - empire->InitResourcePools(); // determines population centers and resource centers of empire, tells resource pools the centers and groups of systems that can share resources (note that being able to share resources doesn't mean a system produces resources) - empire->UpdateResourcePools(); // determines how much of each resources is available in each resource sharing group - } - + UpdateEmpireSupply(); // Update fleet travel restrictions (monsters and empire fleets) UpdateMonsterTravelRestrictions(); - for (std::map::value_type& entry : empires) { + for (auto& entry : empires) { if (!entry.second->Eliminated()) { Empire* empire = entry.second; - empire->UpdateAvailableLanes(); + empire->UpdatePreservedLanes(); empire->UpdateUnobstructedFleets(); // must be done after *all* noneliminated empires have updated their unobstructed systems } } - TraceLogger() << "!!!!!!! AFTER UPDATING RESOURCE POOLS AND SUPPLY STUFF"; - TraceLogger() << objects.Dump(); + TraceLogger(effects) << "!!!!!!! AFTER UPDATING RESOURCE POOLS AND SUPPLY STUFF"; + TraceLogger(effects) << objects.Dump(); DebugLogger() << "ServerApp::PostCombatProcessTurns queue progress checking"; // Consume distributed resources to planets and on queues, create new // objects for completed production and give techs to empires that have // researched them - for (std::map::value_type& entry : empires) { + for (auto& entry : empires) { Empire* empire = entry.second; if (empire->Eliminated()) continue; // skip eliminated empires - empire->CheckResearchProgress(); + for (const auto& tech : empire->CheckResearchProgress()) { + empire->AddNewlyResearchedTechToGrantAtStartOfNextTurn(tech); + } empire->CheckProductionProgress(); empire->CheckTradeSocialProgress(); } - TraceLogger() << "!!!!!!! AFTER CHECKING QUEUE AND RESOURCE PROGRESS"; - TraceLogger() << objects.Dump(); + TraceLogger(effects) << "!!!!!!! AFTER CHECKING QUEUE AND RESOURCE PROGRESS"; + TraceLogger(effects) << objects.Dump(); // execute turn events implemented as Python scripts ExecuteScriptedTurnEvents(); @@ -3010,27 +3556,26 @@ void ServerApp::PostCombatProcessTurns() { // they are created. m_universe.ApplyMeterEffectsAndUpdateMeters(false); - TraceLogger() << "!!!!!!! AFTER UPDATING METERS OF ALL OBJECTS"; - TraceLogger() << objects.Dump(); + TraceLogger(effects) << "!!!!!!! AFTER UPDATING METERS OF ALL OBJECTS"; + TraceLogger(effects) << objects.Dump(); - // Population growth or loss, resource current meter growth, etc. - for (std::shared_ptr obj : objects) { + // Planet depopulation, some in-C++ meter modifications + for (const auto& obj : objects.all()) { obj->PopGrowthProductionResearchPhase(); - obj->ClampMeters(); // ensures growth doesn't leave meters over MAX. should otherwise be redundant with ClampMeters() in Universe::ApplyMeterEffectsAndUpdateMeters() + obj->ClampMeters(); // ensures no meters are over MAX. probably redundant with ClampMeters() in Universe::ApplyMeterEffectsAndUpdateMeters() } - TraceLogger() << "!!!!!!!!!!!!!!!!!!!!!!AFTER GROWTH AND CLAMPING"; - TraceLogger() << objects.Dump(); + TraceLogger(effects) << "!!!!!!!!!!!!!!!!!!!!!!AFTER GROWTH AND CLAMPING"; + TraceLogger(effects) << objects.Dump(); // store initial values of meters for this turn. m_universe.BackPropagateObjectMeters(); empires.BackPropagateMeters(); - // check for loss of empire capitals - for (std::map::value_type& entry : empires) { + for (auto& entry : empires) { int capital_id = entry.second->CapitalID(); - if (std::shared_ptr capital = GetUniverseObject(capital_id)) { + if (auto capital = Objects().get(capital_id)) { if (!capital->OwnedBy(entry.first)) entry.second->SetCapitalID(INVALID_OBJECT_ID); } else { @@ -3054,8 +3599,8 @@ void ServerApp::PostCombatProcessTurns() { // update empire-visibility filtered graphs after visiblity update m_universe.UpdateEmpireVisibilityFilteredSystemGraphs(); - TraceLogger() << "!!!!!!!!!!!!!!!!!!!!!!AFTER TURN PROCESSING POP GROWTH PRODCUTION RESEARCH"; - TraceLogger() << objects.Dump(); + TraceLogger(effects) << "!!!!!!!!!!!!!!!!!!!!!!AFTER TURN PROCESSING POP GROWTH PRODCUTION RESEARCH"; + TraceLogger(effects) << objects.Dump(); // this has to be here for the sitreps it creates to be in the right turn CheckForEmpireElimination(); @@ -3068,12 +3613,38 @@ void ServerApp::PostCombatProcessTurns() { // new turn visibility update m_universe.UpdateEmpireObjectVisibilities(); + + + DebugLogger() << "ServerApp::PostCombatProcessTurns applying Newly Added Techs"; + // apply new techs + for (auto& entry : empires) { + Empire* empire = entry.second; + if (empire && !empire->Eliminated()) + empire->ApplyNewTechs(); + } + + + TraceLogger(effects) << "ServerApp::PostCombatProcessTurns Before Final Meter Estimate Update: "; + TraceLogger(effects) << objects.Dump(); + + + // redo meter estimates to hopefully be consistent with what happens in clients + m_universe.UpdateMeterEstimates(false); + + TraceLogger(effects) << "ServerApp::PostCombatProcessTurns After Final Meter Estimate Update: "; + TraceLogger(effects) << objects.Dump(); + + + // Re-determine supply distribution and exchanging and resource pools for empires + UpdateEmpireSupply(true); + + // copy latest visible gamestate to each empire's known object state m_universe.UpdateEmpireLatestKnownObjectsAndVisibilityTurns(); // misc. other updates and records m_universe.UpdateStatRecords(); - for (std::map::value_type& entry : empires) + for (auto& entry : empires) entry.second->UpdateOwnedObjectCounters(); GetSpeciesManager().UpdatePopulationCounter(); @@ -3084,7 +3655,7 @@ void ServerApp::PostCombatProcessTurns() { // compile map of PlayerInfo, indexed by player ID std::map players; - for (ServerNetworking::const_established_iterator player_it = m_networking.established_begin(); + for (auto player_it = m_networking.established_begin(); player_it != m_networking.established_end(); ++player_it) { PlayerConnectionPtr player = *player_it; @@ -3099,40 +3670,78 @@ void ServerApp::PostCombatProcessTurns() { DebugLogger() << "ServerApp::PostCombatProcessTurns Sending turn updates to players"; // send new-turn updates to all players - for (ServerNetworking::const_established_iterator player_it = m_networking.established_begin(); + // exclude those without empire and who are not Observer or Moderator + for (auto player_it = m_networking.established_begin(); player_it != m_networking.established_end(); ++player_it) { PlayerConnectionPtr player = *player_it; - bool use_binary_serialization = player->ClientVersionStringMatchesThisServer(); - player->SendMessage(TurnUpdateMessage(PlayerEmpireID(player->PlayerID()), m_current_turn, - m_empires, m_universe, - GetSpeciesManager(), GetCombatLogManager(), - GetSupplyManager(), players, - use_binary_serialization)); + int empire_id = PlayerEmpireID(player->PlayerID()); + const Empire* empire = GetEmpire(empire_id); + if (empire || + player->GetClientType() == Networking::CLIENT_TYPE_HUMAN_MODERATOR || + player->GetClientType() == Networking::CLIENT_TYPE_HUMAN_OBSERVER) + { + bool use_binary_serialization = player->IsBinarySerializationUsed(); + player->SendMessage(TurnUpdateMessage(empire_id, m_current_turn, + m_empires, m_universe, + GetSpeciesManager(), GetCombatLogManager(), + GetSupplyManager(), players, + use_binary_serialization)); + } } + m_turn_expired = false; DebugLogger() << "ServerApp::PostCombatProcessTurns done"; } void ServerApp::CheckForEmpireElimination() { std::set surviving_empires; - for (std::map::value_type& entry : Empires()) { + std::set non_eliminated_non_ai_controlled_empires; + for (auto& entry : Empires()) { if (entry.second->Eliminated()) continue; // don't double-eliminate an empire - else if (EmpireEliminated(entry.first)) + else if (EmpireEliminated(entry.first)) { entry.second->Eliminate(); - else + RemoveEmpireTurn(entry.first); + } else { surviving_empires.insert(entry.second); + // empires could be controlled only by connected AI client, connected human client, or + // disconnected human client. + // Disconnected AI client controls non-eliminated empire is an error. + if (GetEmpireClientType(entry.second->EmpireID()) != Networking::CLIENT_TYPE_AI_PLAYER) + non_eliminated_non_ai_controlled_empires.insert(entry.second); + } } if (surviving_empires.size() == 1) // last man standing (*surviving_empires.begin())->Win(UserStringNop("VICTORY_ALL_ENEMIES_ELIMINATED")); + else if (!m_single_player_game && + static_cast(non_eliminated_non_ai_controlled_empires.size()) <= GetGameRules().Get("RULE_THRESHOLD_HUMAN_PLAYER_WIN")) + { + // human victory threshold + if (GetGameRules().Get("RULE_ONLY_ALLIANCE_WIN")) { + for (auto emp1_it = non_eliminated_non_ai_controlled_empires.begin(); + emp1_it != non_eliminated_non_ai_controlled_empires.end(); ++emp1_it) + { + auto emp2_it = emp1_it; + ++emp2_it; + for (; emp2_it != non_eliminated_non_ai_controlled_empires.end(); ++emp2_it) { + if (Empires().GetDiplomaticStatus((*emp1_it)->EmpireID(), (*emp2_it)->EmpireID()) != DIPLO_ALLIED) + return; + } + } + } + + for (auto& empire : non_eliminated_non_ai_controlled_empires) { + empire->Win(UserStringNop("VICTORY_FEW_HUMANS_ALIVE")); + } + } } void ServerApp::HandleDiplomaticStatusChange(int empire1_id, int empire2_id) { DiplomaticStatus status = Empires().GetDiplomaticStatus(empire1_id, empire2_id); DiplomaticStatusUpdateInfo update(empire1_id, empire2_id, status); - for (ServerNetworking::const_established_iterator player_it = m_networking.established_begin(); + for (auto player_it = m_networking.established_begin(); player_it != m_networking.established_end(); ++player_it) { PlayerConnectionPtr player = *player_it; @@ -3148,10 +3757,10 @@ void ServerApp::HandleDiplomaticMessageChange(int empire1_id, int empire2_id) { if (player1_id == Networking::INVALID_PLAYER_ID || player2_id == Networking::INVALID_PLAYER_ID) return; - ServerNetworking::established_iterator player1_it = m_networking.GetPlayer(player1_id); + auto player1_it = m_networking.GetPlayer(player1_id); if (player1_it != m_networking.established_end()) (*player1_it)->SendMessage(DiplomacyMessage(message)); - ServerNetworking::established_iterator player2_it = m_networking.GetPlayer(player2_id); + auto player2_it = m_networking.GetPlayer(player2_id); if (player2_it != m_networking.established_end()) (*player2_it)->SendMessage(DiplomacyMessage(message)); } diff --git a/server/ServerApp.h b/server/ServerApp.h index ad828401b6b..149063858b0 100644 --- a/server/ServerApp.h +++ b/server/ServerApp.h @@ -1,11 +1,11 @@ #ifndef _ServerApp_h_ #define _ServerApp_h_ +#include "ServerFramework.h" +#include "ServerNetworking.h" #include "../util/Process.h" #include "../Empire/EmpireManager.h" #include "../Empire/Supply.h" -#include "../python/server/ServerFramework.h" -#include "../network/ServerNetworking.h" #include "../universe/Universe.h" #include "../util/AppInterface.h" #include "../util/MultiplayerCommon.h" @@ -13,80 +13,35 @@ #include #include +#include + class OrderSet; struct GalaxySetupData; struct SaveGameUIData; struct ServerFSM; -/** Contains basic data about a player in a game. */ -struct PlayerSaveHeaderData { - PlayerSaveHeaderData(); - - PlayerSaveHeaderData(const std::string& name, int empire_id, Networking::ClientType client_type); - - std::string m_name; - int m_empire_id; - Networking::ClientType m_client_type; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Contains data that must be saved for a single player. */ -struct PlayerSaveGameData : public PlayerSaveHeaderData { - PlayerSaveGameData(); - - PlayerSaveGameData(const std::string& name, int empire_id, const std::shared_ptr& orders, - const std::shared_ptr& ui_data, const std::string& save_state_string, - Networking::ClientType client_type); - - std::shared_ptr m_orders; - std::shared_ptr m_ui_data; - std::string m_save_state_string; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** contains data that must be retained by the server when saving and loading a - * game that isn't player data or the universe */ -struct ServerSaveGameData { - ServerSaveGameData(); - - ServerSaveGameData(int current_turn); - - int m_current_turn; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - /** the application framework class for the FreeOrion server. */ class ServerApp : public IApp { public: - /** \name Structors */ //@{ ServerApp(); - ~ServerApp(); - //@} + ServerApp(const ServerApp&) = delete; + + ServerApp(ServerApp&&) = delete; + + ~ServerApp() override; + + const ServerApp& operator=(const ServerApp&) = delete; + + ServerApp& operator=(IApp&&) = delete; /** \name Accessors */ //@{ /** Returns a ClientApp pointer to the singleton instance of the app. */ static ServerApp* GetApp(); - Universe& GetUniverse() override; - EmpireManager& Empires() override; - Empire* GetEmpire(int id) override; - SupplyManager& GetSupplyManager() override; std::shared_ptr GetUniverseObject(int object_id) override; @@ -125,29 +80,43 @@ class ServerApp : public IApp { /** Checks if \a player_name are not used by other players. */ bool IsAvailableName(const std::string& player_name) const; + + /** Checks if server runs in a hostless mode. */ + bool IsHostless() const; + + /** Returns chat history buffer. */ + const boost::circular_buffer& GetChatHistory() const; + + /** Extracts player save game data. */ + std::vector GetPlayerSaveGameData() const; + + bool IsTurnExpired() const; + + bool IsHaveWinner() const; //@} /** \name Mutators */ //@{ void operator()(); ///< external interface to Run() + void StartBackgroundParsing() override; + /** Returns the galaxy setup data used for the current game */ GalaxySetupData& GetGalaxySetupData() { return m_galaxy_setup_data; } /** creates an AI client child process for each element of \a AIs*/ void CreateAIClients(const std::vector& player_setup_data, int max_aggression = 4); - /** Adds an existing empire to turn processing. The position the empire is - * in the vector is it's position in the turn processing.*/ - void AddEmpireTurn(int empire_id); + /** Adds save game data includes turn orders for the given empire for the current turn. + * \a save_game_data will be freed when all processing is done for the turn */ + void SetEmpireSaveGameData(int empire_id, std::unique_ptr&& save_game_data); - /** Removes an empire from turn processing. This is most likely called when - * an empire is eliminated from the game */ - void RemoveEmpireTurn(int empire_id); + /** Updated empire orders without changes in readiness status. Removes all \a deleted orders + * and insert \a added orders. */ + void UpdatePartialOrders(int empire_id, const OrderSet& added, const std::set& deleted); - /** Adds turn orders for the given empire for the current turn. order_set - * will be freed when all processing is done for the turn */ - void SetEmpireTurnOrders(int empire_id, std::unique_ptr&& order_set); + /** Revokes turn order's ready state for the given empire. */ + void RevokeEmpireTurnReadyness(int empire_id); /** Sets all empire turn orders to an empty set. */ void ClearEmpireTurnOrders(); @@ -202,6 +171,38 @@ class ServerApp : public IApp { void LoadMPGameInit(const MultiplayerLobbyData& lobby_data, const std::vector& player_save_game_data, std::shared_ptr server_save_game_data); + + /** Checks if \a player_name requires auth to login and fill \a roles if not. */ + bool IsAuthRequiredOrFillRoles(const std::string& player_name, Networking::AuthRoles& roles); + + /** Checks if \a auth match \a player_name and fill \a roles if successed. */ + bool IsAuthSuccessAndFillRoles(const std::string& player_name, const std::string& auth, Networking::AuthRoles& roles); + + /** Returns list of player for multiplayer quickstart*/ + std::list FillListPlayers(); + + /** Adds new observing player to running game. + * Simply sends GAME_START message so established player knows he is in the game. */ + void AddObserverPlayerIntoGame(const PlayerConnectionPtr& player_connection); + + /** Eliminate player's empire by \a player_connection. Return true if player was eliminated. */ + bool EliminatePlayer(const PlayerConnectionPtr& player_connection); + + /** Drop link between player with \a player_id and his empire. */ + void DropPlayerEmpireLink(int planet_id); + + /** Adds new player to running game. + * Search empire by player's name or delegation list if \a target_empire_id set and return + * empire id if success and ALL_EMPIRES if no empire found. + * Simply sends GAME_START message so established player knows he is in the game. + * Notificates the player about statuses of other empires. */ + int AddPlayerIntoGame(const PlayerConnectionPtr& player_connection, int target_empire_id); + + /** Get list of players delegated by \a player_name */ + std::list GetPlayerDelegation(const std::string& player_name); + + /** Sets turn to be expired. Server doesn't wait for human player turns. */ + void ExpireTurn(); //@} void UpdateSavePreviews(const Message& msg, PlayerConnectionPtr player_connection); @@ -209,12 +210,16 @@ class ServerApp : public IApp { /** Send the requested combat logs to the client.*/ void UpdateCombatLogs(const Message& msg, PlayerConnectionPtr player_connection); - ServerNetworking& Networking(); ///< returns the networking object for the server + /** Loads chat history via python script. */ + void LoadChatHistory(); -private: - const ServerApp& operator=(const ServerApp&); // disabled - ServerApp(const ServerApp&); // disabled + void PushChatMessage(const std::string& text, + const std::string& player_name, + GG::Clr text_color, + const boost::posix_time::ptime& timestamp); + ServerNetworking& Networking(); ///< returns the networking object for the server +private: void Run(); ///< initializes app state, then executes main event handler/render loop (Poll()) /** Initialize the python engine if not already running. Return true on success. */ @@ -231,8 +236,7 @@ class ServerApp : public IApp { const std::vector& player_setup_data); /** Return true if player data is consistent with starting a new game. */ - bool NewGameInitVerifyJoiners(const GalaxySetupData& galaxy_setup_data, - const std::vector& player_setup_data); + bool NewGameInitVerifyJoiners(const std::vector& player_setup_data); /** Sends out initial new game state to clients, and signals clients to start first turn. */ void SendNewGameStartMessages(); @@ -297,7 +301,15 @@ class ServerApp : public IApp { * between two empires. Updates those empires of the change. */ void HandleDiplomaticMessageChange(int empire1_id, int empire2_id); - boost::asio::io_service m_io_service; + /** Adds an existing empire to turn processing. The position the empire is + * in the vector is it's position in the turn processing.*/ + void AddEmpireTurn(int empire_id, const PlayerSaveGameData& psgd); + + /** Removes an empire from turn processing. This is most likely called when + * an empire is eliminated from the game */ + void RemoveEmpireTurn(int empire_id); + + boost::asio::io_context m_io_context; boost::asio::signal_set m_signals; Universe m_universe; @@ -306,16 +318,22 @@ class ServerApp : public IApp { ServerNetworking m_networking; ServerFSM* m_fsm; PythonServer m_python_server; - std::map m_player_empire_ids; ///< map from player id to empire id that the player controls. - int m_current_turn; ///< current turn number - std::vector m_ai_client_processes; ///< AI client child processes - bool m_single_player_game; ///< true when the game being played is single-player - GalaxySetupData m_galaxy_setup_data; ///< stored setup data for the game currently being played + std::map m_player_empire_ids; ///< map from player id to empire id that the player controls. + int m_current_turn = INVALID_GAME_TURN; ///< current turn number + bool m_turn_expired = false; ///< true when turn exceeds its timeout + std::vector m_ai_client_processes; ///< AI client child processes + bool m_single_player_game = false; ///< true when the game being played is single-player + GalaxySetupData m_galaxy_setup_data; ///< stored setup data for the game currently being played + boost::circular_buffer m_chat_history; ///< Stored last chat messages. + /** Turn sequence map is used for turn processing. Each empire is added at * the start of a game or reload and then the map maintains OrderSets for - * that turn. */ - std::map> m_turn_sequence; + * that turn. + * The map contains pointer to orders from empire with ready state which should be true + * to advance turn. + * */ + std::map> m_turn_sequence; // Give FSM and its states direct access. We are using the FSM code as a // control-flow mechanism; it is all notionally part of this class. @@ -332,30 +350,4 @@ class ServerApp : public IApp { friend struct ShuttingDownServer; }; -// template implementations -template -void PlayerSaveHeaderData::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_empire_id) - & BOOST_SERIALIZATION_NVP(m_client_type); -} - -template -void PlayerSaveGameData::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_empire_id) - & BOOST_SERIALIZATION_NVP(m_orders) - & BOOST_SERIALIZATION_NVP(m_ui_data) - & BOOST_SERIALIZATION_NVP(m_save_state_string) - & BOOST_SERIALIZATION_NVP(m_client_type); -} - -template -void ServerSaveGameData::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_NVP(m_current_turn); -} - #endif // _ServerApp_h_ diff --git a/server/ServerFSM.cpp b/server/ServerFSM.cpp index c9091d8ee8d..128c123bd2f 100644 --- a/server/ServerFSM.cpp +++ b/server/ServerFSM.cpp @@ -2,13 +2,14 @@ #include "SaveLoad.h" #include "ServerApp.h" +#include "ServerNetworking.h" #include "../Empire/Empire.h" #include "../Empire/EmpireManager.h" #include "../universe/System.h" #include "../universe/Species.h" -#include "../network/ServerNetworking.h" #include "../network/Message.h" #include "../util/Directories.h" +#include "../util/GameRules.h" #include "../util/i18n.h" #include "../util/Logger.h" #include "../util/LoggerWithOptionsDB.h" @@ -16,13 +17,17 @@ #include "../util/OrderSet.h" #include "../util/Random.h" #include "../util/ModeratorAction.h" -#include "../util/MultiplayerCommon.h" #include #include #include #include #include +#include +#include +#include + +#include #include @@ -40,7 +45,7 @@ namespace { } ServerNetworking& networking = server->Networking(); - for (ServerNetworking::const_established_iterator player_it = networking.established_begin(); + for (auto player_it = networking.established_begin(); player_it != networking.established_end(); ++player_it) { @@ -57,7 +62,7 @@ namespace { } ServerNetworking& networking = server->Networking(); - ServerNetworking::established_iterator host_it = networking.GetPlayer(networking.HostPlayerID()); + auto host_it = networking.GetPlayer(networking.HostPlayerID()); if (host_it == networking.established_end()) { ErrorLogger(FSM) << "SendMessageToHost couldn't get host player."; return; @@ -107,9 +112,14 @@ namespace { case Networking::CLIENT_TYPE_AI_PLAYER: ss << "AI_PLAYER, "; break; case Networking::CLIENT_TYPE_HUMAN_MODERATOR: ss << "MODERATOR, "; break; case Networking::CLIENT_TYPE_HUMAN_OBSERVER: ss << "OBSERVER, "; break; - case Networking::CLIENT_TYPE_HUMAN_PLAYER: ss << "HUMAN_PLAYER, "; break; + case Networking::CLIENT_TYPE_HUMAN_PLAYER: ss << "PLAYER, "; break; default: ss << ", "; } + GG::Clr empire_color = entry.second.m_empire_color; + ss << "(" << static_cast(empire_color.r) + << ", " << static_cast(empire_color.g) + << ", " << static_cast(empire_color.b) + << ", " << static_cast(empire_color.a) << "), "; ss << entry.second.m_starting_species_name; if (entry.second.m_player_ready) ss << ", Ready"; @@ -121,10 +131,10 @@ namespace { std::list>& players) { // load default empire names - std::vector empire_names = UserStringList("EMPIRE_NAMES"); + auto empire_names = UserStringList("EMPIRE_NAMES"); std::set valid_names(empire_names.begin(), empire_names.end()); - for (const std::pair& psd : players) { - std::set::iterator name_it = valid_names.find(psd.second.m_empire_name); + for (const auto& psd : players) { + auto name_it = valid_names.find(psd.second.m_empire_name); if (name_it != valid_names.end()) valid_names.erase(name_it); name_it = valid_names.find(psd.second.m_player_name); @@ -165,6 +175,57 @@ namespace { return fatal; } + + std::string GetAutoSaveFileName(int current_turn) { + std::string subdir = GetGalaxySetupData().GetGameUID(); + boost::filesystem::path autosave_dir_path = GetServerSaveDir() / (subdir.empty() ? "auto" : subdir); + const auto& extension = MP_SAVE_FILE_EXTENSION; + // Add timestamp to autosave generated files + std::string datetime_str = FilenameTimestamp(); + + std::string save_filename = boost::io::str(boost::format("FreeOrion_%04d_%s%s") % current_turn % datetime_str % extension); + boost::filesystem::path save_path(autosave_dir_path / save_filename); + return save_path.string(); + } + + GG::Clr GetUnusedEmpireColour(const std::list>& psd, + const std::map &sged = std::map()) + { + //DebugLogger(FSM) << "finding colours for empire of player " << player_name; + GG::Clr empire_colour = GG::Clr(192, 192, 192, 255); + for (const GG::Clr& possible_colour : EmpireColors()) { + //DebugLogger(FSM) << "trying colour " << possible_colour.r << ", " << possible_colour.g << ", " << possible_colour.b; + + // check if any other player / empire is using this colour + bool colour_is_new = true; + for (const std::pair& entry : psd) { + const GG::Clr& player_colour = entry.second.m_empire_color; + if (player_colour == possible_colour) { + colour_is_new = false; + break; + } + } + + if (colour_is_new) { + for (const auto& entry : sged) { + const GG::Clr& player_colour = entry.second.m_color; + if (player_colour == possible_colour) { + colour_is_new = false; + break; + } + } + } + + // use colour and exit loop if no other empire is using the colour + if (colour_is_new) { + empire_colour = possible_colour; + break; + } + + //DebugLogger(FSM) << " ... colour already used."; + } + return empire_colour; + } } //////////////////////////////////////////////////////////// @@ -177,7 +238,7 @@ Disconnection::Disconnection(PlayerConnectionPtr& player_connection) : //////////////////////////////////////////////////////////// // MessageEventBase //////////////////////////////////////////////////////////// -MessageEventBase::MessageEventBase(const Message& message, PlayerConnectionPtr& player_connection) : +MessageEventBase::MessageEventBase(const Message& message, const PlayerConnectionPtr& player_connection) : m_message(message), m_player_connection(player_connection) {} @@ -212,7 +273,8 @@ void ServerFSM::unconsumed_event(const sc::event_base &event) { for (auto leaf_state_it = state_begin(); leaf_state_it != state_end();) { // The following use of typeid assumes that // BOOST_STATECHART_USE_NATIVE_RTTI is defined - ss << typeid( *leaf_state_it ).name(); + const auto& leaf_state = *leaf_state_it; + ss << typeid(leaf_state).name(); ++leaf_state_it; if (leaf_state_it != state_end()) ss << ", "; @@ -229,57 +291,309 @@ ServerApp& ServerFSM::Server() void ServerFSM::HandleNonLobbyDisconnection(const Disconnection& d) { PlayerConnectionPtr& player_connection = d.m_player_connection; - int id = player_connection->PlayerID(); - DebugLogger(FSM) << "ServerFSM::HandleNonLobbyDisconnection : Lost connection to player #" << id - << ", named \"" << player_connection->PlayerName() << "\"."; - bool must_quit = false; - if (id == ALL_EMPIRES) { - ErrorLogger(FSM) << "Client quit before id was assigned."; + int id = Networking::INVALID_PLAYER_ID; + + if (player_connection->IsEstablished()) { + // update cookie expire date + // so player could reconnect within 15 minutes + m_server.Networking().UpdateCookie(player_connection->Cookie()); + + id = player_connection->PlayerID(); + DebugLogger(FSM) << "ServerFSM::HandleNonLobbyDisconnection : Lost connection to player #" << id + << ", named \"" << player_connection->PlayerName() << "\"."; + + if (player_connection->GetClientType() == Networking::CLIENT_TYPE_AI_PLAYER) { + // AI could safely disconnect only if empire was eliminated + const Empire* empire = GetEmpire(m_server.PlayerEmpireID(id)); + if (empire && !empire->Eliminated()) { + must_quit = true; + // AI abnormally disconnected during a regular game + ErrorLogger(FSM) << "AI Player #" << id << ", named \"" + << player_connection->PlayerName() << "\"quit before empire was eliminated."; + } + } else if (player_connection->GetClientType() == Networking::CLIENT_TYPE_HUMAN_PLAYER) { + const Empire* empire = GetEmpire(m_server.PlayerEmpireID(id)); + // eliminated and non-empire players can leave safely + if (empire && !empire->Eliminated()) { + // player abnormally disconnected during a regular game + WarnLogger(FSM) << "Player #" << id << ", named \"" + << player_connection->PlayerName() << "\"quit before empire was eliminated."; + // detach player from empire + m_server.DropPlayerEmpireLink(id); + } + } + // add "player left game" message + boost::posix_time::ptime timestamp = boost::posix_time::second_clock::universal_time(); + std::string data = std::string("[[") + UserStringNop("PLAYER_LEFT_GAME") + "," + player_connection->PlayerName() + "]]"; + m_server.PushChatMessage(data, "", GG::CLR_WHITE, timestamp); + + // send message to other players + for (auto it = m_server.m_networking.established_begin(); + it != m_server.m_networking.established_end(); ++it) + { + if (player_connection != (*it)) + (*it)->SendMessage(ServerPlayerChatMessage(Networking::INVALID_PLAYER_ID, timestamp, data)); + } + } else { + DebugLogger(FSM) << "Client quit before id was assigned."; + } + + // count of active (non-eliminated) empires, which currently have a connected human players + int empire_connected_plr_cnt = 0; + // count of active (non-eliminated) empires, which currently have a unconnected human players + int empire_unconnected_plr_cnt = 0; + for (const auto& empire : Empires()) { + if (!empire.second->Eliminated()) { + switch (m_server.GetEmpireClientType(empire.first)) { + case Networking::CLIENT_TYPE_HUMAN_PLAYER: + empire_connected_plr_cnt++; + break; + case Networking::INVALID_CLIENT_TYPE: + empire_unconnected_plr_cnt++; + break; + case Networking::CLIENT_TYPE_AI_PLAYER: + // ignore + break; + default: + ErrorLogger(FSM) << "Incorrect client type " << m_server.GetEmpireClientType(empire.first) + << " for empire #" << empire.first; + break; + } + } + } + + // Stop server if connected human player empires count is less than minimum + if (empire_connected_plr_cnt < GetOptionsDB().Get("network.server.conn-human-empire-players.min")) { + ErrorLogger(FSM) << "Too low connected human player " << empire_connected_plr_cnt + << " expected " << GetOptionsDB().Get("network.server.conn-human-empire-players.min") + << "; server terminating."; must_quit = true; } - // Did an active player (AI or Human) disconnect? If so, game is over - if (player_connection->GetClientType() == Networking::CLIENT_TYPE_HUMAN_OBSERVER || - player_connection->GetClientType() == Networking::CLIENT_TYPE_HUMAN_MODERATOR) + // Stop server if unconnected human player empires count exceeds maximum and maximum is set + if (GetOptionsDB().Get("network.server.unconn-human-empire-players.max") > 0 && + empire_unconnected_plr_cnt >= GetOptionsDB().Get("network.server.unconn-human-empire-players.max")) { + ErrorLogger(FSM) << "Too high unconnected human player " << empire_unconnected_plr_cnt + << " expected " << GetOptionsDB().Get("network.server.unconn-human-empire-players.max") + << "; server terminating."; + must_quit = true; + } + + m_server.Networking().CleanupCookies(); + + if (must_quit) { + ErrorLogger(FSM) << "Unable to recover server terminating."; + if (m_server.IsHostless()) { + if (GetOptionsDB().Get("save.auto.hostless.enabled") && + GetOptionsDB().Get("save.auto.exit.enabled")) + { + // save game on exit + std::string save_filename = GetAutoSaveFileName(m_server.CurrentTurn()); + ServerSaveGameData server_data(m_server.CurrentTurn()); + int bytes_written = 0; + // save game... + try { + bytes_written = SaveGame(save_filename, server_data, m_server.GetPlayerSaveGameData(), + GetUniverse(), Empires(), GetSpeciesManager(), + GetCombatLogManager(), m_server.m_galaxy_setup_data, + !m_server.m_single_player_game); + } catch (const std::exception& error) { + ErrorLogger(FSM) << "While saving, catch std::exception: " << error.what(); + SendMessageToAllPlayers(ErrorMessage(UserStringNop("UNABLE_TO_WRITE_SAVE_FILE"), false)); + } + + // inform players that save is complete + SendMessageToAllPlayers(ServerSaveGameCompleteMessage(save_filename, bytes_written)); + } + m_server.m_fsm->process_event(Hostless()); + } else { + m_server.m_fsm->process_event(ShutdownServer()); + } + } else { // can continue. Select new host if necessary. - if (m_server.m_networking.PlayerIsHost(player_connection->PlayerID())) + if (m_server.m_networking.PlayerIsHost(id)) m_server.SelectNewHost(); - } else if (player_connection->GetClientType() == Networking::CLIENT_TYPE_HUMAN_PLAYER || - player_connection->GetClientType() == Networking::CLIENT_TYPE_AI_PLAYER) + // player list changed + // notify those in ingame lobby + UpdateIngameLobby(); + } +} + +void ServerFSM::UpdateIngameLobby() { + GalaxySetupData galaxy_data = m_server.GetGalaxySetupData(); + MultiplayerLobbyData dummy_lobby_data(std::move(galaxy_data)); + dummy_lobby_data.m_any_can_edit = false; + dummy_lobby_data.m_new_game = false; + dummy_lobby_data.m_in_game = true; + dummy_lobby_data.m_start_locked = true; + dummy_lobby_data.m_save_game_current_turn = m_server.CurrentTurn(); + dummy_lobby_data.m_save_game_empire_data = CompileSaveGameEmpireData(); + for (auto player_it = m_server.m_networking.established_begin(); + player_it != m_server.m_networking.established_end(); ++player_it) { - const Empire* empire = GetEmpire(m_server.PlayerEmpireID(id)); - // eliminated and non-empire players can leave safely - if (empire && !empire->Eliminated()) { - must_quit = true; - // player abnormally disconnected during a regular game - ErrorLogger(FSM) << "Player #" << id << ", named \"" - << player_connection->PlayerName() << "\"quit before empire was eliminated."; + PlayerSetupData player_setup_data; + int player_id = (*player_it)->PlayerID(); + player_setup_data.m_player_id = player_id; + player_setup_data.m_player_name = (*player_it)->PlayerName(); + player_setup_data.m_client_type = (*player_it)->GetClientType(); + if (const Empire* empire = GetEmpire(m_server.PlayerEmpireID(player_id))) { + player_setup_data.m_save_game_empire_id = empire->EmpireID(); + player_setup_data.m_empire_name = empire->Name(); + player_setup_data.m_empire_color = empire->Color(); + } else { + player_setup_data.m_empire_color = GG::Clr(255, 255, 255, 255); } + player_setup_data.m_authenticated = (*player_it)->IsAuthenticated(); + dummy_lobby_data.m_players.push_back({player_id, player_setup_data}); } + dummy_lobby_data.m_start_lock_cause = UserStringNop("SERVER_ALREADY_PLAYING_GAME"); + + auto player_info_map = m_server.GetPlayerInfoMap(); - // independently of everything else, if there are no humans left, it's time to terminate - if (m_server.m_networking.empty() - || m_server.m_ai_client_processes.size() == m_server.m_networking.NumEstablishedPlayers()) + // send it to all either in the ingame lobby + // or in the playing game + for (auto player_it = m_server.m_networking.established_begin(); + player_it != m_server.m_networking.established_end(); ++player_it) { - must_quit = true; - DebugLogger(FSM) << "ServerFSM::HandleNonLobbyDisconnection : All human players disconnected; server terminating."; + if ((*player_it)->GetClientType() == Networking::CLIENT_TYPE_HUMAN_PLAYER && + !GetEmpire(m_server.PlayerEmpireID((*player_it)->PlayerID()))) + { + (*player_it)->SendMessage(ServerLobbyUpdateMessage(dummy_lobby_data)); + } else { + (*player_it)->SendMessage(PlayerInfoMessage(player_info_map)); + } } +} - if (must_quit) { - ErrorLogger(FSM) << "Unable to recover server terminating."; - m_server.m_fsm->process_event(ShutdownServer()); +bool ServerFSM::EstablishPlayer(const PlayerConnectionPtr& player_connection, + const std::string& player_name, + Networking::ClientType client_type, + const std::string& client_version_string, + const Networking::AuthRoles& roles) +{ + std::list to_disconnect; + + // set and test roles + player_connection->SetAuthRoles(roles); + + if (client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER && + !player_connection->HasAuthRole(Networking::ROLE_CLIENT_TYPE_OBSERVER)) + { + client_type = Networking::INVALID_CLIENT_TYPE; + } + if (client_type == Networking::CLIENT_TYPE_HUMAN_MODERATOR && + !player_connection->HasAuthRole(Networking::ROLE_CLIENT_TYPE_MODERATOR)) + { + client_type = Networking::INVALID_CLIENT_TYPE; + } + if (client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER && + !player_connection->HasAuthRole(Networking::ROLE_CLIENT_TYPE_PLAYER)) + { + client_type = Networking::INVALID_CLIENT_TYPE; + } + + if (player_connection->IsAuthenticated() || !player_connection->Cookie().is_nil()) { + // drop other connection with same name + for (auto it = m_server.m_networking.established_begin(); + it != m_server.m_networking.established_end(); ++it) + { + if ((*it)->PlayerName() == player_name && player_connection != (*it)) { + (*it)->SendMessage(ErrorMessage(UserString("ERROR_CONNECTION_WAS_REPLACED"), true)); + to_disconnect.push_back(*it); + + // If we're going to establish Human Player + // it will be better to break link with previous connection + // so game won't be stopped on disconnection of previous connection. + if (client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) { + m_server.DropPlayerEmpireLink((*it)->PlayerID()); + (*it)->SetClientType(Networking::INVALID_CLIENT_TYPE); + } + } + } + } + + if (client_type == Networking::INVALID_CLIENT_TYPE) { + player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_CLIENT_TYPE_NOT_ALLOWED"), true)); + to_disconnect.push_back(player_connection); + } else { + // assign unique player ID to newly connected player + int player_id = m_server.m_networking.NewPlayerID(); + DebugLogger() << "ServerFSM.EstablishPlayer Assign new player id " << player_id; + + // establish player with requested client type and acknowldge via connection + player_connection->EstablishPlayer(player_id, player_name, client_type, client_version_string); + + // save cookie for player name + boost::uuids::uuid cookie = player_connection->Cookie(); + // Don't generate cookie if player already have it + if (cookie.is_nil()) + cookie = m_server.m_networking.GenerateCookie(player_name, + roles, + player_connection->IsAuthenticated()); + DebugLogger() << "ServerFSM.EstablishPlayer player " << player_name << " get cookie: " << cookie; + player_connection->SetCookie(cookie); + + player_connection->SendMessage(JoinAckMessage(player_id, cookie)); + if (!GetOptionsDB().Get("skip-checksum")) + player_connection->SendMessage(ContentCheckSumMessage()); + + // inform player of host + player_connection->SendMessage(HostIDMessage(m_server.m_networking.HostPlayerID())); + + // send chat history + if (client_type == Networking::CLIENT_TYPE_HUMAN_MODERATOR || + client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER || + client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) + { + // add "player enter game" message + boost::posix_time::ptime timestamp = boost::posix_time::second_clock::universal_time(); + std::string data = std::string("[[") + UserStringNop("PLAYER_ENTERED_GAME") + "," + player_connection->PlayerName() + "]]"; + m_server.PushChatMessage(data, "", GG::CLR_WHITE, timestamp); + + // send message to other players + for (auto it = m_server.m_networking.established_begin(); + it != m_server.m_networking.established_end(); ++it) + { + if (player_connection != (*it)) + (*it)->SendMessage(ServerPlayerChatMessage(Networking::INVALID_PLAYER_ID, timestamp, data)); + } + + std::vector> chat_history; + for (const auto& elem : m_server.GetChatHistory()) { + chat_history.push_back(std::cref(elem)); + } + if (chat_history.size() > 0) { + player_connection->SendMessage(ChatHistoryMessage(chat_history)); + } + } } + + // disconnect "ghost" connection after establishing new + for (const auto& conn : to_disconnect) + { m_server.Networking().Disconnect(conn); } + + return client_type != Networking::INVALID_CLIENT_TYPE; } + + //////////////////////////////////////////////////////////// // Idle //////////////////////////////////////////////////////////// Idle::Idle(my_context c) : my_base(c) -{ TraceLogger(FSM) << "(ServerFSM) Idle"; } +{ + TraceLogger(FSM) << "(ServerFSM) Idle"; + if (Server().IsHostless()) + post_event(Hostless()); + else if (!GetOptionsDB().Get("load").empty()) + throw std::invalid_argument("Autostart load file was choosed but the server wasn't started in a hostless mode"); + else if (GetOptionsDB().Get("quickstart")) + throw std::invalid_argument("Quickstart was choosed but the server wasn't started in a hostless mode"); +} Idle::~Idle() { TraceLogger(FSM) << "(ServerFSM) ~Idle"; } @@ -306,9 +620,17 @@ sc::result Idle::react(const HostMPGame& msg) { player_connection->EstablishPlayer(host_player_id, host_player_name, Networking::CLIENT_TYPE_HUMAN_PLAYER, client_version_string); server.m_networking.SetHostPlayerID(host_player_id); - player_connection->SendMessage(ContentCheckSumMessage()); + if (!GetOptionsDB().Get("skip-checksum")) + player_connection->SendMessage(ContentCheckSumMessage()); DebugLogger(FSM) << "Idle::react(HostMPGame) about to send acknowledgement to host"; + player_connection->SetAuthRoles({ + Networking::ROLE_HOST, + Networking::ROLE_CLIENT_TYPE_MODERATOR, + Networking::ROLE_CLIENT_TYPE_PLAYER, + Networking::ROLE_CLIENT_TYPE_OBSERVER, + Networking::ROLE_GALAXY_SETUP + }); player_connection->SendMessage(HostMPAckMessage(host_player_id)); server.m_single_player_game = false; @@ -334,7 +656,6 @@ sc::result Idle::react(const HostSPGame& msg) { try { host_player_name = GetHostNameFromSinglePlayerSetupData(*single_player_setup_data); } catch (const std::exception& e) { - const PlayerConnectionPtr& player_connection = msg.m_player_connection; player_connection->SendMessage(ErrorMessage(UserStringNop("UNABLE_TO_READ_SAVE_FILE"), true)); return discard_event(); } @@ -348,7 +669,13 @@ sc::result Idle::react(const HostSPGame& msg) { int host_player_id = server.m_networking.NewPlayerID(); player_connection->EstablishPlayer(host_player_id, host_player_name, Networking::CLIENT_TYPE_HUMAN_PLAYER, client_version_string); server.m_networking.SetHostPlayerID(host_player_id); - player_connection->SendMessage(ContentCheckSumMessage()); + if (!GetOptionsDB().Get("skip-checksum")) + player_connection->SendMessage(ContentCheckSumMessage()); + player_connection->SetAuthRoles({ + Networking::ROLE_HOST, + Networking::ROLE_CLIENT_TYPE_PLAYER, + Networking::ROLE_GALAXY_SETUP + }); player_connection->SendMessage(HostSPAckMessage(host_player_id)); server.m_single_player_game = true; @@ -371,41 +698,300 @@ sc::result Idle::react(const Error& msg) { return discard_event(); } +sc::result Idle::react(const Hostless&) { + TraceLogger(FSM) << "(ServerFSM) Idle.Hostless"; + std::string autostart_load_filename = GetOptionsDB().Get("load"); + bool quickstart = GetOptionsDB().Get("quickstart"); + if (!quickstart && autostart_load_filename.empty()) + return transit(); + + if (GetOptionsDB().Get("network.server.conn-human-empire-players.min") > 0) { + throw std::invalid_argument("A save file to load or quickstart and autostart in hostless mode was specified, but the server has a non-zero minimum number of connected players, so cannot be started without a connected player."); + } + + ServerApp& server = Server(); + std::shared_ptr lobby_data(new MultiplayerLobbyData(server.m_galaxy_setup_data)); + std::shared_ptr server_save_game_data(new ServerSaveGameData()); + std::vector player_save_game_data; + server.InitializePython(); + server.LoadChatHistory(); + if (autostart_load_filename.empty()) { + DebugLogger(FSM) << "Start new game"; + + std::list human_players = server.FillListPlayers(); + + for (auto& player_setup_data : human_players) { + DebugLogger(FSM) << "Create player " << player_setup_data.m_player_name; + player_setup_data.m_player_id = Networking::INVALID_PLAYER_ID; + player_setup_data.m_client_type = Networking::CLIENT_TYPE_HUMAN_PLAYER; + player_setup_data.m_empire_color = GetUnusedEmpireColour(lobby_data->m_players); + player_setup_data.m_authenticated = true; + lobby_data->m_players.push_back({Networking::INVALID_PLAYER_ID, player_setup_data}); + } + + const SpeciesManager& sm = GetSpeciesManager(); + auto max_ai = GetOptionsDB().Get("network.server.ai.max"); + const int ai_count = GetOptionsDB().Get("setup.ai.player.count"); + int ai_next_index = 1; + while (ai_next_index <= ai_count && (ai_next_index <= max_ai || max_ai < 0)) { + PlayerSetupData player_setup_data; + player_setup_data.m_player_id = Networking::INVALID_PLAYER_ID; + player_setup_data.m_player_name = UserString("AI_PLAYER") + "_" + std::to_string(ai_next_index++); + player_setup_data.m_client_type = Networking::CLIENT_TYPE_AI_PLAYER; + player_setup_data.m_empire_name = GenerateEmpireName(player_setup_data.m_player_name, lobby_data->m_players); + player_setup_data.m_empire_color = GetUnusedEmpireColour(lobby_data->m_players); + if (lobby_data->m_seed != "") + player_setup_data.m_starting_species_name = sm.RandomPlayableSpeciesName(); + else + player_setup_data.m_starting_species_name = sm.SequentialPlayableSpeciesName(ai_next_index); + + lobby_data->m_players.push_back({Networking::INVALID_PLAYER_ID, player_setup_data}); + } + } else { + DebugLogger(FSM) << "Loading file " << autostart_load_filename; + try { + LoadGame(autostart_load_filename, *server_save_game_data, + player_save_game_data, GetUniverse(), + Empires(), GetSpeciesManager(), + GetCombatLogManager(), server.m_galaxy_setup_data); + int seed = 0; + try { + seed = boost::lexical_cast(server.m_galaxy_setup_data.m_seed); + } catch (...) { + try { + boost::hash string_hash; + std::size_t h = string_hash(server.m_galaxy_setup_data.m_seed); + seed = static_cast(h); + } catch (...) {} + } + DebugLogger(FSM) << "Seeding with loaded galaxy seed: " << server.m_galaxy_setup_data.m_seed << " interpreted as actual seed: " << seed; + Seed(seed); + + // fill lobby data with AI to start them with server + int ai_next_index = 1; + for (const auto& psgd : player_save_game_data) { + if (psgd.m_client_type != Networking::CLIENT_TYPE_AI_PLAYER) + continue; + PlayerSetupData player_setup_data; + player_setup_data.m_player_id = Networking::INVALID_PLAYER_ID; + player_setup_data.m_player_name = UserString("AI_PLAYER") + "_" + std::to_string(ai_next_index++); + player_setup_data.m_client_type = Networking::CLIENT_TYPE_AI_PLAYER; + player_setup_data.m_save_game_empire_id = psgd.m_empire_id; + lobby_data->m_players.push_back({Networking::INVALID_PLAYER_ID, player_setup_data}); + } + } catch (const std::exception& e) { + throw e; + } + } + + lobby_data->m_game_rules = GetGameRules().GetRulesAsStrings(); + + // copy locally stored data to common server fsm context so it can be + // retreived in WaitingForMPGameJoiners + context().m_lobby_data = lobby_data; + context().m_player_save_game_data = player_save_game_data; + context().m_server_save_game_data = server_save_game_data; + + return transit(); +} //////////////////////////////////////////////////////////// // MPLobby //////////////////////////////////////////////////////////// +namespace { + // return true if player has important changes. + bool IsPlayerChanged(const PlayerSetupData& lhs, const PlayerSetupData& rhs) { + return (lhs.m_client_type != rhs.m_client_type) || + (lhs.m_starting_species_name != rhs.m_starting_species_name) || + (lhs.m_save_game_empire_id != rhs.m_save_game_empire_id) || + (lhs.m_empire_color != rhs.m_empire_color); + } +} + MPLobby::MPLobby(my_context c) : my_base(c), - m_lobby_data(new MultiplayerLobbyData()), - m_server_save_game_data(new ServerSaveGameData()) + m_lobby_data(new MultiplayerLobbyData(std::move(Server().m_galaxy_setup_data))), + m_server_save_game_data(new ServerSaveGameData()), + m_ai_next_index(1) { TraceLogger(FSM) << "(ServerFSM) MPLobby"; + ClockSeed(); ServerApp& server = Server(); + server.InitializePython(); + server.LoadChatHistory(); + m_lobby_data->m_game_rules = GetGameRules().GetRulesAsStrings(); const SpeciesManager& sm = GetSpeciesManager(); - int host_id = server.m_networking.HostPlayerID(); - const PlayerConnectionPtr& player_connection = *(server.m_networking.GetPlayer(host_id)); + if (server.IsHostless()) { + DebugLogger(FSM) << "(ServerFSM) MPLobby. Fill MPLobby data from the previous game."; + + m_lobby_data->m_any_can_edit = true; + + auto max_ai = GetOptionsDB().Get("network.server.ai.max"); + std::list to_disconnect; + // Try to use connections: + for (const auto& player_connection : server.m_networking) { + // If connection was not established disconnect it. + if (!player_connection->IsEstablished()) { + to_disconnect.push_back(player_connection); + continue; + } - ClockSeed(); + int player_id = player_connection->PlayerID(); + DebugLogger(FSM) << "(ServerFSM) MPLobby. Fill MPLobby player " << player_id; + if (player_connection->GetClientType() != Networking::CLIENT_TYPE_AI_PLAYER) { + PlayerSetupData player_setup_data; + player_setup_data.m_player_id = player_id; + player_setup_data.m_player_name = player_connection->PlayerName(); + player_setup_data.m_client_type = player_connection->GetClientType(); + player_setup_data.m_empire_name = (player_connection->GetClientType() == Networking::CLIENT_TYPE_HUMAN_PLAYER) ? player_connection->PlayerName() : GenerateEmpireName(player_setup_data.m_player_name, m_lobby_data->m_players); + player_setup_data.m_empire_color = GetUnusedEmpireColour(m_lobby_data->m_players); + if (m_lobby_data->m_seed != "") + player_setup_data.m_starting_species_name = sm.RandomPlayableSpeciesName(); + else + player_setup_data.m_starting_species_name = sm.SequentialPlayableSpeciesName(player_id); + player_setup_data.m_authenticated = player_connection->IsAuthenticated(); + + m_lobby_data->m_players.push_back({player_id, player_setup_data}); + } else if (player_connection->GetClientType() == Networking::CLIENT_TYPE_AI_PLAYER) { + if (m_ai_next_index <= max_ai || max_ai < 0) { + PlayerSetupData player_setup_data; + player_setup_data.m_player_id = Networking::INVALID_PLAYER_ID; + player_setup_data.m_player_name = UserString("AI_PLAYER") + "_" + std::to_string(m_ai_next_index++); + player_setup_data.m_client_type = Networking::CLIENT_TYPE_AI_PLAYER; + player_setup_data.m_empire_name = GenerateEmpireName(player_setup_data.m_player_name, m_lobby_data->m_players); + player_setup_data.m_empire_color = GetUnusedEmpireColour(m_lobby_data->m_players); + if (m_lobby_data->m_seed != "") + player_setup_data.m_starting_species_name = sm.RandomPlayableSpeciesName(); + else + player_setup_data.m_starting_species_name = sm.SequentialPlayableSpeciesName(m_ai_next_index); + + m_lobby_data->m_players.push_back({Networking::INVALID_PLAYER_ID, player_setup_data}); + } + // disconnect AI + to_disconnect.push_back(player_connection); + } + } + + for (const auto& player_connection : to_disconnect) { + server.Networking().Disconnect(player_connection); + } + + // check if there weren't previous AIs + if (m_ai_next_index == 1) { + // use AI count from option + const int ai_count = GetOptionsDB().Get("setup.ai.player.count"); + while (m_ai_next_index <= ai_count && (m_ai_next_index <= max_ai || max_ai < 0)) { + PlayerSetupData player_setup_data; + player_setup_data.m_player_id = Networking::INVALID_PLAYER_ID; + player_setup_data.m_player_name = UserString("AI_PLAYER") + "_" + std::to_string(m_ai_next_index++); + player_setup_data.m_client_type = Networking::CLIENT_TYPE_AI_PLAYER; + player_setup_data.m_empire_name = GenerateEmpireName(player_setup_data.m_player_name, m_lobby_data->m_players); + player_setup_data.m_empire_color = GetUnusedEmpireColour(m_lobby_data->m_players); + if (!m_lobby_data->m_seed.empty()) + player_setup_data.m_starting_species_name = sm.RandomPlayableSpeciesName(); + else + player_setup_data.m_starting_species_name = sm.SequentialPlayableSpeciesName(m_ai_next_index); + + m_lobby_data->m_players.push_back({Networking::INVALID_PLAYER_ID, player_setup_data}); + } + } + + ValidateClientLimits(); + + server.Networking().SendMessageAll(ServerLobbyUpdateMessage(*m_lobby_data)); + } else { + int host_id = server.m_networking.HostPlayerID(); + const PlayerConnectionPtr& player_connection = *(server.m_networking.GetPlayer(host_id)); - // create player setup data for host, and store in list - m_lobby_data->m_players.push_back(std::make_pair(host_id, PlayerSetupData())); + // create player setup data for host, and store in list + m_lobby_data->m_players.push_back({host_id, PlayerSetupData()}); - PlayerSetupData& player_setup_data = m_lobby_data->m_players.begin()->second; + PlayerSetupData& player_setup_data = m_lobby_data->m_players.begin()->second; - player_setup_data.m_player_name = player_connection->PlayerName(); - player_setup_data.m_empire_name = (player_connection->GetClientType() == Networking::CLIENT_TYPE_HUMAN_PLAYER) ? player_connection->PlayerName() : GenerateEmpireName(player_setup_data.m_player_name, m_lobby_data->m_players); - player_setup_data.m_empire_color = EmpireColors().at(0); // since the host is the first joined player, it can be assumed that no other player is using this colour (unlike subsequent join game message responses) - player_setup_data.m_starting_species_name = sm.RandomPlayableSpeciesName(); - // leaving save game empire id as default - player_setup_data.m_client_type = player_connection->GetClientType(); + player_setup_data.m_player_name = player_connection->PlayerName(); + player_setup_data.m_empire_name = (player_connection->GetClientType() == Networking::CLIENT_TYPE_HUMAN_PLAYER) ? player_connection->PlayerName() : GenerateEmpireName(player_setup_data.m_player_name, m_lobby_data->m_players); + player_setup_data.m_empire_color = EmpireColors().at(0); // since the host is the first joined player, it can be assumed that no other player is using this colour (unlike subsequent join game message responses) + player_setup_data.m_starting_species_name = sm.RandomPlayableSpeciesName(); + // leaving save game empire id as default + player_setup_data.m_client_type = player_connection->GetClientType(); + player_setup_data.m_authenticated = player_connection->IsAuthenticated(); - player_connection->SendMessage(ServerLobbyUpdateMessage(*m_lobby_data)); + player_connection->SendMessage(ServerLobbyUpdateMessage(*m_lobby_data)); + } } MPLobby::~MPLobby() { TraceLogger(FSM) << "(ServerFSM) ~MPLobby"; } +void MPLobby::ValidateClientLimits() { + int human_count = 0; + int ai_count = 0; + for (const auto& plr : m_lobby_data->m_players) { + if (plr.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) + human_count++; + else if (plr.second.m_client_type == Networking::CLIENT_TYPE_AI_PLAYER) + ai_count++; + } + + const int human_connected_count = human_count; + + // for load game consider as human all non-AI empires + // because human player could connect later in game + int non_eliminated_empires_count = 0; + if (!m_lobby_data->m_new_game) { + for (const auto& empire_data : m_lobby_data->m_save_game_empire_data) { + if (!empire_data.second.m_eliminated) + non_eliminated_empires_count ++; + } + + human_count = non_eliminated_empires_count - ai_count; + } + + int min_ai = GetOptionsDB().Get("network.server.ai.min"); + int max_ai = GetOptionsDB().Get("network.server.ai.max"); + if (max_ai >= 0 && max_ai < min_ai) { + WarnLogger(FSM) << "Maximum ai clients less than minimum, setting max to min"; + max_ai = min_ai; + GetOptionsDB().Set("network.server.ai.max", max_ai); + } + int min_human = GetOptionsDB().Get("network.server.human.min"); + int max_human = GetOptionsDB().Get("network.server.human.max"); + if (max_human >= 0 && max_human < min_human) { + WarnLogger(FSM) << "Maximum human clients less than minimum, setting max to min"; + max_human = min_human; + GetOptionsDB().Set("network.server.human.max", max_human); + } + int min_connected_human_empire_players = GetOptionsDB().Get("network.server.conn-human-empire-players.min"); + int max_unconnected_human_empire_players = GetOptionsDB().Get("network.server.unconn-human-empire-players.max"); + + // restrict minimun number of human and ai players + if (human_count < min_human) { + m_lobby_data->m_start_locked = true; + m_lobby_data->m_start_lock_cause = UserStringNop("ERROR_NOT_ENOUGH_HUMAN_PLAYERS"); + } else if (human_connected_count < min_connected_human_empire_players) { + m_lobby_data->m_start_locked = true; + m_lobby_data->m_start_lock_cause = UserStringNop("ERROR_NOT_ENOUGH_CONNECTED_HUMAN_PLAYERS"); + } else if (max_unconnected_human_empire_players > 0 && + !m_lobby_data->m_new_game && + non_eliminated_empires_count - ai_count - human_connected_count >= max_unconnected_human_empire_players) + { + m_lobby_data->m_start_locked = true; + m_lobby_data->m_start_lock_cause = UserStringNop("ERROR_TOO_MANY_UNCONNECTED_HUMAN_PLAYERS"); + } else if (max_human >= 0 && human_count > max_human) { + m_lobby_data->m_start_locked = true; + m_lobby_data->m_start_lock_cause = UserStringNop("ERROR_TOO_MANY_HUMAN_PLAYERS"); + } else if (ai_count < min_ai) { + m_lobby_data->m_start_locked = true; + m_lobby_data->m_start_lock_cause = UserStringNop("ERROR_NOT_ENOUGH_AI_PLAYERS"); + } else if (max_ai >= 0 && ai_count > max_ai) { + m_lobby_data->m_start_locked = true; + m_lobby_data->m_start_lock_cause = UserStringNop("ERROR_TOO_MANY_AI_PLAYERS"); + } else { + m_lobby_data->m_start_locked = false; + m_lobby_data->m_start_lock_cause.clear(); + } +} + sc::result MPLobby::react(const Disconnection& d) { TraceLogger(FSM) << "(ServerFSM) MPLobby.Disconnection"; ServerApp& server = Server(); @@ -413,16 +999,18 @@ sc::result MPLobby::react(const Disconnection& d) { DebugLogger(FSM) << "MPLobby::react(Disconnection) player id: " << player_connection->PlayerID(); DebugLogger(FSM) << "Remaining player ids: "; - for (ServerNetworking::const_established_iterator it = server.m_networking.established_begin(); + for (auto it = server.m_networking.established_begin(); it != server.m_networking.established_end(); ++it) { - DebugLogger(FSM) << " ... " << (*it)->PlayerID(); + DebugLogger(FSM) << " ... " << (*it)->PlayerID() << " (" << (*it)->PlayerName() << ")"; } - // if there are no humans left, it's time to terminate - if (server.m_networking.empty() || server.m_ai_client_processes.size() == server.m_networking.NumEstablishedPlayers()) { - DebugLogger(FSM) << "MPLobby.Disconnection : All human players disconnected; server terminating."; - return transit(); + if (!server.IsHostless()) { + // if there are no humans left, it's time to terminate + if (server.m_networking.empty() || server.m_ai_client_processes.size() == server.m_networking.NumEstablishedPlayers()) { + DebugLogger(FSM) << "MPLobby.Disconnection : All human players disconnected; server terminating."; + return transit(); + } } if (server.m_networking.PlayerIsHost(player_connection->PlayerID())) @@ -431,8 +1019,13 @@ sc::result MPLobby::react(const Disconnection& d) { // if the disconnected player wasn't in the lobby, don't need to do anything more. // if player is in lobby, need to remove it int id = player_connection->PlayerID(); + // Non-established player shouldn't be processed because it wasn't in lobby and AI entries have same id (INVALID_PLAYER_ID). + if (id == Networking::INVALID_PLAYER_ID) { + DebugLogger(FSM) << "MPLobby.Disconnection : Disconnecting player (" << id << ") was not established"; + return discard_event(); + } bool player_was_in_lobby = false; - for (std::list>::iterator it = m_lobby_data->m_players.begin(); + for (auto it = m_lobby_data->m_players.begin(); it != m_lobby_data->m_players.end(); ++it) { if (it->first == id) { @@ -442,17 +1035,36 @@ sc::result MPLobby::react(const Disconnection& d) { } } if (player_was_in_lobby) { + // update cookie's expire date + // so player could reconnect within 15 minutes + Server().Networking().UpdateCookie(player_connection->Cookie()); + // drop ready flag as player list changed - for (std::pair& plrs : m_lobby_data->m_players) { + for (auto& plrs : m_lobby_data->m_players) { plrs.second.m_player_ready = false; } + + // add "player left game" message + boost::posix_time::ptime timestamp = boost::posix_time::second_clock::universal_time(); + std::string data = std::string("[[") + UserStringNop("PLAYER_LEFT_GAME") + "," + player_connection->PlayerName() + "]]"; + server.PushChatMessage(data, "", GG::CLR_WHITE, timestamp); + + // send message to other players + for (auto it = server.m_networking.established_begin(); + it != server.m_networking.established_end(); ++it) + { + if (player_connection != (*it)) + (*it)->SendMessage(ServerPlayerChatMessage(Networking::INVALID_PLAYER_ID, timestamp, data)); + } } else { DebugLogger(FSM) << "MPLobby.Disconnection : Disconnecting player (" << id << ") was not in lobby"; return discard_event(); } + ValidateClientLimits(); + // send updated lobby data to players after disconnection-related changes - for (ServerNetworking::const_established_iterator it = server.m_networking.established_begin(); + for (auto it = server.m_networking.established_begin(); it != server.m_networking.established_end(); ++it) { (*it)->SendMessage(ServerLobbyUpdateMessage(*m_lobby_data)); @@ -461,139 +1073,173 @@ sc::result MPLobby::react(const Disconnection& d) { return discard_event(); } -namespace { - GG::Clr GetUnusedEmpireColour(const std::list>& psd) { - //DebugLogger(FSM) << "finding colours for empire of player " << player_name; - GG::Clr empire_colour = GG::Clr(192, 192, 192, 255); - for (const GG::Clr& possible_colour : EmpireColors()) { - //DebugLogger(FSM) << "trying colour " << possible_colour.r << ", " << possible_colour.g << ", " << possible_colour.b; +void MPLobby::EstablishPlayer(const PlayerConnectionPtr& player_connection, + const std::string& player_name, + Networking::ClientType client_type, + const std::string& client_version_string, + const Networking::AuthRoles& roles) +{ + ServerApp& server = Server(); + const SpeciesManager& sm = GetSpeciesManager(); - // check if any other player / empire is using this colour - bool colour_is_new = true; - for (const std::pair& entry : psd) { - const GG::Clr& player_colour = entry.second.m_empire_color; - if (player_colour == possible_colour) { - colour_is_new = false; - break; - } - } + if (context().EstablishPlayer(player_connection, + player_name, + client_type, + client_version_string, + roles)) + { + int player_id = player_connection->PlayerID(); - // use colour and exit loop if no other empire is using the colour - if (colour_is_new) { - empire_colour = possible_colour; - break; + // Inform AI of logging configuration. + if (client_type == Networking::CLIENT_TYPE_AI_PLAYER) + player_connection->SendMessage( + LoggerConfigMessage(Networking::INVALID_PLAYER_ID, LoggerOptionsLabelsAndLevels(LoggerTypes::both))); + + // assign player info from defaults or from connection to lobby data players list + PlayerSetupData player_setup_data; + player_setup_data.m_player_name = player_name; + player_setup_data.m_client_type = client_type; + player_setup_data.m_empire_name = (client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) ? player_name : GenerateEmpireName(player_name, m_lobby_data->m_players); + player_setup_data.m_empire_color = GetUnusedEmpireColour(m_lobby_data->m_players); + if (m_lobby_data->m_seed != "") + player_setup_data.m_starting_species_name = sm.RandomPlayableSpeciesName(); + else + player_setup_data.m_starting_species_name = sm.SequentialPlayableSpeciesName(player_id); + player_setup_data.m_authenticated = player_connection->IsAuthenticated(); + + // after setting all details, push into lobby data + m_lobby_data->m_players.push_back({player_id, player_setup_data}); + + // drop ready player flag at new player + for (std::pair& plr : m_lobby_data->m_players) { + if (plr.second.m_empire_name == player_name) { + // change empire name + plr.second.m_empire_name = (plr.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) ? plr.second.m_player_name : GenerateEmpireName(plr.second.m_player_name, m_lobby_data->m_players); } - //DebugLogger(FSM) << " ... colour already used."; + plr.second.m_player_ready = false; } - return empire_colour; - } - // return true if player has important changes. - bool IsPlayerChanged(const PlayerSetupData& lhs, const PlayerSetupData& rhs) { - return (lhs.m_client_type != rhs.m_client_type) || - (lhs.m_starting_species_name != rhs.m_starting_species_name) || - (lhs.m_save_game_empire_id != rhs.m_save_game_empire_id) || - (lhs.m_empire_color != rhs.m_empire_color); + ValidateClientLimits(); + + for (auto it = server.m_networking.established_begin(); + it != server.m_networking.established_end(); ++it) + { (*it)->SendMessage(ServerLobbyUpdateMessage(*m_lobby_data)); } } } sc::result MPLobby::react(const JoinGame& msg) { TraceLogger(FSM) << "(ServerFSM) MPLobby.JoinGame"; ServerApp& server = Server(); - const SpeciesManager& sm = GetSpeciesManager(); const Message& message = msg.m_message; const PlayerConnectionPtr& player_connection = msg.m_player_connection; std::string player_name; Networking::ClientType client_type; std::string client_version_string; - ExtractJoinGameMessageData(message, player_name, client_type, client_version_string); + boost::uuids::uuid cookie; + ExtractJoinGameMessageData(message, player_name, client_type, client_version_string, cookie); + + Networking::AuthRoles roles; + bool authenticated; + + DebugLogger() << "MPLobby.JoinGame Try to login player " << player_name << " with cookie: " << cookie; + if (server.Networking().CheckCookie(cookie, player_name, roles, authenticated)) { + // if player have correct and non-expired cookies simply establish him + player_connection->SetCookie(cookie); + if (authenticated) + player_connection->SetAuthenticated(); + } else { + if (client_type != Networking::CLIENT_TYPE_AI_PLAYER && server.IsAuthRequiredOrFillRoles(player_name, roles)) { + // send authentication request + player_connection->AwaitPlayer(client_type, client_version_string); + player_connection->SendMessage(AuthRequestMessage(player_name, "PLAIN-TEXT")); + return discard_event(); + } - std::string original_player_name = player_name; + std::string original_player_name = player_name; - // Remove AI prefix to distinguish Human from AI. - std::string ai_prefix = UserString("AI_PLAYER") + "_"; - if (client_type != Networking::CLIENT_TYPE_AI_PLAYER) { - while (player_name.compare(0, ai_prefix.size(), ai_prefix) == 0) - player_name.erase(0, ai_prefix.size()); - } - if(player_name.empty()) - player_name = "_"; + // Remove AI prefix to distinguish Human from AI. + std::string ai_prefix = UserString("AI_PLAYER") + "_"; + if (client_type != Networking::CLIENT_TYPE_AI_PLAYER) { + while (player_name.compare(0, ai_prefix.size(), ai_prefix) == 0) + player_name.erase(0, ai_prefix.size()); + } + if (player_name.empty()) + player_name = "_"; - std::string new_player_name = player_name; + std::string new_player_name = player_name; - bool collision = true; - std::size_t t = 1; - while (t <= m_lobby_data->m_players.size() + 1 && collision) { - collision = false; - if (!server.IsAvailableName(new_player_name)) { - collision = true; - } else { - for (std::pair& plr : m_lobby_data->m_players) { - if (plr.second.m_empire_name == new_player_name) { - collision = true; - break; + // Try numeric suffixes to get unique name which isn't equal to other player or empire name + // or registered for authentication. + bool collision = true; + std::size_t t = 1; + while (collision && + t <= m_lobby_data->m_players.size() + server.Networking().GetCookiesSize() + 1) + { + collision = false; + roles.Clear(); + if (!server.IsAvailableName(new_player_name) || server.IsAuthRequiredOrFillRoles(new_player_name, roles)) { + collision = true; + } else { + for (auto& plr : m_lobby_data->m_players) { + if (plr.second.m_empire_name == new_player_name) { + collision = true; + break; + } } } + + if (collision) + new_player_name = player_name + std::to_string(++t); // start alternative names from 2 + } + + // There is a small chance of unsolveable collision in case of sequential player names are + // registered for authentication + if (collision) { + ErrorLogger(FSM) << "MPLobby::react(const JoinGame& msg): couldn't find free player name for \"" << original_player_name << "\""; + player_connection->SendMessage(ErrorMessage(str(FlexibleFormat(UserString("ERROR_PLAYER_NAME_ALREADY_USED")) % original_player_name), + true)); + server.Networking().Disconnect(player_connection); + return discard_event(); } - if (collision) - new_player_name = player_name + std::to_string(++t); // start alternative names from 2 + player_name = std::move(new_player_name); } - if (collision) { - player_connection->SendMessage(ErrorMessage(str(FlexibleFormat(UserString("ERROR_PLAYER_NAME_ALREADY_USED")) % original_player_name), - true)); - server.Networking().Disconnect(player_connection); - return discard_event(); - } + EstablishPlayer(player_connection, player_name, client_type, client_version_string, roles); - player_name = new_player_name; - - // assign unique player ID to newly connected player - int player_id = server.m_networking.NewPlayerID(); + return discard_event(); +} - // establish player with requested client type and acknowldge via connection - player_connection->EstablishPlayer(player_id, player_name, client_type, client_version_string); - player_connection->SendMessage(ContentCheckSumMessage()); - player_connection->SendMessage(JoinAckMessage(player_id)); - - // inform player of host - player_connection->SendMessage(HostIDMessage(server.m_networking.HostPlayerID())); - - // Inform AI of logging configuration. - if (client_type == Networking::CLIENT_TYPE_AI_PLAYER) - player_connection->SendMessage( - LoggerConfigMessage(Networking::INVALID_PLAYER_ID, LoggerOptionsLabelsAndLevels(LoggerTypes::both))); - - // assign player info from defaults or from connection to lobby data players list - PlayerSetupData player_setup_data; - player_setup_data.m_player_name = player_name; - player_setup_data.m_client_type = client_type; - player_setup_data.m_empire_name = (client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) ? player_name : GenerateEmpireName(player_name, m_lobby_data->m_players); - player_setup_data.m_empire_color = GetUnusedEmpireColour(m_lobby_data->m_players); - if (m_lobby_data->m_seed != "") - player_setup_data.m_starting_species_name = sm.RandomPlayableSpeciesName(); - else - player_setup_data.m_starting_species_name = sm.SequentialPlayableSpeciesName(player_id); +sc::result MPLobby::react(const AuthResponse& msg) { + TraceLogger(FSM) << "(ServerFSM) MPLobby.AuthResponse"; + ServerApp& server = Server(); + const Message& message = msg.m_message; + const PlayerConnectionPtr& player_connection = msg.m_player_connection; - // after setting all details, push into lobby data - m_lobby_data->m_players.push_back(std::make_pair(player_id, player_setup_data)); + std::string player_name; + std::string auth; + ExtractAuthResponseMessageData(message, player_name, auth); - // drop ready player flag at new player - for (std::pair& plr : m_lobby_data->m_players) { - if (plr.second.m_empire_name == player_name) { - // change empire name - plr.second.m_empire_name = (plr.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) ? plr.second.m_player_name : GenerateEmpireName(plr.second.m_player_name, m_lobby_data->m_players); - } + Networking::AuthRoles roles; - plr.second.m_player_ready = false; + if (!server.IsAuthSuccessAndFillRoles(player_name, auth, roles)) { + // wrong password + player_connection->SendMessage(ErrorMessage(str(FlexibleFormat(UserString("ERROR_WRONG_PASSWORD")) % player_name), + true)); + server.Networking().Disconnect(player_connection); + return discard_event(); } + player_connection->SetAuthenticated(); - for (ServerNetworking::const_established_iterator it = server.m_networking.established_begin(); - it != server.m_networking.established_end(); ++it) - { (*it)->SendMessage(ServerLobbyUpdateMessage(*m_lobby_data)); } + Networking::ClientType client_type = player_connection->GetClientType(); + + EstablishPlayer(player_connection, + player_name, + client_type, + player_connection->ClientVersionString(), + roles); return discard_event(); } @@ -619,21 +1265,33 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { // save files, save game empire data from the save file, player data) // during this copying and is updated below from the save file(s) - if (server.m_networking.PlayerIsHost(sender->PlayerID())) { + if (sender->HasAuthRole(Networking::ROLE_HOST)) { + if (m_lobby_data->m_any_can_edit != incoming_lobby_data.m_any_can_edit) { + has_important_changes = true; + m_lobby_data->m_any_can_edit = incoming_lobby_data.m_any_can_edit; + + // change role ROLE_GALAXY_SETUP for all non-host players + for (const auto& player_connection : server.Networking()) { + if (!player_connection->HasAuthRole(Networking::ROLE_HOST)) { + player_connection->SetAuthRole(Networking::ROLE_GALAXY_SETUP, + m_lobby_data->m_any_can_edit); + } + } + } + } - DebugLogger(FSM) << "Get message from host."; + if (sender->HasAuthRole(Networking::ROLE_GALAXY_SETUP)) { - static int AI_count = 1; - const GG::Clr CLR_NONE = GG::Clr(0, 0, 0, 0); + DebugLogger(FSM) << "Get message from host or allowed player " << sender->PlayerID(); // assign unique names / colours to any lobby entry that lacks them, or // remove empire / colours from observers - for (std::pair& entry : incoming_lobby_data.m_players) { + for (auto& entry : incoming_lobby_data.m_players) { PlayerSetupData& psd = entry.second; if (psd.m_client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER || psd.m_client_type == Networking::CLIENT_TYPE_HUMAN_MODERATOR) { - psd.m_empire_color = CLR_NONE; + psd.m_empire_color = GG::CLR_ZERO; // On OSX the following two lines must not be included. // Clearing empire name and starting species name from // PlayerSetupData causes a weird crash (bus error) deep @@ -646,22 +1304,22 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { psd.m_save_game_empire_id = ALL_EMPIRES; } else if (psd.m_client_type == Networking::CLIENT_TYPE_AI_PLAYER) { - if (psd.m_empire_color == CLR_NONE) + if (psd.m_empire_color == GG::CLR_ZERO) psd.m_empire_color = GetUnusedEmpireColour(incoming_lobby_data.m_players); if (psd.m_player_name.empty()) // ToDo: Should we translate player_name? - psd.m_player_name = UserString("AI_PLAYER") + "_" + std::to_string(AI_count++); + psd.m_player_name = UserString("AI_PLAYER") + "_" + std::to_string(m_ai_next_index++); if (psd.m_empire_name.empty()) psd.m_empire_name = GenerateEmpireName(psd.m_player_name, incoming_lobby_data.m_players); if (psd.m_starting_species_name.empty()) { if (m_lobby_data->m_seed != "") psd.m_starting_species_name = GetSpeciesManager().RandomPlayableSpeciesName(); else - psd.m_starting_species_name = GetSpeciesManager().SequentialPlayableSpeciesName(AI_count); + psd.m_starting_species_name = GetSpeciesManager().SequentialPlayableSpeciesName(m_ai_next_index); } } else if (psd.m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) { - if (psd.m_empire_color == CLR_NONE) + if (psd.m_empire_color == GG::CLR_ZERO) psd.m_empire_color = GetUnusedEmpireColour(incoming_lobby_data.m_players); if (psd.m_empire_name.empty()) psd.m_empire_name = psd.m_player_name; @@ -671,26 +1329,62 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { } bool has_collision = false; - // check for color and names + // check for color, names, and IDs std::set psd_colors; std::set psd_names; - for (std::pair& player : incoming_lobby_data.m_players) { + std::set psd_ids; + for (auto& player : incoming_lobby_data.m_players) { if (psd_colors.count(player.second.m_empire_color) || psd_names.count(player.second.m_empire_name) || psd_names.count(player.second.m_player_name)) { has_collision = true; + WarnLogger(FSM) << "Got color, empire's name or player's name collision for player " + << player.second.m_player_name << "(" << player.first << ")"; break; } else { psd_colors.emplace(player.second.m_empire_color); psd_names.emplace(player.second.m_empire_name); psd_names.emplace(player.second.m_player_name); } + + if (player.first != Networking::INVALID_PLAYER_ID) { + const auto& player_it = server.Networking().GetPlayer(player.first); + if (player_it != server.Networking().established_end()) { + // check for roles and client types + if ((player.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER && + !(*player_it)->HasAuthRole(Networking::ROLE_CLIENT_TYPE_PLAYER)) || + (player.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_MODERATOR && + !(*player_it)->HasAuthRole(Networking::ROLE_CLIENT_TYPE_MODERATOR)) || + (player.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER && + !(*player_it)->HasAuthRole(Networking::ROLE_CLIENT_TYPE_OBSERVER))) + { + has_collision = true; + WarnLogger(FSM) << "Got unallowed client types."; + break; + } + // set correct authentication status + player.second.m_authenticated = (*player_it)->IsAuthenticated(); + } else { + // player wasn't found + // don't allow "ghost" records + has_collision = true; + WarnLogger(FSM) << "Got missing player."; + break; + } + if (!psd_ids.insert(player.first).second) { + // player id was already used + // don't allow ID collision + has_collision = true; + WarnLogger(FSM) << "Got player's id collision."; + break; + } + } } if (has_collision) { player_setup_data_changed = true; - for (std::pair& player : m_lobby_data->m_players) { + for (auto& player : m_lobby_data->m_players) { if (player.first == sender->PlayerID()) { player.second.m_player_ready = false; break; @@ -718,12 +1412,12 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { if (m_lobby_data->m_players.size() != incoming_lobby_data.m_players.size()) { has_important_changes = true; // drop ready at number of players changed } else { - for (std::pair& i_player : m_lobby_data->m_players) { + for (auto& i_player : m_lobby_data->m_players) { if (i_player.first < 0) // ignore changes in AI. continue; int player_id = i_player.first; bool is_found_player = false; - for (std::pair& j_player : incoming_lobby_data.m_players) { + for (auto& j_player : incoming_lobby_data.m_players) { if (player_id == j_player.first) { has_important_changes = has_important_changes || IsPlayerChanged(i_player.second, j_player.second); is_found_player = true; @@ -736,7 +1430,7 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { } // GalaxySetupData - m_lobby_data->m_seed = incoming_lobby_data.m_seed; + m_lobby_data->SetSeed(incoming_lobby_data.m_seed); m_lobby_data->m_size = incoming_lobby_data.m_size; m_lobby_data->m_shape = incoming_lobby_data.m_shape; m_lobby_data->m_age = incoming_lobby_data.m_age; @@ -746,28 +1440,98 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { m_lobby_data->m_monster_freq = incoming_lobby_data.m_monster_freq; m_lobby_data->m_native_freq = incoming_lobby_data.m_native_freq; m_lobby_data->m_ai_aggr = incoming_lobby_data.m_ai_aggr; - m_lobby_data->m_game_rules = incoming_lobby_data.m_game_rules; + + // copy rules from incoming lobby data to server lobby data, only if those rules are + // not locked by the server + for (const auto& incoming_rule : incoming_lobby_data.m_game_rules) { + if (GetOptionsDB().OptionExists("setup.rules.server-locked." + incoming_rule.first) && + !GetOptionsDB().Get("setup.rules.server-locked." + incoming_rule.first)) + { + m_lobby_data->m_game_rules[incoming_rule.first] = incoming_rule.second; + } + } // directly configurable lobby data m_lobby_data->m_new_game = incoming_lobby_data.m_new_game; - m_lobby_data->m_players = incoming_lobby_data.m_players; + if (m_lobby_data->m_new_game) { + // empty save data + m_lobby_data->m_save_game = ""; + m_lobby_data->m_save_game_empire_data.clear(); + // prevent updating lobby by having old and new file name equal + incoming_lobby_data.m_save_game.clear(); + } + + int ai_count = 0; + for (const auto& plr : incoming_lobby_data.m_players) { + if (plr.second.m_client_type == Networking::CLIENT_TYPE_AI_PLAYER) { + ai_count++; + } + } + + // limit count of AI + auto max_ai = GetOptionsDB().Get("network.server.ai.max"); + if (ai_count <= max_ai || max_ai < 0) { + if (sender->HasAuthRole(Networking::ROLE_HOST)) { + // don't check host player + // treat host player as superuser or administrator + m_lobby_data->m_players = incoming_lobby_data.m_players; + } else { + // check players shouldn't set protected empire to themselves + // if they don't have required player's name + bool incorrect_empire = false; + for (const auto& plr : incoming_lobby_data.m_players) { + if (plr.first != sender->PlayerID()) + continue; + if (plr.second.m_save_game_empire_id != ALL_EMPIRES) { + const auto empire_it = m_lobby_data->m_save_game_empire_data.find(plr.second.m_save_game_empire_id); + if (empire_it != m_lobby_data->m_save_game_empire_data.end()) { + if (empire_it->second.m_eliminated) { + WarnLogger(FSM) << "Trying to take over eliminated empire \"" << empire_it->second.m_empire_name << "\""; + incorrect_empire = true; + } else if (empire_it->second.m_authenticated) { + if (empire_it->second.m_player_name != sender->PlayerName()) { + WarnLogger(FSM) << "Unauthorized access to protected empire \"" << empire_it->second.m_empire_name << "\"." + << " Expected player \"" << empire_it->second.m_player_name << "\"" + << " got \"" << sender->PlayerName() << "\""; + incorrect_empire = true; + } + } + } else { + WarnLogger(FSM) << "Unknown empire #" << plr.second.m_save_game_empire_id; + incorrect_empire = true; + } + } + break; + } + if (incorrect_empire) { + has_important_changes = true; + player_setup_data_changed = true; + } else { + // ToDo: non-host player should change only AI and himself + m_lobby_data->m_players = incoming_lobby_data.m_players; + } + } + } else { + has_important_changes = true; + } LogPlayerSetupData(m_lobby_data->m_players); // update player connection types according to modified lobby selections, // while recording connections that are to be dropped std::vector player_connections_to_drop; - for (ServerNetworking::established_iterator player_connection_it = server.m_networking.established_begin(); - player_connection_it != server.m_networking.established_end(); ++player_connection_it) + for (auto player_connection_it = server.m_networking.established_begin(); + player_connection_it != server.m_networking.established_end(); + ++player_connection_it) { PlayerConnectionPtr player_connection = *player_connection_it; - int player_id = player_connection->PlayerID(); - if (player_id == Networking::INVALID_PLAYER_ID) + if (!player_connection->IsEstablished()) continue; + int player_id = player_connection->PlayerID(); // get lobby data for this player connection bool found_player_lobby_data = false; - std::list>::iterator player_setup_it = m_lobby_data->m_players.begin(); + auto player_setup_it = m_lobby_data->m_players.begin(); while (player_setup_it != m_lobby_data->m_players.end()) { if (player_setup_it->first == player_id) { found_player_lobby_data = true; @@ -784,7 +1548,7 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { } // get client type, and drop connections with invalid type, as that indicates a requested drop - Networking::ClientType client_type = player_setup_it->second.m_client_type; + auto client_type = player_setup_it->second.m_client_type; if (client_type != Networking::INVALID_CLIENT_TYPE) { // update player connection type for lobby change @@ -808,10 +1572,10 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { // from the lobby. this will also occur when humans are dropped, but those // cases should have been handled above when checking the lobby data for // each player connection. - std::list>::iterator player_setup_it = m_lobby_data->m_players.begin(); + auto player_setup_it = m_lobby_data->m_players.begin(); while (player_setup_it != m_lobby_data->m_players.end()) { if (player_setup_it->second.m_client_type == Networking::INVALID_CLIENT_TYPE) { - std::list>::iterator erase_it = player_setup_it; + auto erase_it = player_setup_it; ++player_setup_it; m_lobby_data->m_players.erase(erase_it); } else { @@ -822,12 +1586,12 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { } } else { // can change only himself - for (std::pair& i_player : m_lobby_data->m_players) { + for (auto& i_player : m_lobby_data->m_players) { if (i_player.first != sender->PlayerID()) continue; // found sender at m_lobby_data - for (std::pair& j_player : incoming_lobby_data.m_players) { + for (auto& j_player : incoming_lobby_data.m_players) { if (j_player.first != sender->PlayerID()) continue; @@ -836,7 +1600,7 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { // check for color and names std::set psd_colors; std::set psd_names; - for (std::pair& k_player : m_lobby_data->m_players) { + for (auto& k_player : m_lobby_data->m_players) { if (k_player.first == sender->PlayerID()) continue; @@ -845,14 +1609,48 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { psd_names.emplace(k_player.second.m_player_name); } - // if we have collision unset ready flag and ignore changes + // if we have collision or unallowed client type + // unset ready flag and ignore changes if (psd_colors.count(j_player.second.m_empire_color) || psd_names.count(j_player.second.m_empire_name) || - psd_names.count(j_player.second.m_player_name)) + psd_names.count(j_player.second.m_player_name) || + (j_player.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER && + !sender->HasAuthRole(Networking::ROLE_CLIENT_TYPE_PLAYER)) || + (j_player.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_MODERATOR && + !sender->HasAuthRole(Networking::ROLE_CLIENT_TYPE_MODERATOR)) || + (j_player.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER && + !sender->HasAuthRole(Networking::ROLE_CLIENT_TYPE_OBSERVER))) { i_player.second.m_player_ready = false; player_setup_data_changed = true; } else { + // check loaded empire + bool incorrect_empire = false; + if (j_player.second.m_save_game_empire_id != ALL_EMPIRES) { + const auto empire_it = m_lobby_data->m_save_game_empire_data.find(j_player.second.m_save_game_empire_id); + if (empire_it != m_lobby_data->m_save_game_empire_data.end()) { + if (empire_it->second.m_eliminated) { + WarnLogger(FSM) << "Trying to take over eliminated empire \"" << empire_it->second.m_empire_name << "\""; + incorrect_empire = true; + } else if (empire_it->second.m_authenticated) { + if (empire_it->second.m_player_name != sender->PlayerName()) { + WarnLogger(FSM) << "Unauthorized access to protected empire \"" << empire_it->second.m_empire_name << "\"." + << " Expected player \"" << empire_it->second.m_player_name << "\"" + << " got \"" << sender->PlayerName() << "\""; + incorrect_empire = true; + } + } + } else { + WarnLogger(FSM) << "Unknown empire #" << j_player.second.m_save_game_empire_id; + incorrect_empire = true; + } + } + if (incorrect_empire) { + i_player.second.m_player_ready = false; + player_setup_data_changed = true; + break; + } + has_important_changes = IsPlayerChanged(i_player.second, j_player.second); player_setup_data_changed = ! (i_player.second == j_player.second); @@ -865,6 +1663,11 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { } } + ValidateClientLimits(); + if (m_lobby_data->m_start_locked) { + has_important_changes = true; + } + // to determine if a new save file was selected, check if the selected file // index is different, and the new file index is in the valid range bool new_save_file_selected = false; @@ -876,17 +1679,60 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { // update selected file index m_lobby_data->m_save_game = new_file; + // remove all AIs from current lobby data, + // so that when the save is loaded no AI state as appropriate, + // without having potential extra AIs lingering from the previous + m_lobby_data->m_players.remove_if([](const std::pair& plr) { + return plr.second.m_client_type == Networking::CLIENT_TYPE_AI_PLAYER; + }); + m_ai_next_index = 1; + // reset assigned empires in save game for all players. new loaded game may not have the same set of empire IDs to choose from - for (std::pair& psd : m_lobby_data->m_players) { + for (auto& psd : m_lobby_data->m_players) { psd.second.m_save_game_empire_id = ALL_EMPIRES; + psd.second.m_empire_color = GG::CLR_ZERO; } // refresh save game empire data - boost::filesystem::path save_dir(GetSaveDir()); + boost::filesystem::path save_dir(GetServerSaveDir()); + std::vector player_save_header_data; try { LoadEmpireSaveGameData((save_dir / m_lobby_data->m_save_game).string(), - m_lobby_data->m_save_game_empire_data); - } catch (const std::exception&) { + m_lobby_data->m_save_game_empire_data, + player_save_header_data, + *m_lobby_data, + m_lobby_data->m_save_game_current_turn); + + // read all AI players from save game and add them into current lobby + // with appropriate empire's data + for (const auto& pshd : player_save_header_data) { + if (pshd.m_client_type != Networking::CLIENT_TYPE_AI_PLAYER) + continue; + const auto& empire_data_it = m_lobby_data->m_save_game_empire_data.find(pshd.m_empire_id); + if (empire_data_it == m_lobby_data->m_save_game_empire_data.end()) + continue; + + PlayerSetupData player_setup_data; + player_setup_data.m_player_id = Networking::INVALID_PLAYER_ID; + // don't use original names to prevent collision with manually added AIs + player_setup_data.m_player_name = UserString("AI_PLAYER") + "_" + std::to_string(m_ai_next_index++); + player_setup_data.m_client_type = Networking::CLIENT_TYPE_AI_PLAYER; + player_setup_data.m_save_game_empire_id = pshd.m_empire_id; + player_setup_data.m_empire_name = empire_data_it->second.m_empire_name; + player_setup_data.m_empire_color = empire_data_it->second.m_color; + if (m_lobby_data->m_seed != "") + player_setup_data.m_starting_species_name = GetSpeciesManager().RandomPlayableSpeciesName(); + else + player_setup_data.m_starting_species_name = GetSpeciesManager().SequentialPlayableSpeciesName(m_ai_next_index); + m_lobby_data->m_players.push_back({Networking::INVALID_PLAYER_ID, player_setup_data}); + } + + // reset empire color of non-AI player to unused + for (auto& psd : m_lobby_data->m_players) { + if (psd.second.m_save_game_empire_id == ALL_EMPIRES) + psd.second.m_empire_color = GetUnusedEmpireColour(m_lobby_data->m_players, m_lobby_data->m_save_game_empire_data); + } + } catch (...) { // inform player who attempted to change the save file that there was a problem sender->SendMessage(ErrorMessage(UserStringNop("UNABLE_TO_READ_SAVE_FILE"), false)); // revert to old save file @@ -895,12 +1741,12 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { } if (has_important_changes) { - for (std::pair& player : m_lobby_data->m_players) + for (auto& player : m_lobby_data->m_players) player.second.m_player_ready = false; } else { // check if all established human players ready to play bool is_all_ready = true; - for (std::pair& player : m_lobby_data->m_players) { + for (auto& player : m_lobby_data->m_players) { if ((player.first >= 0) && (player.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER || player.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_MODERATOR || player.second.m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER)) @@ -916,7 +1762,7 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { if (!m_lobby_data->m_new_game) { // Load game ... - std::string save_filename = (GetSaveDir() / m_lobby_data->m_save_game).string(); + std::string save_filename = (GetServerSaveDir() / m_lobby_data->m_save_game).string(); try { LoadGame(save_filename, *m_server_save_game_data, @@ -936,7 +1782,7 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { DebugLogger(FSM) << "Seeding with loaded galaxy seed: " << server.m_galaxy_setup_data.m_seed << " interpreted as actual seed: " << seed; Seed(seed); - } catch (const std::exception&) { + } catch (...) { SendMessageToAllPlayers(ErrorMessage(UserStringNop("UNABLE_TO_READ_SAVE_FILE"), true)); return discard_event(); } @@ -954,7 +1800,7 @@ sc::result MPLobby::react(const LobbyUpdate& msg) { // propagate lobby changes to players, so everyone has the latest updated // version of the lobby data - for (ServerNetworking::const_established_iterator player_connection_it = server.m_networking.established_begin(); + for (auto player_connection_it = server.m_networking.established_begin(); player_connection_it != server.m_networking.established_end(); ++player_connection_it) { const PlayerConnectionPtr& player_connection = *player_connection_it; @@ -978,18 +1824,32 @@ sc::result MPLobby::react(const PlayerChat& msg) { const PlayerConnectionPtr& sender = msg.m_player_connection; std::string data; - int receiver; - ExtractPlayerChatMessageData(message, receiver, data); + std::set recipients; + bool pm; + ExtractPlayerChatMessageData(message, recipients, data, pm); + + boost::posix_time::ptime timestamp = boost::posix_time::second_clock::universal_time(); + + if (recipients.empty() && sender->GetClientType() != Networking::CLIENT_TYPE_AI_PLAYER) + { + GG::Clr text_color(255, 255, 255, 255); + for (const auto& player : m_lobby_data->m_players) { + if (player.first != sender->PlayerID()) + continue; + text_color = player.second.m_empire_color; + } + server.PushChatMessage(data, sender->PlayerName(), text_color, timestamp); + } - if (receiver == Networking::INVALID_PLAYER_ID) { // the receiver is everyone (except the sender) - for (ServerNetworking::const_established_iterator it = server.m_networking.established_begin(); it != server.m_networking.established_end(); ++it) { - if ((*it)->PlayerID() != sender->PlayerID()) - (*it)->SendMessage(ServerPlayerChatMessage(sender->PlayerID(), data)); + if (recipients.empty()) { // the receiver is everyone + for (auto it = server.m_networking.established_begin(); it != server.m_networking.established_end(); ++it) { + (*it)->SendMessage(ServerPlayerChatMessage(sender->PlayerID(), timestamp, data, pm)); } } else { - ServerNetworking::const_established_iterator it = server.m_networking.GetPlayer(receiver); - if (it != server.m_networking.established_end()) - (*it)->SendMessage(ServerPlayerChatMessage(sender->PlayerID(), data)); + for (auto it = server.m_networking.established_begin(); it != server.m_networking.established_end(); ++it) { + if (recipients.find((*it)->PlayerID()) != recipients.end()) + (*it)->SendMessage(ServerPlayerChatMessage(sender->PlayerID(), timestamp, data, pm)); + } } return discard_event(); @@ -1013,7 +1873,7 @@ sc::result MPLobby::react(const StartMPGame& msg) { } else { // Load game... - std::string save_filename = (GetSaveDir() / m_lobby_data->m_save_game).string(); + std::string save_filename = (GetServerSaveDir() / m_lobby_data->m_save_game).string(); try { LoadGame(save_filename, *m_server_save_game_data, @@ -1033,7 +1893,7 @@ sc::result MPLobby::react(const StartMPGame& msg) { DebugLogger(FSM) << "Seeding with loaded galaxy seed: " << server.m_galaxy_setup_data.m_seed << " interpreted as actual seed: " << seed; Seed(seed); - } catch (const std::exception&) { + } catch (...) { SendMessageToAllPlayers(ErrorMessage(UserStringNop("UNABLE_TO_READ_SAVE_FILE"), true)); return discard_event(); } @@ -1080,11 +1940,17 @@ sc::result MPLobby::react(const HostSPGame& msg) { } sc::result MPLobby::react(const ShutdownServer& msg) { - TraceLogger(FSM) << "(ServerFSM) PlayingGame.ShutdownServer"; + TraceLogger(FSM) << "(ServerFSM) MPLobby.ShutdownServer"; return transit(); } +sc::result MPLobby::react(const Hostless& msg) { + TraceLogger(FSM) << "(ServerFSM) MPLobby.Hostless"; + + return discard_event(); +} + sc::result MPLobby::react(const Error& msg) { auto fatal = HandleErrorMessage(msg, Server()); if (fatal) @@ -1092,7 +1958,6 @@ sc::result MPLobby::react(const Error& msg) { return discard_event(); } - //////////////////////////////////////////////////////////// // WaitingForSPGameJoiners //////////////////////////////////////////////////////////// @@ -1183,17 +2048,22 @@ WaitingForSPGameJoiners::WaitingForSPGameJoiners(my_context c) : m_expected_ai_names_and_ids.clear(); for (const auto& player_data : players) { if (player_data.m_client_type == Networking::CLIENT_TYPE_AI_PLAYER) - m_expected_ai_names_and_ids.insert(std::make_pair(player_data.m_player_name, player_data.m_player_id)); + m_expected_ai_names_and_ids.insert({player_data.m_player_name, player_data.m_player_id}); } server.CreateAIClients(players, m_single_player_setup_data->m_ai_aggr); // also disconnects any currently-connected AI clients server.InitializePython(); - if (server.m_python_server.IsPythonRunning() && m_single_player_setup_data->m_new_game) { - // For SP game start inializaing while waiting for AI callbacks. - DebugLogger(FSM) << "Initializing new SP game..."; - server.NewSPGameInit(*m_single_player_setup_data); + // if Python fails to initialize, don't bother initializing SP game. + // Still check start conditions, which will abort the server after all + // the expected players have joined if Python is (still) not initialized. + if (server.m_python_server.IsPythonRunning()) { + if (m_single_player_setup_data->m_new_game) { + // For new SP game start inializaing while waiting for AI callbacks. + DebugLogger(FSM) << "Initializing new SP game..."; + server.NewSPGameInit(*m_single_player_setup_data); + } } // force immediate check if all expected AIs are present, so that the FSM @@ -1214,7 +2084,8 @@ sc::result WaitingForSPGameJoiners::react(const JoinGame& msg) { std::string player_name("Default_Player_Name_in_WaitingForSPGameJoiners::react(const JoinGame& msg)"); Networking::ClientType client_type(Networking::INVALID_CLIENT_TYPE); std::string client_version_string; - ExtractJoinGameMessageData(message, player_name, client_type, client_version_string); + boost::uuids::uuid cookie; + ExtractJoinGameMessageData(message, player_name, client_type, client_version_string, cookie); // is this an AI? if (client_type == Networking::CLIENT_TYPE_AI_PLAYER) { @@ -1228,8 +2099,9 @@ sc::result WaitingForSPGameJoiners::react(const JoinGame& msg) { // expected player // let the networking system know what socket this player is on player_connection->EstablishPlayer(expected_it->second, player_name, client_type, client_version_string); - player_connection->SendMessage(ContentCheckSumMessage()); - player_connection->SendMessage(JoinAckMessage(expected_it->second)); + player_connection->SendMessage(JoinAckMessage(expected_it->second, boost::uuids::nil_uuid())); + if (!GetOptionsDB().Get("skip-checksum")) + player_connection->SendMessage(ContentCheckSumMessage()); // Inform AI of logging configuration. player_connection->SendMessage( @@ -1252,8 +2124,9 @@ sc::result WaitingForSPGameJoiners::react(const JoinGame& msg) { // unexpected but welcome human player int host_id = server.Networking().HostPlayerID(); player_connection->EstablishPlayer(host_id, player_name, client_type, client_version_string); - player_connection->SendMessage(ContentCheckSumMessage()); - player_connection->SendMessage(JoinAckMessage(host_id)); + player_connection->SendMessage(JoinAckMessage(host_id, boost::uuids::nil_uuid())); + if (!GetOptionsDB().Get("skip-checksum")) + player_connection->SendMessage(ContentCheckSumMessage()); DebugLogger(FSM) << "Initializing new SP game..."; server.NewSPGameInit(*m_single_player_setup_data); @@ -1283,7 +2156,7 @@ sc::result WaitingForSPGameJoiners::react(const CheckStartConditions& u) { if (m_single_player_setup_data->m_new_game) { DebugLogger(FSM) << "Verify AIs SP game..."; if (server.VerifySPGameAIs(*m_single_player_setup_data)) - server. SendNewGameStartMessages(); + server.SendNewGameStartMessages(); } else { DebugLogger(FSM) << "Loading SP game save file: " << m_single_player_setup_data->m_filename; @@ -1292,7 +2165,7 @@ sc::result WaitingForSPGameJoiners::react(const CheckStartConditions& u) { m_player_save_game_data, GetUniverse(), Empires(), GetSpeciesManager(), GetCombatLogManager(), server.m_galaxy_setup_data); - } catch (const std::exception&) { + } catch (...) { SendMessageToHost(ErrorMessage(UserStringNop("UNABLE_TO_READ_SAVE_FILE"), true)); return transit(); } @@ -1331,8 +2204,7 @@ WaitingForMPGameJoiners::WaitingForMPGameJoiners(my_context c) : my_base(c), m_lobby_data(context().m_lobby_data), m_player_save_game_data(context().m_player_save_game_data), - m_server_save_game_data(context().m_server_save_game_data), - m_num_expected_players(0) + m_server_save_game_data(context().m_server_save_game_data) { TraceLogger(FSM) << "(ServerFSM) WaitingForMPGameJoiners"; context().m_lobby_data.reset(); @@ -1340,8 +2212,6 @@ WaitingForMPGameJoiners::WaitingForMPGameJoiners(my_context c) : context().m_server_save_game_data.reset(); ServerApp& server = Server(); - m_num_expected_players = m_lobby_data->m_players.size(); - std::vector player_setup_data; m_expected_ai_player_names.clear(); @@ -1367,28 +2237,31 @@ WaitingForMPGameJoiners::~WaitingForMPGameJoiners() sc::result WaitingForMPGameJoiners::react(const JoinGame& msg) { TraceLogger(FSM) << "(ServerFSM) WaitingForMPGameJoiners.JoinGame"; ServerApp& server = Server(); + // due disconnection could cause `delete this` in transition + // to MPLobby or ShuttingDownServer gets context before disconnection + ServerFSM& fsm = context(); const Message& message = msg.m_message; const PlayerConnectionPtr& player_connection = msg.m_player_connection; std::string player_name("Default_Player_Name_in_WaitingForMPGameJoiners::react(const JoinGame& msg)"); Networking::ClientType client_type(Networking::INVALID_CLIENT_TYPE); std::string client_version_string; - ExtractJoinGameMessageData(message, player_name, client_type, client_version_string); - - int player_id = server.m_networking.NewPlayerID(); + boost::uuids::uuid cookie; + ExtractJoinGameMessageData(message, player_name, client_type, client_version_string, cookie); // is this an AI? if (client_type == Networking::CLIENT_TYPE_AI_PLAYER) { // verify that player name was expected - if (m_expected_ai_player_names.find(player_name) == m_expected_ai_player_names.end()) { + if (!m_expected_ai_player_names.count(player_name)) { // unexpected ai player ErrorLogger(FSM) << "WaitingForMPGameJoiners::react(const JoinGame& msg) received join game message for player \"" << player_name << "\" which was not an expected AI player name. Terminating connection."; server.m_networking.Disconnect(player_connection); } else { // expected player // let the networking system know what socket this player is on + int player_id = server.m_networking.NewPlayerID(); player_connection->EstablishPlayer(player_id, player_name, client_type, client_version_string); - player_connection->SendMessage(JoinAckMessage(player_id)); + player_connection->SendMessage(JoinAckMessage(player_id, boost::uuids::nil_uuid())); // Inform AI of logging configuration. player_connection->SendMessage( @@ -1399,17 +2272,96 @@ sc::result WaitingForMPGameJoiners::react(const JoinGame& msg) { } } else if (client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) { - // verify that there is room left for this player - int already_connected_players = m_expected_ai_player_names.size() + server.m_networking.NumEstablishedPlayers(); - if (already_connected_players >= m_num_expected_players) { - // too many human players - ErrorLogger(FSM) << "WaitingForSPGameJoiners.JoinGame : A human player attempted to join the game but there was not enough room. Terminating connection."; - server.m_networking.Disconnect(player_connection); + // if we don't need to authenticate player we got default roles here + Networking::AuthRoles roles; + bool authenticated; + + DebugLogger() << "WaitingForMPGameJoiners.JoinGame Try to login player " << player_name << " with cookie: " << cookie; + if (server.Networking().CheckCookie(cookie, player_name, roles, authenticated)) { + // if player has correct and non-expired cookies simply establish him + player_connection->SetCookie(cookie); + if (authenticated) + player_connection->SetAuthenticated(); + + // drop other connection with same name before checks for expected players + std::list to_disconnect; + for (auto it = server.m_networking.established_begin(); + it != server.m_networking.established_end(); ++it) + { + if ((*it)->PlayerName() == player_name && player_connection != (*it)) { + (*it)->SendMessage(ErrorMessage(UserString("ERROR_CONNECTION_WAS_REPLACED"), true)); + to_disconnect.push_back(*it); + } + } + for (const auto& conn : to_disconnect) + { server.Networking().Disconnect(conn); } } else { - // expected human player - player_connection->EstablishPlayer(player_id, player_name, client_type, client_version_string); - player_connection->SendMessage(JoinAckMessage(player_id)); + if (server.IsAuthRequiredOrFillRoles(player_name, roles)) { + // send authentication request + player_connection->AwaitPlayer(client_type, client_version_string); + player_connection->SendMessage(AuthRequestMessage(player_name, "PLAIN-TEXT")); + return discard_event(); + } + } + + std::string original_player_name = player_name; + + // Remove AI prefix to distinguish Human from AI. + std::string ai_prefix = UserString("AI_PLAYER") + "_"; + while (player_name.compare(0, ai_prefix.size(), ai_prefix) == 0) + player_name.erase(0, ai_prefix.size()); + if (player_name.empty()) + player_name = "_"; + + std::string new_player_name = player_name; + + // Try numeric suffixes to get unique name which isn't equal to other player or empire name + // or registered for authentication. + bool collision = true; + std::size_t t = 1; + while (collision && + t <= m_lobby_data->m_players.size() + server.Networking().GetCookiesSize() + 1) + { + collision = false; + roles.Clear(); + if (!server.IsAvailableName(new_player_name) || server.IsAuthRequiredOrFillRoles(new_player_name, roles)) { + collision = true; + } else { + for (std::pair& plr : m_lobby_data->m_players) { + if (plr.second.m_empire_name == new_player_name) { + collision = true; + break; + } + } + } + + if (collision) + new_player_name = player_name + std::to_string(++t); // start alternative names from 2 + } + + // There is a small chance of unsolveable collision in case of sequential player names are + // registered for authentication + if (collision) { + ErrorLogger(FSM) << "WaitingForMPGameJoiners::react(const JoinGame& msg): couldn't find free player name for \"" << original_player_name << "\""; + player_connection->SendMessage(ErrorMessage(str(FlexibleFormat(UserString("ERROR_PLAYER_NAME_ALREADY_USED")) % original_player_name), + true)); + server.Networking().Disconnect(player_connection); + return discard_event(); + } + + if (!player_connection->HasAuthRole(Networking::ROLE_CLIENT_TYPE_PLAYER)) { + if (player_connection->HasAuthRole(Networking::ROLE_CLIENT_TYPE_OBSERVER)) + client_type = Networking::CLIENT_TYPE_HUMAN_OBSERVER; + else if (player_connection->HasAuthRole(Networking::ROLE_CLIENT_TYPE_MODERATOR)) + client_type = Networking::CLIENT_TYPE_HUMAN_MODERATOR; + else { + player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_CLIENT_TYPE_NOT_ALLOWED"), true)); + server.Networking().Disconnect(player_connection); + return discard_event(); + } } + + fsm.EstablishPlayer(player_connection, new_player_name, client_type, client_version_string, roles); } else { ErrorLogger(FSM) << "WaitingForMPGameJoiners::react(const JoinGame& msg): Received JoinGame message with invalid client type: " << client_type; return discard_event(); @@ -1423,11 +2375,83 @@ sc::result WaitingForMPGameJoiners::react(const JoinGame& msg) { return discard_event(); } +sc::result WaitingForMPGameJoiners::react(const AuthResponse& msg) { + TraceLogger(FSM) << "(ServerFSM) WaitingForMPGameJoiners.AuthResponse"; + ServerApp& server = Server(); + // due disconnection could cause `delete this` in transition + // to MPLobby or ShuttingDownServer gets context before disconnection + ServerFSM& fsm = context(); + const Message& message = msg.m_message; + const PlayerConnectionPtr& player_connection = msg.m_player_connection; + + std::string player_name; + std::string auth; + ExtractAuthResponseMessageData(message, player_name, auth); + + Networking::AuthRoles roles; + + if (!server.IsAuthSuccessAndFillRoles(player_name, auth, roles)) { + // wrong password + player_connection->SendMessage(ErrorMessage(str(FlexibleFormat(UserString("ERROR_WRONG_PASSWORD")) % player_name), + true)); + server.Networking().Disconnect(player_connection); + return discard_event(); + } + + player_connection->SetAuthenticated(); + Networking::ClientType client_type = player_connection->GetClientType(); + + if (client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) { + // drop other connection with same name before checks for expected players + std::list to_disconnect; + for (ServerNetworking::const_established_iterator it = server.m_networking.established_begin(); + it != server.m_networking.established_end(); ++it) + { + if ((*it)->PlayerName() == player_name && player_connection != (*it)) { + (*it)->SendMessage(ErrorMessage(UserString("ERROR_CONNECTION_WAS_REPLACED"), true)); + to_disconnect.push_back(*it); + } + } + for (const auto& conn : to_disconnect) + { server.Networking().Disconnect(conn); } + + // expected human player + if (!player_connection->HasAuthRole(Networking::ROLE_CLIENT_TYPE_PLAYER)) { + if (player_connection->HasAuthRole(Networking::ROLE_CLIENT_TYPE_OBSERVER)) + client_type = Networking::CLIENT_TYPE_HUMAN_OBSERVER; + else if (player_connection->HasAuthRole(Networking::ROLE_CLIENT_TYPE_MODERATOR)) + client_type = Networking::CLIENT_TYPE_HUMAN_MODERATOR; + else { + player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_CLIENT_TYPE_NOT_ALLOWED"), true)); + server.Networking().Disconnect(player_connection); + return discard_event(); + } + } + + fsm.EstablishPlayer(player_connection, + player_name, + client_type, + player_connection->ClientVersionString(), + roles); + } else { + // non-human player + ErrorLogger(FSM) << "WaitingForMPGameJoiners.AuthResponse : A non-human player attempted to join the game."; + server.m_networking.Disconnect(player_connection); + } + + // force immediate check if all expected AIs are present, so that the FSM + // won't get stuck in this state waiting for JoinGame messages that will + // never come since no other AIs are left to join + post_event(CheckStartConditions()); + + return discard_event(); +} + sc::result WaitingForMPGameJoiners::react(const CheckStartConditions& u) { TraceLogger(FSM) << "(ServerFSM) WaitingForMPGameJoiners.CheckStartConditions"; ServerApp& server = Server(); - if (static_cast(server.m_networking.NumEstablishedPlayers()) == m_num_expected_players) { + if (m_expected_ai_player_names.empty()) { if (!server.m_python_server.IsPythonRunning()) { post_event(ShutdownServer()); return discard_event(); @@ -1446,6 +2470,17 @@ sc::result WaitingForMPGameJoiners::react(const CheckStartConditions& u) { return discard_event(); } +sc::result WaitingForMPGameJoiners::react(const Hostless& msg) { + TraceLogger(FSM) << "(ServerFSM) WaitingForMPGameJoiners.Hostless"; + ServerApp& server = Server(); + + // Remove the ai processes. They either all acknowledged the shutdown and are free or were all killed. + server.m_ai_client_processes.clear(); + + // Don't DisconnectAll. It cause segfault here. + return transit(); +} + sc::result WaitingForMPGameJoiners::react(const ShutdownServer& msg) { TraceLogger(FSM) << "(ServerFSM) WaitingForMPGameJoiners.ShutdownServer"; return transit(); @@ -1462,11 +2497,42 @@ sc::result WaitingForMPGameJoiners::react(const Error& msg) { // PlayingGame //////////////////////////////////////////////////////////// PlayingGame::PlayingGame(my_context c) : - my_base(c) -{ TraceLogger(FSM) << "(ServerFSM) PlayingGame"; } + my_base(c), + m_turn_timeout(Server().m_io_context), + m_start(std::chrono::high_resolution_clock::now()) +{ + TraceLogger(FSM) << "(ServerFSM) PlayingGame"; + + if (!GetOptionsDB().Get("network.server.turn-timeout.first-turn-time").empty()) { + // Set first turn advance to absolute time point + try { + m_turn_timeout.expires_at(boost::posix_time::time_from_string(GetOptionsDB().Get("network.server.turn-timeout.first-turn-time"))); + m_turn_timeout.async_wait(boost::bind(&PlayingGame::TurnTimedoutHandler, + this, + boost::asio::placeholders::error)); + return; + } catch (...) { + WarnLogger(FSM) << "(ServerFSM) PlayingGame: Cann't parse first turn time: " + << GetOptionsDB().Get("network.server.turn-timeout.first-turn-time"); + } + } + + if (GetOptionsDB().Get("network.server.turn-timeout.max-interval") > 0 && !Server().IsHaveWinner()) { + // Set turn advance after time interval + m_turn_timeout.expires_from_now(boost::posix_time::seconds(GetOptionsDB().Get("network.server.turn-timeout.max-interval"))); + m_turn_timeout.async_wait(boost::bind(&PlayingGame::TurnTimedoutHandler, + this, + boost::asio::placeholders::error)); + } +} + +PlayingGame::~PlayingGame() { + auto duration = std::chrono::high_resolution_clock::now() - m_start; + DebugLogger(FSM) << "PlayingGame time: " << std::chrono::duration_cast(duration).count() << " s"; -PlayingGame::~PlayingGame() -{ TraceLogger(FSM) << "(ServerFSM) ~PlayingGame"; } + TraceLogger(FSM) << "(ServerFSM) ~PlayingGame"; + m_turn_timeout.cancel(); +} sc::result PlayingGame::react(const PlayerChat& msg) { TraceLogger(FSM) << "(ServerFSM) PlayingGame.PlayerChat"; @@ -1475,17 +2541,30 @@ sc::result PlayingGame::react(const PlayerChat& msg) { const PlayerConnectionPtr& sender = msg.m_player_connection; std::string data; - int receiver; - ExtractPlayerChatMessageData(message, receiver, data); + std::set recipients; + bool pm; + ExtractPlayerChatMessageData(message, recipients, data, pm); + + boost::posix_time::ptime timestamp = boost::posix_time::second_clock::universal_time(); - for (ServerNetworking::const_established_iterator it = server.m_networking.established_begin(); + if (recipients.empty() && sender->GetClientType() != Networking::CLIENT_TYPE_AI_PLAYER) + { + GG::Clr text_color(255, 255, 255, 255); + if (auto empire = GetEmpire(sender->PlayerID())) + text_color = empire->Color(); + + server.PushChatMessage(data, sender->PlayerName(), text_color, timestamp); + } + + for (auto it = server.m_networking.established_begin(); it != server.m_networking.established_end(); ++it) { - if (receiver == Networking::INVALID_PLAYER_ID || - receiver == (*it)->PlayerID()) + // send message to: (1) specified recipients, (2) all if no recipient specified, (3) sender + if (recipients.find((*it)->PlayerID()) != recipients.end() || recipients.empty() + || ((*it)->PlayerID() == sender->PlayerID())) { - (*it)->SendMessage(ServerPlayerChatMessage(sender->PlayerID(), - data)); + (*it)->SendMessage(ServerPlayerChatMessage(sender->PlayerID(), timestamp, + data, pm)); } } return discard_event(); @@ -1526,7 +2605,7 @@ sc::result PlayingGame::react(const ModeratorAct& msg) { action->Execute(); // update player(s) of changed gamestate as result of action - bool use_binary_serialization = sender->ClientVersionStringMatchesThisServer(); + bool use_binary_serialization = sender->IsBinarySerializationUsed(); sender->SendMessage(TurnProgressMessage(Message::DOWNLOADING)); sender->SendMessage(TurnPartialUpdateMessage(server.PlayerEmpireID(player_id), GetUniverse(), use_binary_serialization)); @@ -1540,15 +2619,244 @@ sc::result PlayingGame::react(const ModeratorAct& msg) { sc::result PlayingGame::react(const ShutdownServer& msg) { TraceLogger(FSM) << "(ServerFSM) PlayingGame.ShutdownServer"; + ServerApp& server = Server(); + + if (server.IsHostless() && + GetOptionsDB().Get("save.auto.hostless.enabled") && + GetOptionsDB().Get("save.auto.exit.enabled")) + { + // save game on exit + std::string save_filename = GetAutoSaveFileName(server.CurrentTurn()); + ServerSaveGameData server_data(server.CurrentTurn()); + int bytes_written = 0; + // save game... + try { + bytes_written = SaveGame(save_filename, server_data, server.GetPlayerSaveGameData(), + GetUniverse(), Empires(), GetSpeciesManager(), + GetCombatLogManager(), server.m_galaxy_setup_data, + !server.m_single_player_game); + } catch (const std::exception& error) { + ErrorLogger(FSM) << "While saving, catch std::exception: " << error.what(); + SendMessageToAllPlayers(ErrorMessage(UserStringNop("UNABLE_TO_WRITE_SAVE_FILE"), false)); + } + + // inform players that save is complete + SendMessageToAllPlayers(ServerSaveGameCompleteMessage(save_filename, bytes_written)); + } + return transit(); } +sc::result PlayingGame::react(const Hostless& msg) { + TraceLogger(FSM) << "(ServerFSM) PlayingGame.Hostless"; + + ServerApp& server = Server(); + + // Remove the ai processes. They either all acknowledged the shutdown and are free or were all killed. + server.m_ai_client_processes.clear(); + + // Don't DisconnectAll. It cause segfault here. + + return transit(); +} + sc::result PlayingGame::react(const RequestCombatLogs& msg) { DebugLogger(FSM) << "(ServerFSM) PlayingGame::RequestCombatLogs message received"; Server().UpdateCombatLogs(msg.m_message, msg.m_player_connection); return discard_event(); } +void PlayingGame::EstablishPlayer(const PlayerConnectionPtr& player_connection, + const std::string& player_name, + Networking::ClientType client_type, + const std::string& client_version_string, + const Networking::AuthRoles& roles) +{ + ServerApp& server = Server(); + // due disconnection could cause `delete this` in transition + // to MPLobby or ShuttingDownServer gets context before disconnection + ServerFSM& fsm = context(); + + if (fsm.EstablishPlayer(player_connection, + player_name, + client_type, + player_connection->ClientVersionString(), + roles)) + { + // it possible to be not in PlayingGame here + bool is_in_mplobby = false; + std::stringstream ss; + for (auto leaf_state_it = fsm.state_begin(); leaf_state_it != fsm.state_end();) { + // The following use of typeid assumes that + // BOOST_STATECHART_USE_NATIVE_RTTI is defined + const auto& leaf_state = *leaf_state_it; + ss << typeid(leaf_state).name(); + if (typeid(leaf_state) == typeid(MPLobby)) + is_in_mplobby = true; + ++leaf_state_it; + if (leaf_state_it != fsm.state_end()) + ss << ", "; + } + ss << "]"; + DebugLogger(FSM) << "(ServerFSM) PlayingGame.EstablishPlayer at " << ss.str(); + + if (!is_in_mplobby) { + if (client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER || + client_type == Networking::CLIENT_TYPE_HUMAN_MODERATOR) + { + // send playing game + server.AddObserverPlayerIntoGame(player_connection); + } else if (client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) { + // previous connection was dropped + // set empire link to new connection by name + // send playing game + server.AddPlayerIntoGame(player_connection, ALL_EMPIRES); + } + // In both cases update ingame lobby + fsm.UpdateIngameLobby(); + + // send timeout data + if (GetOptionsDB().Get("network.server.turn-timeout.max-interval") > 0 && !Server().IsHaveWinner()) { + auto remaining = m_turn_timeout.expires_from_now(); + player_connection->SendMessage(TurnTimeoutMessage(remaining.total_seconds())); + } else { + player_connection->SendMessage(TurnTimeoutMessage(0)); + } + } + } +} + +sc::result PlayingGame::react(const JoinGame& msg) { + DebugLogger(FSM) << "(ServerFSM) PlayingGame::JoinGame message received"; + ServerApp& server = Server(); + const Message& message = msg.m_message; + const PlayerConnectionPtr& player_connection = msg.m_player_connection; + + std::string player_name; + Networking::ClientType client_type; + std::string client_version_string; + boost::uuids::uuid cookie; + ExtractJoinGameMessageData(message, player_name, client_type, client_version_string, cookie); + + Networking::AuthRoles roles; + bool authenticated; + + DebugLogger() << "PlayingGame.JoinGame Try to login player " << player_name << " with cookie: " << cookie; + if (server.Networking().CheckCookie(cookie, player_name, roles, authenticated)) { + // if player have correct and non-expired cookies simply establish him + player_connection->SetCookie(cookie); + if (authenticated) + player_connection->SetAuthenticated(); + } else { + if (server.IsAuthRequiredOrFillRoles(player_name, roles)) { + // send authentication request + player_connection->AwaitPlayer(client_type, client_version_string); + player_connection->SendMessage(AuthRequestMessage(player_name, "PLAIN-TEXT")); + return discard_event(); + } + + std::string original_player_name = player_name; + // Remove AI prefix to distinguish Human from AI. + std::string ai_prefix = UserString("AI_PLAYER") + "_"; + while (player_name.compare(0, ai_prefix.size(), ai_prefix) == 0) + player_name.erase(0, ai_prefix.size()); + if (player_name.empty()) + player_name = "_"; + + std::string new_player_name = player_name; + + bool collision = true; + std::size_t t = 1; + while (collision && + t <= server.Networking().NumEstablishedPlayers() + server.Networking().GetCookiesSize() + 1) + { + collision = false; + roles.Clear(); + if (!server.IsAvailableName(new_player_name) || server.IsAuthRequiredOrFillRoles(new_player_name, roles)) { + collision = true; + } else { + for (auto& plr : server.Empires() ) { + if (plr.second->Name() == new_player_name) { + collision = true; + break; + } + } + } + + if (collision) + new_player_name = player_name + std::to_string(++t); // start alternative names from 2 + } + + if (collision) { + WarnLogger() << "Reject player " << original_player_name; + player_connection->SendMessage(ErrorMessage(str(FlexibleFormat(UserString("ERROR_PLAYER_NAME_ALREADY_USED")) % original_player_name), + true)); + server.Networking().Disconnect(player_connection); + return discard_event(); + } + + player_name = std::move(new_player_name); + } + + EstablishPlayer(player_connection, player_name, client_type, + client_version_string, roles); + + return discard_event(); +} + +sc::result PlayingGame::react(const AuthResponse& msg) { + DebugLogger(FSM) << "(ServerFSM) PlayingGame::AuthResponse message received"; + ServerApp& server = Server(); + const Message& message = msg.m_message; + const PlayerConnectionPtr& player_connection = msg.m_player_connection; + + std::string player_name; + std::string auth; + ExtractAuthResponseMessageData(message, player_name, auth); + + Networking::AuthRoles roles; + + if (!server.IsAuthSuccessAndFillRoles(player_name, auth, roles)) { + // wrong password + player_connection->SendMessage(ErrorMessage(str(FlexibleFormat(UserString("ERROR_WRONG_PASSWORD")) % player_name), + true)); + server.Networking().Disconnect(player_connection); + return discard_event(); + } + + player_connection->SetAuthenticated(); + Networking::ClientType client_type = player_connection->GetClientType(); + + EstablishPlayer(player_connection, player_name, client_type, + player_connection->ClientVersionString(), roles); + + return discard_event(); +} + +sc::result PlayingGame::react(const EliminateSelf& msg) { + DebugLogger(FSM) << "(ServerFSM) PlayingGame::EliminateSelf message received"; + ServerApp& server = Server(); + const PlayerConnectionPtr& player_connection = msg.m_player_connection; + + if (player_connection->GetClientType() != Networking::CLIENT_TYPE_HUMAN_PLAYER + && player_connection->GetClientType() != Networking::CLIENT_TYPE_AI_PLAYER) + { + WarnLogger(FSM) << "(ServerFSM) PlayingGame::EliminateSelf non-player connection " << player_connection->PlayerID(); + player_connection->SendMessage(ErrorMessage(UserStringNop("ERROR_NONPLAYER_CANNOT_CONCEDE"), true)); + server.Networking().Disconnect(player_connection); + return discard_event(); + } + + if (!server.EliminatePlayer(player_connection)) { + WarnLogger(FSM) << "(ServerFSM) PlayingGame::EliminateSelf player " << player_connection->PlayerID() << " not allowed to concede"; + return discard_event(); + } + + server.Networking().Disconnect(player_connection); + + return discard_event(); +} + sc::result PlayingGame::react(const Error& msg) { auto fatal = HandleErrorMessage(msg, Server()); if (fatal) { @@ -1558,17 +2866,105 @@ sc::result PlayingGame::react(const Error& msg) { return discard_event(); } +sc::result PlayingGame::react(const LobbyUpdate& msg) { + TraceLogger(FSM) << "(ServerFSM) PlayingGame.LobbyUpdate"; + ServerApp& server = Server(); + const PlayerConnectionPtr& sender = msg.m_player_connection; + const Message& message = msg.m_message; + + MultiplayerLobbyData incoming_lobby_data; + ExtractLobbyUpdateMessageData(message, incoming_lobby_data); + + // try to add the player into the game if he choose empire + for (const auto& player : incoming_lobby_data.m_players) { + if (player.first == sender->PlayerID() && player.second.m_save_game_empire_id != ALL_EMPIRES) { + int empire_id = server.AddPlayerIntoGame(sender, player.second.m_save_game_empire_id); + if (empire_id != ALL_EMPIRES) { + context().UpdateIngameLobby(); + return discard_event(); + } + } + } + + // ignore data + sender->SendMessage(ErrorMessage(UserStringNop("SERVER_ALREADY_PLAYING_GAME"))); + server.Networking().Disconnect(sender); + + return discard_event(); +} + +void PlayingGame::TurnTimedoutHandler(const boost::system::error_code& error) { + DebugLogger(FSM) << "(ServerFSM) PlayingGame::TurnTimedoutHandler"; + + if (error) { + DebugLogger() << "Turn timed out cancelled"; + return; + } + + if (GetOptionsDB().Get("network.server.turn-timeout.fixed-interval") && + GetOptionsDB().Get("network.server.turn-timeout.max-interval") > 0 && + !Server().IsHaveWinner()) + { + auto turn_expired_time = m_turn_timeout.expires_at(); + turn_expired_time += boost::posix_time::seconds(GetOptionsDB().Get("network.server.turn-timeout.max-interval")); + m_turn_timeout.expires_at(turn_expired_time); + m_turn_timeout.async_wait(boost::bind(&PlayingGame::TurnTimedoutHandler, + this, + boost::asio::placeholders::error)); + } + Server().ExpireTurn(); + // check if AI players made their orders and advance turn + Server().m_fsm->process_event(CheckTurnEndConditions()); +} + //////////////////////////////////////////////////////////// // WaitingForTurnEnd //////////////////////////////////////////////////////////// WaitingForTurnEnd::WaitingForTurnEnd(my_context c) : - my_base(c) + my_base(c), + m_timeout(Server().m_io_context), + m_start(std::chrono::high_resolution_clock::now()) { TraceLogger(FSM) << "(ServerFSM) WaitingForTurnEnd"; + if (GetOptionsDB().Get("save.auto.interval") > 0) { +#if BOOST_VERSION >= 106600 + m_timeout.expires_after(std::chrono::seconds(GetOptionsDB().Get("save.auto.interval"))); +#else + m_timeout.expires_from_now(std::chrono::seconds(GetOptionsDB().Get("save.auto.interval"))); +#endif + m_timeout.async_wait(boost::bind(&WaitingForTurnEnd::SaveTimedoutHandler, + this, + boost::asio::placeholders::error)); + } + + auto& playing_game = context(); + + // reset turn timer if there no fixed interval + if (!GetOptionsDB().Get("network.server.turn-timeout.fixed-interval")) { + playing_game.m_turn_timeout.cancel(); + if (GetOptionsDB().Get("network.server.turn-timeout.max-interval") > 0 && !Server().IsHaveWinner()) { + playing_game.m_turn_timeout.expires_from_now(boost::posix_time::seconds(GetOptionsDB().Get("network.server.turn-timeout.max-interval"))); + playing_game.m_turn_timeout.async_wait(boost::bind(&PlayingGame::TurnTimedoutHandler, + &playing_game, + boost::asio::placeholders::error)); + } + } + + if (GetOptionsDB().Get("network.server.turn-timeout.max-interval") > 0 && !Server().IsHaveWinner()) { + auto remaining = playing_game.m_turn_timeout.expires_from_now(); + Server().Networking().SendMessageAll(TurnTimeoutMessage(remaining.total_seconds())); + } else { + Server().Networking().SendMessageAll(TurnTimeoutMessage(0)); + } } -WaitingForTurnEnd::~WaitingForTurnEnd() -{ TraceLogger(FSM) << "(ServerFSM) ~WaitingForTurnEnd"; } +WaitingForTurnEnd::~WaitingForTurnEnd() { + auto duration = std::chrono::high_resolution_clock::now() - m_start; + DebugLogger(FSM) << "WaitingForTurnEnd time: " << std::chrono::duration_cast(duration).count() << " s"; + + TraceLogger(FSM) << "(ServerFSM) ~WaitingForTurnEnd"; + m_timeout.cancel(); +} sc::result WaitingForTurnEnd::react(const TurnOrders& msg) { TraceLogger(FSM) << "(ServerFSM) WaitingForTurnEnd.TurnOrders"; @@ -1576,12 +2972,27 @@ sc::result WaitingForTurnEnd::react(const TurnOrders& msg) { const Message& message = msg.m_message; const PlayerConnectionPtr& sender = msg.m_player_connection; - auto order_set = std::unique_ptr(new OrderSet); - ExtractTurnOrdersMessageData(message, *order_set); + auto order_set = std::make_shared(); + auto ui_data = std::make_shared(); + bool ui_data_available = false; + std::string save_state_string; + bool save_state_string_available = false; + try { + ExtractTurnOrdersMessageData(message, *order_set, ui_data_available, *ui_data, save_state_string_available, save_state_string); + } catch (const std::exception& err) { + // incorrect turn orders. disconnect player with wrong client. + sender->SendMessage(ErrorMessage(UserStringNop("ERROR_INCOMPATIBLE_VERSION"))); + server.Networking().Disconnect(sender); + return discard_event(); + } int player_id = sender->PlayerID(); Networking::ClientType client_type = sender->GetClientType(); + // ensure ui data availability flag is consistent with ui data + if (!ui_data_available) + ui_data.reset(); + if (client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER) { // observers cannot submit orders. ignore. ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnOrders&) received orders from player " @@ -1611,40 +3022,52 @@ sc::result WaitingForTurnEnd::react(const TurnOrders& msg) { client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) { // store empire orders and resume waiting for more - const Empire* empire = GetEmpire(server.PlayerEmpireID(player_id)); + Empire* empire = GetEmpire(server.PlayerEmpireID(player_id)); if (!empire) { ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnOrders&) couldn't get empire for player with id:" << player_id; sender->SendMessage(ErrorMessage(UserStringNop("EMPIRE_NOT_FOUND_CANT_HANDLE_ORDERS"), false)); return discard_event(); } + int empire_id = empire->EmpireID(); + if (empire->Eliminated()) { + ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnOrders&) received orders from player " << empire->PlayerName() << "(id: " + << player_id << ") who controls empire " << empire_id + << " but empire was eliminated"; + sender->SendMessage(ErrorMessage(UserStringNop("ORDERS_FOR_WRONG_EMPIRE"), false)); + return discard_event(); + } + for (const auto& id_and_order : *order_set) { auto& order = id_and_order.second; if (!order) { ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnOrders&) couldn't get order from order set!"; continue; } - if (empire->EmpireID() != order->EmpireID()) { + if (empire_id != order->EmpireID()) { ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnOrders&) received orders from player " << empire->PlayerName() << "(id: " - << player_id << ") who controls empire " << empire->EmpireID() + << player_id << ") who controls empire " << empire_id << " but those orders were for empire " << order->EmpireID() << ". Orders being ignored."; sender->SendMessage(ErrorMessage(UserStringNop("ORDERS_FOR_WRONG_EMPIRE"), false)); return discard_event(); } } - TraceLogger(FSM) << "WaitingForTurnEnd.TurnOrders : Received orders from player " << player_id; - - server.SetEmpireTurnOrders(empire->EmpireID(), std::move(order_set)); - } + DebugLogger(FSM) << "WaitingForTurnEnd.TurnOrders : Received orders from player " << player_id + << " for empire " << empire_id << " count of " << order_set->size(); + server.SetEmpireSaveGameData(empire_id, std::make_unique(sender->PlayerName(), empire_id, + order_set, ui_data, save_state_string, + client_type)); + empire->SetReady(true); - // notify other player that this player submitted orders - for (ServerNetworking::const_established_iterator player_it = server.m_networking.established_begin(); - player_it != server.m_networking.established_end(); ++player_it) - { - PlayerConnectionPtr player_ctn = *player_it; - player_ctn->SendMessage(PlayerStatusMessage(player_id, Message::WAITING)); + // notify other player that this empire submitted orders + for (auto player_it = server.m_networking.established_begin(); + player_it != server.m_networking.established_end(); ++player_it) + { + PlayerConnectionPtr player_ctn = *player_it; + player_ctn->SendMessage(PlayerStatusMessage(Message::WAITING, empire_id)); + } } // inform player who just submitted of their new status. Note: not sure why @@ -1668,178 +3091,231 @@ sc::result WaitingForTurnEnd::react(const TurnOrders& msg) { return discard_event(); } -sc::result WaitingForTurnEnd::react(const CheckTurnEndConditions& c) { - TraceLogger(FSM) << "(ServerFSM) WaitingForTurnEnd.CheckTurnEndConditions"; +sc::result WaitingForTurnEnd::react(const TurnPartialOrders& msg) { + TraceLogger(FSM) << "(ServerFSM) WaitingForTurnEnd.TurnPartialOrders"; ServerApp& server = Server(); - // is there a moderator in the game? If so, do nothing, as the turn does - // not proceed until the moderator orders it - if (server.m_networking.ModeratorsInGame()) - return discard_event(); + const Message& message = msg.m_message; + const PlayerConnectionPtr& sender = msg.m_player_connection; - // no moderator; wait for all player orders to be submitted before - // processing turn. - if (server.AllOrdersReceived()) { - // if all players have submitted orders, proceed to turn processing - TraceLogger(FSM) << "WaitingForTurnEnd.TurnOrders : All orders received."; - post_event(ProcessTurn()); - return transit(); + auto added = std::make_shared(); + std::set deleted; + try { + ExtractTurnPartialOrdersMessageData(message, *added, deleted); + } catch (const std::exception& err) { + // incorrect turn orders. disconnect player with wrong client. + sender->SendMessage(ErrorMessage(UserStringNop("ERROR_INCOMPATIBLE_VERSION"))); + server.Networking().Disconnect(sender); + return discard_event(); } - return discard_event(); -} - -//////////////////////////////////////////////////////////// -// WaitingForTurnEndIdle -//////////////////////////////////////////////////////////// -WaitingForTurnEndIdle::WaitingForTurnEndIdle(my_context c) : - my_base(c) -{ - TraceLogger(FSM) << "(ServerFSM) WaitingForTurnEndIdle"; -} - -WaitingForTurnEndIdle::~WaitingForTurnEndIdle() -{ TraceLogger(FSM) << "(ServerFSM) ~WaitingForTurnEndIdle"; } + int player_id = sender->PlayerID(); + Networking::ClientType client_type = sender->GetClientType(); -sc::result WaitingForTurnEndIdle::react(const SaveGameRequest& msg) { - TraceLogger(FSM) << "(ServerFSM) WaitingForTurnEndIdle.SaveGameRequest"; - ServerApp& server = Server(); - const Message& message = msg.m_message; - const PlayerConnectionPtr& player_connection = msg.m_player_connection; + if (client_type == Networking::CLIENT_TYPE_HUMAN_OBSERVER) { + // observers cannot submit orders. ignore. + ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnPartialOrders&) received orders from player " + << sender->PlayerName() + << "(player id: " << player_id << ") " + << "who is an observer and should not be sending orders. Orders being ignored."; + sender->SendMessage(ErrorMessage(UserStringNop("ORDERS_FOR_WRONG_EMPIRE"), false)); + return discard_event(); - if (!server.m_networking.PlayerIsHost(player_connection->PlayerID())) { - ErrorLogger(FSM) << "WaitingForTurnEndIdle.SaveGameRequest : Player #" << player_connection->PlayerID() - << " attempted to initiate a game save, but is not the host. Ignoring request connection."; - player_connection->SendMessage(ErrorMessage(UserStringNop("NON_HOST_SAVE_REQUEST_IGNORED"), false)); + } else if (client_type == Networking::INVALID_CLIENT_TYPE) { + // ??? lingering connection? shouldn't get to here. ignore. + ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnPartialOrders&) received orders from player " + << sender->PlayerName() + << "(player id: " << player_id << ") " + << "who has an invalid player type. The server is confused, and the orders being ignored."; + sender->SendMessage(ErrorMessage(UserStringNop("ORDERS_FOR_WRONG_EMPIRE"), false)); return discard_event(); - } - context().m_save_filename = message.Text(); // store requested save file name in Base state context so that sibling state can retreive it + } else if (client_type == Networking::CLIENT_TYPE_AI_PLAYER || + client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) + { + // store empire orders and resume waiting for more + const Empire* empire = GetEmpire(server.PlayerEmpireID(player_id)); + if (!empire) { + ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnPartialOrders&) couldn't get empire for player with id:" << player_id; + sender->SendMessage(ErrorMessage(UserStringNop("EMPIRE_NOT_FOUND_CANT_HANDLE_ORDERS"), false)); + return discard_event(); + } - return transit(); -} + int empire_id = empire->EmpireID(); + if (empire->Eliminated()) { + ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnPartialOrders&) received orders from player " << empire->PlayerName() << "(id: " + << player_id << ") who controls empire " << empire_id + << " but empire was eliminated"; + sender->SendMessage(ErrorMessage(UserStringNop("ORDERS_FOR_WRONG_EMPIRE"), false)); + return discard_event(); + } + for (const auto& id_and_order : *added) { + auto& order = id_and_order.second; + if (!order) { + ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnPartialOrders&) couldn't get order from order set!"; + continue; + } + if (empire_id != order->EmpireID()) { + ErrorLogger(FSM) << "WaitingForTurnEnd::react(TurnPartialOrders&) received orders from player " << empire->PlayerName() << "(id: " + << player_id << ") who controls empire " << empire_id + << " but those orders were for empire " << order->EmpireID() << ". Orders being ignored."; + sender->SendMessage(ErrorMessage(UserStringNop("ORDERS_FOR_WRONG_EMPIRE"), false)); + return discard_event(); + } + } -//////////////////////////////////////////////////////////// -// WaitingForSaveData -//////////////////////////////////////////////////////////// -WaitingForSaveData::WaitingForSaveData(my_context c) : - my_base(c) -{ - TraceLogger(FSM) << "(ServerFSM) WaitingForSaveData"; + TraceLogger(FSM) << "WaitingForTurnEnd.TurnPartialOrders : Received partial orders from player " << player_id; - ServerApp& server = Server(); - for (ServerNetworking::const_established_iterator player_it = server.m_networking.established_begin(); - player_it != server.m_networking.established_end(); ++player_it) - { - PlayerConnectionPtr player = *player_it; - int player_id = player->PlayerID(); - player->SendMessage(ServerSaveGameDataRequestMessage(false)); - m_needed_reponses.insert(player_id); + server.UpdatePartialOrders(empire_id, *added, deleted); } -} -WaitingForSaveData::~WaitingForSaveData() -{ TraceLogger(FSM) << "(ServerFSM) ~WaitingForSaveData"; } + return discard_event(); +} -sc::result WaitingForSaveData::react(const ClientSaveData& msg) { - TraceLogger(FSM) << "(ServerFSM) WaitingForSaveData.ClientSaveData"; +sc::result WaitingForTurnEnd::react(const RevokeReadiness& msg) { + TraceLogger(FSM) << "(ServerFSM) WaitingForTurnEnd.RevokeReadiness"; ServerApp& server = Server(); - const Message& message = msg.m_message; - const PlayerConnectionPtr& player_connection = msg.m_player_connection; + const PlayerConnectionPtr& sender = msg.m_player_connection; - int player_id = player_connection->PlayerID(); + int player_id = sender->PlayerID(); + Networking::ClientType client_type = sender->GetClientType(); - // extract client save information in message - OrderSet received_orders; - auto ui_data = std::make_shared(); - bool ui_data_available = false; - std::string save_state_string; - bool save_state_string_available = false; + if (client_type == Networking::CLIENT_TYPE_AI_PLAYER || + client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER) + { + // store empire orders and resume waiting for more + const Empire* empire = GetEmpire(server.PlayerEmpireID(player_id)); + if (!empire) { + ErrorLogger(FSM) << "WaitingForTurnEnd::react(RevokeReadiness&) couldn't get empire for player with id:" << player_id; + sender->SendMessage(ErrorMessage(UserStringNop("EMPIRE_NOT_FOUND_CANT_HANDLE_ORDERS"), false)); + return discard_event(); + } - try { - ExtractClientSaveDataMessageData(message, received_orders, ui_data_available, *ui_data, save_state_string_available, save_state_string); - } catch (const std::exception& e) { - DebugLogger(FSM) << "WaitingForSaveData::react(const ClientSaveData& msg) received invalid save data from player " << player_connection->PlayerName(); - player_connection->SendMessage(ErrorMessage(UserStringNop("INVALID_CLIENT_SAVE_DATA_RECEIVED"), false)); + int empire_id = empire->EmpireID(); + if (empire->Eliminated()) { + ErrorLogger(FSM) << "WaitingForTurnEnd::react(RevokeReadiness&) received orders from player " << empire->PlayerName() << "(id: " + << player_id << ") who controls empire " << empire_id + << " but empire was eliminated"; + sender->SendMessage(ErrorMessage(UserStringNop("ORDERS_FOR_WRONG_EMPIRE"), false)); + return discard_event(); + } - // TODO: use whatever portion of message data was extracted, and leave the rest as defaults. - } + TraceLogger(FSM) << "WaitingForTurnEnd.RevokeReadiness : Revoke orders from player " << player_id; - // store recieved orders or already existing orders. I'm not sure what's - // going on here with the two possible sets of orders. apparently the - // received orders are ignored if there are already existing orders? - std::shared_ptr order_set; - if (const Empire* empire = GetEmpire(server.PlayerEmpireID(player_id))) { - const auto& existing_orders = server.m_turn_sequence[empire->EmpireID()]; - if (existing_orders) - order_set.reset(new OrderSet(*existing_orders)); - else - order_set.reset(new OrderSet(received_orders)); - } else { - ErrorLogger(FSM) << "WaitingForSaveData::react(const ClientSaveData& msg) couldn't get empire for player " << player_id; - order_set.reset(new OrderSet(received_orders)); - } + server.RevokeEmpireTurnReadyness(empire_id); - // ensure ui data availability flag is consistent with ui data - if (!ui_data_available) - ui_data.reset(); + // inform player who just submitted of acknowledge revoking status. + sender->SendMessage(msg.m_message); - // what type of client is this? - Networking::ClientType client_type = player_connection->GetClientType(); + // notify other player that this empire revoked orders + for (auto player_it = server.m_networking.established_begin(); + player_it != server.m_networking.established_end(); ++player_it) + { + PlayerConnectionPtr player_ctn = *player_it; + player_ctn->SendMessage(PlayerStatusMessage(Message::PLAYING_TURN, empire_id)); + } + } + return discard_event(); +} - // pack data into struct - m_player_save_game_data.push_back( - PlayerSaveGameData(player_connection->PlayerName(), - server.PlayerEmpireID(player_connection->PlayerID()), - order_set, ui_data, save_state_string, - client_type)); +sc::result WaitingForTurnEnd::react(const CheckTurnEndConditions& c) { + TraceLogger(FSM) << "(ServerFSM) WaitingForTurnEnd.CheckTurnEndConditions"; + ServerApp& server = Server(); + // is there a moderator in the game? If so, do nothing, as the turn does + // not proceed until the moderator orders it + if (server.m_networking.ModeratorsInGame()) + return discard_event(); + // no moderator; wait for all player orders to be submitted before + // processing turn. + if (server.AllOrdersReceived() && + (server.IsTurnExpired() || + !GetOptionsDB().Get("network.server.turn-timeout.fixed-interval"))) + { + // if all players have submitted orders and the server doesn't wait until fixed turn timeout + // expires, proceed to turn processing + TraceLogger(FSM) << "WaitingForTurnEnd.TurnOrders : All orders received."; + post_event(ProcessTurn()); + return transit(); + } - // if all players have responded, proceed with save and continue game - m_players_responded.insert(player_connection->PlayerID()); - if (m_players_responded == m_needed_reponses) { - ServerSaveGameData server_data(server.m_current_turn); + return discard_event(); +} - // retreive requested save name from Base state, which should have been - // set in WaitingForTurnEndIdle::react(const SaveGameRequest& msg) - const std::string& save_filename = context().m_save_filename; - int bytes_written = 0; +sc::result WaitingForTurnEnd::react(const SaveGameRequest& msg) { + TraceLogger(FSM) << "(ServerFSM) WaitingForTurnEnd.SaveGameRequest"; + ServerApp& server = Server(); + const Message& message = msg.m_message; + const PlayerConnectionPtr& player_connection = msg.m_player_connection; - // save game... - try { - bytes_written = SaveGame(save_filename, server_data, m_player_save_game_data, - GetUniverse(), Empires(), GetSpeciesManager(), - GetCombatLogManager(), server.m_galaxy_setup_data, - !server.m_single_player_game); + if (player_connection && !server.m_networking.PlayerIsHost(player_connection->PlayerID())) { + ErrorLogger(FSM) << "WaitingForTurnEnd.SaveGameRequest : Player #" << player_connection->PlayerID() + << " attempted to initiate a game save, but is not the host. Ignoring request connection."; + player_connection->SendMessage(ErrorMessage(UserStringNop("NON_HOST_SAVE_REQUEST_IGNORED"), false)); + return discard_event(); + } - } catch (const std::exception&) { - DebugLogger(FSM) << "Catch std::exception&"; - SendMessageToAllPlayers(ErrorMessage(UserStringNop("UNABLE_TO_WRITE_SAVE_FILE"), false)); - } + std::string save_filename = message.Text(); // store requested save file name in Base state context so that sibling state can retreive it - // inform players that save is complete - SendMessageToAllPlayers(ServerSaveGameCompleteMessage(save_filename, bytes_written)); + ServerSaveGameData server_data(server.m_current_turn); - DebugLogger(FSM) << "Finished ClientSaveData from within if."; - context().m_save_filename = ""; - return transit(); + // retreive requested save name from Base state, which should have been + // set in WaitingForTurnEnd::react(const SaveGameRequest& msg) + int bytes_written = 0; + + // save game... + try { + bytes_written = SaveGame(save_filename, server_data, server.GetPlayerSaveGameData(), + GetUniverse(), Empires(), GetSpeciesManager(), + GetCombatLogManager(), server.m_galaxy_setup_data, + !server.m_single_player_game); + + } catch (const std::exception& error) { + ErrorLogger(FSM) << "While saving, catch std::exception: " << error.what(); + SendMessageToAllPlayers(ErrorMessage(UserStringNop("UNABLE_TO_WRITE_SAVE_FILE"), false)); } - DebugLogger(FSM) << "Finished ClientSaveData from outside of if."; + // inform players that save is complete + SendMessageToAllPlayers(ServerSaveGameCompleteMessage(save_filename, bytes_written)); + return discard_event(); } +void WaitingForTurnEnd::SaveTimedoutHandler(const boost::system::error_code& error) { + if (error) { + DebugLogger() << "Save timed out cancelled"; + return; + } + + DebugLogger() << "Save timed out."; + PlayerConnectionPtr dummy_connection = nullptr; + Server().m_fsm->process_event(SaveGameRequest(HostSaveGameInitiateMessage(GetAutoSaveFileName(Server().CurrentTurn())), dummy_connection)); + if (GetOptionsDB().Get("save.auto.interval") > 0) { +#if BOOST_VERSION >= 106600 + m_timeout.expires_after(std::chrono::seconds(GetOptionsDB().Get("save.auto.interval"))); +#else + m_timeout.expires_from_now(std::chrono::seconds(GetOptionsDB().Get("save.auto.interval"))); +#endif + m_timeout.async_wait(boost::bind(&WaitingForTurnEnd::SaveTimedoutHandler, + this, + boost::asio::placeholders::error)); + } +} //////////////////////////////////////////////////////////// // ProcessingTurn //////////////////////////////////////////////////////////// ProcessingTurn::ProcessingTurn(my_context c) : - my_base(c) + my_base(c), + m_start(std::chrono::high_resolution_clock::now()) { TraceLogger(FSM) << "(ServerFSM) ProcessingTurn"; } -ProcessingTurn::~ProcessingTurn() -{ TraceLogger(FSM) << "(ServerFSM) ~ProcessingTurn"; } +ProcessingTurn::~ProcessingTurn() { + auto duration = std::chrono::high_resolution_clock::now() - m_start; + DebugLogger(FSM) << "ProcessingTurn time: " << std::chrono::duration_cast(duration).count() << " s"; + TraceLogger(FSM) << "(ServerFSM) ~ProcessingTurn"; +} sc::result ProcessingTurn::react(const ProcessTurn& u) { TraceLogger(FSM) << "(ServerFSM) ProcessingTurn.ProcessTurn"; @@ -1853,29 +3329,25 @@ sc::result ProcessingTurn::react(const ProcessTurn& u) { server.ProcessCombats(); server.PostCombatProcessTurns(); - // update players that other players are now playing their turn - for (ServerNetworking::const_established_iterator player_it = server.m_networking.established_begin(); - player_it != server.m_networking.established_end(); - ++player_it) - { - PlayerConnectionPtr player_ctn = *player_it; - if (player_ctn->GetClientType() == Networking::CLIENT_TYPE_AI_PLAYER || - player_ctn->GetClientType() == Networking::CLIENT_TYPE_HUMAN_PLAYER || - player_ctn->GetClientType() == Networking::CLIENT_TYPE_HUMAN_OBSERVER || - player_ctn->GetClientType() == Networking::CLIENT_TYPE_HUMAN_MODERATOR) + // update players that other empires are now playing their turn + for (const auto& empire : server.Empires()) { + // inform all players that this empire is playing a turn if not eliminated + for (auto recipient_player_it = server.m_networking.established_begin(); + recipient_player_it != server.m_networking.established_end(); + ++recipient_player_it) { - // inform all players that this player is playing a turn - for (ServerNetworking::const_established_iterator recipient_player_it = server.m_networking.established_begin(); - recipient_player_it != server.m_networking.established_end(); - ++recipient_player_it) - { - const PlayerConnectionPtr& recipient_player_ctn = *recipient_player_it; - recipient_player_ctn->SendMessage(PlayerStatusMessage(player_ctn->PlayerID(), - Message::PLAYING_TURN)); - } + const PlayerConnectionPtr& recipient_player_ctn = *recipient_player_it; + recipient_player_ctn->SendMessage(PlayerStatusMessage(empire.second->Eliminated() ? + Message::WAITING : + Message::PLAYING_TURN, + empire.first)); } } + if (server.IsHostless() && GetOptionsDB().Get("save.auto.hostless.enabled")) { + PlayerConnectionPtr dummy_connection = nullptr; + post_event(SaveGameRequest(HostSaveGameInitiateMessage(GetAutoSaveFileName(server.CurrentTurn())), dummy_connection)); + } return transit(); } @@ -1893,7 +3365,7 @@ const auto SHUTDOWN_POLLING_TIME = std::chrono::milliseconds(5000); ShuttingDownServer::ShuttingDownServer(my_context c) : my_base(c), m_player_id_ack_expected(), - m_timeout(Server().m_io_service, Clock::now() + SHUTDOWN_POLLING_TIME) + m_timeout(Server().m_io_context, Clock::now() + SHUTDOWN_POLLING_TIME) { TraceLogger(FSM) << "(ServerFSM) ShuttingDownServer"; @@ -1907,12 +3379,11 @@ ShuttingDownServer::ShuttingDownServer(my_context c) : // Inform all players that the game is ending. Only check the AIs for acknowledgement, because // they are the server's child processes. for (PlayerConnectionPtr player : server.m_networking) { - auto good_connection = player->SendMessage(EndGameMessage(Message::PLAYER_DISCONNECT)); + player->SendMessage(EndGameMessage(Message::PLAYER_DISCONNECT)); if (player->GetClientType() == Networking::CLIENT_TYPE_AI_PLAYER) { - if (good_connection) { - // Only expect acknowledgement from sockets that are up. - m_player_id_ack_expected.insert(player->PlayerID()); - } + // If sending message will result in error it will be processed in Disconnection + // handle. + m_player_id_ack_expected.insert(player->PlayerID()); } } diff --git a/server/ServerFSM.h b/server/ServerFSM.h index eff6c2560e4..2e85b36c6e5 100644 --- a/server/ServerFSM.h +++ b/server/ServerFSM.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -45,6 +46,7 @@ struct CheckTurnEndConditions : sc::event {}; struct ProcessTurn : sc::event {}; struct DisconnectClients : sc::event {}; struct ShutdownServer : sc::event {}; +struct Hostless : sc::event {}; // Empty event to move server into MPLobby state. /** This is a Boost.Preprocessor list of all non MessageEventBase events. It is * used to generate the unconsumed events message. */ @@ -55,12 +57,13 @@ struct ShutdownServer : sc::event {}; (CheckTurnEndConditions) \ (ProcessTurn) \ (DisconnectClients) \ - (ShutdownServer) + (ShutdownServer) \ + (Hostless) // Message events /** The base class for all state machine events that are based on Messages. */ struct MessageEventBase { - MessageEventBase(const Message& message, PlayerConnectionPtr& player_connection); + MessageEventBase(const Message& message, const PlayerConnectionPtr& player_connection); Message m_message; PlayerConnectionPtr m_player_connection; @@ -76,23 +79,26 @@ struct MessageEventBase { (LeaveGame) \ (SaveGameRequest) \ (TurnOrders) \ + (TurnPartialOrders) \ + (RevokeReadiness) \ (CombatTurnOrders) \ - (ClientSaveData) \ (RequestCombatLogs) \ (PlayerChat) \ (Diplomacy) \ (ModeratorAct) \ + (AuthResponse) \ + (EliminateSelf) \ (Error) -#define DECLARE_MESSAGE_EVENT(r, data, name) \ - struct name : \ - sc::event, \ - MessageEventBase \ - { \ - name(const Message& message, PlayerConnectionPtr& player_connection) : \ - MessageEventBase(message, player_connection) \ - {} \ +#define DECLARE_MESSAGE_EVENT(r, data, name) \ + struct name : \ + sc::event, \ + MessageEventBase \ + { \ + name(const Message& message, const PlayerConnectionPtr& player_connection) : \ + MessageEventBase(message, player_connection) \ + {} \ }; BOOST_PP_SEQ_FOR_EACH(DECLARE_MESSAGE_EVENT, _, MESSAGE_EVENTS) @@ -112,11 +118,6 @@ struct ShuttingDownServer; struct WaitingForTurnEnd; struct ProcessingTurn; -// Substates of WaitingForTurnEnd -struct WaitingForTurnEndIdle; -struct WaitingForSaveData; - - #define SERVER_ACCESSOR private: ServerApp& Server() { return context().Server(); } /** The finite state machine that represents the server's operation. */ @@ -126,6 +127,12 @@ struct ServerFSM : sc::state_machine { void unconsumed_event(const sc::event_base &event); ServerApp& Server(); void HandleNonLobbyDisconnection(const Disconnection& d); + void UpdateIngameLobby(); + bool EstablishPlayer(const PlayerConnectionPtr& player_connection, + const std::string& player_name, + Networking::ClientType client_type, + const std::string& client_version_string, + const Networking::AuthRoles& roles); std::shared_ptr m_lobby_data; std::shared_ptr m_single_player_setup_data; @@ -136,14 +143,14 @@ struct ServerFSM : sc::state_machine { ServerApp& m_server; }; - /** The server's initial state. */ struct Idle : sc::state { typedef boost::mpl::list< sc::custom_reaction, sc::custom_reaction, sc::custom_reaction, - sc::custom_reaction + sc::custom_reaction, + sc::custom_reaction > reactions; Idle(my_context c); @@ -153,6 +160,7 @@ struct Idle : sc::state { sc::result react(const HostSPGame& msg); sc::result react(const ShutdownServer& u); sc::result react(const Error& msg); + sc::result react(const Hostless&); SERVER_ACCESSOR }; @@ -163,12 +171,14 @@ struct MPLobby : sc::state { typedef boost::mpl::list< sc::custom_reaction, sc::custom_reaction, + sc::custom_reaction, sc::custom_reaction, sc::custom_reaction, sc::custom_reaction, sc::custom_reaction, sc::custom_reaction, sc::custom_reaction, + sc::custom_reaction, sc::custom_reaction > reactions; @@ -177,17 +187,28 @@ struct MPLobby : sc::state { sc::result react(const Disconnection& d); sc::result react(const JoinGame& msg); + sc::result react(const AuthResponse& msg); sc::result react(const LobbyUpdate& msg); sc::result react(const PlayerChat& msg); sc::result react(const StartMPGame& msg); sc::result react(const HostMPGame& msg); sc::result react(const HostSPGame& msg); sc::result react(const ShutdownServer& u); + sc::result react(const Hostless& u); sc::result react(const Error& msg); std::shared_ptr m_lobby_data; std::vector m_player_save_game_data; std::shared_ptr m_server_save_game_data; + int m_ai_next_index; + +private: + void EstablishPlayer(const PlayerConnectionPtr& player_connection, + const std::string& player_name, + Networking::ClientType client_type, + const std::string& client_version_string, + const Networking::AuthRoles& roles); + void ValidateClientLimits(); SERVER_ACCESSOR }; @@ -232,8 +253,10 @@ struct WaitingForMPGameJoiners : sc::state { typedef boost::mpl::list< sc::in_state_reaction, sc::custom_reaction, + sc::custom_reaction, sc::custom_reaction, sc::custom_reaction, + sc::custom_reaction, sc::custom_reaction > reactions; @@ -241,18 +264,19 @@ struct WaitingForMPGameJoiners : sc::state { ~WaitingForMPGameJoiners(); sc::result react(const JoinGame& msg); + sc::result react(const AuthResponse& msg); sc::result react(const CheckStartConditions& u); // unlike in SP game setup, no save file data needs to be loaded in this // state, as all the relevant info about AIs is provided by the lobby data. // as such, no file load error handling reaction is needed in this state. sc::result react(const ShutdownServer& u); + sc::result react(const Hostless& u); sc::result react(const Error& msg); std::shared_ptr m_lobby_data; std::vector m_player_save_game_data; std::shared_ptr m_server_save_game_data; std::set m_expected_ai_player_names; - int m_num_expected_players; SERVER_ACCESSOR }; @@ -268,7 +292,12 @@ struct PlayingGame : sc::state { sc::custom_reaction, sc::custom_reaction, sc::custom_reaction, - sc::custom_reaction + sc::custom_reaction, + sc::custom_reaction, + sc::custom_reaction, + sc::custom_reaction, + sc::custom_reaction, + sc::custom_reaction > reactions; PlayingGame(my_context c); @@ -278,8 +307,23 @@ struct PlayingGame : sc::state { sc::result react(const Diplomacy& msg); sc::result react(const ModeratorAct& msg); sc::result react(const ShutdownServer& u); + sc::result react(const Hostless& u); sc::result react(const RequestCombatLogs& msg); + sc::result react(const JoinGame& msg); + sc::result react(const AuthResponse& msg); + sc::result react(const EliminateSelf& msg); sc::result react(const Error& msg); + sc::result react(const LobbyUpdate& msg); + + void EstablishPlayer(const PlayerConnectionPtr& player_connection, + const std::string& player_name, + Networking::ClientType client_type, + const std::string& client_version_string, + const Networking::AuthRoles& roles); + void TurnTimedoutHandler(const boost::system::error_code& error); + + boost::asio::deadline_timer m_turn_timeout; + std::chrono::high_resolution_clock::time_point m_start; SERVER_ACCESSOR }; @@ -288,64 +332,33 @@ struct PlayingGame : sc::state { /** The substate of PlayingGame in which players are playing their turns and * the server is waiting for all players to finish their moves, after which * the server will process the turn. */ -struct WaitingForTurnEnd : sc::state { +struct WaitingForTurnEnd : sc::state { typedef boost::mpl::list< sc::custom_reaction, - sc::custom_reaction + sc::custom_reaction, + sc::custom_reaction, + sc::custom_reaction, + sc::custom_reaction > reactions; WaitingForTurnEnd(my_context c); ~WaitingForTurnEnd(); sc::result react(const TurnOrders& msg); + sc::result react(const TurnPartialOrders& msg); + sc::result react(const RevokeReadiness& msg); sc::result react(const CheckTurnEndConditions& c); - - std::string m_save_filename; - - SERVER_ACCESSOR -}; - - -/** The default substate of WaitingForTurnEnd. */ -struct WaitingForTurnEndIdle : sc::state { - typedef boost::mpl::list< - sc::custom_reaction - > reactions; - - WaitingForTurnEndIdle(my_context c); - ~WaitingForTurnEndIdle(); - sc::result react(const SaveGameRequest& msg); - SERVER_ACCESSOR -}; - + void SaveTimedoutHandler(const boost::system::error_code& error); -/** The substate of WaitingForTurnEnd in which a player has initiated a save - * and the server is waiting for all players to send their save data, after - * which the server will actually preform the save. */ -struct WaitingForSaveData : sc::state { - typedef boost::mpl::list< - sc::custom_reaction, - sc::deferral, - sc::deferral, - sc::deferral, - sc::deferral - > reactions; - - WaitingForSaveData(my_context c); - ~WaitingForSaveData(); - - sc::result react(const ClientSaveData& msg); - - std::set m_needed_reponses; - std::set m_players_responded; - std::vector m_player_save_game_data; + std::string m_save_filename; + boost::asio::high_resolution_timer m_timeout; + std::chrono::high_resolution_clock::time_point m_start; SERVER_ACCESSOR }; - /** The substate of PlayingGame in which the server has received turn orders * from players and is determining what happens between turns. This includes * executing orders, resolving combat, various steps in determining what @@ -356,6 +369,8 @@ struct ProcessingTurn : sc::state { sc::custom_reaction, sc::deferral, sc::deferral, + sc::deferral, + sc::deferral, sc::deferral, sc::custom_reaction > reactions; @@ -366,6 +381,8 @@ struct ProcessingTurn : sc::state { sc::result react(const ProcessTurn& u); sc::result react(const CheckTurnEndConditions& c); + std::chrono::high_resolution_clock::time_point m_start; + SERVER_ACCESSOR }; diff --git a/server/ServerFramework.cpp b/server/ServerFramework.cpp new file mode 100644 index 00000000000..b6ae34b5342 --- /dev/null +++ b/server/ServerFramework.cpp @@ -0,0 +1,315 @@ +#include "ServerFramework.h" + +#include "ServerWrapper.h" +#include "../python/SetWrapper.h" +#include "../python/CommonWrappers.h" + +#include "../util/Logger.h" +#include "../universe/Condition.h" +#include "../universe/Universe.h" + +#include +#include +#include +#include +#include +#include + +#include + +using boost::python::object; +using boost::python::class_; +using boost::python::import; +using boost::python::dict; +using boost::python::list; +using boost::python::vector_indexing_suite; +using boost::python::map_indexing_suite; + +namespace fs = boost::filesystem; + +BOOST_PYTHON_MODULE(freeorion) { + boost::python::docstring_options doc_options(true, true, false); + + FreeOrionPython::WrapGameStateEnums(); + FreeOrionPython::WrapGalaxySetupData(); + FreeOrionPython::WrapEmpire(); + FreeOrionPython::WrapUniverseClasses(); + FreeOrionPython::WrapServer(); + FreeOrionPython::WrapConfig(); + + // STL Containers + class_>("IntVec") + .def(vector_indexing_suite>()) + ; + class_>("StringVec") + .def(vector_indexing_suite>()) + ; + + class_>("IntBoolMap") + .def(map_indexing_suite>()) + ; + + FreeOrionPython::SetWrapper::Wrap("IntSet"); + FreeOrionPython::SetWrapper::Wrap("StringSet"); +} + +namespace { +} + +bool PythonServer::InitImports() { + DebugLogger() << "Initializing server Python imports"; + // Allow the "freeorion" C++ module to be imported within Python code + return PyImport_AppendInittab("freeorion", &PyInit_freeorion) != -1; +} + +bool PythonServer::InitModules() { + DebugLogger() << "Initializing server Python modules"; + + // Confirm existence of the directory containing the universe generation + // Python scripts and add it to Pythons sys.path to make sure Python will + // find our scripts + if (!fs::exists(GetPythonUniverseGeneratorDir())) { + ErrorLogger() << "Can't find folder containing universe generation scripts"; + return false; + } + AddToSysPath(GetPythonUniverseGeneratorDir()); + + // Confirm existence of the directory containing the turn event Python + // scripts and add it to Pythons sys.path to make sure Python will find + // our scripts + if (!fs::exists(GetPythonTurnEventsDir())) { + ErrorLogger() << "Can't find folder containing turn events scripts"; + return false; + } + AddToSysPath(GetPythonTurnEventsDir()); + + // import universe generator script file + m_python_module_turn_events = import("turn_events"); + + // Confirm existence of the directory containing the auth Python scripts + // and add it to Pythons sys.path to make sure Python will find our scripts + if (!fs::exists(GetPythonAuthDir())) { + ErrorLogger() << "Can't find folder containing auth scripts"; + return false; + } + AddToSysPath(GetPythonAuthDir()); + + // import auth script file + m_python_module_auth = import("auth"); + + // Save AuthProvider instance in auth module's namespace + m_python_module_auth.attr("__dict__")["auth_provider"] = m_python_module_auth.attr("AuthProvider")(); + + AddToSysPath(GetPythonChatDir()); + + // import chat script file + m_python_module_chat = import("chat"); + + // Save ChatProvider instance in chat module's namespace + m_python_module_chat.attr("__dict__")["chat_history_provider"] = m_python_module_chat.attr("ChatHistoryProvider")(); + + DebugLogger() << "Server Python modules successfully initialized!"; + return true; +} + +bool PythonServer::IsRequireAuthOrReturnRoles(const std::string& player_name, bool &result, Networking::AuthRoles& roles) const { + boost::python::object auth_provider = m_python_module_auth.attr("__dict__")["auth_provider"]; + if (!auth_provider) { + ErrorLogger() << "Unable to get Python object auth_provider"; + return false; + } + boost::python::object f = auth_provider.attr("is_require_auth_or_return_roles"); + if (!f) { + ErrorLogger() << "Unable to call Python method is_require_auth"; + return false; + } + boost::python::object r = f(player_name); + boost::python::extract py_roles(r); + if (py_roles.check()) { + result = false; + boost::python::stl_input_iterator role_begin(py_roles), role_end; + for (auto& it = role_begin; it != role_end; ++ it) { + roles.SetRole(*it, true); + } + } else { + result = true; + } + return true; +} + +bool PythonServer::IsSuccessAuthAndReturnRoles(const std::string& player_name, const std::string& auth, bool &result, Networking::AuthRoles& roles) const { + boost::python::object auth_provider = m_python_module_auth.attr("__dict__")["auth_provider"]; + if (!auth_provider) { + ErrorLogger() << "Unable to get Python object auth_provider"; + return false; + } + boost::python::object f = auth_provider.attr("is_success_auth_and_return_roles"); + if (!f) { + ErrorLogger() << "Unable to call Python method is_success_auth"; + return false; + } + boost::python::object r = f(player_name, auth); + boost::python::extract py_roles(r); + if (py_roles.check()) { + result = true; + + boost::python::stl_input_iterator role_begin(py_roles), role_end; + for (auto& it = role_begin; it != role_end; ++ it) { + roles.SetRole(*it, true); + } + } else { + result = false; + DebugLogger() << "Wrong auth data for \"" << player_name << "\": check returns " << boost::python::extract(boost::python::str(r))(); + } + return true; +} + +bool PythonServer::FillListPlayers(std::list& players) const { + boost::python::object auth_provider = m_python_module_auth.attr("__dict__")["auth_provider"]; + if (!auth_provider) { + ErrorLogger() << "Unable to get Python object auth_provider"; + return false; + } + boost::python::object f = auth_provider.attr("list_players"); + if (!f) { + ErrorLogger() << "Unable to call Python method list_players"; + return false; + } + boost::python::object r = f(); + boost::python::extract py_players(r); + if (py_players.check()) { + boost::python::stl_input_iterator players_begin(py_players), players_end; + for (auto& it = players_begin; it != players_end; ++ it) { + players.push_back(*it); + } + } else { + DebugLogger() << "Wrong players list data: check returns " << boost::python::extract(boost::python::str(r))(); + return false; + } + return true; +} + +bool PythonServer::GetPlayerDelegation(const std::string& player_name, std::list &result) const { + boost::python::object auth_provider = m_python_module_auth.attr("__dict__")["auth_provider"]; + if (!auth_provider) { + ErrorLogger() << "Unable to get Python object auth_provider"; + return false; + } + boost::python::object f = auth_provider.attr("get_player_delegation"); + if (!f) { + ErrorLogger() << "Unable to call Python method get_player_delegation"; + return false; + } + boost::python::object r = f(player_name); + boost::python::extract py_players(r); + if (py_players.check()) { + boost::python::stl_input_iterator players_begin(py_players), players_end; + for (auto& it = players_begin; it != players_end; ++ it) { + result.push_back(*it); + } + } else { + DebugLogger() << "Wrong delegated players list data: check returns " << boost::python::extract(boost::python::str(r))(); + return false; + } + + return true; +} + +bool PythonServer::LoadChatHistory(boost::circular_buffer& chat_history) { + boost::python::object chat_provider = m_python_module_chat.attr("__dict__")["chat_history_provider"]; + if (!chat_provider) { + ErrorLogger() << "Unable to get Python object chat_history_provider"; + return false; + } + boost::python::object f = chat_provider.attr("load_history"); + if (!f) { + ErrorLogger() << "Unable to call Python method load_history"; + return false; + } + boost::python::object r = f(); + boost::python::extract py_history(r); + if (py_history.check()) { + boost::python::stl_input_iterator entity_begin(py_history), entity_end; + for (auto& it = entity_begin; it != entity_end; ++ it) { + ChatHistoryEntity e; + e.m_timestamp = boost::posix_time::from_time_t(boost::python::extract((*it)[0]));; + e.m_player_name = boost::python::extract((*it)[1]); + e.m_text = boost::python::extract((*it)[2]); + e.m_text_color = boost::python::extract((*it)[3]); + chat_history.push_back(e); + } + } + + return true; +} + +bool PythonServer::PutChatHistoryEntity(const ChatHistoryEntity& chat_history_entity) { + boost::python::object chat_provider = m_python_module_chat.attr("__dict__")["chat_history_provider"]; + if (!chat_provider) { + ErrorLogger() << "Unable to get Python object chat_history_provider"; + return false; + } + boost::python::object f = chat_provider.attr("put_history_entity"); + if (!f) { + ErrorLogger() << "Unable to call Python method put_history_entity"; + return false; + } + + return f(boost::python::long_(to_time_t(chat_history_entity.m_timestamp)), + chat_history_entity.m_player_name, + chat_history_entity.m_text, + chat_history_entity.m_text_color); +} + +bool PythonServer::CreateUniverse(std::map& player_setup_data) { + // Confirm existence of the directory containing the universe generation + // Python scripts and add it to Pythons sys.path to make sure Python will + // find our scripts + if (!fs::exists(GetPythonUniverseGeneratorDir())) { + ErrorLogger() << "Can't find folder containing universe generation scripts"; + return false; + } + AddToSysPath(GetPythonUniverseGeneratorDir()); + + // import universe generator script file + m_python_module_universe_generator = import("universe_generator"); + + dict py_player_setup_data; + + // the universe generator module should contain an "error_report" function, + // so set the ErrorReport member function to use it + SetErrorModule(m_python_module_universe_generator); + + for (auto& psd : player_setup_data) { + py_player_setup_data[psd.first] = object(psd.second); + } + + object f = m_python_module_universe_generator.attr("create_universe"); + if (!f) { + ErrorLogger() << "Unable to call Python function create_universe "; + return false; + } + + return f(py_player_setup_data); +} + +bool PythonServer::ExecuteTurnEvents() { + object f = m_python_module_turn_events.attr("execute_turn_events"); + if (!f) { + ErrorLogger() << "Unable to call Python function execute_turn_events "; + return false; + } + return f(); +} + +const std::string GetPythonUniverseGeneratorDir() +{ return GetPythonDir() + "/universe_generation"; } + +const std::string GetPythonTurnEventsDir() +{ return GetPythonDir() + "/turn_events"; } + +const std::string GetPythonAuthDir() +{ return GetPythonDir() + "/auth"; } + +const std::string GetPythonChatDir() +{ return GetPythonDir() + "/chat"; } diff --git a/server/ServerFramework.h b/server/ServerFramework.h new file mode 100644 index 00000000000..764bc5b04a7 --- /dev/null +++ b/server/ServerFramework.h @@ -0,0 +1,54 @@ +#ifndef _ServerFramework_h_ +#define _ServerFramework_h_ + +#include "../python/CommonFramework.h" +#include "../util/MultiplayerCommon.h" + +#include + +#include + + +class PythonServer : public PythonBase { +public: + /** Initializes server Python imports. */ + bool InitImports() override; + /** Initializes server Python modules. */ + bool InitModules() override; + + bool CreateUniverse(std::map& player_setup_data); // Wraps call to the main Python universe generator function + bool ExecuteTurnEvents(); // Wraps call to the main Python turn events function + bool IsRequireAuthOrReturnRoles(const std::string& player_name, bool &result, Networking::AuthRoles& roles) const; // Wraps call to AuthProvider's method is_require_auth + bool IsSuccessAuthAndReturnRoles(const std::string& player_name, const std::string& auth, bool &result, Networking::AuthRoles& roles) const; // Wraps call to AuthProvider's method is_success_auth + bool FillListPlayers(std::list& players) const; // Wraps call to AuthProvider's method list_player + bool GetPlayerDelegation(const std::string& player_name, std::list &result) const; // Wraps call to AuthProvider's method get_player_delegation + bool LoadChatHistory(boost::circular_buffer& chat_history); // Wraps call to ChatProvider's method load_history + bool PutChatHistoryEntity(const ChatHistoryEntity& chat_history_entity); // Wraps call to ChatProvider's method put_history_entity + +private: + // reference to imported Python universe generator module + boost::python::object m_python_module_universe_generator; + + // reference to imported Python turn events module + boost::python::object m_python_module_turn_events; + + // reference to imported Python auth module + boost::python::object m_python_module_auth; + + // reference to importer Python chat module + boost::python::object m_python_module_chat; +}; + +// Returns folder containing the Python universe generator scripts +const std::string GetPythonUniverseGeneratorDir(); + +// Returns folder containing the Python turn events scripts +const std::string GetPythonTurnEventsDir(); + +// Returns folder containing the Python auth scripts +const std::string GetPythonAuthDir(); + +// Returns folder containing the Python chat scripts +const std::string GetPythonChatDir(); + +#endif // _ServerFramework_h_ diff --git a/network/ServerNetworking.cpp b/server/ServerNetworking.cpp similarity index 55% rename from network/ServerNetworking.cpp rename to server/ServerNetworking.cpp index d6e10c5cf51..a3aa9c060f6 100644 --- a/network/ServerNetworking.cpp +++ b/server/ServerNetworking.cpp @@ -3,9 +3,13 @@ #include "../util/Logger.h" #include "../util/OptionsDB.h" #include "../util/Version.h" +#include "../universe/ValueRefs.h" +#include "../parse/Parse.h" #include #include +#include +#include #include @@ -13,6 +17,7 @@ using boost::asio::ip::tcp; using boost::asio::ip::udp; using namespace Networking; + namespace { DeclareThreadSafeLogger(network); } @@ -21,16 +26,114 @@ namespace { on the local network and sends out responses to them. */ class DiscoveryServer { public: - DiscoveryServer(boost::asio::io_service& io_service); + DiscoveryServer(boost::asio::io_context& io_context) : + m_socket(io_context) + { + // use a dual stack (ipv6 + ipv4) socket + udp::endpoint discovery_endpoint(udp::v6(), Networking::DiscoveryPort()); + + if (GetOptionsDB().Get("singleplayer")) { + // when hosting a single player game only accept connections from + // the localhost via the loopback interface instead of the any + // interface. + // This should prevent unnecessary triggering of Desktop Firewalls as + // reported by various users when running single player games. + discovery_endpoint.address(boost::asio::ip::address_v4::loopback()); + } + + try { + m_socket = udp::socket(io_context, discovery_endpoint); + } catch (const std::exception &e) { + ErrorLogger(network) << "DiscoveryServer cannot open IPv6 socket: " << e.what() + << ". Fallback to IPv4"; + discovery_endpoint = udp::endpoint(udp::v4(), Networking::DiscoveryPort()); + if (GetOptionsDB().Get("singleplayer")) + discovery_endpoint.address(boost::asio::ip::address_v4::loopback()); + + m_socket = udp::socket(io_context, discovery_endpoint); + } + + Listen(); + } private: - void Listen(); - void HandleReceive(const boost::system::error_code& error); + void Listen() { + m_recv_buffer.fill('\0'); + m_socket.async_receive_from( + boost::asio::buffer(m_recv_buffer), + m_remote_endpoint, + boost::bind(&DiscoveryServer::HandleReceive, this, + boost::asio::placeholders::error)); + } + + + void HandleReceive(const boost::system::error_code& error) { + if (error) { + ErrorLogger(network) << "DiscoveryServer received and ignored error: " << error + << "\nfrom: " << m_remote_endpoint; + Listen(); + return; + } + + auto message = std::string(m_recv_buffer.begin(), m_recv_buffer.end()); + message.erase(std::find(message.begin(), message.end(), '\0'), message.end()); + boost::trim(message); + + if (message == DISCOVERY_QUESTION) { + auto reply = DISCOVERY_ANSWER; + m_socket.send_to( + boost::asio::buffer(reply), + m_remote_endpoint); + DebugLogger(network) << "DiscoveryServer received from: " << m_remote_endpoint // operator<< outputs "IP:port" + << "\nmessage: " << message + << "\nreplied: " << reply; + Listen(); + return; + } - boost::asio::ip::udp::socket m_socket; - boost::asio::ip::udp::endpoint m_remote_endpoint; + DebugLogger(network) << "DiscoveryServer evaluating FOCS expression: " << message; + std::string reply; + try { + if (parse::int_free_variable(message)) { + auto value_ref = std::make_unique>(ValueRef::NON_OBJECT_REFERENCE, message); + reply = std::to_string(value_ref->Eval(ScriptingContext())); + DebugLogger(network) << "DiscoveryServer evaluated expression as integer with result: " << reply; + + } else if (parse::double_free_variable(message)) { + auto value_ref = std::make_unique>(ValueRef::NON_OBJECT_REFERENCE, message); + reply = std::to_string(value_ref->Eval(ScriptingContext())); + DebugLogger(network) << "DiscoveryServer evaluated expression as double with result: " << reply; + + } else if (parse::string_free_variable(message)) { + auto value_ref = std::make_unique>(ValueRef::NON_OBJECT_REFERENCE, message); + reply = value_ref->Eval(ScriptingContext()); + DebugLogger(network) << "DiscoveryServer evaluated expression as string with result: " << reply; + + //} else { + // auto value_ref = std::make_unique>>(ValueRef::NON_OBJECT_REFERENCE, message); + // auto result = value_ref->Eval(ScriptingContext()); + // for (auto entry : result) + // reply += entry + "\n"; + // DebugLogger(network) << "DiscoveryServer evaluated expression as string vector with result: " << reply; - std::array m_recv_buffer; + } else { + ErrorLogger(network) << "DiscoveryServer couldn't interpret message"; + reply = "FOCS ERROR"; + } + } catch (...) { + ErrorLogger(network) << "DiscoveryServer caught exception processing message"; + reply = "EXCEPTION ERROR"; + } + + m_socket.send_to(boost::asio::buffer(reply), m_remote_endpoint); + + Listen(); + } + + boost::asio::ip::udp::socket m_socket; + boost::asio::ip::udp::endpoint m_remote_endpoint; + + std::array m_recv_buffer = {}; }; namespace { @@ -50,15 +153,13 @@ namespace { //////////////////////////////////////////////////////////////////////////////// // PlayerConnection //////////////////////////////////////////////////////////////////////////////// -PlayerConnection::PlayerConnection(boost::asio::io_service& io_service, +PlayerConnection::PlayerConnection(boost::asio::io_context& io_context, MessageAndConnectionFn nonplayer_message_callback, MessageAndConnectionFn player_message_callback, ConnectionFn disconnected_callback) : - m_service(io_service), - m_socket(io_service), - m_ID(INVALID_PLAYER_ID), - m_new_connection(true), - m_client_type(Networking::INVALID_CLIENT_TYPE), + m_service(io_context), + m_socket(io_context), + m_cookie(boost::uuids::nil_uuid()), m_nonplayer_message_callback(nonplayer_message_callback), m_player_message_callback(player_message_callback), m_disconnected_callback(disconnected_callback) @@ -108,8 +209,44 @@ bool PlayerConnection::IsLocalConnection() const void PlayerConnection::Start() { AsyncReadMessage(); } -bool PlayerConnection::SendMessage(const Message& message) { - return SyncWriteMessage(message); +void PlayerConnection::SendMessage(const Message& message) { + if (!m_valid) { + ErrorLogger(network) << "PlayerConnection::SendMessage can't send message when not transmit connected"; + return; + } + m_service.post(boost::bind(&PlayerConnection::SendMessageImpl, shared_from_this(), message)); +} + +bool PlayerConnection::IsEstablished() const { + return (m_ID != INVALID_PLAYER_ID && !m_player_name.empty() && m_client_type != Networking::INVALID_CLIENT_TYPE); +} + +bool PlayerConnection::IsAuthenticated() const { + return m_authenticated; +} + +bool PlayerConnection::HasAuthRole(Networking::RoleType role) const { + return m_roles.HasRole(role); +} + +boost::uuids::uuid PlayerConnection::Cookie() const +{ return m_cookie; } + +void PlayerConnection::AwaitPlayer(Networking::ClientType client_type, + const std::string& client_version_string) +{ + TraceLogger(network) << "PlayerConnection(@ " << this << ")::AwaitPlayer(" + << client_type << ", " << client_version_string << ")"; + if (m_client_type != Networking::INVALID_CLIENT_TYPE) { + ErrorLogger(network) << "PlayerConnection::AwaitPlayer attempting to re-await an already awaiting connection."; + return; + } + if (client_type == Networking::INVALID_CLIENT_TYPE || client_type >= NUM_CLIENT_TYPES) { + ErrorLogger(network) << "PlayerConnection::EstablishPlayer passed invalid client type: " << client_type; + return; + } + m_client_type = client_type; + m_client_version_string = client_version_string; } void PlayerConnection::EstablishPlayer(int id, const std::string& player_name, Networking::ClientType client_type, @@ -120,7 +257,7 @@ void PlayerConnection::EstablishPlayer(int id, const std::string& player_name, N << client_type << ", " << client_version_string << ")"; // ensure that this connection isn't already established - if (m_ID != INVALID_PLAYER_ID || !m_player_name.empty() || m_client_type != Networking::INVALID_CLIENT_TYPE) { + if (IsEstablished()) { ErrorLogger(network) << "PlayerConnection::EstablishPlayer attempting to re-establish an already established connection."; return; } @@ -151,25 +288,50 @@ void PlayerConnection::SetClientType(Networking::ClientType client_type) { ErrorLogger(network) << "PlayerConnection client type set to INVALID_CLIENT_TYPE...?"; } +void PlayerConnection::SetAuthenticated() { + m_authenticated = true; +} + +void PlayerConnection::SetAuthRoles(const std::initializer_list& roles) { + m_roles = Networking::AuthRoles(roles); + SendMessage(SetAuthorizationRolesMessage(m_roles)); +} + +void PlayerConnection::SetAuthRoles(const Networking::AuthRoles& roles) { + m_roles = roles; + SendMessage(SetAuthorizationRolesMessage(m_roles)); +} + +void PlayerConnection::SetAuthRole(Networking::RoleType role, bool value) { + m_roles.SetRole(role, value); + SendMessage(SetAuthorizationRolesMessage(m_roles)); +} + +void PlayerConnection::SetCookie(boost::uuids::uuid cookie) +{ m_cookie = cookie; } + const std::string& PlayerConnection::ClientVersionString() const { return m_client_version_string; } -bool PlayerConnection::ClientVersionStringMatchesThisServer() const -{ return !m_client_version_string.empty() && m_client_version_string == FreeOrionVersionString(); } +bool PlayerConnection::IsBinarySerializationUsed() const { + return GetOptionsDB().Get("network.server.binary.enabled") + && !m_client_version_string.empty() + && m_client_version_string == FreeOrionVersionString(); +} -PlayerConnectionPtr PlayerConnection::NewConnection(boost::asio::io_service& io_service, +PlayerConnectionPtr PlayerConnection::NewConnection(boost::asio::io_context& io_context, MessageAndConnectionFn nonplayer_message_callback, MessageAndConnectionFn player_message_callback, ConnectionFn disconnected_callback) { return PlayerConnectionPtr( - new PlayerConnection(io_service, nonplayer_message_callback, player_message_callback, + new PlayerConnection(io_context, nonplayer_message_callback, player_message_callback, disconnected_callback)); } namespace { std::string MessageTypeName(Message::MessageType type) { - switch(type) { + switch (type) { case Message::UNDEFINED: return "Undefined"; case Message::DEBUG: return "Debug"; case Message::ERROR_MSG: return "Error"; @@ -181,7 +343,6 @@ namespace { case Message::LOBBY_EXIT: return "Lobby Exit"; case Message::START_MP_GAME: return "Start MP Game"; case Message::SAVE_GAME_INITIATE: return "Save Game"; - case Message::SAVE_GAME_DATA_REQUEST: return "Save Game"; case Message::SAVE_GAME_COMPLETE: return "Save Game"; case Message::LOAD_GAME: return "Load Game"; case Message::GAME_START: return "Game Start"; @@ -190,7 +351,6 @@ namespace { case Message::TURN_ORDERS: return "Turn Orders"; case Message::TURN_PROGRESS: return "Turn Progress"; case Message::PLAYER_STATUS: return "Player Status"; - case Message::CLIENT_SAVE_DATA: return "Client Save Data"; case Message::PLAYER_CHAT: return "Player Chat"; case Message::DIPLOMACY: return "Diplomacy"; case Message::DIPLOMATIC_STATUS: return "Diplomatic Status"; @@ -199,12 +359,22 @@ namespace { case Message::REQUEST_NEW_DESIGN_ID: return "Request New Design ID"; case Message::DISPATCH_NEW_DESIGN_ID: return "Dispatch New Design ID"; case Message::END_GAME: return "End Game"; + case Message::AI_END_GAME_ACK: return "Acknowledge Shut Down Server"; case Message::MODERATOR_ACTION: return "Moderator Action"; case Message::SHUT_DOWN_SERVER: return "Shut Down Server"; - case Message::AI_END_GAME_ACK: return "Acknowledge Shut Down Server"; case Message::REQUEST_SAVE_PREVIEWS: return "Request save previews"; + case Message::DISPATCH_SAVE_PREVIEWS: return "Dispatch save previews"; case Message::REQUEST_COMBAT_LOGS: return "Request combat logs"; - default: return "Unknown Type"; + case Message::DISPATCH_COMBAT_LOGS: return "Dispatch combat logs"; + case Message::LOGGER_CONFIG: return "Logger config"; + case Message::CHECKSUM: return "Checksum"; + case Message::AUTH_REQUEST: return "Authentication request"; + case Message::AUTH_RESPONSE: return "Authentication response"; + case Message::CHAT_HISTORY: return "Chat history"; + case Message::SET_AUTH_ROLES: return "Set authorization roles"; + case Message::ELIMINATE_SELF: return "Eliminate self"; + case Message::UNREADY: return "Unready"; + default: return std::string("Unknown Type(") + std::to_string(static_cast(type)) + ")"; }; } } @@ -275,7 +445,9 @@ void PlayerConnection::HandleMessageHeaderRead(boost::system::error_code error, } } else { if (error == boost::asio::error::eof || - error == boost::asio::error::connection_reset) { + error == boost::asio::error::connection_reset || + error == boost::asio::error::timed_out) + { EventSignal(boost::bind(m_disconnected_callback, shared_from_this())); } else { ErrorLogger(network) << "PlayerConnection::HandleMessageHeaderRead(): " @@ -287,6 +459,17 @@ void PlayerConnection::HandleMessageHeaderRead(boost::system::error_code error, assert(bytes_transferred <= Message::HeaderBufferSize); if (bytes_transferred == Message::HeaderBufferSize) { BufferToHeader(m_incoming_header_buffer, m_incoming_message); + auto msg_size = m_incoming_header_buffer[Message::Parts::SIZE]; + if (GetOptionsDB().Get("network.server.client-message-size.max") > 0 && + msg_size > GetOptionsDB().Get("network.server.client-message-size.max")) + { + ErrorLogger(network) << "PlayerConnection::HandleMessageHeaderRead(): " + << "too big message " << msg_size << " bytes "; + boost::system::error_code error; + m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); + m_socket.close(); + return; + } m_incoming_message.Resize(m_incoming_header_buffer[Message::Parts::SIZE]); boost::asio::async_read( m_socket, @@ -306,55 +489,57 @@ void PlayerConnection::AsyncReadMessage() { boost::asio::placeholders::bytes_transferred)); } -bool PlayerConnection::SyncWriteMessage(const Message& message) { - // Synchronously write and asynchronously signal the errors. This prevents PlayerConnections - // being removed from the list while iterating to transmit to multiple receivers. - Message::HeaderBuffer header_buf; - HeaderToBuffer(message, header_buf); - std::vector buffers; - buffers.push_back(boost::asio::buffer(header_buf)); - buffers.push_back(boost::asio::buffer(message.Data(), message.Size())); +void PlayerConnection::SendMessageImpl(PlayerConnectionPtr self, Message message) { + bool start_write = self->m_outgoing_messages.empty(); + self->m_outgoing_messages.push_back(Message()); + swap(self->m_outgoing_messages.back(), message); + if (start_write) + self->AsyncWriteMessage(); +} - boost::system::error_code error; - boost::asio::write(m_socket, buffers, error); +void PlayerConnection::AsyncWriteMessage() { + if (!m_valid) { + ErrorLogger(network) << "PlayerConnection::AsyncWriteMessage(): player id = " << m_ID + << ". Socket is closed. Dropping message."; + return; + } + HeaderToBuffer(m_outgoing_messages.front(), m_outgoing_header); + std::vector buffers; + buffers.push_back(boost::asio::buffer(m_outgoing_header)); + buffers.push_back(boost::asio::buffer(m_outgoing_messages.front().Data(), + m_outgoing_messages.front().Size())); + boost::asio::async_write(m_socket, buffers, + boost::bind(&PlayerConnection::HandleMessageWrite, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred)); +} + +void PlayerConnection::HandleMessageWrite(PlayerConnectionPtr self, + boost::system::error_code error, + std::size_t bytes_transferred) +{ if (error) { - ErrorLogger(network) << "PlayerConnection::WriteMessage(): player id = " << m_ID - << " error #" << error.value() << " \"" << error.message(); - boost::asio::high_resolution_timer t(m_service); - t.async_wait(boost::bind(&PlayerConnection::AsyncErrorHandler, this, error, boost::asio::placeholders::error)); + self->m_valid = false; + ErrorLogger(network) << "PlayerConnection::AsyncWriteMessage(): player id = " << self->m_ID + << " error #" << error.value() << " \"" << error.message() << "\""; + boost::asio::high_resolution_timer t(self->m_service); + t.async_wait(boost::bind(&PlayerConnection::AsyncErrorHandler, self, error, boost::asio::placeholders::error)); + return; } - return (!error); -} + if (static_cast(bytes_transferred) != static_cast(Message::HeaderBufferSize) + self->m_outgoing_header[Message::Parts::SIZE]) + return; -void PlayerConnection::AsyncErrorHandler(boost::system::error_code handled_error, boost::system::error_code error) { - EventSignal(boost::bind(m_disconnected_callback, shared_from_this())); + self->m_outgoing_messages.pop_front(); + if (!self->m_outgoing_messages.empty()) + self->AsyncWriteMessage(); } -//////////////////////////////////////////////////////////////////////////////// -// DiscoveryServer -//////////////////////////////////////////////////////////////////////////////// -DiscoveryServer::DiscoveryServer(boost::asio::io_service& io_service) : - m_socket(io_service, udp::endpoint(udp::v4(), Networking::DiscoveryPort())) -{ Listen(); } - -void DiscoveryServer::Listen() { - m_socket.async_receive_from( - boost::asio::buffer(m_recv_buffer), m_remote_endpoint, - boost::bind(&DiscoveryServer::HandleReceive, this, - boost::asio::placeholders::error)); -} +void PlayerConnection::AsyncErrorHandler(PlayerConnectionPtr self, boost::system::error_code handled_error, + boost::system::error_code error) +{ self->EventSignal(boost::bind(self->m_disconnected_callback, self)); } -void DiscoveryServer::HandleReceive(const boost::system::error_code& error) { - if (!error && - std::string(m_recv_buffer.begin(), m_recv_buffer.end()) == DISCOVERY_QUESTION) { - m_socket.send_to( - boost::asio::buffer(DISCOVERY_ANSWER + boost::asio::ip::host_name()), - m_remote_endpoint); - } - Listen(); -} //////////////////////////////////////////////////////////////////////////////// // ServerNetworking @@ -363,24 +548,17 @@ bool ServerNetworking::EstablishedPlayer::operator()( const PlayerConnectionPtr& player_connection) const { return player_connection->EstablishedPlayer(); } -ServerNetworking::ServerNetworking(boost::asio::io_service& io_service, +ServerNetworking::ServerNetworking(boost::asio::io_context& io_context, MessageAndConnectionFn nonplayer_message_callback, MessageAndConnectionFn player_message_callback, ConnectionFn disconnected_callback) : m_host_player_id(Networking::INVALID_PLAYER_ID), - m_discovery_server(nullptr), - m_player_connection_acceptor(io_service), + m_discovery_server(new DiscoveryServer(io_context)), + m_player_connection_acceptor(io_context), m_nonplayer_message_callback(nonplayer_message_callback), m_player_message_callback(player_message_callback), m_disconnected_callback(disconnected_callback) -{ - if (!GetOptionsDB().Get("singleplayer")) { - // only start discovery service for multiplayer servers. - m_discovery_server = new DiscoveryServer(io_service); - } - - Init(); -} +{ Init(); } ServerNetworking::~ServerNetworking() { delete m_discovery_server; } @@ -442,14 +620,44 @@ bool ServerNetworking::ModeratorsInGame() const { return false; } -bool ServerNetworking::SendMessageAll(const Message& message) { - bool success = true; - for (ServerNetworking::const_established_iterator player_it = established_begin(); +bool ServerNetworking::IsAvailableNameInCookies(const std::string& player_name) const { + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + for (const auto& cookie : m_cookies) { + if (cookie.second.expired >= now && cookie.second.player_name == player_name) + return false; + } + return true; +} + +bool ServerNetworking::CheckCookie(boost::uuids::uuid cookie, + const std::string& player_name, + Networking::AuthRoles& roles, + bool& authenticated) const +{ + if (cookie.is_nil()) + return false; + + auto it = m_cookies.find(cookie); + if (it != m_cookies.end() && player_name == it->second.player_name) { + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + if (it->second.expired >= now) { + roles = it->second.roles; + authenticated = it->second.authenticated; + return true; + } + } + return false; +} + +int ServerNetworking::GetCookiesSize() const +{ return m_cookies.size(); } + +void ServerNetworking::SendMessageAll(const Message& message) { + for (auto player_it = established_begin(); player_it != established_end(); ++player_it) { - success = success && (*player_it)->SendMessage(message); + (*player_it)->SendMessage(message); } - return success; } void ServerNetworking::Disconnect(int id) { @@ -504,7 +712,7 @@ ServerNetworking::established_iterator ServerNetworking::established_end() { void ServerNetworking::HandleNextEvent() { if (!m_event_queue.empty()) { - boost::function f = m_event_queue.front(); + std::function f = m_event_queue.front(); m_event_queue.pop(); f(); } @@ -513,9 +721,52 @@ void ServerNetworking::HandleNextEvent() { void ServerNetworking::SetHostPlayerID(int host_player_id) { m_host_player_id = host_player_id; } +boost::uuids::uuid ServerNetworking::GenerateCookie(const std::string& player_name, + const Networking::AuthRoles& roles, + bool authenticated) +{ + boost::uuids::uuid cookie = boost::uuids::random_generator()(); + m_cookies.erase(cookie); // remove previous cookie if exists + m_cookies.emplace(cookie, CookieData(player_name, + boost::posix_time::second_clock::local_time() + + boost::posix_time::minutes(GetOptionsDB().Get("network.server.cookies.expire-minutes")), + roles, + authenticated)); + return cookie; +} + +void ServerNetworking::UpdateCookie(boost::uuids::uuid cookie) { + if (cookie.is_nil()) + return; + + auto it = m_cookies.find(cookie); + if (it != m_cookies.end()) { + it->second.expired = boost::posix_time::second_clock::local_time() + + boost::posix_time::minutes(GetOptionsDB().Get("network.server.cookies.expire-minutes")); + } +} + +void ServerNetworking::CleanupCookies() { + std::unordered_set> to_delete; + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + // clean up expired cookies + for (const auto& cookie : m_cookies) { + if (cookie.second.expired < now) + to_delete.insert(cookie.first); + } + // don't clean up cookies from active connections + for (auto it = established_begin(); + it != established_end(); ++it) + { + to_delete.erase((*it)->Cookie()); + } + for (auto cookie : to_delete) + m_cookies.erase(cookie); +} + void ServerNetworking::Init() { // use a dual stack (ipv6 + ipv4) socket - tcp::endpoint endpoint(tcp::v6(), Networking::MessagePort()); + tcp::endpoint message_endpoint(tcp::v6(), Networking::MessagePort()); if (GetOptionsDB().Get("singleplayer")) { // when hosting a single player game only accept connections from @@ -523,47 +774,61 @@ void ServerNetworking::Init() { // interface. // This should prevent unnecessary triggering of Desktop Firewalls as // reported by various users when running single player games. - endpoint.address(boost::asio::ip::address_v4::loopback()); + message_endpoint.address(boost::asio::ip::address_v4::loopback()); } - m_player_connection_acceptor.open(endpoint.protocol()); + try { + m_player_connection_acceptor.open(message_endpoint.protocol()); + } catch (const std::exception &e) { + ErrorLogger(network) << "Server cannot open IPv6 socket: " << e.what() + << ". Fallback to IPv4"; + message_endpoint = tcp::endpoint(tcp::v4(), Networking::MessagePort()); + if (GetOptionsDB().Get("singleplayer")) + message_endpoint.address(boost::asio::ip::address_v4::loopback()); + + m_player_connection_acceptor.open(message_endpoint.protocol()); + } m_player_connection_acceptor.set_option( boost::asio::socket_base::reuse_address(true)); - if (endpoint.protocol() == boost::asio::ip::tcp::v6()) + if (message_endpoint.protocol() == boost::asio::ip::tcp::v6()) m_player_connection_acceptor.set_option( boost::asio::ip::v6_only(false)); // may be true by default on some systems m_player_connection_acceptor.set_option( boost::asio::socket_base::linger(true, SOCKET_LINGER_TIME)); - m_player_connection_acceptor.bind(endpoint); + m_player_connection_acceptor.bind(message_endpoint); m_player_connection_acceptor.listen(); - AcceptNextConnection(); + + AcceptNextMessagingConnection(); } -void ServerNetworking::AcceptNextConnection() { - PlayerConnectionPtr next_connection = - PlayerConnection::NewConnection( - m_player_connection_acceptor.get_io_service(), - m_nonplayer_message_callback, - m_player_message_callback, - boost::bind(&ServerNetworking::DisconnectImpl, this, _1)); +void ServerNetworking::AcceptNextMessagingConnection() { + auto next_connection = PlayerConnection::NewConnection( +#if BOOST_VERSION >= 106600 + m_player_connection_acceptor.get_executor().context(), +#else + m_player_connection_acceptor.get_io_service(), +#endif + m_nonplayer_message_callback, + m_player_message_callback, + boost::bind(&ServerNetworking::DisconnectImpl, this, _1)); next_connection->EventSignal.connect( boost::bind(&ServerNetworking::EnqueueEvent, this, _1)); m_player_connection_acceptor.async_accept( next_connection->m_socket, - boost::bind(&ServerNetworking::AcceptConnection, + boost::bind(&ServerNetworking::AcceptPlayerMessagingConnection, this, next_connection, boost::asio::placeholders::error)); } -void ServerNetworking::AcceptConnection(PlayerConnectionPtr player_connection, - const boost::system::error_code& error) +void ServerNetworking::AcceptPlayerMessagingConnection(PlayerConnectionPtr player_connection, + const boost::system::error_code& error) { if (!error) { - TraceLogger(network) << "ServerNetworking::AcceptConnection : connected to new player"; + TraceLogger(network) << "ServerNetworking::AcceptPlayerMessagingConnection : connected to new player"; m_player_connections.insert(player_connection); player_connection->Start(); - AcceptNextConnection(); + AcceptNextMessagingConnection(); } else { throw error; } diff --git a/network/ServerNetworking.h b/server/ServerNetworking.h similarity index 59% rename from network/ServerNetworking.h rename to server/ServerNetworking.h index 359bcd817e6..efecff799ae 100644 --- a/network/ServerNetworking.h +++ b/server/ServerNetworking.h @@ -1,25 +1,56 @@ #ifndef _ServerNetworking_h_ #define _ServerNetworking_h_ -#include "Message.h" +#include "../network/Message.h" #include -#include #include #include +#include +#include #include #include #include - +#include class DiscoveryServer; class PlayerConnection; typedef std::shared_ptr PlayerConnectionPtr; -typedef boost::function MessageAndConnectionFn; -typedef boost::function ConnectionFn; -typedef boost::function NullaryFn; +typedef std::function MessageAndConnectionFn; +typedef std::function ConnectionFn; +typedef std::function NullaryFn; + +/** Data associated with cookie */ +struct CookieData { + std::string player_name; + boost::posix_time::ptime expired; + Networking::AuthRoles roles; + bool authenticated; + + CookieData(const std::string& player_name_, + const boost::posix_time::ptime& expired_, + const Networking::AuthRoles& roles_, + bool authenticated_) : + player_name(player_name_), + expired(expired_), + roles(roles_), + authenticated(authenticated_) + {} +}; + + +/** In Boost 1.66, io_service was replaced with a typedef of io_context. + * That typedef was removed in Boost 1.70 along with other interface changes. + * This code uses io_context for future compatibility and adds the typedef + * here for old versions of Boost. */ +#if BOOST_VERSION < 106600 +namespace boost { namespace asio { + typedef io_service io_context; +}} +#endif + /** Encapsulates the networking facilities of the server. This class listens for incoming UDP LAN server-discovery requests and TCP player connections. @@ -38,7 +69,7 @@ class ServerNetworking { typedef boost::filter_iterator const_established_iterator; /** \name Structors */ //@{ - ServerNetworking(boost::asio::io_service& io_service, + ServerNetworking(boost::asio::io_context& io_context, MessageAndConnectionFn nonplayer_message_callback, MessageAndConnectionFn player_message_callback, ConnectionFn disconnected_callback); @@ -85,11 +116,24 @@ class ServerNetworking { /** Returns whether there are any moderators in the game. */ bool ModeratorsInGame() const; + + /** Returns whether there no non-expired cookie with this player name. */ + bool IsAvailableNameInCookies(const std::string& player_name) const; + + /** Returns whether player have non-expired cookie with this player name. + * Fills roles and authentication status on success. */ + bool CheckCookie(boost::uuids::uuid cookie, + const std::string& player_name, + Networking::AuthRoles& roles, + bool& authenticated) const; + + /** Returns count of stored cookies so we don't collide with reserved player names. */ + int GetCookiesSize() const; //@} /** \name Mutators */ //@{ - /** Sends a synchronous message \a message to the player indicated in the message and returns true on success. */ - bool SendMessageAll(const Message& message); + /** Sends a synchronous message \a message to the all established players. */ + void SendMessageAll(const Message& message); /** Disconnects the server from player \a id. */ void Disconnect(int id); @@ -124,22 +168,39 @@ class ServerNetworking { /** Sets Host player ID. */ void SetHostPlayerID(int host_player_id); + + /** Generate cookies for player's name, roles, and authentication status. */ + boost::uuids::uuid GenerateCookie(const std::string& player_name, + const Networking::AuthRoles& roles, + bool authenticated); + + /** Bump cookie's expired date. */ + void UpdateCookie(boost::uuids::uuid cookie); + + /** Clean up expired cookies. */ + void CleanupCookies(); //@} private: void Init(); - void AcceptNextConnection(); - void AcceptConnection(PlayerConnectionPtr player_connection, - const boost::system::error_code& error); + void AcceptNextMessagingConnection(); + void AcceptPlayerMessagingConnection(PlayerConnectionPtr player_connection, + const boost::system::error_code& error); void DisconnectImpl(PlayerConnectionPtr player_connection); void EnqueueEvent(const NullaryFn& fn); int m_host_player_id; DiscoveryServer* m_discovery_server; +#if BOOST_VERSION >= 107000 + boost::asio::basic_socket_acceptor + m_player_connection_acceptor; +#else boost::asio::ip::tcp::acceptor m_player_connection_acceptor; +#endif PlayerConnections m_player_connections; std::queue m_event_queue; + std::unordered_map> m_cookies; MessageAndConnectionFn m_nonplayer_message_callback; MessageAndConnectionFn m_player_message_callback; @@ -181,21 +242,36 @@ class PlayerConnection : /** Returns the version string the client provided when joining. */ const std::string& ClientVersionString() const; - /** Returns true iff the client's specified version string matches the - * version string of this server process. */ - bool ClientVersionStringMatchesThisServer() const; + /** Checks if the server will enable binary serialization for this client's connection. */ + bool IsBinarySerializationUsed() const; /** Checks if client associated with this connection runs on the same physical machine as the server */ bool IsLocalConnection() const; + + /** Checks if the player is established, has a valid name, id and client type. */ + bool IsEstablished() const; + + /** Checks if the player was authenticated. */ + bool IsAuthenticated() const; + + /** Checks if the player has a some role */ + bool HasAuthRole(Networking::RoleType role) const; + + /** Get cookie associated with this connection. */ + boost::uuids::uuid Cookie() const; //@} /** \name Mutators */ //@{ /** Starts the connection reading incoming messages on its socket. */ void Start(); - /** Sends \a synchronous message to out on the connection and return true on success. */ - bool SendMessage(const Message& message); + /** Sends \a synchronous message to out on the connection. */ + void SendMessage(const Message& message); + + /** Set player properties to use them after authentication successed. */ + void AwaitPlayer(Networking::ClientType client_type, + const std::string& client_version_string); /** Establishes a connection as a player with a specific name and id. This function must only be called once. */ @@ -205,34 +281,61 @@ class PlayerConnection : /** Sets this connection's client type. Useful for already-connected players * changing type such as in the multiplayer lobby. */ void SetClientType(Networking::ClientType client_type); + + /** Sets authenticated status for connection. */ + void SetAuthenticated(); + + /** Sets authorization roles and send message to client. */ + void SetAuthRoles(const std::initializer_list& roles); + + void SetAuthRoles(const Networking::AuthRoles& roles); + + /** Sets or unset authorizaion role and send message to client. */ + void SetAuthRole(Networking::RoleType role, bool value = true); + + /** Sets cookie value to this connection to update expire date. */ + void SetCookie(boost::uuids::uuid cookie); //@} mutable boost::signals2::signal EventSignal; /** Creates a new PlayerConnection and returns it as a shared_ptr. */ static PlayerConnectionPtr - NewConnection(boost::asio::io_service& io_service, MessageAndConnectionFn nonplayer_message_callback, + NewConnection(boost::asio::io_context& io_context, MessageAndConnectionFn nonplayer_message_callback, MessageAndConnectionFn player_message_callback, ConnectionFn disconnected_callback); private: - PlayerConnection(boost::asio::io_service& io_service, MessageAndConnectionFn nonplayer_message_callback, + PlayerConnection(boost::asio::io_context& io_context, MessageAndConnectionFn nonplayer_message_callback, MessageAndConnectionFn player_message_callback, ConnectionFn disconnected_callback); void HandleMessageBodyRead(boost::system::error_code error, std::size_t bytes_transferred); void HandleMessageHeaderRead(boost::system::error_code error, std::size_t bytes_transferred); void AsyncReadMessage(); - bool SyncWriteMessage(const Message& message); - void AsyncErrorHandler(boost::system::error_code handled_error, boost::system::error_code error); + void AsyncWriteMessage(); + static void HandleMessageWrite(PlayerConnectionPtr self, + boost::system::error_code error, + std::size_t bytes_transferred); + + /** Places message to the end of sending queue and start asynchronous write if \a message was + first in the queue. */ + static void SendMessageImpl(PlayerConnectionPtr self, Message message); + static void AsyncErrorHandler(PlayerConnectionPtr self, boost::system::error_code handled_error, boost::system::error_code error); - boost::asio::io_service& m_service; + boost::asio::io_context& m_service; boost::asio::ip::tcp::socket m_socket; Message::HeaderBuffer m_incoming_header_buffer; Message m_incoming_message; - int m_ID; + Message::HeaderBuffer m_outgoing_header; + std::list m_outgoing_messages; + int m_ID = Networking::INVALID_PLAYER_ID; std::string m_player_name; - bool m_new_connection; - Networking::ClientType m_client_type; + bool m_new_connection = true; + Networking::ClientType m_client_type = Networking::INVALID_CLIENT_TYPE; std::string m_client_version_string; + bool m_authenticated = false; + Networking::AuthRoles m_roles; + boost::uuids::uuid m_cookie; + bool m_valid = true; MessageAndConnectionFn m_nonplayer_message_callback; MessageAndConnectionFn m_player_message_callback; @@ -241,4 +344,4 @@ class PlayerConnection : friend class ServerNetworking; }; -#endif +#endif // _ServerNetworking_h_ diff --git a/python/server/ServerWrapper.cpp b/server/ServerWrapper.cpp similarity index 72% rename from python/server/ServerWrapper.cpp rename to server/ServerWrapper.cpp index 276a6880681..5251012d527 100644 --- a/python/server/ServerWrapper.cpp +++ b/server/ServerWrapper.cpp @@ -1,35 +1,41 @@ #include "ServerWrapper.h" -#include "../../universe/Condition.h" -#include "../../universe/Species.h" -#include "../../universe/Special.h" -#include "../../universe/System.h" -#include "../../universe/Planet.h" -#include "../../universe/Building.h" -#include "../../universe/Fleet.h" -#include "../../universe/Ship.h" -#include "../../universe/Field.h" -#include "../../universe/Tech.h" -#include "../../universe/Pathfinder.h" -#include "../../universe/Universe.h" -#include "../../universe/UniverseGenerator.h" -#include "../../universe/Enums.h" -#include "../../universe/ValueRef.h" - -#include "../../server/ServerApp.h" -#include "../../util/Directories.h" -#include "../../util/Logger.h" -#include "../../util/Random.h" -#include "../../util/i18n.h" -#include "../../util/OptionsDB.h" -#include "../../util/SitRepEntry.h" -#include "../../parse/Parse.h" - -#include "../../Empire/Empire.h" -#include "../../Empire/EmpireManager.h" - -#include "../SetWrapper.h" -#include "../CommonWrappers.h" +#include "ServerApp.h" +#include "UniverseGenerator.h" + +#include "../universe/Condition.h" +#include "../universe/ScriptingContext.h" +#include "../universe/Species.h" +#include "../universe/Special.h" +#include "../universe/System.h" +#include "../universe/Planet.h" +#include "../universe/Building.h" +#include "../universe/BuildingType.h" +#include "../universe/Fleet.h" +#include "../universe/FleetPlan.h" +#include "../universe/Ship.h" +#include "../universe/ShipDesign.h" +#include "../universe/Field.h" +#include "../universe/FieldType.h" +#include "../universe/Tech.h" +#include "../universe/Pathfinder.h" +#include "../universe/Universe.h" +#include "../universe/UnlockableItem.h" +#include "../universe/Enums.h" +#include "../universe/ValueRef.h" + +#include "../util/Directories.h" +#include "../util/Logger.h" +#include "../util/Random.h" +#include "../util/i18n.h" +#include "../util/OptionsDB.h" +#include "../util/SitRepEntry.h" + +#include "../Empire/Empire.h" +#include "../Empire/EmpireManager.h" + +#include "../python/SetWrapper.h" +#include "../python/CommonWrappers.h" #include #include @@ -60,7 +66,6 @@ using boost::python::return_internal_reference; using boost::python::object; using boost::python::import; -using boost::python::error_already_set; using boost::python::exec; using boost::python::dict; using boost::python::list; @@ -68,6 +73,7 @@ using boost::python::tuple; using boost::python::make_tuple; using boost::python::extract; using boost::python::len; +using boost::python::long_; FO_COMMON_API extern const int INVALID_DESIGN_ID; @@ -90,12 +96,12 @@ namespace { // Wrapper for GetResourceDir object GetResourceDirWrapper() - { return object(PathString(GetResourceDir())); } + { return object(PathToString(GetResourceDir())); } // Wrapper for getting empire objects list GetAllEmpires() { list empire_list; - for (const std::map::value_type& entry : Empires()) + for (const auto& entry : Empires()) empire_list.append(entry.second->EmpireID()); return empire_list; } @@ -112,13 +118,14 @@ namespace { for (int i = 0; i < len(py_params); i++) { std::string k = extract(py_params.keys()[i]); std::string v = extract(py_params.values()[i]); - params.push_back(std::make_pair(k, v)); + params.push_back({k, v}); } } if (empire_id == ALL_EMPIRES) { - for (const std::map::value_type& entry : Empires()) { - entry.second->AddSitRepEntry(CreateSitRep(template_string, sitrep_turn, icon, params)); + for (const auto& entry : Empires()) { + entry.second->AddSitRepEntry(CreateSitRep(template_string, sitrep_turn, + icon, params)); } } else { Empire* empire = GetEmpire(empire_id); @@ -183,7 +190,7 @@ namespace { list GetAllSpecies() { list species_list; - for (const std::map::value_type& entry : GetSpeciesManager()) { + for (const auto& entry : GetSpeciesManager()) { species_list.append(object(entry.first)); } return species_list; @@ -192,31 +199,31 @@ namespace { list GetPlayableSpecies() { list species_list; SpeciesManager& species_manager = GetSpeciesManager(); - for (SpeciesManager::playable_iterator it = species_manager.playable_begin(); it != species_manager.playable_end(); ++it) { - species_list.append(object(it->first)); - } + for (auto it = species_manager.playable_begin(); + it != species_manager.playable_end(); ++it) + { species_list.append(object(it->first)); } return species_list; } list GetNativeSpecies() { list species_list; SpeciesManager& species_manager = GetSpeciesManager(); - for (SpeciesManager::native_iterator it = species_manager.native_begin(); it != species_manager.native_end(); ++it) { - species_list.append(object(it->first)); - } + for (auto it = species_manager.native_begin(); + it != species_manager.native_end(); ++it) + { species_list.append(object(it->first)); } return species_list; } //Checks the condition against many objects at once. //Checking many systems is more efficient because for example monster fleet plans //typically uses WithinStarLaneJumps to exclude placement near empires. - list FilterIDsWithCondition(const Condition::ConditionBase* cond, const list &obj_ids) { + list FilterIDsWithCondition(const Condition::Condition* cond, const list &obj_ids) { list permitted_ids; Condition::ObjectSet objs; boost::python::stl_input_iterator end; for (boost::python::stl_input_iterator id(obj_ids); id != end; ++id) { - if (std::shared_ptr obj = GetUniverseObject(*id)) + if (auto obj = Objects().get(*id)) objs.push_back(obj); else ErrorLogger() << "FilterIDsWithCondition:: Passed an invalid universe object id " << *id; @@ -230,10 +237,10 @@ namespace { // get location condition and evaluate it with the specified universe object // if no location condition has been defined, all objects matches - if (cond) + if (cond && cond->SourceInvariant()) cond->Eval(ScriptingContext(), permitted_objs, objs); else - permitted_objs = objs; + permitted_objs = std::move(objs); for (auto &obj : permitted_objs) { permitted_ids.append(obj->ID()); @@ -261,7 +268,7 @@ namespace { return special->SpawnLimit(); } - list SpecialLocations(const std::string special_name, list object_ids) { + list SpecialLocations(const std::string special_name, const list& object_ids) { // get special and check if it exists const Special* special = GetSpecial(special_name); if (!special) { @@ -284,7 +291,7 @@ namespace { list GetAllSpecials() { list py_specials; - for (const std::string& special_name : SpecialNames()) { + for (const auto& special_name : SpecialNames()) { py_specials.append(object(special_name)); } return py_specials; @@ -309,13 +316,15 @@ namespace { return SetEmpireHomeworld(empire, planet_id, species_name); } - void EmpireUnlockItem(int empire_id, UnlockableItemType item_type, const std::string& item_name) { + void EmpireUnlockItem(int empire_id, UnlockableItemType item_type, + const std::string& item_name) + { Empire* empire = GetEmpire(empire_id); if (!empire) { ErrorLogger() << "EmpireUnlockItem: couldn't get empire with ID " << empire_id; return; } - ItemSpec item = ItemSpec(item_type, item_name); + UnlockableItem item = UnlockableItem(item_type, item_name); empire->UnlockItem(item); } @@ -340,11 +349,10 @@ namespace { } // Wrapper for preunlocked items - list LoadItemSpecList() { + list LoadUnlockableItemList() { list py_items; - std::vector items; - parse::items(items); - for (const ItemSpec& item : items) { + auto& items = GetUniverse().InitiallyUnlockedItems(); + for (const auto& item : items) { py_items.append(object(item)); } return py_items; @@ -353,8 +361,7 @@ namespace { // Wrapper for starting buildings list LoadStartingBuildings() { list py_items; - std::vector buildings; - parse::starting_buildings(buildings); + auto& buildings = GetUniverse().InitiallyUnlockedBuildings(); for (auto building : buildings) { if (GetBuildingType(building.name)) py_items.append(object(building)); @@ -365,9 +372,10 @@ namespace { } // Wrappers for ship designs and premade ship designs - bool ShipDesignCreate(const std::string& name, const std::string& description, const std::string& hull, - const list& py_parts, const std::string& icon, const std::string& model, - bool monster) + bool ShipDesignCreate(const std::string& name, const std::string& description, + const std::string& hull, const list& py_parts, + const std::string& icon, const std::string& model, + bool monster) { Universe& universe = GetUniverse(); // Check for empty name @@ -392,7 +400,8 @@ namespace { // Create the design and add it to the universe ShipDesign* design; try { - design = new ShipDesign(std::invalid_argument(""), name, description, BEFORE_FIRST_TURN, ALL_EMPIRES, + design = new ShipDesign(std::invalid_argument(""), name, description, + BEFORE_FIRST_TURN, ALL_EMPIRES, hull, parts, icon, model, true, monster); } catch (const std::invalid_argument&) { ErrorLogger() << "CreateShipDesign: invalid ship design"; @@ -418,7 +427,7 @@ namespace { list ShipDesignGetMonsterList() { list py_monster_designs; - const PredefinedShipDesignManager& manager = GetPredefinedShipDesignManager(); + const auto& manager = GetPredefinedShipDesignManager(); for (const auto& monster : manager.GetOrderedMonsterDesigns()) { py_monster_designs.append(object(monster->Name(false))); } @@ -429,8 +438,9 @@ namespace { class FleetPlanWrapper { public: // name ctors - FleetPlanWrapper(FleetPlan* fleet_plan) - { m_fleet_plan = fleet_plan; } + FleetPlanWrapper(FleetPlan* fleet_plan) : + m_fleet_plan(std::make_shared(*fleet_plan)) + {} FleetPlanWrapper(const std::string& fleet_name, const list& py_designs) { @@ -438,19 +448,16 @@ namespace { for (int i = 0; i < len(py_designs); i++) { designs.push_back(extract(py_designs[i])); } - m_fleet_plan = new FleetPlan(fleet_name, designs, false); + m_fleet_plan = std::make_shared(fleet_name, designs, false); } - virtual ~FleetPlanWrapper() - { delete m_fleet_plan; } - // name accessors object Name() { return object(m_fleet_plan->Name()); } list ShipDesigns() { list py_designs; - for (const std::string& design_name : m_fleet_plan->ShipDesigns()) { + for (const auto& design_name : m_fleet_plan->ShipDesigns()) { py_designs.append(object(design_name)); } return list(py_designs); @@ -460,26 +467,26 @@ namespace { { return *m_fleet_plan; } private: - FleetPlan* m_fleet_plan; + // Use shared_ptr insead of unique_ptr because boost::python requires a deleter + std::shared_ptr m_fleet_plan; }; list LoadFleetPlanList() { list py_fleet_plans; - std::vector fleet_plans; - parse::fleet_plans(fleet_plans); + auto&& fleet_plans = GetUniverse().InitiallyUnlockedFleetPlans(); for (FleetPlan* fleet_plan : fleet_plans) { - py_fleet_plans.append(new FleetPlanWrapper(fleet_plan)); + py_fleet_plans.append(FleetPlanWrapper(fleet_plan)); } - return list(py_fleet_plans); + return py_fleet_plans; } // Wrappers for starting monster fleet plans class MonsterFleetPlanWrapper { public: // name ctors - MonsterFleetPlanWrapper(MonsterFleetPlan* monster_fleet_plan) { - m_monster_fleet_plan = monster_fleet_plan; - } + MonsterFleetPlanWrapper(MonsterFleetPlan* monster_fleet_plan) : + m_monster_fleet_plan(std::make_shared(*monster_fleet_plan)) + {} MonsterFleetPlanWrapper(const std::string& fleet_name, const list& py_designs, double spawn_rate, int spawn_limit) @@ -489,20 +496,17 @@ namespace { designs.push_back(extract(py_designs[i])); } m_monster_fleet_plan = - new MonsterFleetPlan(fleet_name, designs, spawn_rate, - spawn_limit, 0, false); + std::make_shared(fleet_name, designs, spawn_rate, + spawn_limit, nullptr, false); } - virtual ~MonsterFleetPlanWrapper() - { delete m_monster_fleet_plan; } - // name accessors object Name() { return object(m_monster_fleet_plan->Name()); } list ShipDesigns() { list py_designs; - for (const std::string& design_name : m_monster_fleet_plan->ShipDesigns()) { + for (const auto& design_name : m_monster_fleet_plan->ShipDesigns()) { py_designs.append(object(design_name)); } return list(py_designs); @@ -522,17 +526,17 @@ namespace { { return *m_monster_fleet_plan; } private: - MonsterFleetPlan* m_monster_fleet_plan; + // Use shared_ptr insead of unique_ptr because boost::python requires a deleter + std::shared_ptr m_monster_fleet_plan; }; list LoadMonsterFleetPlanList() { list py_monster_fleet_plans; - std::vector monster_fleet_plans; - parse::monster_fleet_plans(monster_fleet_plans); - for (MonsterFleetPlan* fleet_plan : monster_fleet_plans) { - py_monster_fleet_plans.append(new MonsterFleetPlanWrapper(fleet_plan)); + auto&& monster_fleet_plans = GetUniverse().MonsterFleetPlans(); + for (auto* fleet_plan : monster_fleet_plans) { + py_monster_fleet_plans.append(MonsterFleetPlanWrapper(fleet_plan)); } - return list(py_monster_fleet_plans); + return py_monster_fleet_plans; } // Wrappers for the various universe object classes member funtions @@ -543,7 +547,7 @@ namespace { // // Wrappers for common UniverseObject class member funtions object GetName(int object_id) { - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); if (!obj) { ErrorLogger() << "GetName: Couldn't get object with ID " << object_id; return object(""); @@ -552,7 +556,7 @@ namespace { } void SetName(int object_id, const std::string& name) { - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); if (!obj) { ErrorLogger() << "RenameUniverseObject: Couldn't get object with ID " << object_id; return; @@ -561,7 +565,7 @@ namespace { } double GetX(int object_id) { - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); if (!obj) { ErrorLogger() << "GetX: Couldn't get object with ID " << object_id; return UniverseObject::INVALID_POSITION; @@ -570,7 +574,7 @@ namespace { } double GetY(int object_id) { - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); if (!obj) { ErrorLogger() << "GetY: Couldn't get object with ID " << object_id; return UniverseObject::INVALID_POSITION; @@ -579,16 +583,17 @@ namespace { } tuple GetPos(int object_id) { - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); if (!obj) { ErrorLogger() << "GetPos: Couldn't get object with ID " << object_id; - return make_tuple(UniverseObject::INVALID_POSITION, UniverseObject::INVALID_POSITION); + return make_tuple(UniverseObject::INVALID_POSITION, + UniverseObject::INVALID_POSITION); } return make_tuple(obj->X(), obj->Y()); } int GetOwner(int object_id) { - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); if (!obj) { ErrorLogger() << "GetOwner: Couldn't get object with ID " << object_id; return ALL_EMPIRES; @@ -598,7 +603,7 @@ namespace { void AddSpecial(int object_id, const std::string special_name) { // get the universe object and check if it exists - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); if (!obj) { ErrorLogger() << "AddSpecial: Couldn't get object with ID " << object_id; return; @@ -617,7 +622,7 @@ namespace { void RemoveSpecial(int object_id, const std::string special_name) { // get the universe object and check if it exists - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); if (!obj) { ErrorLogger() << "RemoveSpecial: Couldn't get object with ID " << object_id; return; @@ -645,16 +650,16 @@ namespace { list GetAllObjects() { list py_all_objects; - for (int object_id : Objects().FindObjectIDs()) { - py_all_objects.append(object_id); + for (const auto& object : Objects().all()) { + py_all_objects.append(object->ID()); } return py_all_objects; } list GetSystems() { list py_systems; - for (int system_id : Objects().FindObjectIDs()) { - py_systems.append(system_id); + for (const auto& system : Objects().all()) { + py_systems.append(system->ID()); } return py_systems; } @@ -677,7 +682,7 @@ namespace { } int CreatePlanet(PlanetSize size, PlanetType planet_type, int system_id, int orbit, const std::string& name) { - std::shared_ptr system = Objects().Object(system_id); + auto system = Objects().get(system_id); // Perform some validity checks // Check if system with id system_id exists @@ -735,13 +740,13 @@ namespace { } int CreateBuilding(const std::string& building_type, int planet_id, int empire_id) { - std::shared_ptr planet = Objects().Object(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "CreateBuilding: couldn't get planet with ID " << planet_id; return INVALID_OBJECT_ID; } - std::shared_ptr system = GetSystem(planet->SystemID()); + auto system = Objects().get(planet->SystemID()); if (!system) { ErrorLogger() << "CreateBuilding: couldn't get system for planet"; return INVALID_OBJECT_ID; @@ -765,9 +770,9 @@ namespace { return building->ID(); } - int CreateFleet(const std::string& name, int system_id, int empire_id) { + int CreateFleet(const std::string& name, int system_id, int empire_id, bool aggressive = false) { // Get system and check if it exists - std::shared_ptr system = Objects().Object(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "CreateFleet: couldn't get system with ID " << system_id; return INVALID_OBJECT_ID; @@ -789,6 +794,8 @@ namespace { fleet->Rename(UserString("OBJ_FLEET") + " " + std::to_string(fleet->ID())); } + fleet->SetAggressive(aggressive); + // return fleet ID return fleet->ID(); } @@ -810,13 +817,13 @@ namespace { } // get fleet and check if it exists - std::shared_ptr fleet = GetFleet(fleet_id); + auto fleet = Objects().get(fleet_id); if (!fleet) { ErrorLogger() << "CreateShip: couldn't get fleet with ID " << fleet_id; return INVALID_OBJECT_ID; } - std::shared_ptr system = GetSystem(fleet->SystemID()); + auto system = Objects().get(fleet->SystemID()); if (!system) { ErrorLogger() << "CreateShip: couldn't get system for fleet"; return INVALID_OBJECT_ID; @@ -861,8 +868,7 @@ namespace { // add ship to fleet, this also moves the ship to the // fleets location and inserts it into the system - fleet->AddShip(ship->ID()); - fleet->SetAggressive(fleet->HasArmedShips() || fleet->HasFighterShips()); + fleet->AddShips({ship->ID()}); ship->SetFleetID(fleet->ID()); // set the meters of the ship to max values @@ -877,12 +883,14 @@ namespace { } int CreateMonsterFleet(int system_id) - { return CreateFleet(UserString("MONSTERS"), system_id, ALL_EMPIRES); } + { return CreateFleet(UserString("MONSTERS"), system_id, ALL_EMPIRES, true); } int CreateMonster(const std::string& design_name, int fleet_id) { return CreateShip(NewMonsterName(), design_name, "", fleet_id); } - std::shared_ptr CreateFieldImpl(const std::string& field_type_name, double x, double y, double size) { + std::shared_ptr CreateFieldImpl(const std::string& field_type_name, + double x, double y, double size) + { // check if a field type with the specified field type name exists and get the field type const FieldType* field_type = GetFieldType(field_type_name); if (!field_type) { @@ -913,7 +921,7 @@ namespace { } int CreateField(const std::string& field_type_name, double x, double y, double size) { - std::shared_ptr field = CreateFieldImpl(field_type_name, x, y, size); + auto field = CreateFieldImpl(field_type_name, x, y, size); if (field) return field->ID(); else @@ -922,13 +930,13 @@ namespace { int CreateFieldInSystem(const std::string& field_type_name, double size, int system_id) { // check if system exists and get system - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "CreateFieldInSystem: couldn't get system with ID" << system_id; return INVALID_OBJECT_ID; } // create the field with the coordinates of the system - std::shared_ptr field = CreateFieldImpl(field_type_name, system->X(), system->Y(), size); + auto field = CreateFieldImpl(field_type_name, system->X(), system->Y(), size); if (!field) return INVALID_OBJECT_ID; system->Insert(field); // insert the field into the system @@ -936,12 +944,12 @@ namespace { } // Return a list of system ids of universe objects with @p obj_ids. - list ObjectsGetSystems(list obj_ids) { + list ObjectsGetSystems(const list& obj_ids) { list py_systems; boost::python::stl_input_iterator end; for (boost::python::stl_input_iterator id(obj_ids); id != end; ++id) { - if (std::shared_ptr obj = GetUniverseObject(*id)) { + if (auto obj = Objects().get(*id)) { py_systems.append(obj->SystemID()); } else { ErrorLogger() << "Passed an invalid universe object id " << *id; @@ -952,7 +960,7 @@ namespace { } // Return all systems within \p jumps of \p sys_ids - list SystemsWithinJumps(size_t jumps, list sys_ids) { + list SystemsWithinJumps(size_t jumps, const list& sys_ids) { list py_systems; boost::python::stl_input_iterator end; @@ -972,7 +980,7 @@ namespace { // Wrappers for System class member functions StarType SystemGetStarType(int system_id) { - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "SystemGetStarType: couldn't get system with ID " << system_id; return INVALID_STAR_TYPE; @@ -987,7 +995,7 @@ namespace { return; } - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "SystemSetStarType : Couldn't get system with ID " << system_id; return; @@ -997,7 +1005,7 @@ namespace { } int SystemGetNumOrbits(int system_id) { - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "SystemGetNumOrbits : Couldn't get system with ID " << system_id; return 0; @@ -1007,7 +1015,7 @@ namespace { list SystemFreeOrbits(int system_id) { list py_orbits; - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "SystemFreeOrbits : Couldn't get system with ID " << system_id; return py_orbits; @@ -1018,7 +1026,7 @@ namespace { } bool SystemOrbitOccupied(int system_id, int orbit) { - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "SystemOrbitOccupied : Couldn't get system with ID " << system_id; return 0; @@ -1027,7 +1035,7 @@ namespace { } int SystemOrbitOfPlanet(int system_id, int planet_id) { - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "SystemOrbitOfPlanet : Couldn't get system with ID " << system_id; return 0; @@ -1037,7 +1045,7 @@ namespace { list SystemGetPlanets(int system_id) { list py_planets; - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "SystemGetPlanets : Couldn't get system with ID " << system_id; return py_planets; @@ -1049,7 +1057,7 @@ namespace { list SystemGetFleets(int system_id) { list py_fleets; - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "SystemGetFleets : Couldn't get system with ID " << system_id; return py_fleets; @@ -1062,7 +1070,7 @@ namespace { list SystemGetStarlanes(int system_id) { list py_starlanes; // get source system - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "SystemGetStarlanes : Couldn't get system with ID " << system_id; return py_starlanes; @@ -1070,7 +1078,7 @@ namespace { // get list of systems the source system has starlanes to // we actually get a map of ids and a bool indicating if the entry is a starlane (false) or wormhole (true) // iterate over the map we got, only copy starlanes to the python list object we are going to return - for (const std::map::value_type& lane : system->StarlanesWormholes()) { + for (const auto& lane : system->StarlanesWormholes()) { // if the bool value is false, we have a starlane // in this case copy the destination system id to our starlane list if (!(lane.second)) { @@ -1082,12 +1090,12 @@ namespace { void SystemAddStarlane(int from_sys_id, int to_sys_id) { // get source and destination system, check that both exist - std::shared_ptr from_sys = GetSystem(from_sys_id); + auto from_sys = Objects().get(from_sys_id); if (!from_sys) { ErrorLogger() << "SystemAddStarlane : Couldn't find system with ID " << from_sys_id; return; } - std::shared_ptr to_sys = GetSystem(to_sys_id); + auto to_sys = Objects().get(to_sys_id); if (!to_sys) { ErrorLogger() << "SystemAddStarlane : Couldn't find system with ID " << to_sys_id; return; @@ -1099,12 +1107,12 @@ namespace { void SystemRemoveStarlane(int from_sys_id, int to_sys_id) { // get source and destination system, check that both exist - std::shared_ptr from_sys = GetSystem(from_sys_id); + auto from_sys = Objects().get(from_sys_id); if (!from_sys) { ErrorLogger() << "SystemRemoveStarlane : Couldn't find system with ID " << from_sys_id; return; } - std::shared_ptr to_sys = GetSystem(to_sys_id); + auto to_sys = Objects().get(to_sys_id); if (!to_sys) { ErrorLogger() << "SystemRemoveStarlane : Couldn't find system with ID " << to_sys_id; return; @@ -1116,7 +1124,7 @@ namespace { // Wrapper for Planet class member functions PlanetType PlanetGetType(int planet_id) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetGetType: Couldn't get planet with ID " << planet_id; return INVALID_PLANET_TYPE; @@ -1125,7 +1133,7 @@ namespace { } void PlanetSetType(int planet_id, PlanetType planet_type) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetSetType: Couldn't get planet with ID " << planet_id; return; @@ -1143,7 +1151,7 @@ namespace { } PlanetSize PlanetGetSize(int planet_id) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetGetSize: Couldn't get planet with ID " << planet_id; return INVALID_PLANET_SIZE; @@ -1152,7 +1160,7 @@ namespace { } void PlanetSetSize(int planet_id, PlanetSize planet_size) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetSetSize: Couldn't get planet with ID " << planet_id; return; @@ -1168,7 +1176,7 @@ namespace { } object PlanetGetSpecies(int planet_id) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetGetSpecies: Couldn't get planet with ID " << planet_id; return object(""); @@ -1177,7 +1185,7 @@ namespace { } void PlanetSetSpecies(int planet_id, const std::string& species_name) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetSetSpecies: Couldn't get planet with ID " << planet_id; return; @@ -1186,7 +1194,7 @@ namespace { } object PlanetGetFocus(int planet_id) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetGetFocus: Couldn't get planet with ID " << planet_id; return object(""); @@ -1195,7 +1203,7 @@ namespace { } void PlanetSetFocus(int planet_id, const std::string& focus) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetSetSpecies: Couldn't get planet with ID " << planet_id; return; @@ -1205,7 +1213,7 @@ namespace { list PlanetAvailableFoci(int planet_id) { list py_foci; - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetAvailableFoci: Couldn't get planet with ID " << planet_id; return py_foci; @@ -1217,7 +1225,7 @@ namespace { } bool PlanetMakeOutpost(int planet_id, int empire_id) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetMakeOutpost: couldn't get planet with ID:" << planet_id; return false; @@ -1232,7 +1240,7 @@ namespace { } bool PlanetMakeColony(int planet_id, int empire_id, const std::string& species, double population) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetMakeColony: couldn't get planet with ID:" << planet_id; return false; @@ -1255,7 +1263,7 @@ namespace { } object PlanetCardinalSuffix(int planet_id) { - std::shared_ptr planet = GetPlanet(planet_id); + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "PlanetCardinalSuffix: couldn't get planet with ID:" << planet_id; return object(UserString("ERROR")); @@ -1268,122 +1276,123 @@ namespace { namespace FreeOrionPython { void WrapServer() { class_("PlayerSetupData") - .def_readonly("player_name", &PlayerSetupData::m_player_name) - .def_readonly("empire_name", &PlayerSetupData::m_empire_name) - .def_readonly("empire_color", &PlayerSetupData::m_empire_color) - .def_readonly("starting_species", &PlayerSetupData::m_starting_species_name); + .def_readwrite("player_name", &PlayerSetupData::m_player_name) + .def_readwrite("empire_name", &PlayerSetupData::m_empire_name) + .def_readonly("empire_color", &PlayerSetupData::m_empire_color) + .def_readwrite("starting_species", &PlayerSetupData::m_starting_species_name) + .def_readwrite("starting_team", &PlayerSetupData::m_starting_team); class_("FleetPlan", init()) - .def("name", &FleetPlanWrapper::Name) - .def("ship_designs", &FleetPlanWrapper::ShipDesigns); + .def("name", &FleetPlanWrapper::Name) + .def("ship_designs", &FleetPlanWrapper::ShipDesigns); class_("MonsterFleetPlan", init()) - .def("name", &MonsterFleetPlanWrapper::Name) - .def("ship_designs", &MonsterFleetPlanWrapper::ShipDesigns) - .def("spawn_rate", &MonsterFleetPlanWrapper::SpawnRate) - .def("spawn_limit", &MonsterFleetPlanWrapper::SpawnLimit) - .def("locations", &MonsterFleetPlanWrapper::Locations); - - def("get_universe", GetUniverse, return_value_policy()); - def("get_all_empires", GetAllEmpires); - def("get_empire", GetEmpire, return_value_policy()); - - def("user_string", make_function(&UserString, return_value_policy())); - def("roman_number", RomanNumber); - def("get_resource_dir", GetResourceDirWrapper); - - def("all_empires", AllEmpires); - def("invalid_object", InvalidObjectID); - def("large_meter_value", LargeMeterValue); - def("invalid_position", InvalidPosition); - - def("get_galaxy_setup_data", GetGalaxySetupData, return_value_policy()); - def("current_turn", CurrentTurn); - def("generate_sitrep", GenerateSitRep); - def("generate_sitrep", GenerateSitRep1); - def("generate_starlanes", GenerateStarlanes); - - def("species_preferred_focus", SpeciesPreferredFocus); - def("species_get_planet_environment", SpeciesGetPlanetEnvironment); - def("species_add_homeworld", SpeciesAddHomeworld); - def("species_remove_homeworld", SpeciesRemoveHomeworld); - def("species_can_colonize", SpeciesCanColonize); - def("get_all_species", GetAllSpecies); - def("get_playable_species", GetPlayableSpecies); - def("get_native_species", GetNativeSpecies); - - def("special_spawn_rate", SpecialSpawnRate); - def("special_spawn_limit", SpecialSpawnLimit); - def("special_locations", SpecialLocations); - def("special_has_location", SpecialHasLocation); - def("get_all_specials", GetAllSpecials); - - def("empire_set_name", EmpireSetName); - def("empire_set_homeworld", EmpireSetHomeworld); - def("empire_unlock_item", EmpireUnlockItem); - def("empire_add_ship_design", EmpireAddShipDesign); - - def("design_create", ShipDesignCreate); - def("design_get_premade_list", ShipDesignGetPremadeList); - def("design_get_monster_list", ShipDesignGetMonsterList); - - def("load_item_spec_list", LoadItemSpecList); - def("load_starting_buildings", LoadStartingBuildings); - def("load_fleet_plan_list", LoadFleetPlanList); - def("load_monster_fleet_plan_list", LoadMonsterFleetPlanList); - - def("get_name", GetName); - def("set_name", SetName); - def("get_x", GetX); - def("get_y", GetY); - def("get_pos", GetPos); - def("get_owner", GetOwner); - def("add_special", AddSpecial); - def("remove_special", RemoveSpecial); - - def("get_universe_width", GetUniverseWidth); - def("set_universe_width", SetUniverseWidth); - def("linear_distance", LinearDistance); - def("jump_distance", JumpDistanceBetweenSystems); - def("get_all_objects", GetAllObjects); - def("get_systems", GetSystems); - def("create_system", CreateSystem); - def("create_planet", CreatePlanet); - def("create_building", CreateBuilding); - def("create_fleet", CreateFleet); - def("create_ship", CreateShip); - def("create_monster_fleet", CreateMonsterFleet); - def("create_monster", CreateMonster); - def("create_field", CreateField); - def("create_field_in_system", CreateFieldInSystem); - - def("objs_get_systems", ObjectsGetSystems); - - def("systems_within_jumps_unordered", SystemsWithinJumps, "Return all systems within ''jumps'' of the systems with ids ''sys_ids''"); - - def("sys_get_star_type", SystemGetStarType); - def("sys_set_star_type", SystemSetStarType); - def("sys_get_num_orbits", SystemGetNumOrbits); - def("sys_free_orbits", SystemFreeOrbits); - def("sys_orbit_occupied", SystemOrbitOccupied); - def("sys_orbit_of_planet", SystemOrbitOfPlanet); - def("sys_get_planets", SystemGetPlanets); - def("sys_get_fleets", SystemGetFleets); - def("sys_get_starlanes", SystemGetStarlanes); - def("sys_add_starlane", SystemAddStarlane); - def("sys_remove_starlane", SystemRemoveStarlane); - - def("planet_get_type", PlanetGetType); - def("planet_set_type", PlanetSetType); - def("planet_get_size", PlanetGetSize); - def("planet_set_size", PlanetSetSize); - def("planet_get_species", PlanetGetSpecies); - def("planet_set_species", PlanetSetSpecies); - def("planet_get_focus", PlanetGetFocus); - def("planet_set_focus", PlanetSetFocus); - def("planet_available_foci", PlanetAvailableFoci); - def("planet_make_outpost", PlanetMakeOutpost); - def("planet_make_colony", PlanetMakeColony); - def("planet_cardinal_suffix", PlanetCardinalSuffix); + .def("name", &MonsterFleetPlanWrapper::Name) + .def("ship_designs", &MonsterFleetPlanWrapper::ShipDesigns) + .def("spawn_rate", &MonsterFleetPlanWrapper::SpawnRate) + .def("spawn_limit", &MonsterFleetPlanWrapper::SpawnLimit) + .def("locations", &MonsterFleetPlanWrapper::Locations); + + def("get_universe", GetUniverse, return_value_policy()); + def("get_all_empires", GetAllEmpires); + def("get_empire", GetEmpire, return_value_policy()); + + def("user_string", make_function(&UserString, return_value_policy())); + def("roman_number", RomanNumber); + def("get_resource_dir", GetResourceDirWrapper); + + def("all_empires", AllEmpires); + def("invalid_object", InvalidObjectID); + def("large_meter_value", LargeMeterValue); + def("invalid_position", InvalidPosition); + + def("get_galaxy_setup_data", GetGalaxySetupData, return_value_policy()); + def("current_turn", CurrentTurn); + def("generate_sitrep", GenerateSitRep); + def("generate_sitrep", GenerateSitRep1); + def("generate_starlanes", GenerateStarlanes); + + def("species_preferred_focus", SpeciesPreferredFocus); + def("species_get_planet_environment", SpeciesGetPlanetEnvironment); + def("species_add_homeworld", SpeciesAddHomeworld); + def("species_remove_homeworld", SpeciesRemoveHomeworld); + def("species_can_colonize", SpeciesCanColonize); + def("get_all_species", GetAllSpecies); + def("get_playable_species", GetPlayableSpecies); + def("get_native_species", GetNativeSpecies); + + def("special_spawn_rate", SpecialSpawnRate); + def("special_spawn_limit", SpecialSpawnLimit); + def("special_locations", SpecialLocations); + def("special_has_location", SpecialHasLocation); + def("get_all_specials", GetAllSpecials); + + def("empire_set_name", EmpireSetName); + def("empire_set_homeworld", EmpireSetHomeworld); + def("empire_unlock_item", EmpireUnlockItem); + def("empire_add_ship_design", EmpireAddShipDesign); + + def("design_create", ShipDesignCreate); + def("design_get_premade_list", ShipDesignGetPremadeList); + def("design_get_monster_list", ShipDesignGetMonsterList); + + def("load_unlockable_item_list", LoadUnlockableItemList); + def("load_starting_buildings", LoadStartingBuildings); + def("load_fleet_plan_list", LoadFleetPlanList); + def("load_monster_fleet_plan_list", LoadMonsterFleetPlanList); + + def("get_name", GetName); + def("set_name", SetName); + def("get_x", GetX); + def("get_y", GetY); + def("get_pos", GetPos); + def("get_owner", GetOwner); + def("add_special", AddSpecial); + def("remove_special", RemoveSpecial); + + def("get_universe_width", GetUniverseWidth); + def("set_universe_width", SetUniverseWidth); + def("linear_distance", LinearDistance); + def("jump_distance", JumpDistanceBetweenSystems); + def("get_all_objects", GetAllObjects); + def("get_systems", GetSystems); + def("create_system", CreateSystem); + def("create_planet", CreatePlanet); + def("create_building", CreateBuilding); + def("create_fleet", CreateFleet); + def("create_ship", CreateShip); + def("create_monster_fleet", CreateMonsterFleet); + def("create_monster", CreateMonster); + def("create_field", CreateField); + def("create_field_in_system", CreateFieldInSystem); + + def("objs_get_systems", ObjectsGetSystems); + + def("systems_within_jumps_unordered", SystemsWithinJumps, "Return all systems within ''jumps'' of the systems with ids ''sys_ids''"); + + def("sys_get_star_type", SystemGetStarType); + def("sys_set_star_type", SystemSetStarType); + def("sys_get_num_orbits", SystemGetNumOrbits); + def("sys_free_orbits", SystemFreeOrbits); + def("sys_orbit_occupied", SystemOrbitOccupied); + def("sys_orbit_of_planet", SystemOrbitOfPlanet); + def("sys_get_planets", SystemGetPlanets); + def("sys_get_fleets", SystemGetFleets); + def("sys_get_starlanes", SystemGetStarlanes); + def("sys_add_starlane", SystemAddStarlane); + def("sys_remove_starlane", SystemRemoveStarlane); + + def("planet_get_type", PlanetGetType); + def("planet_set_type", PlanetSetType); + def("planet_get_size", PlanetGetSize); + def("planet_set_size", PlanetSetSize); + def("planet_get_species", PlanetGetSpecies); + def("planet_set_species", PlanetSetSpecies); + def("planet_get_focus", PlanetGetFocus); + def("planet_set_focus", PlanetSetFocus); + def("planet_available_foci", PlanetAvailableFoci); + def("planet_make_outpost", PlanetMakeOutpost); + def("planet_make_colony", PlanetMakeColony); + def("planet_cardinal_suffix", PlanetCardinalSuffix); } } diff --git a/server/ServerWrapper.h b/server/ServerWrapper.h new file mode 100644 index 00000000000..67b8df7bede --- /dev/null +++ b/server/ServerWrapper.h @@ -0,0 +1,8 @@ +#ifndef _ServerWrapper_h_ +#define _ServerWrapper_h_ + +namespace FreeOrionPython { + void WrapServer(); +} + +#endif // _ServerWrapper_h_ diff --git a/server/UniverseGenerator.cpp b/server/UniverseGenerator.cpp new file mode 100644 index 00000000000..11ce849a602 --- /dev/null +++ b/server/UniverseGenerator.cpp @@ -0,0 +1,916 @@ +#include "UniverseGenerator.h" + +#include "../util/Random.h" +#include "../util/i18n.h" +#include "../util/MultiplayerCommon.h" +#include "../util/GameRules.h" +#include "../util/AppInterface.h" +#include "../Empire/Empire.h" +#include "../Empire/EmpireManager.h" + +#include "../universe/Planet.h" +#include "../universe/System.h" +#include "../universe/Species.h" + +#include + +namespace { + DeclareThreadSafeLogger(effects); +} + +////////////////////////////////////////// +// Universe Setup Functions // +////////////////////////////////////////// + +namespace Delauney { + /** simple 2D point. would have used array of systems, but System + * class has limits on the range of positions that would interfere + * with the triangulation algorithm (need a single large covering + * triangle that overlaps all actual points being triangulated) */ + class DTPoint { + public: + DTPoint() : + x(0.0), + y(0.0) + {} + DTPoint(double x_, double y_) : + x(x_), + y(y_) + {} + double x; + double y; + }; + + /* simple class for an integer that has an associated "sorting value", + * so the integer can be stored in a list sorted by something other than + * the value of the integer */ + class SortValInt { + public: + SortValInt(int num_, double sort_val_) : + num(num_), + sortVal(sort_val_) + {} + int num; + double sortVal; + }; + + + /** list of three interger array indices, and some additional info about + * the triangle that the corresponding points make up, such as the + * circumcentre and radius, and a function to find if another point is in + * the circumcircle */ + class DTTriangle { + public: + DTTriangle(); + DTTriangle(int vert1, int vert2, int vert3, + const std::vector &points); + + ///< determines whether a specified point is within the circumcircle of the triangle + bool PointInCircumCircle(const Delauney::DTPoint &p); + const std::vector& Verts() {return verts;} + + private: + std::vector verts; ///< indices of vertices of triangle + Delauney::DTPoint centre; ///< location of circumcentre of triangle + double radius2; ///< radius of circumcircle squared + }; + + DTTriangle::DTTriangle(int vert1, int vert2, int vert3, + const std::vector& points) + { + if (vert1 == vert2 || vert1 == vert3 || vert2 == vert3) + throw std::runtime_error("Attempted to create Triangle with two of the same vertex indices."); + + // record indices of vertices of triangle + verts = {vert1, vert2, vert3}; + + // extract position info for vertices + const double& x1 = points[vert1].x; + const double& x2 = points[vert2].x; + const double& x3 = points[vert3].x; + const double& y1 = points[vert1].y; + const double& y2 = points[vert2].y; + const double& y3 = points[vert3].y; + + // calculate circumcircle and circumcentre of triangle + double a, Sx, Sy, b; + + a = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2); + + Sx = 0.5 * ((x1 * x1 + y1 * y1) * (y2 - y3) + + (x2 * x2 + y2 * y2) * (y3 - y1) + + (x3 * x3 + y3 * y3) * (y1 - y2)); + + Sy = -0.5* ((x1 * x1 + y1 * y1) * (x2 - x3) + + (x2 * x2 + y2 * y2) * (x3 - x1) + + (x3 * x3 + y3 * y3) * (x1 - x2)); + + b = ((x1 * x1 + y1 * y1) * (x2 * y3 - x3 * y2) + + (x2 * x2 + y2 * y2) * (x3 * y1 - x1 * y3) + + (x3 * x3 + y3 * y3) * (x1 * y2 - x2 * y1)); + + // make sure nothing funky's going on... + if (std::abs(a) < 0.01) + throw std::runtime_error("Attempted to find circumcircle for a triangle with vertices in a line."); + + // finish! + centre.x = Sx / a; + centre.y = Sy / a; + radius2 = (Sx*Sx + Sy*Sy)/(a*a) + b/a; + }; + + DTTriangle::DTTriangle() : + verts{0, 0, 0}, + centre{0.0, 0.0}, + radius2{0.0} + {}; + + bool DTTriangle::PointInCircumCircle(const Delauney::DTPoint &p) { + double vectX = p.x - centre.x; + double vectY = p.y - centre.y; + if (vectX*vectX + vectY*vectY < radius2) + return true; + return false; + }; + + + + /** Runs a Delauney Triangulation on a set of 2D points corresponding + * to the locations of the systems in \a systems_vec */ + std::list DelauneyTriangulate( + const std::vector> &systems_vec) + { + // ensure a useful list of systems was passed... + if (systems_vec.empty()) { + ErrorLogger() << "Attempted to run Delauney Triangulation on empty array of systems"; + return std::list(); + } + + + // extract systems positions from system objects. + std::vector points_vec; + points_vec.reserve(systems_vec.size() + 3); + for (auto& system : systems_vec) + points_vec.push_back({system->X(), system->Y()}); + + // entries in points_vec correspond to entries in \a systems_vec + // so that the index of an item in systems_vec will have a + // corresponding points at that index in points_vec + + + // add points for covering triangle. the point positions should be big + // enough to form a triangle that encloses all the points in points_vec + // (or at least one whose circumcircle covers all points) + points_vec.push_back({-1.0, -1.0}); + points_vec.push_back({2.0 * (GetUniverse().UniverseWidth() + 1.0), -1.0}); + points_vec.push_back({-1.0, 2.0 * (GetUniverse().UniverseWidth() + 1.0)}); + + + // initialize triangle_list. + // add last three points into the first triangle, the "covering triangle" + std::list triangle_list; + int num_points_in_vec = points_vec.size(); + triangle_list.push_front({num_points_in_vec - 1, num_points_in_vec - 2, + num_points_in_vec - 3, points_vec}); + + if (points_vec.size() > static_cast(std::numeric_limits::max())) { + ErrorLogger() << "Attempted to run Delauney Triangulation on " << points_vec.size() + << " points. The limit is " << std::numeric_limits::max(); + return std::list(); + } + + // loop through points generated from systems, excluding the final + // 3 points added for the covering triangle + for (std::size_t n = 0; n < points_vec.size() - 3; n++) { + // list of indices in vector of points extracted from removed + // triangles that need to be retriangulated + std::list point_idx_list; + + const auto& cur_point = points_vec.at(n); + + // check each triangle to see if the current point lies in its + // circumcircle. if so, delete the triangle and add its vertices to a list + auto cur_tri_it = triangle_list.begin(); + while (cur_tri_it != triangle_list.end()) { + // get current triangle + Delauney::DTTriangle& tri = *cur_tri_it; + + // check if point to be added to triangulation is within the + // circumcircle for the current triangle + if (!tri.PointInCircumCircle(cur_point)) { + // point not in circumcircle for this triangle. + // go next triangle in list + ++cur_tri_it; + continue; + } + + // point is in circumcircle for this triangle. + // insert the triangle's vertices' indices into the + // list. add in sorted position based on angle of direction + // to current point n being inserted. don't add if doing so + // would duplicate an index already in the list + for (int tri_vert_idx : tri.Verts()) { + + // get sorting value to order points clockwise + // circumferentially around point n + + // vector from point n to current point + double vx = points_vec[tri_vert_idx].x - points_vec[n].x; + double vy = points_vec[tri_vert_idx].y - points_vec[n].y; + double mag = std::sqrt(vx*vx + vy*vy); + // normalize + vx /= mag; + vy /= mag; + + // dot product with (0, 1) is vy, magnitude of cross + // product is vx this gives a range of "sort value" + // from -2 to 2, around the circle + double sort_value = (vx >= 0) ? (vy + 1) : (-vy - 1); + + // iterate through list, finding insert spot and + // verifying uniqueness (or add if list is empty) + auto idx_list_it = point_idx_list.begin(); + if (idx_list_it == point_idx_list.end()) { + // list is empty + point_idx_list.push_back({tri_vert_idx, sort_value}); + + } else { + while (idx_list_it != point_idx_list.end()) { + if (idx_list_it->num == tri_vert_idx) + break; + if (idx_list_it->sortVal > sort_value) { + point_idx_list.insert(idx_list_it, {tri_vert_idx, sort_value}); + break; + } + ++idx_list_it; + } + if (idx_list_it == point_idx_list.end()) { + // point wasn't added, so should go at end + point_idx_list.push_back({tri_vert_idx, sort_value}); + } + } + } // end for c + + // remove current triangle from list of triangles + cur_tri_it = triangle_list.erase(cur_tri_it); + } // end while + + + // add triangle for last and first points and n + triangle_list.push_front( + {static_cast(n), (point_idx_list.front()).num, (point_idx_list.back()).num, points_vec}); + + + // go through list of points, making new triangles out of them + auto idx_list_it = point_idx_list.begin(); + int num = idx_list_it->num; + ++idx_list_it; + while (idx_list_it != point_idx_list.end()) { + int num2 = num; + num = idx_list_it->num; + + triangle_list.push_front({static_cast(n), num2, num, points_vec}); + + ++idx_list_it; + } // end while + + } // end for + + DebugLogger() << "DelauneyTriangulate generated list of " + << triangle_list.size() << " triangles"; + + return triangle_list; + } +} + +namespace { + int IntSetMapSizeCount(const std::map>& in) { + int retval{0}; + for (const auto& entry : in) + retval += entry.second.size(); + return retval; + } + + /** Used by GenerateStarlanes. Determines if two systems are connected by + * maxLaneJumps or less edges on graph. */ + bool ConnectedWithin(int system1, int system2, int maxLaneJumps, + const std::map>& system_lanes) + { + // list of indices of systems that are accessible from previously visited systems. + // when a new system is found to be accessible, it is added to the back of the + // list. the list is iterated through from front to back to find systems + // to examine + std::list accessibleSystemsList; + std::list::iterator sysListIter, sysListEnd; + + // map using star index number as the key, and also storing the number of starlane + // jumps away from system1 a given system is. this is used to determine if a + // system has already been added to the accessibleSystemsList without needing + // to iterate through the list. it also provides some indication of the + // current depth of the search, which allows the serch to terminate after searching + // to the depth of maxLaneJumps without finding system2 + // (considered using a vector for this, but felt that for large galaxies, the + // size of the vector and the time to intialize would be too much) + std::map accessibleSystemsMap; + + // system currently being investigated, destination of a starlane origination at cur_sys_id + int cur_sys_id, curLaneDest; + // "depth" level in tree of system currently being investigated + int curDepth; + + // iterators to set of starlanes, in graph, for the current system + std::set::iterator curSysLanesSetIter, curSysLanesSetEnd; + + // check for simple cases for quick termination + if (system1 == system2) return true; // system is always connected to itself + if (0 == maxLaneJumps) return false; // no system is connected to any other system by less than 1 jump + + if (!system_lanes.count(system1)) return false; // start system not in lanes map + if (!system_lanes.count(system2)) return false; // destination system not in lanes map + + if (system_lanes.at(system1).empty()) return false; // no lanes out of start system + if (system_lanes.at(system2).empty()) return false; // no lanes out of destination system + + + // add starting system to list and set of accessible systems + accessibleSystemsList.push_back(system1); + accessibleSystemsMap.insert({system1, 0}); + + // loop through visited systems + sysListIter = accessibleSystemsList.begin(); + sysListEnd = accessibleSystemsList.end(); + while (sysListIter != sysListEnd) { + cur_sys_id = *sysListIter; + + // check that iteration hasn't reached maxLaneJumps levels deep, which would + // mean that system2 isn't within maxLaneJumps starlane jumps of system1 + curDepth = (*accessibleSystemsMap.find(cur_sys_id)).second; + + if (curDepth >= maxLaneJumps) return false; + + // get set of starlanes for this system + curSysLanesSetIter = system_lanes.at(cur_sys_id).begin(); + curSysLanesSetEnd = system_lanes.at(cur_sys_id).end(); + + // add starlanes accessible from this system to list and set of accessible starlanes + // (and check for the goal starlane) + while (curSysLanesSetIter != curSysLanesSetEnd) { + curLaneDest = *curSysLanesSetIter; + + // check if curLaneDest has been added to the map of accessible systems + if (!accessibleSystemsMap.count(curLaneDest)) { + // check for goal + if (curLaneDest == system2) return true; + + // add curLaneDest to accessible systems list and map + accessibleSystemsList.push_back(curLaneDest); + accessibleSystemsMap.insert({curLaneDest, curDepth + 1}); + } + ++curSysLanesSetIter; + } + ++sysListIter; + } + return false; // default + } + + /** Removes lanes from passed graph that are angularly too close to + * each other. */ + void CullAngularlyTooCloseLanes(double max_lane_uvect_dot_product, + std::map>& system_lanes, + const std::map>& systems) + { + // 2 component vector and vect + magnitude typedefs + typedef std::pair VectTypeQQ; + typedef std::pair VectAndMagTypeQQ; + + std::set> lanesToRemoveSet; // start and end stars of lanes to be removed in final step... + + // make sure data is consistent + if (system_lanes.size() != systems.size()) { + ErrorLogger() << "CullAngularlyTooCloseLanes got different size vectors of lane sets and systems. Doing nothing."; + return; + } + + if (systems.size() < 3) return; // nothing worth doing for less than three systems + + //DebugLogger() << "Culling Too Close Angularly Lanes"; + + // loop through systems + for (const auto& entry : systems) { + // can't have pairs of lanes departing from a system if that system + // has less than two lanes + if (system_lanes.at(entry.first).size() < 2) + continue; + + // get position of current system (for use in calculated vectors) + auto startX = entry.second->X(); + auto startY = entry.second->Y(); + auto cur_sys_id = entry.first; + + /** componenets of vectors of lanes of current system, indexed by destination system number */ + std::map laneVectsMap; + + // get unit vectors for all lanes of this system + auto laneSetIter1 = system_lanes[cur_sys_id].begin(); + while (laneSetIter1 != system_lanes[cur_sys_id].end()) { + // get destination for this lane + auto dest1 = *laneSetIter1; + // get vector to this lane destination + auto vectX1 = systems.at(dest1)->X() - startX; + auto vectY1 = systems.at(dest1)->Y() - startY; + // normalize + auto mag1 = std::sqrt(vectX1 * vectX1 + vectY1 * vectY1); + vectX1 /= mag1; + vectY1 /= mag1; + + // store lane in map of lane vectors + laneVectsMap.insert({dest1, {{vectX1, vectY1}, mag1}}); + + ++laneSetIter1; + } + + // iterate through lanes of cur_sys_id + laneSetIter1 = system_lanes[cur_sys_id].begin(); + ++laneSetIter1; // start at second, since iterators are used in pairs, and starting both at the first wouldn't be a valid pair + while (laneSetIter1 != system_lanes[cur_sys_id].end()) { + // get destination of current starlane + auto dest1 = *laneSetIter1; + + std::pair lane1; + if (cur_sys_id < dest1) + lane1 = {cur_sys_id, dest1}; + else + lane1 = {dest1, cur_sys_id}; + + // check if this lane has already been added to the set of lanes to remove + if (lanesToRemoveSet.count(lane1)) { + ++laneSetIter1; + continue; + } + + // extract data on starlane vector... + auto laneVectsMapIter = laneVectsMap.find(dest1); + assert(laneVectsMapIter != laneVectsMap.end()); + auto tempVectAndMag = laneVectsMapIter->second; + auto tempVect = tempVectAndMag.first; + auto vectX1 = tempVect.first; + auto vectY1 = tempVect.second; + auto mag1 = tempVectAndMag.second; + + // iterate through other lanes of cur_sys_id, in order + // to get all possible pairs of lanes + auto laneSetIter2 = system_lanes[cur_sys_id].begin(); + while (laneSetIter2 != laneSetIter1) { + auto dest2 = *laneSetIter2; + + std::pair lane2; + if (cur_sys_id < dest2) + lane2 = {cur_sys_id, dest2}; + else + lane2 = {dest2, cur_sys_id}; + + // check if this lane has already been added to the + // set of lanes to remove + if (lanesToRemoveSet.count(lane2)) { + ++laneSetIter2; + continue; + } + + // extract data on starlane vector... + laneVectsMapIter = laneVectsMap.find(dest2); + assert(laneVectsMapIter != laneVectsMap.end()); + tempVectAndMag = laneVectsMapIter->second; + tempVect = tempVectAndMag.first; + auto vectX2 = tempVect.first; + auto vectY2 = tempVect.second; + auto mag2 = tempVectAndMag.second; + + // find dot product + auto dotProd = vectX1 * vectX2 + vectY1 * vectY2; + + // if dotProd is big enough, then lanes are too close angularly + // thus one needs to be removed. + if (dotProd > max_lane_uvect_dot_product) { + // preferentially remove the longer lane + if (mag1 > mag2) { + lanesToRemoveSet.insert(lane1); + break; // don't need to check any more lanes against lane1, since lane1 has been removed + } else { + lanesToRemoveSet.insert(lane2); + } + } + + ++laneSetIter2; + } + + ++laneSetIter1; + } + } + + // iterate through set of lanes to remove, and remove them in turn... + auto lanes_to_remove_it = lanesToRemoveSet.begin(); + auto lanes_to_remove_end = lanesToRemoveSet.end(); + while (lanes_to_remove_it != lanes_to_remove_end) { + auto lane1 = *lanes_to_remove_it; + + system_lanes[lane1.first].erase(lane1.second); + system_lanes[lane1.second].erase(lane1.first); + + // check that removing lane hasn't disconnected systems + if (!ConnectedWithin(lane1.first, lane1.second, systems.size(), system_lanes)) { + // they aren't connected... reconnect them + system_lanes[lane1.first].insert(lane1.second); + system_lanes[lane1.second].insert(lane1.first); + } + + ++lanes_to_remove_it; + } + } + + /** Removes lanes from passed graph that are angularly too close to + * each other. */ + void CullTooLongLanes(double max_lane_length, + std::map>& system_lanes, + const std::map>& systems) + { + DebugLogger() << "CullTooLongLanes max lane length: " << max_lane_length + << " potential lanes: " << IntSetMapSizeCount(system_lanes) + << " systems: " << systems.size(); + // map, indexed by lane length, of start and end stars of lanes to be removed + std::multimap, std::greater> lanes_to_remove; + + // make sure data is consistent + if (system_lanes.size() != systems.size()) + return; + // nothing worth doing for less than two systems (no lanes!) + if (systems.size() < 2) + return; + + // get squared max lane lenth, so as to eliminate the need to take square roots of lane lenths... + double max_lane_length2 = max_lane_length*max_lane_length; + + // loop through systems + for (const auto& system_entry : systems) { + int cur_sys_id = system_entry.first; + const auto& cur_system = system_entry.second; + + // get position of current system (for use in calculating vector) + double startX = cur_system->X(); + double startY = cur_system->Y(); + + // iterate through all lanes in system, checking lengths and + // marking to be removed if necessary + auto lane_it = system_lanes[cur_sys_id].begin(); + auto lane_end = system_lanes[cur_sys_id].end(); + while (lane_it != lane_end) { + // get destination for this lane + int dest_sys_id = *lane_it; + // convert start and end into ordered pair to represent lane + std::pair lane; + if (cur_sys_id < dest_sys_id) + lane = {cur_sys_id, dest_sys_id}; + else + lane = {dest_sys_id, cur_sys_id}; + + // get vector to this lane destination + const auto& dest_system = systems.at(dest_sys_id); + auto vectX = dest_system->X() - startX; + auto vectY = dest_system->Y() - startY; + + // compare magnitude of vector to max allowed + double lane_length2 = vectX*vectX + vectY*vectY; + if (lane_length2 > max_lane_length2) { + // lane is too long! mark it to be removed + TraceLogger() << "CullTooLongLanes wants to remove lane of length " + << std::sqrt(lane_length2) + << " between systems with ids: " + << cur_sys_id << " and " << dest_sys_id; + lanes_to_remove.insert({lane_length2, lane}); + } + + ++lane_it; + } + } + + DebugLogger() << "CullTooLongLanes identified " << lanes_to_remove.size() + << " long lanes to possibly remove"; + + // Iterate through set of lanes to remove, and remove them in turn. Since lanes were inserted in the map indexed by + // their length, iteration starting with begin starts with the longest lane first, then moves through the lanes as + // they get shorter, ensuring that the longest lanes are removed first. + auto lanes_to_remove_it = lanes_to_remove.begin(); + while (lanes_to_remove_it != lanes_to_remove.end()) { + auto lane = lanes_to_remove_it->second; + + // ensure the lane still exists + if (system_lanes[lane.first].count(lane.second) > 0 && + system_lanes[lane.second].count(lane.first) > 0) + { + // remove lane + system_lanes[lane.first].erase(lane.second); + system_lanes[lane.second].erase(lane.first); + + // if removing lane has disconnected systems, reconnect them + if (!ConnectedWithin(lane.first, lane.second, systems.size(), system_lanes)) { + system_lanes[lane.first].insert(lane.second); + system_lanes[lane.second].insert(lane.first); + TraceLogger() << "CullTooLongLanes can't remove lane between systems with ids: " + << lane.first << " and " << lane.second + << " because they would then be disconnected (more than " + << systems.size() << " jumps apart)"; + } else { + TraceLogger() << "CullTooLongLanes removing lane between systems with ids: " + << lane.first << " and " << lane.second; + } + } + ++lanes_to_remove_it; + } + + DebugLogger() << "CullTooLongLanes left with " << IntSetMapSizeCount(system_lanes) + << " lanes"; + } +} + +void GenerateStarlanes(int max_jumps_between_systems, int max_starlane_length) { + DebugLogger() << "GenerateStarlanes max jumps b/w sys: " << max_jumps_between_systems + << " max lane length: " << max_starlane_length; + + std::vector triangle_vertices; // indices of stars that form vertices of a triangle + + // array of set to store final, included starlanes for each star + std::map> system_lanes; + + // array of set to store possible starlanes for each star, + // as extracted form triangulation + std::map> potential_system_lanes; + + // get systems + auto sys_rng = Objects().all(); + std::vector> sys_vec; + std::map> sys_map; + std::copy(sys_rng.begin(), sys_rng.end(), std::back_inserter(sys_vec)); + std::transform(sys_rng.begin(), sys_rng.end(), std::inserter(sys_map, sys_map.end()), + [](const std::shared_ptr& p) { return std::make_pair(p->ID(), p); }); + + // generate lanes + if (GetGameRules().Get("RULE_STARLANES_EVERYWHERE")) { + // if the lanes everywhere rules is true, add starlanes to every star + // to every other star... + for (const auto& sys1 : sys_vec) { + for (const auto& sys2 : sys_vec) { + if (sys1->ID() == sys2->ID()) + continue; + potential_system_lanes[sys1->ID()].insert(sys2->ID()); + } + } + DebugLogger() << "Generated " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes between all system pairs"; + CullTooLongLanes(max_starlane_length, potential_system_lanes, sys_map); + DebugLogger() << "Left with " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes after length culling"; + system_lanes = potential_system_lanes; + + } else { + // pass systems to Delauney Triangulation routine, getting array of triangles back + auto triangle_list = Delauney::DelauneyTriangulate(sys_vec); + if (triangle_list.empty()) { + ErrorLogger() << "Got blank list of triangles from Triangulation."; + return; + } + + // extract triangles from list, add edges to sets of potential starlanes + // for each star (in array) + while (!triangle_list.empty()) { + auto tri = triangle_list.front(); + + // extract indices for the corners of the triangles, which should + // correspond to indices in sys_vec, except that there can also be + // indices up to sys_vec.size() + 2, which correspond to extra points + // used by the algorithm + triangle_vertices = tri.Verts(); + int s1 = triangle_vertices[0]; + int s2 = triangle_vertices[1]; + int s3 = triangle_vertices[2]; + + if (s1 < 0 || s2 < 0 || s3 < 0) { + ErrorLogger() << "Got negative vector indices from DelauneyTriangulate!"; + triangle_list.pop_front(); + continue; + } + + // get system ids for ends of lanes from the sys_vec indices + int sys1_id = INVALID_OBJECT_ID; + if (static_cast(s1) < sys_vec.size()) + sys1_id = sys_vec.at(s1)->ID(); + int sys2_id = INVALID_OBJECT_ID; + if (static_cast(s2) < sys_vec.size()) + sys2_id = sys_vec.at(s2)->ID(); + int sys3_id = INVALID_OBJECT_ID; + if (static_cast(s3) < sys_vec.size()) + sys3_id = sys_vec.at(s3)->ID(); + + + // add starlanes to list of potential starlanes for each star, + // making sure each pair involves only valid indices into sys_vec + if (sys1_id != INVALID_OBJECT_ID && sys2_id != INVALID_OBJECT_ID) { + potential_system_lanes[sys1_id].insert(sys2_id); + potential_system_lanes[sys2_id].insert(sys1_id); + } + if (sys2_id != INVALID_OBJECT_ID && sys3_id != INVALID_OBJECT_ID) { + potential_system_lanes[sys2_id].insert(sys3_id); + potential_system_lanes[sys3_id].insert(sys2_id); + } + if (sys3_id != INVALID_OBJECT_ID && sys1_id != INVALID_OBJECT_ID) { + potential_system_lanes[sys3_id].insert(sys1_id); + potential_system_lanes[sys1_id].insert(sys3_id); + } + + triangle_list.pop_front(); + } + + DebugLogger() << "Extracted " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes from triangulation"; + CullTooLongLanes(max_starlane_length, potential_system_lanes, sys_map); + DebugLogger() << "Left with " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes after length culling"; + CullAngularlyTooCloseLanes(0.98, potential_system_lanes, sys_map); + DebugLogger() << "Left with " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes after angular culling"; + + system_lanes = potential_system_lanes; + + // attempt removing lanes, but don't do so if it would make the systems + // the lane connects too far apart + for (auto& sys_lanes_pair : potential_system_lanes) { + auto sys1_id = sys_lanes_pair.first; + for (auto& sys2_id : sys_lanes_pair.second) { + // TODO: skip cases where sys2 < sys1 since these should already + // have been handled by previous loop iterations, since + // all lanes should exist in both directions + + // try removing lane + system_lanes[sys1_id].erase(sys2_id); + system_lanes[sys2_id].erase(sys1_id); + + if (!ConnectedWithin(sys1_id, sys2_id, max_jumps_between_systems, system_lanes)) { + // lane removal was a bad idea. restore it + system_lanes[sys1_id].insert(sys2_id); + system_lanes[sys2_id].insert(sys1_id); + } + } + } + } + + // add the starlane to the stars + for (auto& sys : Objects().all()) { + const auto& sys_lanes = system_lanes[sys->ID()]; + for (auto& lane_end_id : sys_lanes) + sys->AddStarlane(lane_end_id); + } + + DebugLogger() << "Initializing System Graph"; + GetUniverse().InitializeSystemGraph(); +} + +void SetActiveMetersToTargetMaxCurrentValues(ObjectMap& object_map) { + TraceLogger(effects) << "SetActiveMetersToTargetMaxCurrentValues"; + // check for each pair of meter types. if both exist, set active + // meter current value equal to target meter current value. + for (const auto& object : object_map.all()) { + TraceLogger(effects) << " object: " << object->Name() << " (" << object->ID() << ")"; + for (auto& entry : AssociatedMeterTypes()) { + if (Meter* meter = object->GetMeter(entry.first)) { + if (Meter* targetmax_meter = object->GetMeter(entry.second)) { + TraceLogger(effects) << " meter: " << entry.first + << " before: " << meter->Current() + << " set to: " << targetmax_meter->Current(); + meter->SetCurrent(targetmax_meter->Current()); + } + } + } + } +} + +void SetNativePopulationValues(ObjectMap& object_map) { + for (const auto& object : object_map.all()) { + Meter* meter = object->GetMeter(METER_POPULATION); + Meter* targetmax_meter = object->GetMeter(METER_TARGET_POPULATION); + // only applies to unowned planets + if (meter && targetmax_meter && object->Unowned()) { + double r = RandZeroToOne(); + double factor = (0.1 < r) ? r : 0.1; + meter->SetCurrent(targetmax_meter->Current() * factor); + } + } +} + +bool SetEmpireHomeworld(Empire* empire, int planet_id, std::string species_name) { + // get home planet and system, check if they exist + auto home_planet = Objects().get(planet_id); + if (!home_planet) + return false; + auto home_system = Objects().get(home_planet->SystemID()); + if (!home_system) + return false; + + DebugLogger() << "SetEmpireHomeworld: setting system " << home_system->ID() + << " (planet " << home_planet->ID() << ") to be home system for empire " << empire->EmpireID(); + + // get species, check if it exists + Species* species = GetSpeciesManager().GetSpecies(species_name); + if (!species) { + ErrorLogger() << "SetEmpireHomeworld: couldn't get species \"" + << species_name << "\" to set with homeworld id " << home_planet->ID(); + return false; + } + + // set homeword's planet type to the preferred type for this species + const std::map& spte = species->PlanetEnvironments(); + if (!spte.empty()) { + // invert map from planet type to environments to map from + // environments to type, sorted by environment + std::map sept; + for (const auto& entry : spte) + sept[entry.second] = entry.first; + // assuming enum values are ordered in increasing goodness... + PlanetType preferred_planet_type = sept.rbegin()->second; + + // both the current as well as the original type need to be set to the preferred type + home_planet->SetType(preferred_planet_type); + home_planet->SetOriginalType(preferred_planet_type); + // set planet size according to planet type + if (preferred_planet_type == PT_ASTEROIDS) + home_planet->SetSize(SZ_ASTEROIDS); + else if (preferred_planet_type == PT_GASGIANT) + home_planet->SetSize(SZ_GASGIANT); + else + home_planet->SetSize(SZ_MEDIUM); + } + + home_planet->Colonize(empire->EmpireID(), species_name, Meter::LARGE_VALUE); + species->AddHomeworld(home_planet->ID()); + empire->SetCapitalID(home_planet->ID()); + empire->AddExploredSystem(home_planet->SystemID()); + + return true; +} + +void InitEmpires(const std::map& player_setup_data) { + DebugLogger() << "Initializing " << player_setup_data.size() << " empires"; + + // copy empire colour table, so that individual colours can be removed after they're used + auto colors = EmpireColors(); + + // create empire objects and do some basic initilization for each player + for (const auto& entry : player_setup_data) { + // use map key for empire ID so that the calling code can get the + // correct empire for each player in player_setup_data + int empire_id = entry.first; + if (empire_id == ALL_EMPIRES) + ErrorLogger() << "InitEmpires empire id (" << empire_id << ") is invalid"; + + std::string player_name = entry.second.m_player_name; + GG::Clr empire_colour = entry.second.m_empire_color; + bool authenticated = entry.second.m_authenticated; + + // validate or generate empire colour + // ensure no other empire gets auto-assigned this colour automatically + auto color_it = std::find(colors.begin(), colors.end(), empire_colour); + if (color_it != colors.end()) + colors.erase(color_it); + + // if no colour already set, do so automatically + if (empire_colour == GG::Clr(0, 0, 0, 0)) { + if (!colors.empty()) { + // take next colour from list + empire_colour = colors[0]; + colors.erase(colors.begin()); + } else { + // as a last resort, make up a colour + empire_colour = GG::FloatClr(static_cast(RandZeroToOne()), static_cast(RandZeroToOne()), + static_cast(RandZeroToOne()), 1.0f); + } + } + + // set generic default empire name + std::string empire_name = UserString("EMPIRE") + std::to_string(empire_id); + + DebugLogger() << "Universe::InitEmpires creating new empire" << " with ID: " << empire_id + << " for player: " << player_name << " in team: " << entry.second.m_starting_team; + + // create new Empire object through empire manager + Empires().CreateEmpire(empire_id, empire_name, player_name, empire_colour, authenticated); + } + + Empires().ResetDiplomacy(); + + for (const auto& entry : player_setup_data) { + if (entry.second.m_starting_team < 0) + continue; + + for (const auto& other_entry : player_setup_data) { + if (entry.first == other_entry.first) + continue; + + if (entry.second.m_starting_team != other_entry.second.m_starting_team) + continue; + + Empires().SetDiplomaticStatus(entry.first, other_entry.first, DIPLO_ALLIED); + } + } +} diff --git a/server/UniverseGenerator.h b/server/UniverseGenerator.h new file mode 100644 index 00000000000..67aeb7b22b2 --- /dev/null +++ b/server/UniverseGenerator.h @@ -0,0 +1,35 @@ +#ifndef _UniverseGenerator_h_ +#define _UniverseGenerator_h_ + + +#include +#include + +class Empire; +class ObjectMap; +struct PlayerSetupData; + + +//! Set active meter current values equal to target/max meter current values. +//! Useful when creating new object after applying effects. +void SetActiveMetersToTargetMaxCurrentValues(ObjectMap& object_map); + +//! Set the population of unowned planets to a random fraction of heir target +//! values. +void SetNativePopulationValues(ObjectMap& object_map); + +//! Creates starlanes and adds them systems already generated. +void GenerateStarlanes(int max_jumps_between_systems, int max_starlane_length); + +//! Sets empire homeworld +//! This includes setting ownership, capital, species, preferred environment +//! (planet type) for the species +bool SetEmpireHomeworld(Empire* empire, int planet_id, std::string species_name); + +//! Creates Empires objects for each entry in \a player_setup_data with empire +//! ids equal to the corresponding map keys (so that the calling code can know +//! which empire belongs to which player). +void InitEmpires(const std::map& player_setup_data); + + +#endif // _UniverseGenerator_h_ diff --git a/server/dmain.cpp b/server/dmain.cpp index 24ff1149e23..160281827e7 100644 --- a/server/dmain.cpp +++ b/server/dmain.cpp @@ -1,6 +1,5 @@ #include "ServerApp.h" -#include "../parse/Parse.h" #include "../util/OptionsDB.h" #include "../util/Directories.h" #include "../util/i18n.h" @@ -48,9 +47,29 @@ int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) { #ifndef FREEORION_DMAIN_KEEP_STACKTRACE try { #endif - GetOptionsDB().AddFlag('h', "help", UserStringNop("OPTIONS_DB_HELP"), false); - GetOptionsDB().AddFlag('v', "version", UserStringNop("OPTIONS_DB_VERSION"), false); - GetOptionsDB().AddFlag('s', "singleplayer", UserStringNop("OPTIONS_DB_SINGLEPLAYER"), false); + GetOptionsDB().Add('h', "help", UserStringNop("OPTIONS_DB_HELP"), "NOOP", + Validator(), false); + GetOptionsDB().AddFlag('v', "version", UserStringNop("OPTIONS_DB_VERSION"), false); + GetOptionsDB().AddFlag('s', "singleplayer", UserStringNop("OPTIONS_DB_SINGLEPLAYER"), false); + GetOptionsDB().AddFlag("hostless", UserStringNop("OPTIONS_DB_HOSTLESS"), false); + GetOptionsDB().AddFlag("skip-checksum", UserStringNop("OPTIONS_DB_SKIP_CHECKSUM"), false); + GetOptionsDB().AddFlag("testing", UserStringNop("OPTIONS_DB_TESTING"), false); + GetOptionsDB().Add("network.server.ai.min", UserStringNop("OPTIONS_DB_MP_AI_MIN"), 0); + GetOptionsDB().Add("network.server.ai.max", UserStringNop("OPTIONS_DB_MP_AI_MAX"), -1); + GetOptionsDB().Add("network.server.human.min", UserStringNop("OPTIONS_DB_MP_HUMAN_MIN"), 0); + GetOptionsDB().Add("network.server.human.max", UserStringNop("OPTIONS_DB_MP_HUMAN_MAX"), -1); + GetOptionsDB().Add("network.server.conn-human-empire-players.min", UserStringNop("OPTIONS_DB_MP_CONN_HUMAN_MIN"), 0); + GetOptionsDB().Add("network.server.unconn-human-empire-players.max", UserStringNop("OPTIONS_DB_MP_UNCONN_HUMAN_MAX"), 1); + GetOptionsDB().Add("network.server.cookies.expire-minutes", UserStringNop("OPTIONS_DB_COOKIES_EXPIRE"), 15); + GetOptionsDB().Add("network.server.publish-statistics", UserStringNop("OPTIONS_DB_PUBLISH_STATISTICS"), true); + GetOptionsDB().Add("network.server.publish-seed", UserStringNop("OPTIONS_DB_PUBLISH_SEED"), true); + GetOptionsDB().Add("network.server.binary.enabled", UserStringNop("OPTIONS_DB_SERVER_BINARY_SERIALIZATION"),true); + GetOptionsDB().Add("network.server.turn-timeout.first-turn-time", UserStringNop("OPTIONS_DB_FIRST_TURN_TIME"), ""); + GetOptionsDB().Add("network.server.turn-timeout.max-interval", UserStringNop("OPTIONS_DB_TIMEOUT_INTERVAL"), 0); + GetOptionsDB().Add("network.server.turn-timeout.fixed-interval", UserStringNop("OPTIONS_DB_TIMEOUT_FIXED_INTERVAL"), false); + GetOptionsDB().Add("setup.game.uid", UserStringNop("OPTIONS_DB_GAMESETUP_UID"), ""); + GetOptionsDB().Add("network.server.client-message-size.max", UserStringNop("OPTIONS_DB_CLIENT_MESSAGE_SIZE_MAX"), 0); + GetOptionsDB().Add("network.server.drop-empire-ready", UserStringNop("OPTIONS_DB_DROP_EMPIRE_READY"), true); // if config.xml and persistent_config.xml are present, read and set options entries GetOptionsDB().SetFromFile(GetConfigPath(), FreeOrionVersionString()); @@ -59,8 +78,9 @@ int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) { // override previously-saved and default options with command line parameters and flags GetOptionsDB().SetFromCommandLine(args); - if (GetOptionsDB().Get("help")) { - GetOptionsDB().GetUsage(std::cerr); + auto help_arg = GetOptionsDB().Get("help"); + if (help_arg != "NOOP") { + GetOptionsDB().GetUsage(std::cerr, help_arg); ShutdownLoggingSystemFileSink(); return 0; } @@ -72,8 +92,6 @@ int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) { return 0; // quit without actually starting server } - parse::init(); - ServerApp g_app; g_app(); // Calls ServerApp::Run() to run app (intialization and main process loop) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 00000000000..ff611c88ca6 --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,70 @@ +name: freeorion +summary: turn-based space empire and galactic conquest (4X) computer game +description: | + FreeOrion is a free, open source, turn-based space empire and galactic + conquest (4X) computer game being designed and built by the FreeOrion project. + FreeOrion is inspired by the tradition of the Master of Orion games, but is + not a clone or remake of that series or any other game. +confinement: strict +adopt-info: freeorion + +apps: + freeorion: + command: desktop-launch freeorion -S $SNAP_USER_COMMON/save + plugs: [home, pulseaudio, opengl, network, screen-inhibit-control, browser-support, x11] + desktop: share/applications/freeorion.desktop + environment: + LD_LIBRARY_PATH: $LD_LIBRARY_PATH:$SNAP/lib/freeorion + LIBGL_DRIVERS_PATH: $SNAP/usr/lib/x86_64-linux-gnu/dri +parts: + freeorion: + source: . + override-build: | + sed -i.bak -e 's|Icon=freeorion$|Icon=${SNAP}/meta/gui/icon.png|g' ../src/packaging/freeorion.desktop + snapcraftctl build + plugin: cmake + override-pull: | + snapcraftctl pull + version="$(git describe --tags --always --dirty)" + case $version in + v*) version=$(echo $version | tail -c +2) ;; + *) version=$(echo $version | head -c 32) ;; + esac + [ -n "$(echo $version | grep '-')" ] && grade=devel || grade=stable + snapcraftctl set-version "$version" + snapcraftctl set-grade "$grade" + override-prime: | + snapcraftctl prime + mkdir -p ${SNAPCRAFT_PRIME}/meta/gui + cp ${SNAPCRAFT_PART_SRC}/default/data/art/icons/FO_Icon_256x256.png ${SNAPCRAFT_PRIME}/meta/gui/icon.png + build-packages: + - cmake + - debhelper + - dpkg-dev + - libalut-dev + - libboost-all-dev + - libfreetype6-dev + - libgl1-mesa-dev + - libglew-dev + - libjpeg-dev + - libogg-dev + - libopenal-dev + - libpng-dev + - libsdl2-dev + - libtiff-dev + - libvorbis-dev + - pkg-config + - python + stage-packages: + - mesa-utils + - libgl1-mesa-dri + - python + after: [ desktop-glib-only ] + desktop-glib-only: + source: https://github.com/ubuntu/snapcraft-desktop-helpers.git + source-subdir: glib-only + plugin: make + build-packages: + - libglib2.0-dev + stage-packages: + - libglib2.0-bin diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b3572e4c5c0..a4d0c75d8bd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,2 +1,7 @@ add_subdirectory(parse) add_subdirectory(util) +add_subdirectory(system) + +if (NOT BUILD_HEADLESS) + add_subdirectory(UI) +endif () diff --git a/test/UI/CMakeLists.txt b/test/UI/CMakeLists.txt new file mode 100644 index 00000000000..ada5283d5c9 --- /dev/null +++ b/test/UI/CMakeLists.txt @@ -0,0 +1,36 @@ +macro(ADD_FO_ACCEPTANCE_TEST name) + add_executable(fo_acceptance_${name} + ${ARGN} + ) + target_link_libraries(fo_acceptance_${name} + fo_acceptance_runner + ) +endmacro() + +find_package(GLEW REQUIRED) + +include_directories( + SYSTEM + ${OPENGL_INCLUDE_DIR} +) + +add_library(fo_acceptance_runner STATIC + runner/Application.cpp + runner/Dialog.cpp + runner/ResourceCursor.cpp + ../../UI/SDLGUI.cpp +) + +target_link_libraries(fo_acceptance_runner + PUBLIC + GiGi + GLEW::GLEW + ${SDL_LIBRARIES} +) + +target_include_directories(fo_acceptance_runner + PUBLIC + ${SDL_INCLUDE_DIR} +) + +add_fo_acceptance_test(text_control TestTextControl.cpp) diff --git a/GG/test/acceptance/TestTextControl.cpp b/test/UI/TestTextControl.cpp similarity index 100% rename from GG/test/acceptance/TestTextControl.cpp rename to test/UI/TestTextControl.cpp diff --git a/GG/test/acceptance/runner/Application.cpp b/test/UI/runner/Application.cpp similarity index 88% rename from GG/test/acceptance/runner/Application.cpp rename to test/UI/runner/Application.cpp index d147a4f81e5..a515e277ffe 100644 --- a/GG/test/acceptance/runner/Application.cpp +++ b/test/UI/runner/Application.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include "../../../UI/SDLGUI.h" #include @@ -27,7 +27,7 @@ class Application::Impl { }; -class MinimalGGApp : public GG::SDLGUI { +class MinimalGGApp : public SDLGUI { public: MinimalGGApp(int width, int height, bool calculate_FPS, const std::string& name, int x, int y, bool fullscreen, @@ -135,8 +135,6 @@ void MinimalGGApp::Render() { glPushMatrix(); - // DeltaT() returns the time in whole milliseconds since the last frame - // was rendered (in other words, since this method was last invoked). glRotated ( (Ticks() % 60000) * DEGREES_PER_MS, 0.0, 1.0, 0.0); glBegin (GL_LINES); @@ -181,8 +179,6 @@ void MinimalGGApp::Render() { // application-dependent. Note that this method is called before Render() is // ever called. void MinimalGGApp::GLInit() { - double ratio = Value(AppWidth() * 1.0) / Value(AppHeight()); - glEnable(GL_BLEND); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); @@ -190,10 +186,34 @@ void MinimalGGApp::GLInit() { glViewport(0, 0, Value(AppWidth()), Value(AppHeight())); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - gluPerspective(50.0, ratio, 1.0, 10.0); - gluLookAt(0.0, 0.0, 5.0, - 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0); + + // set up perspective with vertical FOV of 50°. 1:1 application + // window ratio, near plane of 1.0 and far plane of 10.0 + float ratio = Value(AppWidth() * 1.0f) / Value(AppHeight()); + float radians = 50.0f * M_PI / 180.f; + float near = 1.0f; + float far = 10.0f; + float cotangent = std::cos(radians) / std::sin(radians); + + float projection[4][4] = { 0.0f }; + projection[0][0] = cotangent / ratio; + projection[1][1] = cotangent; + projection[2][2] = -((far + near) / (far - near)); + projection[2][3] = -1.0f; + projection[3][2] = -((2.0f * far * near) / (far - near)); + projection[3][3] = 0.0f; + + glMultMatrixf(&projection[0][0]); + + // set up camera in -5.0 z offset from origin looking at origin + GLfloat view[4][4] = { 0.0 }; + view[0][0] = -1.0; + view[1][1] = -1.0; + view[2][2] = 1.0; + view[3][3] = 1.0; + + glMultMatrixf(&view[0][0]); + glTranslated(-0.0, -0.0, -5.0); glMatrixMode(GL_MODELVIEW); } @@ -241,7 +261,7 @@ void Application::Impl::Run(std::shared_ptr window) { try { m_app->Register(std::forward>(window)); - (*m_app)(); + m_app->Run(); } catch (const std::invalid_argument& e) { std::cerr << "main() caught exception(std::invalid_arg): " << e.what() << std::endl; diff --git a/GG/test/acceptance/runner/Application.h b/test/UI/runner/Application.h similarity index 100% rename from GG/test/acceptance/runner/Application.h rename to test/UI/runner/Application.h diff --git a/GG/test/acceptance/runner/Dialog.cpp b/test/UI/runner/Dialog.cpp similarity index 100% rename from GG/test/acceptance/runner/Dialog.cpp rename to test/UI/runner/Dialog.cpp diff --git a/GG/test/acceptance/runner/Dialog.h b/test/UI/runner/Dialog.h similarity index 100% rename from GG/test/acceptance/runner/Dialog.h rename to test/UI/runner/Dialog.h diff --git a/GG/test/acceptance/runner/ResourceCursor.cpp b/test/UI/runner/ResourceCursor.cpp similarity index 100% rename from GG/test/acceptance/runner/ResourceCursor.cpp rename to test/UI/runner/ResourceCursor.cpp diff --git a/test/parse/CMakeLists.txt b/test/parse/CMakeLists.txt index 48796145128..73f8adb12c5 100644 --- a/test/parse/CMakeLists.txt +++ b/test/parse/CMakeLists.txt @@ -9,7 +9,6 @@ add_executable(fo_unittest_parse target_compile_definitions(fo_unittest_parse PRIVATE -DFREEORION_BUILD_SERVER - -DBOOST_TEST_DYN_LINK ) target_include_directories(fo_unittest_parse @@ -20,10 +19,15 @@ target_include_directories(fo_unittest_parse target_link_libraries(fo_unittest_parse freeorioncommon - ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES} - ${CMAKE_THREAD_LIBS_INIT} + Threads::Threads + Boost::boost + Boost::disable_autolinking + Boost::dynamic_linking + Boost::unit_test_framework ) +target_dependencies_copy_to_build(fo_unittest_parse) + add_coverage(fo_unittest_parse unittest ) diff --git a/test/parse/CommonTest.h b/test/parse/CommonTest.h index f19267c1ef5..e9fd20ead42 100644 --- a/test/parse/CommonTest.h +++ b/test/parse/CommonTest.h @@ -6,7 +6,7 @@ #include -#include "universe/ValueRefFwd.h" +#include "universe/ValueRefs.h" #include "parse/Lexer.h" namespace std { diff --git a/test/parse/TestEnumParser.cpp b/test/parse/TestEnumParser.cpp index bb560a76ed8..6cf0e92545e 100644 --- a/test/parse/TestEnumParser.cpp +++ b/test/parse/TestEnumParser.cpp @@ -2,14 +2,54 @@ #include "parse/EnumParser.h" #include "parse/ValueRefParser.h" -#include "parse/ValueRefParserImpl.h" +#include "parse/EnumValueRefRules.h" +#include "parse/ConditionParserImpl.h" #include "universe/Enums.h" +#include "universe/ShipPart.h" +#include "universe/UnlockableItem.h" +#include + +namespace { + // File scope lexer since: 1) initializing the lexer deterministic finite + // automaton (DFA) is slow; 2) the tests are single threaded and + // non-concurrent. + const parse::lexer lexer; +} struct EnumParserFixture { - template - bool parse(std::string phrase, Type& result, parse::enum_rule& rule) { - const parse::lexer& lexer = lexer.instance(); + template + Grammar& make_grammar( + const parse::lexer& lexer, + typename std::enable_if::value, std::nullptr_t>::type = nullptr) + { + static Grammar grammar(lexer); + return grammar; + } + + template + Grammar& make_grammar( + const parse::lexer& lexer, + typename std::enable_if::value, std::nullptr_t>::type = nullptr) + { + parse::detail::Labeller label; + static Grammar grammar(lexer, label); + return grammar; + } + + template + Grammar& make_grammar( + const parse::lexer& lexer, + typename std::enable_if::value, std::nullptr_t>::type = nullptr) + { + parse::detail::Labeller label; + parse::conditions_parser_grammar cond(lexer, label); + static Grammar grammar(lexer, label, cond); + return grammar; + } + + template + bool parse_core(std::string phrase, Type& result, const parse::lexer& lexer, Grammar& grammar) { boost::spirit::qi::in_state_type in_state; boost::spirit::qi::eoi_type eoi; boost::spirit::qi::_1_type _1; @@ -22,634 +62,297 @@ struct EnumParserFixture { bool matched = boost::spirit::qi::phrase_parse( begin, end, - rule[boost::phoenix::ref(result) = _1] > eoi, + grammar[boost::phoenix::ref(result) = _1] > eoi, in_state("WS")[lexer.self] ); return matched && begin == end; } + + template + bool parse(std::string phrase, Type& result) { + Grammar& grammar = make_grammar(lexer); + return parse_core(phrase, result, lexer, grammar); + } + + template + bool parse_enum_expr(std::string phrase, Type& result) { + Rules& rules = make_grammar(lexer); + return parse_core(phrase, result, lexer, rules.enum_expr); + } }; BOOST_FIXTURE_TEST_SUITE(TestEnumParser, EnumParserFixture) -BOOST_AUTO_TEST_CASE(CaptureResultParser) { - CaptureResult result; - - // XXX: No enum number value to validate enum coverage. +#define CHECK_ENUM_AND_RESULT(string, expected, result_type, grammar_type) \ + { \ + result_type result; \ + auto pass = parse(string, result); \ + BOOST_CHECK(pass); \ + BOOST_CHECK(result == expected); \ + } - BOOST_CHECK(parse("Capture", result, parse::capture_result_enum())); - BOOST_CHECK(result == CR_CAPTURE); +#define CHECK_ENUM_EXPR_AND_RESULT(string, expected, result_type, grammar_type) \ + { \ + result_type result; \ + auto pass = parse_enum_expr(string, result); \ + BOOST_CHECK(pass); \ + BOOST_CHECK(result == expected); \ + } - BOOST_CHECK(parse("Destroy", result, parse::capture_result_enum())); - BOOST_CHECK(result == CR_DESTROY); +#define CHECK_FAILED_ENUM(result_type, grammar_type) \ + { \ + result_type result; \ + auto pass = parse("ThisEnumerationDoesNotExist", result); \ + BOOST_CHECK(!pass); \ + } - BOOST_CHECK(parse("Retain", result, parse::capture_result_enum())); - BOOST_CHECK(result == CR_RETAIN); +#define CHECK_FAILED_ENUM_EXPR(result_type, grammar_type) \ + { \ + result_type result; \ + auto pass = parse_enum_expr("ThisEnumerationDoesNotExist", result); \ + BOOST_CHECK(!pass); \ + } - // XXX: is not modifying result the correct behaviour? - result = INVALID_CAPTURE_RESULT; - BOOST_CHECK(!parse("DoesNotExist", result, parse::capture_result_enum())); - BOOST_CHECK(result == INVALID_CAPTURE_RESULT); +BOOST_AUTO_TEST_CASE(CaptureResultParser) { + // XXX: No enum number value to validate enum coverage. + CHECK_ENUM_AND_RESULT("Capture", CR_CAPTURE, CaptureResult, parse::capture_result_enum_grammar); + CHECK_ENUM_AND_RESULT("Destroy", CR_DESTROY, CaptureResult, parse::capture_result_enum_grammar); + CHECK_ENUM_AND_RESULT("Retain", CR_RETAIN, CaptureResult, parse::capture_result_enum_grammar); + CHECK_FAILED_ENUM(CaptureResult, parse::capture_result_enum_grammar); } BOOST_AUTO_TEST_CASE(EmpireAffiliationTypeParser) { - EmpireAffiliationType result; - // Literal is number of tests, not number of enums. - BOOST_REQUIRE_MESSAGE(NUM_AFFIL_TYPES == 7, "Untested enumeration value."); - - BOOST_CHECK(parse("TheEmpire", result, parse::empire_affiliation_type_enum())); - BOOST_CHECK(result == AFFIL_SELF); - - BOOST_CHECK(parse("EnemyOf", result, parse::empire_affiliation_type_enum())); - BOOST_CHECK(result == AFFIL_ENEMY); - - BOOST_CHECK(parse("AllyOf", result, parse::empire_affiliation_type_enum())); - BOOST_CHECK(result == AFFIL_ALLY); - - BOOST_CHECK(parse("AnyEmpire", result, parse::empire_affiliation_type_enum())); - BOOST_CHECK(result == AFFIL_ANY); - - BOOST_CHECK(parse("None", result, parse::empire_affiliation_type_enum())); - BOOST_CHECK(result == AFFIL_NONE); - - BOOST_CHECK(parse("CanSee", result, parse::empire_affiliation_type_enum())); - BOOST_CHECK(result == AFFIL_CAN_SEE); - - BOOST_CHECK(parse("HUMAN", result, parse::empire_affiliation_type_enum())); - BOOST_CHECK(result == AFFIL_HUMAN); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_EMPIRE_AFFIL_TYPE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::empire_affiliation_type_enum())); - BOOST_CHECK(result == INVALID_EMPIRE_AFFIL_TYPE); - - // XXX: is not modifying result the correct behaviour? - result = NUM_AFFIL_TYPES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::empire_affiliation_type_enum())); - BOOST_CHECK(result == NUM_AFFIL_TYPES); + BOOST_REQUIRE_MESSAGE(NUM_AFFIL_TYPES == 8, "Untested enumeration value."); + + CHECK_ENUM_AND_RESULT("TheEmpire", AFFIL_SELF, EmpireAffiliationType, parse::empire_affiliation_enum_grammar); + CHECK_ENUM_AND_RESULT("EnemyOf", AFFIL_ENEMY, EmpireAffiliationType, parse::empire_affiliation_enum_grammar); + CHECK_ENUM_AND_RESULT("PeaceWith", AFFIL_PEACE, EmpireAffiliationType, parse::empire_affiliation_enum_grammar); + CHECK_ENUM_AND_RESULT("AllyOf", AFFIL_ALLY, EmpireAffiliationType, parse::empire_affiliation_enum_grammar); + CHECK_ENUM_AND_RESULT("AnyEmpire", AFFIL_ANY, EmpireAffiliationType, parse::empire_affiliation_enum_grammar); + CHECK_ENUM_AND_RESULT("None", AFFIL_NONE, EmpireAffiliationType, parse::empire_affiliation_enum_grammar); + CHECK_ENUM_AND_RESULT("CanSee", AFFIL_CAN_SEE, EmpireAffiliationType, parse::empire_affiliation_enum_grammar); + CHECK_ENUM_AND_RESULT("HUMAN", AFFIL_HUMAN, EmpireAffiliationType, parse::empire_affiliation_enum_grammar); + CHECK_FAILED_ENUM(EmpireAffiliationType, parse::empire_affiliation_enum_grammar); } BOOST_AUTO_TEST_CASE(NonShipPartMeterTypeParser) { - MeterType result; - // XXX: METER_SIZE not handled, still used? // XXX: HAPPINESS meters not handled, still used? // XXX: REBEL_TROOPS meters not handled, still used? // XXX: No enum number value to validate enum coverage. // Maybe the Meter enum should be split. - BOOST_CHECK(parse("TargetPopulation", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TARGET_POPULATION); - BOOST_CHECK(parse("SetTargetPopulation", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TARGET_POPULATION); - - BOOST_CHECK(parse("TargetIndustry", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TARGET_INDUSTRY); - BOOST_CHECK(parse("SetTargetIndustry", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TARGET_INDUSTRY); - - BOOST_CHECK(parse("TargetResearch", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TARGET_RESEARCH); - BOOST_CHECK(parse("SetTargetResearch", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TARGET_RESEARCH); - - BOOST_CHECK(parse("TargetTrade", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TARGET_TRADE); - BOOST_CHECK(parse("SetTargetTrade", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TARGET_TRADE); - - BOOST_CHECK(parse("TargetConstruction", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TARGET_CONSTRUCTION); - BOOST_CHECK(parse("SetTargetConstruction", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TARGET_CONSTRUCTION); - - BOOST_CHECK(parse("MaxFuel", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_FUEL); - BOOST_CHECK(parse("SetMaxFuel", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_FUEL); - - BOOST_CHECK(parse("MaxShield", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_SHIELD); - BOOST_CHECK(parse("SetMaxShield", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_SHIELD); - - BOOST_CHECK(parse("MaxStructure", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_STRUCTURE); - BOOST_CHECK(parse("SetMaxStructure", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_STRUCTURE); - - BOOST_CHECK(parse("MaxDefense", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_DEFENSE); - BOOST_CHECK(parse("SetMaxDefense", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_DEFENSE); - - BOOST_CHECK(parse("MaxTroops", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_TROOPS); - BOOST_CHECK(parse("SetMaxTroops", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_TROOPS); - - BOOST_CHECK(parse("Population", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_POPULATION); - BOOST_CHECK(parse("SetPopulation", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_POPULATION); - - BOOST_CHECK(parse("Industry", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_INDUSTRY); - BOOST_CHECK(parse("SetIndustry", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_INDUSTRY); - - BOOST_CHECK(parse("Research", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_RESEARCH); - BOOST_CHECK(parse("SetResearch", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_RESEARCH); - - BOOST_CHECK(parse("Trade", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TRADE); - BOOST_CHECK(parse("SetTrade", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TRADE); - - BOOST_CHECK(parse("Construction", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_CONSTRUCTION); - BOOST_CHECK(parse("SetConstruction", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_CONSTRUCTION); - - BOOST_CHECK(parse("Fuel", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_FUEL); - BOOST_CHECK(parse("SetFuel", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_FUEL); - - BOOST_CHECK(parse("Shield", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_SHIELD); - BOOST_CHECK(parse("SetShield", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_SHIELD); - - BOOST_CHECK(parse("Structure", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_STRUCTURE); - BOOST_CHECK(parse("SetStructure", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_STRUCTURE); - - BOOST_CHECK(parse("Defense", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_DEFENSE); - BOOST_CHECK(parse("SetDefense", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_DEFENSE); - - BOOST_CHECK(parse("Troops", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TROOPS); - BOOST_CHECK(parse("SetTroops", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_TROOPS); - - BOOST_CHECK(parse("Supply", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_SUPPLY); - BOOST_CHECK(parse("SetSupply", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_SUPPLY); - - BOOST_CHECK(parse("Stealth", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_STEALTH); - BOOST_CHECK(parse("SetStealth", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_STEALTH); - - BOOST_CHECK(parse("Detection", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_DETECTION); - BOOST_CHECK(parse("SetDetection", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_DETECTION); - - BOOST_CHECK(parse("Speed", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_SPEED); - BOOST_CHECK(parse("SetSpeed", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_SPEED); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_METER_TYPE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == INVALID_METER_TYPE); - result = INVALID_METER_TYPE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == INVALID_METER_TYPE); - - // XXX: is not modifying result the correct behaviour? - result = NUM_METER_TYPES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::non_ship_part_meter_type_enum())); - BOOST_CHECK(result == NUM_METER_TYPES); - result = NUM_METER_TYPES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::set_non_ship_part_meter_type_enum())); - BOOST_CHECK(result == NUM_METER_TYPES); + CHECK_ENUM_AND_RESULT("TargetPopulation", METER_TARGET_POPULATION, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetTargetPopulation", METER_TARGET_POPULATION, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("TargetIndustry", METER_TARGET_INDUSTRY, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetTargetIndustry", METER_TARGET_INDUSTRY, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("TargetResearch", METER_TARGET_RESEARCH, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetTargetResearch", METER_TARGET_RESEARCH, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("TargetTrade", METER_TARGET_TRADE, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetTargetTrade", METER_TARGET_TRADE, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("TargetConstruction", METER_TARGET_CONSTRUCTION, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetTargetConstruction", METER_TARGET_CONSTRUCTION, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("MaxFuel", METER_MAX_FUEL, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetMaxFuel", METER_MAX_FUEL, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("MaxShield", METER_MAX_SHIELD, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetMaxShield", METER_MAX_SHIELD, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("MaxStructure", METER_MAX_STRUCTURE, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetMaxStructure", METER_MAX_STRUCTURE, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("MaxDefense", METER_MAX_DEFENSE, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetMaxDefense", METER_MAX_DEFENSE, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("MaxTroops", METER_MAX_TROOPS, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetMaxTroops", METER_MAX_TROOPS, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Population", METER_POPULATION, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetPopulation", METER_POPULATION, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Industry", METER_INDUSTRY, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetIndustry", METER_INDUSTRY, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Research", METER_RESEARCH, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetResearch", METER_RESEARCH, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Trade", METER_TRADE, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetTrade", METER_TRADE, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Construction", METER_CONSTRUCTION, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetConstruction", METER_CONSTRUCTION, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Fuel", METER_FUEL, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetFuel", METER_FUEL, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Shield", METER_SHIELD, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetShield", METER_SHIELD, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Structure", METER_STRUCTURE, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetStructure", METER_STRUCTURE, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Defense", METER_DEFENSE, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetDefense", METER_DEFENSE, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Troops", METER_TROOPS, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetTroops", METER_TROOPS, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Supply", METER_SUPPLY, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetSupply", METER_SUPPLY, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Stealth", METER_STEALTH, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetStealth", METER_STEALTH, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Detection", METER_DETECTION, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetDetection", METER_DETECTION, MeterType, parse::set_non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Speed", METER_SPEED, MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetSpeed", METER_SPEED, MeterType, parse::set_non_ship_part_meter_enum_grammar); + + CHECK_FAILED_ENUM(MeterType, parse::non_ship_part_meter_enum_grammar); + CHECK_FAILED_ENUM(MeterType, parse::set_non_ship_part_meter_enum_grammar); } -BOOST_AUTO_TEST_CASE(PlanetEnvironmentParser) { - PlanetEnvironment result; - +BOOST_AUTO_TEST_CASE(PlanetEnvironmentEnumParser) { // Literal is number of tests, not number of enums. BOOST_REQUIRE_MESSAGE(NUM_PLANET_ENVIRONMENTS == 5, "Untested enumeration value."); - BOOST_CHECK(parse("Uninhabitable", result, parse::detail::planet_environment_rules().enum_expr)); - BOOST_CHECK(result == PE_UNINHABITABLE); - - BOOST_CHECK(parse("Hostile", result, parse::detail::planet_environment_rules().enum_expr)); - BOOST_CHECK(result == PE_HOSTILE); - - BOOST_CHECK(parse("Poor", result, parse::detail::planet_environment_rules().enum_expr)); - BOOST_CHECK(result == PE_POOR); - - BOOST_CHECK(parse("Adequate", result, parse::detail::planet_environment_rules().enum_expr)); - BOOST_CHECK(result == PE_ADEQUATE); - - BOOST_CHECK(parse("Good", result, parse::detail::planet_environment_rules().enum_expr)); - BOOST_CHECK(result == PE_GOOD); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_PLANET_ENVIRONMENT; - BOOST_CHECK(!parse("DoesNotExist", result, parse::detail::planet_environment_rules().enum_expr)); - BOOST_CHECK(result == INVALID_PLANET_ENVIRONMENT); - - // XXX: is not modifying result the correct behaviour? - result = NUM_PLANET_ENVIRONMENTS; - BOOST_CHECK(!parse("DoesNotExist", result, parse::detail::planet_environment_rules().enum_expr)); - BOOST_CHECK(result == NUM_PLANET_ENVIRONMENTS); + CHECK_ENUM_EXPR_AND_RESULT("Uninhabitable", PE_UNINHABITABLE, PlanetEnvironment, parse::detail::planet_environment_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Hostile", PE_HOSTILE, PlanetEnvironment, parse::detail::planet_environment_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Poor", PE_POOR, PlanetEnvironment, parse::detail::planet_environment_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Adequate", PE_ADEQUATE, PlanetEnvironment, parse::detail::planet_environment_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Good", PE_GOOD, PlanetEnvironment, parse::detail::planet_environment_parser_rules); + CHECK_FAILED_ENUM_EXPR(PlanetEnvironment, parse::detail::planet_environment_parser_rules); } BOOST_AUTO_TEST_CASE(PlanetSizeParser) { - PlanetSize result; - // Literal is number of tests, not number of enums. // XXX: SZ_NOWORLD has no token, so no test, +1 BOOST_REQUIRE_MESSAGE(NUM_PLANET_SIZES == 7 + 1, "Untested enumeration value."); - BOOST_CHECK(parse("Tiny", result, parse::detail::planet_size_rules().enum_expr)); - BOOST_CHECK(result == SZ_TINY); - - BOOST_CHECK(parse("Small", result, parse::detail::planet_size_rules().enum_expr)); - BOOST_CHECK(result == SZ_SMALL); - - BOOST_CHECK(parse("Medium", result, parse::detail::planet_size_rules().enum_expr)); - BOOST_CHECK(result == SZ_MEDIUM); - - BOOST_CHECK(parse("Large", result, parse::detail::planet_size_rules().enum_expr)); - BOOST_CHECK(result == SZ_LARGE); - - BOOST_CHECK(parse("Huge", result, parse::detail::planet_size_rules().enum_expr)); - BOOST_CHECK(result == SZ_HUGE); - - BOOST_CHECK(parse("Asteroids", result, parse::detail::planet_size_rules().enum_expr)); - BOOST_CHECK(result == SZ_ASTEROIDS); - - BOOST_CHECK(parse("GasGiant", result, parse::detail::planet_size_rules().enum_expr)); - BOOST_CHECK(result == SZ_GASGIANT); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_PLANET_SIZE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::detail::planet_size_rules().enum_expr)); - BOOST_CHECK(result == INVALID_PLANET_SIZE); - - // XXX: is not modifying result the correct behaviour? - result = NUM_PLANET_SIZES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::detail::planet_size_rules().enum_expr)); - BOOST_CHECK(result == NUM_PLANET_SIZES); + CHECK_ENUM_EXPR_AND_RESULT("Tiny", SZ_TINY, PlanetSize, parse::detail::planet_size_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Small", SZ_SMALL, PlanetSize, parse::detail::planet_size_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Medium", SZ_MEDIUM, PlanetSize, parse::detail::planet_size_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Large", SZ_LARGE, PlanetSize, parse::detail::planet_size_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Huge", SZ_HUGE, PlanetSize, parse::detail::planet_size_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Asteroids", SZ_ASTEROIDS, PlanetSize, parse::detail::planet_size_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("GasGiant", SZ_GASGIANT, PlanetSize, parse::detail::planet_size_parser_rules); + CHECK_FAILED_ENUM_EXPR(PlanetSize, parse::detail::planet_size_parser_rules); } BOOST_AUTO_TEST_CASE(PlanetTypeParser) { - PlanetType result; - // Literal is number of tests, not number of enums. BOOST_REQUIRE_MESSAGE(NUM_PLANET_TYPES == 11, "Untested enumeration value."); - BOOST_CHECK(parse("Swamp", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_SWAMP); - - BOOST_CHECK(parse("Toxic", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_TOXIC); - - BOOST_CHECK(parse("Inferno", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_INFERNO); - - BOOST_CHECK(parse("Radiated", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_RADIATED); - - BOOST_CHECK(parse("Barren", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_BARREN); - - BOOST_CHECK(parse("Tundra", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_TUNDRA); - - BOOST_CHECK(parse("Desert", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_DESERT); - - BOOST_CHECK(parse("Terran", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_TERRAN); - - BOOST_CHECK(parse("Ocean", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_OCEAN); - - BOOST_CHECK(parse("Asteroids", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_ASTEROIDS); - - BOOST_CHECK(parse("GasGiant", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == PT_GASGIANT); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_PLANET_TYPE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == INVALID_PLANET_TYPE); - - // XXX: is not modifying result the correct behaviour? - result = NUM_PLANET_TYPES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::detail::planet_type_rules().enum_expr)); - BOOST_CHECK(result == NUM_PLANET_TYPES); + CHECK_ENUM_EXPR_AND_RESULT("Swamp", PT_SWAMP, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Toxic", PT_TOXIC, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Inferno", PT_INFERNO, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Radiated", PT_RADIATED, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Barren", PT_BARREN, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Tundra", PT_TUNDRA, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Desert", PT_DESERT, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Terran", PT_TERRAN, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Ocean", PT_OCEAN, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Asteroids", PT_ASTEROIDS, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("GasGiant", PT_GASGIANT, PlanetType, parse::detail::planet_type_parser_rules); + CHECK_FAILED_ENUM_EXPR(PlanetType, parse::detail::planet_type_parser_rules); } BOOST_AUTO_TEST_CASE(ShipPartsClassParser) { - ShipPartClass result; - // Literal is number of tests, not number of enums. BOOST_REQUIRE_MESSAGE(NUM_SHIP_PART_CLASSES == 17, "Untested enumeration value."); - BOOST_CHECK(parse("ShortRange", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_DIRECT_WEAPON); - - BOOST_CHECK(parse("FighterBay", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_FIGHTER_BAY); - - BOOST_CHECK(parse("FighterHangar", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_FIGHTER_HANGAR); - - BOOST_CHECK(parse("Shield", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_SHIELD); - - BOOST_CHECK(parse("Armour", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_ARMOUR); - - BOOST_CHECK(parse("Troops", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_TROOPS); - - BOOST_CHECK(parse("Detection", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_DETECTION); - - BOOST_CHECK(parse("Stealth", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_STEALTH); - - BOOST_CHECK(parse("Fuel", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_FUEL); - - BOOST_CHECK(parse("Colony", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_COLONY); - - BOOST_CHECK(parse("Speed", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_SPEED); - - BOOST_CHECK(parse("General", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_GENERAL); - - BOOST_CHECK(parse("Bombard", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_BOMBARD); - - BOOST_CHECK(parse("Industry", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_INDUSTRY); - - BOOST_CHECK(parse("Research", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_RESEARCH); - - BOOST_CHECK(parse("Trade", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_TRADE); - - BOOST_CHECK(parse("ProductionLocation", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == PC_PRODUCTION_LOCATION); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_SHIP_PART_CLASS; - BOOST_CHECK(!parse("DoesNotExist", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == INVALID_SHIP_PART_CLASS); - - // XXX: is not modifying result the correct behaviour? - result = NUM_SHIP_PART_CLASSES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::ship_part_class_enum())); - BOOST_CHECK(result == NUM_SHIP_PART_CLASSES); + CHECK_ENUM_AND_RESULT("ShortRange", PC_DIRECT_WEAPON, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("FighterBay", PC_FIGHTER_BAY, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("FighterHangar", PC_FIGHTER_HANGAR, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Shield", PC_SHIELD, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Armour", PC_ARMOUR, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Troops", PC_TROOPS, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Detection", PC_DETECTION, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Stealth", PC_STEALTH, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Fuel", PC_FUEL, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Colony", PC_COLONY, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Speed", PC_SPEED, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("General", PC_GENERAL, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Bombard", PC_BOMBARD, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Industry", PC_INDUSTRY, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Research", PC_RESEARCH, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("Trade", PC_TRADE, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_ENUM_AND_RESULT("ProductionLocation", PC_PRODUCTION_LOCATION, ShipPartClass, parse::ship_part_class_enum_grammar); + CHECK_FAILED_ENUM(ShipPartClass, parse::ship_part_class_enum_grammar); } BOOST_AUTO_TEST_CASE(ShipPartMeterTypeParser) { - MeterType result; - // XXX: No enum number value to validate enum coverage. // Maybe the Meter enum should be split. - BOOST_CHECK(parse("Damage", result, parse::ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_CAPACITY); - BOOST_CHECK(parse("SetDamage", result, parse::set_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_CAPACITY); - - BOOST_CHECK(parse("Capacity", result, parse::ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_CAPACITY); - BOOST_CHECK(parse("SetCapacity", result, parse::set_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_CAPACITY); - - BOOST_CHECK(parse("MaxDamage", result, parse::ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_CAPACITY); - BOOST_CHECK(parse("SetMaxDamage", result, parse::set_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_CAPACITY); - - BOOST_CHECK(parse("MaxCapacity", result, parse::ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_CAPACITY); - BOOST_CHECK(parse("SetMaxCapacity", result, parse::set_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_CAPACITY); - - BOOST_CHECK(parse("SetSecondaryStat", result, parse::set_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_SECONDARY_STAT); - - BOOST_CHECK(parse("SetMaxSecondaryStat", result, parse::set_ship_part_meter_type_enum())); - BOOST_CHECK(result == METER_MAX_SECONDARY_STAT); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_METER_TYPE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::set_ship_part_meter_type_enum())); - BOOST_CHECK(result == INVALID_METER_TYPE); - - // XXX: is not modifying result the correct behaviour? - result = NUM_METER_TYPES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::set_ship_part_meter_type_enum())); - BOOST_CHECK(result == NUM_METER_TYPES); + CHECK_ENUM_AND_RESULT("Damage", METER_CAPACITY, MeterType, parse::ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetDamage", METER_CAPACITY, MeterType, parse::set_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("Capacity", METER_CAPACITY, MeterType, parse::ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetCapacity", METER_CAPACITY, MeterType, parse::set_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("MaxDamage", METER_MAX_CAPACITY, MeterType, parse::ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetMaxDamage", METER_MAX_CAPACITY, MeterType, parse::set_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("MaxCapacity", METER_MAX_CAPACITY, MeterType, parse::ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetMaxCapacity", METER_MAX_CAPACITY, MeterType, parse::set_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetSecondaryStat", METER_SECONDARY_STAT, MeterType, parse::set_ship_part_meter_enum_grammar); + CHECK_ENUM_AND_RESULT("SetMaxSecondaryStat", METER_MAX_SECONDARY_STAT, MeterType, parse::set_ship_part_meter_enum_grammar); + CHECK_FAILED_ENUM(MeterType, parse::ship_part_meter_enum_grammar); + CHECK_FAILED_ENUM(MeterType, parse::set_ship_part_meter_enum_grammar); } BOOST_AUTO_TEST_CASE(ShipSlotTypeParser) { - ShipSlotType result; - // Literal is number of tests, not number of enums. BOOST_REQUIRE_MESSAGE(NUM_SHIP_SLOT_TYPES == 3, "Untested enumeration value."); - BOOST_CHECK(parse("External", result, parse::ship_slot_type_enum())); - BOOST_CHECK(result == SL_EXTERNAL); - - BOOST_CHECK(parse("Internal", result, parse::ship_slot_type_enum())); - BOOST_CHECK(result == SL_INTERNAL); - - BOOST_CHECK(parse("Core", result, parse::ship_slot_type_enum())); - BOOST_CHECK(result == SL_CORE); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_SHIP_SLOT_TYPE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::ship_slot_type_enum())); - BOOST_CHECK(result == INVALID_SHIP_SLOT_TYPE); - - // XXX: is not modifying result the correct behaviour? - result = NUM_SHIP_SLOT_TYPES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::ship_slot_type_enum())); - BOOST_CHECK(result == NUM_SHIP_SLOT_TYPES); + CHECK_ENUM_AND_RESULT("External", SL_EXTERNAL, ShipSlotType, parse::ship_slot_enum_grammar); + CHECK_ENUM_AND_RESULT("Internal", SL_INTERNAL, ShipSlotType, parse::ship_slot_enum_grammar); + CHECK_ENUM_AND_RESULT("Core", SL_CORE, ShipSlotType, parse::ship_slot_enum_grammar); + CHECK_FAILED_ENUM(ShipSlotType, parse::ship_slot_enum_grammar); } BOOST_AUTO_TEST_CASE(StatisticTypeParser) { - ValueRef::StatisticType result; - // XXX: No enum number value to validate enum coverage. - BOOST_CHECK(parse("Count", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::COUNT); - - BOOST_CHECK(parse("Sum", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::SUM); - - BOOST_CHECK(parse("Mean", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::MEAN); - - BOOST_CHECK(parse("Rms", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::RMS); - - BOOST_CHECK(parse("Mode", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::MODE); - - BOOST_CHECK(parse("Max", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::MAX); - - BOOST_CHECK(parse("Min", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::MIN); - - BOOST_CHECK(parse("Spread", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::SPREAD); - - BOOST_CHECK(parse("StDev", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::STDEV); - - BOOST_CHECK(parse("Product", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::PRODUCT); - - // XXX: is not modifying result the correct behaviour? - result = ValueRef::INVALID_STATISTIC_TYPE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::statistic_type_enum())); - BOOST_CHECK(result == ValueRef::INVALID_STATISTIC_TYPE); + CHECK_ENUM_AND_RESULT("Count", ValueRef::COUNT, ValueRef::StatisticType, parse::statistic_enum_grammar); + CHECK_ENUM_AND_RESULT("Sum", ValueRef::SUM, ValueRef::StatisticType, parse::statistic_enum_grammar); + CHECK_ENUM_AND_RESULT("Mean", ValueRef::MEAN, ValueRef::StatisticType, parse::statistic_enum_grammar); + CHECK_ENUM_AND_RESULT("Rms", ValueRef::RMS, ValueRef::StatisticType, parse::statistic_enum_grammar); + CHECK_ENUM_AND_RESULT("Mode", ValueRef::MODE, ValueRef::StatisticType, parse::statistic_enum_grammar); + CHECK_ENUM_AND_RESULT("Max", ValueRef::MAX, ValueRef::StatisticType, parse::statistic_enum_grammar); + CHECK_ENUM_AND_RESULT("Min", ValueRef::MIN, ValueRef::StatisticType, parse::statistic_enum_grammar); + CHECK_ENUM_AND_RESULT("Spread", ValueRef::SPREAD, ValueRef::StatisticType, parse::statistic_enum_grammar); + CHECK_ENUM_AND_RESULT("StDev", ValueRef::STDEV, ValueRef::StatisticType, parse::statistic_enum_grammar); + CHECK_ENUM_AND_RESULT("Product", ValueRef::PRODUCT, ValueRef::StatisticType, parse::statistic_enum_grammar); + CHECK_FAILED_ENUM(ValueRef::StatisticType, parse::statistic_enum_grammar); } BOOST_AUTO_TEST_CASE(StarTypeParser) { - StarType result; - // Literal is number of tests, not number of enums. BOOST_REQUIRE_MESSAGE(NUM_STAR_TYPES == 8, "Untested enumeration value."); - BOOST_CHECK(parse("Blue", result, parse::detail::star_type_rules().enum_expr)); - BOOST_CHECK(result == STAR_BLUE); - - BOOST_CHECK(parse("White", result, parse::detail::star_type_rules().enum_expr)); - BOOST_CHECK(result == STAR_WHITE); - - BOOST_CHECK(parse("Yellow", result, parse::detail::star_type_rules().enum_expr)); - BOOST_CHECK(result == STAR_YELLOW); - - BOOST_CHECK(parse("Orange", result, parse::detail::star_type_rules().enum_expr)); - BOOST_CHECK(result == STAR_ORANGE); - - BOOST_CHECK(parse("Red", result, parse::detail::star_type_rules().enum_expr)); - BOOST_CHECK(result == STAR_RED); - - BOOST_CHECK(parse("Neutron", result, parse::detail::star_type_rules().enum_expr)); - BOOST_CHECK(result == STAR_NEUTRON); - - BOOST_CHECK(parse("BlackHole", result, parse::detail::star_type_rules().enum_expr)); - BOOST_CHECK(result == STAR_BLACK); - - BOOST_CHECK(parse("NoStar", result, parse::detail::star_type_rules().enum_expr)); - BOOST_CHECK(result == STAR_NONE); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_STAR_TYPE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::detail::star_type_rules().enum_expr)); - BOOST_CHECK(result == INVALID_STAR_TYPE); - - // XXX: is not modifying result the correct behaviour? - result = NUM_STAR_TYPES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::detail::star_type_rules().enum_expr)); - BOOST_CHECK(result == NUM_STAR_TYPES); + CHECK_ENUM_EXPR_AND_RESULT("Blue", STAR_BLUE, StarType, parse::detail::star_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("White", STAR_WHITE, StarType, parse::detail::star_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Yellow", STAR_YELLOW, StarType, parse::detail::star_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Orange", STAR_ORANGE, StarType, parse::detail::star_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Red", STAR_RED, StarType, parse::detail::star_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Neutron", STAR_NEUTRON, StarType, parse::detail::star_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("BlackHole", STAR_BLACK, StarType, parse::detail::star_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("NoStar", STAR_NONE, StarType, parse::detail::star_type_parser_rules); + CHECK_FAILED_ENUM_EXPR(StarType, parse::detail::star_type_parser_rules); } BOOST_AUTO_TEST_CASE(UnlockableItemTypeParser) { - UnlockableItemType result; - // Literal is number of tests, not number of enums. BOOST_REQUIRE_MESSAGE(NUM_UNLOCKABLE_ITEM_TYPES == 5, "Untested enumeration value."); - BOOST_CHECK(parse("Building", result, parse::unlockable_item_type_enum())); - BOOST_CHECK(result == UIT_BUILDING); - - BOOST_CHECK(parse("ShipPart", result, parse::unlockable_item_type_enum())); - BOOST_CHECK(result == UIT_SHIP_PART); - - BOOST_CHECK(parse("ShipHull", result, parse::unlockable_item_type_enum())); - BOOST_CHECK(result == UIT_SHIP_HULL); - - BOOST_CHECK(parse("ShipDesign", result, parse::unlockable_item_type_enum())); - BOOST_CHECK(result == UIT_SHIP_DESIGN); - - BOOST_CHECK(parse("Tech", result, parse::unlockable_item_type_enum())); - BOOST_CHECK(result == UIT_TECH); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_UNLOCKABLE_ITEM_TYPE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::unlockable_item_type_enum())); - BOOST_CHECK(result == INVALID_UNLOCKABLE_ITEM_TYPE); - - // XXX: is not modifying result the correct behaviour? - result = NUM_UNLOCKABLE_ITEM_TYPES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::unlockable_item_type_enum())); - BOOST_CHECK(result == NUM_UNLOCKABLE_ITEM_TYPES); + CHECK_ENUM_AND_RESULT("Building", UIT_BUILDING, UnlockableItemType, parse::unlockable_item_enum_grammar); + CHECK_ENUM_AND_RESULT("ShipPart", UIT_SHIP_PART, UnlockableItemType, parse::unlockable_item_enum_grammar); + CHECK_ENUM_AND_RESULT("ShipHull", UIT_SHIP_HULL, UnlockableItemType, parse::unlockable_item_enum_grammar); + CHECK_ENUM_AND_RESULT("ShipDesign", UIT_SHIP_DESIGN, UnlockableItemType, parse::unlockable_item_enum_grammar); + CHECK_ENUM_AND_RESULT("Tech", UIT_TECH, UnlockableItemType, parse::unlockable_item_enum_grammar); + CHECK_FAILED_ENUM(UnlockableItemType, parse::unlockable_item_enum_grammar); } BOOST_AUTO_TEST_CASE(UniverseObjectTypeParser) { - UniverseObjectType result; - // Literal is number of tests, not number of enums. // XXX: OBJ_FIGHTER has no token, so no test, +1 BOOST_REQUIRE_MESSAGE(NUM_OBJ_TYPES == 8 + 1, "Untested enumeration value."); - BOOST_CHECK(parse("Building", result, parse::detail::universe_object_type_rules().enum_expr)); - BOOST_CHECK(result == OBJ_BUILDING); - - BOOST_CHECK(parse("Ship", result, parse::detail::universe_object_type_rules().enum_expr)); - BOOST_CHECK(result == OBJ_SHIP); - - BOOST_CHECK(parse("Fleet ", result, parse::detail::universe_object_type_rules().enum_expr)); - BOOST_CHECK(result == OBJ_FLEET ); - - BOOST_CHECK(parse("Planet", result, parse::detail::universe_object_type_rules().enum_expr)); - BOOST_CHECK(result == OBJ_PLANET); - - BOOST_CHECK(parse("PopulationCenter", result, parse::detail::universe_object_type_rules().enum_expr)); - BOOST_CHECK(result == OBJ_POP_CENTER); - - BOOST_CHECK(parse("ProductionCenter", result, parse::detail::universe_object_type_rules().enum_expr)); - BOOST_CHECK(result == OBJ_PROD_CENTER); - - BOOST_CHECK(parse("System", result, parse::detail::universe_object_type_rules().enum_expr)); - BOOST_CHECK(result == OBJ_SYSTEM); - - BOOST_CHECK(parse("Field", result, parse::detail::universe_object_type_rules().enum_expr)); - BOOST_CHECK(result == OBJ_FIELD); - - // XXX: is not modifying result the correct behaviour? - result = INVALID_UNIVERSE_OBJECT_TYPE; - BOOST_CHECK(!parse("DoesNotExist", result, parse::detail::universe_object_type_rules().enum_expr)); - BOOST_CHECK(result == INVALID_UNIVERSE_OBJECT_TYPE); - - // XXX: is not modifying result the correct behaviour? - result = NUM_OBJ_TYPES; - BOOST_CHECK(!parse("DoesNotExist", result, parse::detail::universe_object_type_rules().enum_expr)); - BOOST_CHECK(result == NUM_OBJ_TYPES); + CHECK_ENUM_EXPR_AND_RESULT("Building", OBJ_BUILDING, UniverseObjectType, parse::detail::universe_object_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Ship", OBJ_SHIP, UniverseObjectType, parse::detail::universe_object_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Fleet ", OBJ_FLEET , UniverseObjectType, parse::detail::universe_object_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Planet", OBJ_PLANET, UniverseObjectType, parse::detail::universe_object_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("PopulationCenter", OBJ_POP_CENTER, UniverseObjectType, parse::detail::universe_object_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("ProductionCenter", OBJ_PROD_CENTER, UniverseObjectType, parse::detail::universe_object_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("System", OBJ_SYSTEM, UniverseObjectType, parse::detail::universe_object_type_parser_rules); + CHECK_ENUM_EXPR_AND_RESULT("Field", OBJ_FIELD, UniverseObjectType, parse::detail::universe_object_type_parser_rules); + CHECK_FAILED_ENUM_EXPR(UniverseObjectType, parse::detail::universe_object_type_parser_rules); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/parse/TestValueRefDoubleParser.cpp b/test/parse/TestValueRefDoubleParser.cpp index 2ea3110a1c3..d38a5bd2831 100644 --- a/test/parse/TestValueRefDoubleParser.cpp +++ b/test/parse/TestValueRefDoubleParser.cpp @@ -1,7 +1,7 @@ #include #include "parse/ValueRefParser.h" -#include "universe/ValueRef.h" +#include "universe/ValueRefs.h" #include "CommonTest.h" struct ValueRefDoubleFixture: boost::unit_test::test_observer { @@ -24,7 +24,7 @@ struct ValueRefDoubleFixture: boost::unit_test::test_observer { } } - void printTree(const ValueRef::ValueRefBase* root, int depth) { + void printTree(const ValueRef::ValueRef* root, int depth) { if(depth > 10) { std::cout << "Tree print overflow" << std::endl; } @@ -40,7 +40,7 @@ struct ValueRefDoubleFixture: boost::unit_test::test_observer { } } - bool parse(std::string phrase, ValueRef::ValueRefBase*& result) { + bool parse(std::string phrase, ValueRef::ValueRef*& result) { const parse::lexer& lexer = lexer.instance(); boost::spirit::qi::in_state_type in_state; boost::spirit::qi::eoi_type eoi; @@ -54,7 +54,7 @@ struct ValueRefDoubleFixture: boost::unit_test::test_observer { bool matched = boost::spirit::qi::phrase_parse( begin, end, - parse::double_value_ref()[boost::phoenix::ref(result) = _1] > eoi, + double_rules[boost::phoenix::ref(result) = _1] > eoi, in_state("WS")[lexer.self] ); @@ -69,7 +69,7 @@ struct ValueRefDoubleFixture: boost::unit_test::test_observer { static const std::array containerTypes; static const std::array attributes; - ValueRef::ValueRefBase* result; + ValueRef::ValueRef* result; const ValueRef::Operation* operation1; const ValueRef::Operation* operation2; const ValueRef::Operation* operation3; @@ -140,6 +140,7 @@ const std::array ValueRefDoubleFixture::attributes = {{ "X", "Y", "SizeAsDouble", + "HabitableSize", "NextTurnPopGrowth", "Size", "DistanceFromOriginalType" diff --git a/test/parse/TestValueRefIntParser.cpp b/test/parse/TestValueRefIntParser.cpp index bc4114cd65c..41425aa9e6c 100644 --- a/test/parse/TestValueRefIntParser.cpp +++ b/test/parse/TestValueRefIntParser.cpp @@ -1,7 +1,7 @@ #include #include "parse/ValueRefParser.h" -#include "universe/ValueRef.h" +#include "universe/ValueRefs.h" #include "CommonTest.h" struct ValueRefIntFixture: boost::unit_test::test_observer { @@ -24,7 +24,7 @@ struct ValueRefIntFixture: boost::unit_test::test_observer { } } - void printTree(const ValueRef::ValueRefBase* root, int depth) { + void printTree(const ValueRef::ValueRef* root, int depth) { if(depth > 10) { std::cout << "Tree print overflow" << std::endl; } @@ -40,7 +40,7 @@ struct ValueRefIntFixture: boost::unit_test::test_observer { } } - bool parse(std::string phrase, ValueRef::ValueRefBase*& result) { + bool parse(std::string phrase, ValueRef::ValueRef*& result) { const parse::lexer& lexer = lexer.instance(); boost::spirit::qi::in_state_type in_state; boost::spirit::qi::eoi_type eoi; @@ -54,7 +54,7 @@ struct ValueRefIntFixture: boost::unit_test::test_observer { bool matched = boost::spirit::qi::phrase_parse( begin, end, - parse::int_value_ref()[boost::phoenix::ref(result) = _1] > eoi, + int_rules.expr[boost::phoenix::ref(result) = _1] > eoi, in_state("WS")[lexer.self] ); @@ -69,7 +69,7 @@ struct ValueRefIntFixture: boost::unit_test::test_observer { static const std::array containerTypes; static const std::array attributes; - ValueRef::ValueRefBase* result; + ValueRef::ValueRef* result; const ValueRef::Operation* operation1; const ValueRef::Operation* operation2; const ValueRef::Operation* operation3; diff --git a/test/parse/TestValueRefStringParser.cpp b/test/parse/TestValueRefStringParser.cpp index fecd2855dce..bb585fc2894 100644 --- a/test/parse/TestValueRefStringParser.cpp +++ b/test/parse/TestValueRefStringParser.cpp @@ -1,7 +1,7 @@ #include #include "parse/ValueRefParser.h" -#include "universe/ValueRef.h" +#include "universe/ValueRefs.h" #include "CommonTest.h" struct ValueRefStringFixture { @@ -16,7 +16,7 @@ struct ValueRefStringFixture { delete result; } - bool parse(std::string phrase, ValueRef::ValueRefBase*& result) { + bool parse(std::string phrase, ValueRef::ValueRef*& result) { const parse::lexer& lexer = lexer.instance(); boost::spirit::qi::in_state_type in_state; boost::spirit::qi::eoi_type eoi; @@ -30,7 +30,7 @@ struct ValueRefStringFixture { bool matched = boost::spirit::qi::phrase_parse( begin, end, - parse::string_value_ref()[boost::phoenix::ref(result) = _1] > eoi, + string_grammar[boost::phoenix::ref(result) = _1] > eoi, in_state("WS")[lexer.self] ); @@ -45,7 +45,7 @@ struct ValueRefStringFixture { static const std::array containerTypes; static const std::array attributes; - ValueRef::ValueRefBase* result; + ValueRef::ValueRef* result; const ValueRef::Constant* value; const ValueRef::Statistic* statistic; const ValueRef::Variable* variable; diff --git a/test/parse/TestValueRefUniverseObjectTypeParser.cpp b/test/parse/TestValueRefUniverseObjectTypeParser.cpp index a1bc63e0d34..6d352950be4 100644 --- a/test/parse/TestValueRefUniverseObjectTypeParser.cpp +++ b/test/parse/TestValueRefUniverseObjectTypeParser.cpp @@ -2,7 +2,7 @@ #include "parse/ValueRefParser.h" #include "universe/Enums.h" -#include "universe/ValueRef.h" +#include "universe/ValueRefs.h" #include "CommonTest.h" struct ValueRefUniverseObjectTypeFixture { @@ -23,7 +23,7 @@ struct ValueRefUniverseObjectTypeFixture { delete result; } - bool parse(std::string phrase, ValueRef::ValueRefBase*& result) { + bool parse(std::string phrase, ValueRef::ValueRef*& result) { const parse::lexer& lexer = lexer.instance(); boost::spirit::qi::in_state_type in_state; boost::spirit::qi::eoi_type eoi; @@ -52,7 +52,7 @@ struct ValueRefUniverseObjectTypeFixture { static const std::array containerTypes; static const std::array attributes; - ValueRef::ValueRefBase* result; + ValueRef::ValueRef* result; const ValueRef::Operation* operation1; const ValueRef::Operation* operation2; const ValueRef::Operation* operation3; diff --git a/test/system/CMakeLists.txt b/test/system/CMakeLists.txt new file mode 100644 index 00000000000..940677b03d3 --- /dev/null +++ b/test/system/CMakeLists.txt @@ -0,0 +1,68 @@ +find_package(Boost ${MINIMUM_BOOST_VERSION} + COMPONENTS + unit_test_framework + REQUIRED) + +add_executable(fo_systemtest_game + main.cpp + ClientAppFixture.cpp +) + +target_compile_definitions(fo_systemtest_game + PRIVATE + -DFREEORION_BUILD_HUMAN + -DBOOST_TEST_IGNORE_SIGCHLD +) + +target_include_directories(fo_systemtest_game + PRIVATE + ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/GG/ +) + +target_link_libraries(fo_systemtest_game + freeorioncommon + freeorionparse + Boost::unit_test_framework +) + +target_dependencies_copy_to_build(fo_systemtest_game) + +add_coverage(fo_systemtest_game + unittest +) + +add_dependencies(unittest + freeoriond + fo_systemtest_game +) + +target_sources(fo_systemtest_game + PUBLIC + ${PROJECT_SOURCE_DIR}/client/ClientApp.h + ${PROJECT_SOURCE_DIR}/client/ClientFSMEvents.h + ${PROJECT_SOURCE_DIR}/GG/GG/ClrConstants.h + ${PROJECT_SOURCE_DIR}/client/ClientNetworking.h + PRIVATE + ${PROJECT_SOURCE_DIR}/client/ClientApp.cpp + ${PROJECT_SOURCE_DIR}/client/ClientFSMEvents.cpp + ${PROJECT_SOURCE_DIR}/client/ClientNetworking.cpp +) + +set(FO_TEST_GAME + SmokeTestGame + SmokeTestHostless +) + +foreach(_TEST ${FO_TEST_GAME}) + target_sources(fo_systemtest_game + PRIVATE + ${_TEST}.cpp + ) + add_test( + NAME ${_TEST} + COMMAND fo_systemtest_game --log_level=message "--run_test=${_TEST}" --catch_system_error=yes + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) +endforeach() + diff --git a/test/system/ClientAppFixture.cpp b/test/system/ClientAppFixture.cpp new file mode 100644 index 00000000000..35d3299c467 --- /dev/null +++ b/test/system/ClientAppFixture.cpp @@ -0,0 +1,285 @@ +#include "ClientAppFixture.h" + +#include "combat/CombatLogManager.h" +#include "client/ClientNetworking.h" +#include "universe/Species.h" +#include "util/Directories.h" +#include "util/GameRules.h" +#include "util/Version.h" + +#include "GG/GG/ClrConstants.h" + +#include +#include +#include + +ClientAppFixture::ClientAppFixture() : + m_game_started(false), + m_cookie(boost::uuids::nil_uuid()) +{ +#ifdef FREEORION_LINUX + // Dirty hack to output log to console. + InitLoggingSystem("/proc/self/fd/1", "Test"); +#else + InitLoggingSystem((GetUserDataDir() / "test.log").string(), "Test"); +#endif + //InitLoggingOptionsDBSystem(); + + InfoLogger() << FreeOrionVersionString(); + DebugLogger() << "Test client initialized"; + + StartBackgroundParsing(); +} + +int ClientAppFixture::EffectsProcessingThreads() const +{ return 1; } + +bool ClientAppFixture::PingLocalHostServer() +{ return m_networking->PingLocalHostServer(std::chrono::milliseconds(100)); } + +bool ClientAppFixture::ConnectToLocalHostServer() +{ return m_networking->ConnectToLocalHostServer(); } + +bool ClientAppFixture::ConnectToServer(const std::string& ip_address) +{ return m_networking->ConnectToServer(ip_address); } + +void ClientAppFixture::DisconnectFromServer() +{ return m_networking->DisconnectFromServer(); } + +void ClientAppFixture::HostSPGame(unsigned int num_AIs) { + auto game_rules = GetGameRules().GetRulesAsStrings(); + + SinglePlayerSetupData setup_data; + setup_data.m_new_game = true; + setup_data.m_filename.clear(); // not used for new game + + // GalaxySetupData + setup_data.SetSeed("TestSeed1"); + setup_data.m_size = 100; + setup_data.m_shape = Shape::SPIRAL_4; + setup_data.m_age = GalaxySetupOption::GALAXY_SETUP_MEDIUM; + setup_data.m_starlane_freq = GalaxySetupOption::GALAXY_SETUP_MEDIUM; + setup_data.m_planet_density = GalaxySetupOption::GALAXY_SETUP_MEDIUM; + setup_data.m_specials_freq = GalaxySetupOption::GALAXY_SETUP_MEDIUM; + setup_data.m_monster_freq = GalaxySetupOption::GALAXY_SETUP_MEDIUM; + setup_data.m_native_freq = GalaxySetupOption::GALAXY_SETUP_MEDIUM; + setup_data.m_ai_aggr = Aggression::MANIACAL; + setup_data.m_game_rules = game_rules; + + // SinglePlayerSetupData contains a map of PlayerSetupData, for + // the human and AI players. Need to compile this information + // from the specified human options and number of requested AIs + + // Human player setup data + PlayerSetupData human_player_setup_data; + human_player_setup_data.m_player_name = "TestPlayer"; + human_player_setup_data.m_empire_name = "TestEmpire"; + human_player_setup_data.m_empire_color = GG::CLR_GREEN; + + human_player_setup_data.m_starting_species_name = "SP_HUMAN"; + human_player_setup_data.m_save_game_empire_id = ALL_EMPIRES; // not used for new games + human_player_setup_data.m_client_type = Networking::CLIENT_TYPE_HUMAN_PLAYER; + + // add to setup data players + setup_data.m_players.push_back(human_player_setup_data); + + // AI player setup data. One entry for each requested AI + + for (unsigned int ai_i = 1; ai_i <= num_AIs; ++ai_i) { + PlayerSetupData ai_setup_data; + + ai_setup_data.m_player_name = "AI_" + std::to_string(ai_i); + ai_setup_data.m_empire_name.clear(); // leave blank, to be set by server in Universe::GenerateEmpires + ai_setup_data.m_empire_color = GG::CLR_ZERO; // to be set by server + ai_setup_data.m_starting_species_name.clear(); // leave blank, to be set by server + ai_setup_data.m_save_game_empire_id = ALL_EMPIRES; // not used for new games + ai_setup_data.m_client_type = Networking::CLIENT_TYPE_AI_PLAYER; + + setup_data.m_players.push_back(ai_setup_data); + } + + m_networking->SendMessage(HostSPGameMessage(setup_data)); +} + +void ClientAppFixture::JoinGame() { + m_lobby_updated = false; + m_networking->SendMessage(JoinGameMessage("TestPlayer", + Networking::CLIENT_TYPE_HUMAN_PLAYER, + m_cookie)); +} + +bool ClientAppFixture::ProcessMessages(const boost::posix_time::ptime& start_time, int max_seconds) { + bool to_process = true; + while ((boost::posix_time::microsec_clock::local_time() - start_time).total_seconds() < max_seconds) { + if (!m_networking->IsConnected()) { + ErrorLogger() << "Disconnected"; + return false; + } + auto opt_msg = m_networking->GetMessage(); + if (opt_msg) { + Message msg = *opt_msg; + if (! HandleMessage(msg)) { + return false; + } + to_process = true; + } else { + if (to_process) { + boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); + to_process = false; + } else { + return true; + } + } + } + ErrorLogger() << "Timeout"; + return false; // return on timeout +} + +bool ClientAppFixture::HandleMessage(Message& msg) { + InfoLogger() << "Handle message " << msg.Type(); + switch (msg.Type()) { + case Message::CHECKSUM: { + bool result = VerifyCheckSum(msg); + if (!result) + ErrorLogger() << "Wrong checksum"; + return result; + } + case Message::SET_AUTH_ROLES: + ExtractSetAuthorizationRolesMessage(msg, m_networking->AuthorizationRoles()); + return true; + case Message::HOST_SP_GAME: + try { + int host_id = boost::lexical_cast(msg.Text()); + m_networking->SetPlayerID(host_id); + m_networking->SetHostPlayerID(host_id); + return true; + } catch (const boost::bad_lexical_cast& ex) { + ErrorLogger() << "Host id " << msg.Text() << " is not a number: " << ex.what(); + return false; + } + case Message::TURN_PROGRESS: { + Message::TurnProgressPhase phase_id; + ExtractTurnProgressMessageData(msg, phase_id); + InfoLogger() << "Turn progress: " << phase_id; + return true; + } + case Message::GAME_START: { + bool single_player_game; // ignored + bool loaded_game_data; // ignored + bool ui_data_available; // ignored + SaveGameUIData ui_data; // ignored + bool state_string_available; // ignored + std::string save_state_string; + + ExtractGameStartMessageData(msg, single_player_game, m_empire_id, + m_current_turn, m_empires, m_universe, + GetSpeciesManager(), GetCombatLogManager(), GetSupplyManager(), + m_player_info, m_orders, loaded_game_data, + ui_data_available, ui_data, state_string_available, + save_state_string, m_galaxy_setup_data); + + InfoLogger() << "Extracted GameStart message for turn: " << m_current_turn << " with empire: " << m_empire_id; + + m_ai_empires.clear(); + for (const auto& empire : Empires()) { + if (GetEmpireClientType(empire.first) == Networking::CLIENT_TYPE_AI_PLAYER) + m_ai_empires.insert(empire.first); + } + m_ai_waiting = m_ai_empires; + + m_game_started = true; + return true; + } + case Message::DIPLOMATIC_STATUS: + case Message::PLAYER_CHAT: + case Message::CHAT_HISTORY: + case Message::TURN_TIMEOUT: + case Message::PLAYER_INFO: + return true; // ignore + case Message::PLAYER_STATUS: { + int about_empire_id; + Message::PlayerStatus status; + ExtractPlayerStatusMessageData(msg, status, about_empire_id); + SetEmpireStatus(about_empire_id, status); + + if (status == Message::WAITING) { + m_ai_waiting.erase(about_empire_id); + } + return true; + } + case Message::TURN_PARTIAL_UPDATE: { + ExtractTurnPartialUpdateMessageData(msg, EmpireID(), GetUniverse()); + return true; + } + case Message::TURN_UPDATE: { + ExtractTurnUpdateMessageData(msg, EmpireID(), m_current_turn, + Empires(), GetUniverse(), GetSpeciesManager(), + GetCombatLogManager(), GetSupplyManager(), Players()); + m_turn_done = true; + return true; + } + case Message::SAVE_GAME_COMPLETE: + m_save_completed = true; + return true; + case Message::JOIN_GAME: { + int player_id; + ExtractJoinAckMessageData(msg, player_id, m_cookie); + m_networking->SetPlayerID(player_id); + return true; + } + case Message::HOST_ID: { + int host_id = Networking::INVALID_PLAYER_ID; + try { + host_id = boost::lexical_cast(msg.Text()); + m_networking->SetHostPlayerID(host_id); + } catch (const boost::bad_lexical_cast& ex) { + ErrorLogger() << "HOST_ID: Could not convert \"" << msg.Text() << "\" to host id"; + return false; + } + return true; + } + case Message::LOBBY_UPDATE: + m_lobby_updated = true; + ExtractLobbyUpdateMessageData(msg, m_lobby_data); + return true; + case Message::ERROR_MSG: { + int player_id; + std::string problem; + bool fatal; + ExtractErrorMessageData(msg, player_id, problem, fatal); + ErrorLogger() << "Catch " << (fatal ? "fatal " : "") << "error " << problem << " from player " << player_id; + } + return false; + default: + ErrorLogger() << "Unknown message type: " << msg.Type(); + return false; + } +} + +void ClientAppFixture::SaveGame() { + std::string save_filename = boost::io::str(boost::format("FreeOrionTestGame_%04d_%s%s") % CurrentTurn() % FilenameTimestamp() % SP_SAVE_FILE_EXTENSION); + boost::filesystem::path save_dir_path(GetSaveDir() / "test"); + boost::filesystem::path save_path(save_dir_path / save_filename); + if (!exists(save_dir_path)) + boost::filesystem::create_directories(save_dir_path); + + auto path_string = PathToString(save_path); + m_save_completed = false; + m_networking->SendMessage(HostSaveGameInitiateMessage(path_string)); + +} + +void ClientAppFixture::UpdateLobby() { + m_lobby_updated = false; + m_networking->SendMessage(LobbyUpdateMessage(m_lobby_data)); +} + +unsigned int ClientAppFixture::GetLobbyAICount() const { + unsigned int res = 0; + for (const auto& plr: m_lobby_data.m_players) { + if (plr.second.m_client_type == Networking::CLIENT_TYPE_AI_PLAYER) + ++ res; + } + return res; +} + diff --git a/test/system/ClientAppFixture.h b/test/system/ClientAppFixture.h new file mode 100644 index 00000000000..7fad93f4c4d --- /dev/null +++ b/test/system/ClientAppFixture.h @@ -0,0 +1,38 @@ +#ifndef _ClientAppFixture_h_ +#define _ClientAppFixture_h_ + +#include "client/ClientApp.h" + +class ClientAppFixture : public ClientApp { +public: + ClientAppFixture(); + + bool PingLocalHostServer(); + bool ConnectToLocalHostServer(); + bool ConnectToServer(const std::string& ip_address); + void DisconnectFromServer(); + + void HostSPGame(unsigned int num_AIs); + void JoinGame(); + + bool ProcessMessages(const boost::posix_time::ptime& start_time, int max_seconds); + bool HandleMessage(Message& msg); + void SaveGame(); + void UpdateLobby(); + + unsigned int GetLobbyAICount() const; + + int EffectsProcessingThreads() const override; +protected: + bool m_game_started; ///< Is server started the game? + std::set m_ai_empires; ///< Ids of AI empires in game. + std::set m_ai_waiting; ///< Ids of AI empires not yet send orders. + bool m_turn_done; ///< Is server processed turn? + bool m_save_completed; ///< Is server saved game? + boost::uuids::uuid m_cookie; ///< Cookie from server login. + bool m_lobby_updated; ///< Did player get updated lobby. + MultiplayerLobbyData m_lobby_data; ///< Lobby data. +}; + +#endif // _ClientAppFixture_h_ + diff --git a/test/system/SmokeTestGame.cpp b/test/system/SmokeTestGame.cpp new file mode 100644 index 00000000000..a9f93bbdf61 --- /dev/null +++ b/test/system/SmokeTestGame.cpp @@ -0,0 +1,196 @@ +#include + +#include "ClientAppFixture.h" +#include "Empire/Empire.h" +#include "universe/Enums.h" +#include "util/Directories.h" +#include "util/Process.h" +#include "util/SitRepEntry.h" + +namespace { + std::string ServerClientExe() { +#ifdef FREEORION_WIN32 + return PathToString(GetBinDir() / "freeoriond.exe"); +#else + return (GetBinDir() / "freeoriond").string(); +#endif + } + + constexpr static int MAX_WAITING_SEC = 120; +} + +#ifdef FREEORION_MACOSX +#include +#endif + +BOOST_FIXTURE_TEST_SUITE(SmokeTestGame, ClientAppFixture) + +/** + * - Do start a server with as a host mock player on localhost. + * - Expect successfully connection to localhost server. + * - Do launch singleplayer game with `FO_TEST_GAME_AIS` AIs (by default 2). + * - Expect singleplayer game is correctly lauched. + * - Do make empty turns until turn number reach `FO_TEST_GAME_TURNS` or mock player will be + * eliminated or someone will win. + * - Expect turns're processing without error. + * - Do save game after each turn if `FO_TEST_GAME_SAVE` was set. + * - Expect saving is processing correctly. + * - Do shut down server. + * - Expect that server and AIs were shut down. + */ + +BOOST_AUTO_TEST_CASE(host_server) { + + BOOST_REQUIRE(!PingLocalHostServer()); + + std::string SERVER_CLIENT_EXE = ServerClientExe(); + + BOOST_TEST_MESSAGE(SERVER_CLIENT_EXE); + +#ifdef FREEORION_MACOSX + // On OSX set environment variable DYLD_LIBRARY_PATH to python framework folder + // bundled with app, so the dynamic linker uses the bundled python library. + // Otherwise the dynamic linker will look for a correct python lib in system + // paths, and if it can't find it, throw an error and terminate! + // Setting environment variable here, spawned child processes will inherit it. + setenv("DYLD_LIBRARY_PATH", GetPythonHome().string().c_str(), 1); +#endif + + std::vector args; + args.push_back("\"" + SERVER_CLIENT_EXE + "\""); + args.push_back("--singleplayer"); + args.push_back("--testing"); + +#ifdef FREEORION_LINUX + // Dirty hack to output log to console. + args.push_back("--log-file"); + args.push_back("/proc/self/fd/1"); +#endif + + Process server = Process(SERVER_CLIENT_EXE, args); + + BOOST_REQUIRE(ConnectToLocalHostServer()); + + BOOST_TEST_MESSAGE("Connected to server"); + + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + + BOOST_TEST_MESSAGE("First messages processed. Starting game..."); + + unsigned int num_AIs = 2; + int num_turns = 3; + bool save_game = true; + + const char *env_num_AIs = std::getenv("FO_TEST_GAME_AIS"); + if (env_num_AIs) { + try { + num_AIs = boost::lexical_cast(env_num_AIs); + } catch (...) { + // ignore + } + } + + const char *env_num_turns = std::getenv("FO_TEST_GAME_TURNS"); + if (env_num_turns) { + try { + num_turns = boost::lexical_cast(env_num_turns); + } catch (...) { + // ignore + } + } + + const char *env_save_game = std::getenv("FO_TEST_GAME_SAVE"); + if (env_save_game) { + try { + save_game = boost::lexical_cast(env_save_game) != 0; + } catch (...) { + // ignore + } + } + + HostSPGame(num_AIs); + + BOOST_TEST_MESSAGE("Waiting game to start..."); + + start_time = boost::posix_time::microsec_clock::local_time(); + while (!m_game_started) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + + BOOST_REQUIRE_EQUAL(m_ai_empires.size(), num_AIs); + + BOOST_TEST_MESSAGE("Game started. Waiting AI for turns..."); + + start_time = boost::posix_time::microsec_clock::local_time(); + while (! m_ai_waiting.empty()) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + + SaveGameUIData ui_data; + + while (m_current_turn <= num_turns) { + SendPartialOrders(); + + StartTurn(ui_data); + + m_turn_done = false; + m_ai_waiting = m_ai_empires; + + BOOST_TEST_MESSAGE("Turn done. Waiting server for update..."); + start_time = boost::posix_time::microsec_clock::local_time(); + while (!m_turn_done) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + + // output sitreps + const Empire* my_empire = m_empires.GetEmpire(m_empire_id); + BOOST_REQUIRE(my_empire != nullptr); + for (auto sitrep_it = my_empire->SitRepBegin(); sitrep_it != my_empire->SitRepEnd(); ++sitrep_it) { + if (sitrep_it->GetTurn() == m_current_turn) { + BOOST_TEST_MESSAGE("Sitrep: " << sitrep_it->Dump()); + } + } + + if (my_empire->Eliminated()) { + BOOST_TEST_MESSAGE("Test player empire was eliminated."); + break; + } + bool have_winner = false; + for (auto empire : m_empires) { + if (empire.second->Won()) { + have_winner = true; + break; + } + } + if (have_winner) { + BOOST_TEST_MESSAGE("Someone wins game."); + break; + } + + BOOST_TEST_MESSAGE("Waiting AI for turns..."); + start_time = boost::posix_time::microsec_clock::local_time(); + while (! m_ai_waiting.empty()) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + + if (save_game) { + BOOST_TEST_MESSAGE("Save game"); + + SaveGame(); + start_time = boost::posix_time::microsec_clock::local_time(); + while (! m_save_completed) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + } + } + + BOOST_TEST_MESSAGE("Terminating server..."); + + BOOST_REQUIRE(server.Terminate()); + + BOOST_TEST_MESSAGE("Server terminated"); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/test/system/SmokeTestHostless.cpp b/test/system/SmokeTestHostless.cpp new file mode 100644 index 00000000000..f5bf0b3bc5c --- /dev/null +++ b/test/system/SmokeTestHostless.cpp @@ -0,0 +1,252 @@ +#include + +#include "ClientAppFixture.h" +#include "Empire/Empire.h" +#include "util/Directories.h" +#include "util/Process.h" +#include "util/SitRepEntry.h" + +namespace { + std::string ServerClientExe() { +#ifdef FREEORION_WIN32 + return PathToString(GetBinDir() / "freeoriond.exe"); +#else + return (GetBinDir() / "freeoriond").string(); +#endif + } + + constexpr static int MAX_WAITING_SEC = 120; +} + +#ifdef FREEORION_MACOSX +#include +#endif + +BOOST_FIXTURE_TEST_SUITE(SmokeTestHostless, ClientAppFixture) + +/** + * - Do start a server with hostless mode if `FO_TEST_HOSTLESS_LAUNCH_SERVER` was set with save + * enabled if `FO_TEST_HOSTLESS_SAVE` was set. + * - Do connect to lobby to localhost server as a Player. + * - Expect successfully connection to localhost server. + * - Do add `FO_TEST_HOSTLESS_AIS` AIs to lobby (by default 2). + * - Expect AIs will be added successfully. + * - Do make player ready. + * - Expect game started. + * - Do make empty turns until turn number reach `FO_TEST_HOSTLESS_TURNS` or mock player will be + * eliminated or someone will win. + * - Expect turns're processing without error. + * - Do disconnect from server. + * - Do reconnect to server until number of played games reach `FO_TEST_HOSTLESS_GAMES`. + * - Expect got to lobby. + * - Expect number of AIs was preserved. + * - Do repeat game steps. + * - Expect all games processed correctly. + * - Do shut down server. + * - Expect that serve and AIs were shut down. + */ + +BOOST_AUTO_TEST_CASE(hostless_server) { + unsigned int num_AIs = 2; + unsigned int num_games = 3; + int num_turns = 3; + bool save_game = true; + bool launch_server = true; + + const char *env_num_AIs = std::getenv("FO_TEST_HOSTLESS_AIS"); + if (env_num_AIs) { + try { + num_AIs = boost::lexical_cast(env_num_AIs); + } catch (...) { + // ignore + } + } + + const char *env_num_turns = std::getenv("FO_TEST_HOSTLESS_TURNS"); + if (env_num_turns) { + try { + num_turns = boost::lexical_cast(env_num_turns); + } catch (...) { + // ignore + } + } + + const char *env_save_game = std::getenv("FO_TEST_HOSTLESS_SAVE"); + if (env_save_game) { + try { + save_game = boost::lexical_cast(env_save_game) != 0; + } catch (...) { + // ignore + } + } + + const char *env_launch_server = std::getenv("FO_TEST_HOSTLESS_LAUNCH_SERVER"); + if (env_launch_server) { + try { + launch_server = boost::lexical_cast(env_launch_server) != 0; + } catch (...) { + // ignore + } + } + + const char *env_games = std::getenv("FO_TEST_HOSTLESS_GAMES"); + if (env_games) { + try { + num_games = boost::lexical_cast(env_games); + } catch (...) { + // ignore + } + } + + boost::optional server; + if (launch_server) { + BOOST_REQUIRE(!PingLocalHostServer()); + + std::string SERVER_CLIENT_EXE = ServerClientExe(); + + BOOST_TEST_MESSAGE(SERVER_CLIENT_EXE); + +#ifdef FREEORION_MACOSX + // On OSX set environment variable DYLD_LIBRARY_PATH to python framework folder + // bundled with app, so the dynamic linker uses the bundled python library. + // Otherwise the dynamic linker will look for a correct python lib in system + // paths, and if it can't find it, throw an error and terminate! + // Setting environment variable here, spawned child processes will inherit it. + setenv("DYLD_LIBRARY_PATH", GetPythonHome().string().c_str(), 1); +#endif + + std::vector args; + args.push_back("\"" + SERVER_CLIENT_EXE + "\""); + args.push_back("--hostless"); + args.push_back("--save.auto.hostless.enabled"); + args.push_back(save_game ? "1" : "0"); + args.push_back("--setup.ai.player.count"); + args.push_back("0"); + args.push_back("--testing"); + +#ifdef FREEORION_LINUX + // Dirty hack to output log to console. + args.push_back("--log-file"); + args.push_back("/proc/self/fd/1"); +#endif + + server = Process(SERVER_CLIENT_EXE, args); + + BOOST_TEST_MESSAGE("Server started."); + } + + for (unsigned int g = 0; g < num_games; ++g) { + m_game_started = false; + BOOST_TEST_MESSAGE("Game " << g << ". Connecting to server..."); + + BOOST_REQUIRE(ConnectToServer("localhost")); + + JoinGame(); + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + start_time = boost::posix_time::microsec_clock::local_time(); + while (!m_lobby_updated) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + BOOST_TEST_MESSAGE("Entered to lobby"); + + if (g == 0) { + // if first game lobby should be empty + BOOST_REQUIRE_EQUAL(GetLobbyAICount(), 0); + + // fill lobby with AIs + for (unsigned int ai_i = 1; ai_i <= num_AIs; ++ai_i) { + PlayerSetupData ai_plr; + ai_plr.m_client_type = Networking::CLIENT_TYPE_AI_PLAYER; + m_lobby_data.m_players.push_back({Networking::INVALID_PLAYER_ID, ai_plr}); + // publish changes + UpdateLobby(); + start_time = boost::posix_time::microsec_clock::local_time(); + while (!m_lobby_updated) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + } + } + // after filling first game there should be corrent number of AIs + // and other game should retain number of AIs from previous game + BOOST_REQUIRE_EQUAL(GetLobbyAICount(), num_AIs); + + // get ready + for (auto& plr : m_lobby_data.m_players) { + if (plr.first == PlayerID()) + plr.second.m_player_ready = true; + } + UpdateLobby(); + + start_time = boost::posix_time::microsec_clock::local_time(); + while (!m_game_started) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + + BOOST_REQUIRE_EQUAL(m_ai_empires.size(), num_AIs); + + start_time = boost::posix_time::microsec_clock::local_time(); + while (! m_ai_waiting.empty()) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + + SaveGameUIData ui_data; + + while (m_current_turn <= num_turns) { + SendPartialOrders(); + + StartTurn(ui_data); + + m_turn_done = false; + m_ai_waiting = m_ai_empires; + + BOOST_TEST_MESSAGE("Turn done. Waiting server for update..."); + start_time = boost::posix_time::microsec_clock::local_time(); + while (!m_turn_done) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + + // output sitreps + const Empire* my_empire = m_empires.GetEmpire(m_empire_id); + BOOST_REQUIRE(my_empire != nullptr); + for (auto sitrep_it = my_empire->SitRepBegin(); sitrep_it != my_empire->SitRepEnd(); ++sitrep_it) { + if (sitrep_it->GetTurn() == m_current_turn) { + BOOST_TEST_MESSAGE("Sitrep: " << sitrep_it->Dump()); + } + } + + if (my_empire->Eliminated()) { + BOOST_TEST_MESSAGE("Test player empire was eliminated."); + break; + } + bool have_winner = false; + for (auto empire : m_empires) { + if (empire.second->Won()) { + have_winner = true; + break; + } + } + if (have_winner) { + BOOST_TEST_MESSAGE("Someone wins game."); + break; + } + + BOOST_TEST_MESSAGE("Waiting AI for turns..."); + start_time = boost::posix_time::microsec_clock::local_time(); + while (! m_ai_waiting.empty()) { + BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC)); + } + } + + DisconnectFromServer(); + + start_time = boost::posix_time::microsec_clock::local_time(); + while (ProcessMessages(start_time, MAX_WAITING_SEC)); + } + + if (launch_server && server) { + BOOST_REQUIRE(server->Terminate()); + } +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/test/system/main.cpp b/test/system/main.cpp new file mode 100644 index 00000000000..eda6b9e2ac2 --- /dev/null +++ b/test/system/main.cpp @@ -0,0 +1,22 @@ +#define BOOST_TEST_MODULE "FreeOrion unit/coverage tests game" + +#include +#include +#include + +// Produces an GCC-ish message prefix for various frontends that understand gcc output. +struct gcc_log_formatter : + public boost::unit_test::output::compiler_log_formatter +{ + void print_prefix(std::ostream& output, boost::unit_test::const_string file_name, std::size_t line) override + { output << file_name << ':' << line << ": error: "; } +}; + +struct boost_test_config +{ + boost_test_config() + { boost::unit_test::unit_test_log.set_formatter(new gcc_log_formatter); } +}; + +BOOST_GLOBAL_FIXTURE(boost_test_config); + diff --git a/test/util/CMakeLists.txt b/test/util/CMakeLists.txt index 94be4e80bdb..12e32eeee13 100644 --- a/test/util/CMakeLists.txt +++ b/test/util/CMakeLists.txt @@ -4,11 +4,6 @@ add_executable(fo_unittest_util main.cpp ) -target_compile_definitions(fo_unittest_util - PRIVATE - -DBOOST_TEST_DYN_LINK -) - target_include_directories(fo_unittest_util PRIVATE ${PROJECT_SOURCE_DIR} @@ -16,10 +11,15 @@ target_include_directories(fo_unittest_util ) target_link_libraries(fo_unittest_util - ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES} - ${CMAKE_THREAD_LIBS_INIT} + Threads::Threads + Boost::boost + Boost::disable_autolinking + Boost::dynamic_linking + Boost::unit_test_framework ) +target_dependencies_copy_to_build(fo_unittest_util) + add_dependencies(unittest fo_unittest_util ) diff --git a/universe/Building.cpp b/universe/Building.cpp index 969731bb8c5..9fc7fbeb4a1 100644 --- a/universe/Building.cpp +++ b/universe/Building.cpp @@ -1,36 +1,14 @@ #include "Building.h" -#include "Effect.h" -#include "Condition.h" -#include "Planet.h" +#include "BuildingType.h" #include "Predicates.h" #include "Universe.h" #include "Enums.h" -#include "ValueRef.h" -#include "../Empire/Empire.h" #include "../Empire/EmpireManager.h" -#include "../parse/Parse.h" -#include "../util/OptionsDB.h" -#include "../util/Logger.h" #include "../util/AppInterface.h" -#include "../util/MultiplayerCommon.h" -#include "../util/CheckSums.h" +#include "../util/i18n.h" -#include -namespace { - void AddRules(GameRules& rules) { - // makes all buildings cost 1 PP and take 1 turn to produce - rules.Add("RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION", - "RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION_DESC", - "", false, true); - } - bool temp_bool = RegisterGameRules(&AddRules); -} - -///////////////////////////////////////////////// -// Building // -///////////////////////////////////////////////// Building::Building(int empire_id, const std::string& building_type, int produced_by_empire_id/* = ALL_EMPIRES*/) : m_building_type(building_type), @@ -60,7 +38,7 @@ Building* Building::Clone(int empire_id) const { void Building::Copy(std::shared_ptr copied_object, int empire_id) { if (copied_object.get() == this) return; - std::shared_ptr copied_building = std::dynamic_pointer_cast(copied_object); + auto copied_building = std::dynamic_pointer_cast(copied_object); if (!copied_building) { ErrorLogger() << "Building::Copy passed an object that wasn't a Building"; return; @@ -68,7 +46,7 @@ void Building::Copy(std::shared_ptr copied_object, int emp int copied_object_id = copied_object->ID(); Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(copied_object_id, empire_id); - std::set visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id); + auto visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id); UniverseObject::Copy(copied_object, vis, visible_specials); @@ -88,6 +66,13 @@ void Building::Copy(std::shared_ptr copied_object, int emp } } +bool Building::HostileToEmpire(int empire_id) const { + if (OwnedBy(empire_id)) + return false; + return empire_id == ALL_EMPIRES || Unowned() || + Empires().GetDiplomaticStatus(Owner(), empire_id) == DIPLO_WAR; +} + std::set Building::Tags() const { const BuildingType* type = ::GetBuildingType(m_building_type); if (!type) @@ -110,12 +95,11 @@ bool Building::ContainedBy(int object_id) const { || object_id == this->SystemID()); } -std::string Building::Dump() const { +std::string Building::Dump(unsigned short ntabs) const { std::stringstream os; - os << UniverseObject::Dump(); + os << UniverseObject::Dump(ntabs); os << " building type: " << m_building_type - << " produced by empire id: " << m_produced_by_empire_id - << " \n characteristics " << GetBuildingType(m_building_type)->Dump(); + << " produced by empire id: " << m_produced_by_empire_id; return os.str(); } @@ -149,250 +133,3 @@ void Building::SetOrderedScrapped(bool b) { StateChangedSignal(); } -///////////////////////////////////////////////// -// BuildingType // -///////////////////////////////////////////////// -BuildingType::~BuildingType() -{ delete m_location; } - -void BuildingType::Init() { - if (m_production_cost) - m_production_cost->SetTopLevelContent(m_name); - if (m_production_time) - m_production_time->SetTopLevelContent(m_name); - if (m_location) - m_location->SetTopLevelContent(m_name); - if (m_enqueue_location) - m_enqueue_location->SetTopLevelContent(m_name); - for (std::shared_ptr effect : m_effects) { - effect->SetTopLevelContent(m_name); - } -} - -std::string BuildingType::Dump() const { - std::string retval = DumpIndent() + "BuildingType\n"; - ++g_indent; - retval += DumpIndent() + "name = \"" + m_name + "\"\n"; - retval += DumpIndent() + "description = \"" + m_description + "\"\n"; - if (m_production_cost) - retval += DumpIndent() + "buildcost = " + m_production_cost->Dump() + "\n"; - if (m_production_time) - retval += DumpIndent() + "buildtime = " + m_production_time->Dump() + "\n"; - retval += DumpIndent() + (m_producible ? "Producible" : "Unproducible") + "\n"; - retval += DumpIndent() + "captureresult = " + - boost::lexical_cast(m_capture_result) + "\n"; - - if (!m_tags.empty()) { - if (m_tags.size() == 1) { - retval += DumpIndent() + "tags = \"" + *m_tags.begin() + "\"\n"; - } else { - retval += DumpIndent() + "tags = [ "; - for (const std::string& tag : m_tags) { - retval += "\"" + tag + "\" "; - } - retval += " ]\n"; - } - } - - if (m_location) { - retval += DumpIndent() + "location = \n"; - ++g_indent; - retval += m_location->Dump(); - --g_indent; - } - if (m_enqueue_location) { - retval += DumpIndent() + "enqueue location = \n"; - ++g_indent; - retval += m_enqueue_location->Dump(); - --g_indent; - } - - if (m_effects.size() == 1) { - retval += DumpIndent() + "effectsgroups =\n"; - ++g_indent; - retval += m_effects[0]->Dump(); - --g_indent; - } else { - retval += DumpIndent() + "effectsgroups = [\n"; - ++g_indent; - for (std::shared_ptr effect : m_effects) { - retval += effect->Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; - } - retval += DumpIndent() + "icon = \"" + m_icon + "\"\n"; - --g_indent; - return retval; -} - -bool BuildingType::ProductionCostTimeLocationInvariant() const { - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION")) - return true; - if (m_production_cost && !(m_production_cost->TargetInvariant() && m_production_cost->SourceInvariant())) - return false; - if (m_production_time && !(m_production_time->TargetInvariant() && m_production_time->SourceInvariant())) - return false; - return true; -} - -float BuildingType::ProductionCost(int empire_id, int location_id) const { - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION") || !m_production_cost) { - return 1.0f; - } else { - if (m_production_cost && m_production_cost->ConstantExpr()) - return m_production_cost->Eval(); - else if (m_production_cost->SourceInvariant() && m_production_cost->TargetInvariant()) - return m_production_cost->Eval(); - - const auto arbitrary_large_number = 999999.9f; - - std::shared_ptr location = GetUniverseObject(location_id); - if (!location && !m_production_cost->TargetInvariant()) - return arbitrary_large_number; - - std::shared_ptr source = Empires().GetSource(empire_id); - if (!source && !m_production_cost->SourceInvariant()) - return arbitrary_large_number; - - ScriptingContext context(source, location); - - return m_production_cost->Eval(context); - } -} - -float BuildingType::PerTurnCost(int empire_id, int location_id) const -{ return ProductionCost(empire_id, location_id) / std::max(1, ProductionTime(empire_id, location_id)); } - -int BuildingType::ProductionTime(int empire_id, int location_id) const { - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION") || !m_production_time) { - return 1; - } else { - if (m_production_time && m_production_time->ConstantExpr()) - return m_production_time->Eval(); - else if (m_production_time->SourceInvariant() && m_production_time->TargetInvariant()) - return m_production_time->Eval(); - - const auto arbitrary_large_number = 9999; - - std::shared_ptr location = GetUniverseObject(location_id); - if (!location && !m_production_time->TargetInvariant()) - return arbitrary_large_number; - - std::shared_ptr source = Empires().GetSource(empire_id); - if (!source && !m_production_time->SourceInvariant()) - return arbitrary_large_number; - - ScriptingContext context(source, location); - - return m_production_time->Eval(context); - } -} - -bool BuildingType::ProductionLocation(int empire_id, int location_id) const { - if (!m_location) - return true; - - std::shared_ptr location = GetUniverseObject(location_id); - if (!location) - return false; - - std::shared_ptr source = Empires().GetSource(empire_id); - if (!source) - return false; - - return m_location->Eval(ScriptingContext(source), location); -} - -bool BuildingType::EnqueueLocation(int empire_id, int location_id) const { - if (!m_enqueue_location) - return ProductionLocation(empire_id, location_id); - - std::shared_ptr location = GetUniverseObject(location_id); - if (!location) - return false; - - std::shared_ptr source = Empires().GetSource(empire_id); - if (!source) - return false; - - return m_enqueue_location->Eval(ScriptingContext(source), location); -} - -unsigned int BuildingType::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, m_name); - CheckSums::CheckSumCombine(retval, m_description); - CheckSums::CheckSumCombine(retval, m_production_cost); - CheckSums::CheckSumCombine(retval, m_production_time); - CheckSums::CheckSumCombine(retval, m_producible); - CheckSums::CheckSumCombine(retval, m_capture_result); - CheckSums::CheckSumCombine(retval, m_tags); - CheckSums::CheckSumCombine(retval, m_production_meter_consumption); - CheckSums::CheckSumCombine(retval, m_production_special_consumption); - CheckSums::CheckSumCombine(retval, m_location); - CheckSums::CheckSumCombine(retval, m_enqueue_location); - CheckSums::CheckSumCombine(retval, m_effects); - CheckSums::CheckSumCombine(retval, m_icon); - - return retval; -} - -///////////////////////////////////////////////// -// BuildingTypeManager // -///////////////////////////////////////////////// -// static(s) -BuildingTypeManager* BuildingTypeManager::s_instance = nullptr; - -BuildingTypeManager::BuildingTypeManager() { - if (s_instance) - throw std::runtime_error("Attempted to create more than one BuildingTypeManager."); - - TraceLogger() << "BuildingTypeManager::BuildingTypeManager() about to parse buildings."; - - try { - parse::buildings(m_building_types); - } catch (const std::exception& e) { - ErrorLogger() << "Failed parsing buildings: error: " << e.what(); - throw e; - } - - TraceLogger() << "Building Types:"; - for (const auto& entry : m_building_types) { - TraceLogger() << " ... " << entry.first; - } - - // Only update the global pointer on sucessful construction. - s_instance = this; - - DebugLogger() << "BuildingTypeManager checksum: " << GetCheckSum(); -} - -const BuildingType* BuildingTypeManager::GetBuildingType(const std::string& name) const { - const auto& it = m_building_types.find(name); - return it != m_building_types.end() ? it->second.get() : nullptr; -} - -BuildingTypeManager& BuildingTypeManager::GetBuildingTypeManager() { - static BuildingTypeManager manager; - return manager; -} - -unsigned int BuildingTypeManager::GetCheckSum() const { - unsigned int retval{0}; - for (auto const& name_type_pair : m_building_types) - CheckSums::CheckSumCombine(retval, name_type_pair); - CheckSums::CheckSumCombine(retval, m_building_types.size()); - - return retval; -} - -/////////////////////////////////////////////////////////// -// Free Functions // -/////////////////////////////////////////////////////////// -BuildingTypeManager& GetBuildingTypeManager() -{ return BuildingTypeManager::GetBuildingTypeManager(); } - -const BuildingType* GetBuildingType(const std::string& name) -{ return GetBuildingTypeManager().GetBuildingType(name); } diff --git a/universe/Building.h b/universe/Building.h index 932e3d9c478..daba6664504 100644 --- a/universe/Building.h +++ b/universe/Building.h @@ -3,58 +3,35 @@ #include "UniverseObject.h" #include "ObjectMap.h" -#include "ValueRefFwd.h" -#include "ShipDesign.h" #include "../util/Export.h" -#include - -class BuildingType; -namespace Effect { - class EffectsGroup; -} -namespace Condition { - struct ConditionBase; -} /** A Building UniverseObject type. */ class FO_COMMON_API Building : public UniverseObject { public: /** \name Accessors */ //@{ - std::set Tags() const override; - - bool HasTag(const std::string& name) const override; - - UniverseObjectType ObjectType() const override; - - std::string Dump() const override; - - int ContainerObjectID() const override - { return m_planet_id; } - - bool ContainedBy(int object_id) const override; + bool HostileToEmpire(int empire_id) const override; + std::set Tags() const override; + bool HasTag(const std::string& name) const override; + UniverseObjectType ObjectType() const override; + std::string Dump(unsigned short ntabs = 0) const override; + int ContainerObjectID() const override { return m_planet_id; } + bool ContainedBy(int object_id) const override; std::shared_ptr Accept(const UniverseObjectVisitor& visitor) const override; /** Returns the name of the BuildingType object for this building. */ - const std::string& BuildingTypeName() const - { return m_building_type; }; - + const std::string& BuildingTypeName() const { return m_building_type; }; int PlanetID() const { return m_planet_id; } ///< returns the ID number of the planet this building is on - int ProducedByEmpireID() const { return m_produced_by_empire_id; } ///< returns the empire ID of the empire that produced this building - bool OrderedScrapped() const { return m_ordered_scrapped; } //@} /** \name Mutators */ //@{ void Copy(std::shared_ptr copied_object, int empire_id = ALL_EMPIRES) override; - - void SetPlanetID(int planet_id); ///< sets the planet on which the building is located - - void Reset(); ///< resets any building state, and removes owners - void SetOrderedScrapped(bool b = true); ///< flags building for scrapping - + void SetPlanetID(int planet_id); ///< sets the planet on which the building is located + void Reset(); ///< resets any building state, and removes owners + void SetOrderedScrapped(bool b = true); ///< flags building for scrapping void ResetTargetMaxUnpairedMeters() override; //@} @@ -63,10 +40,12 @@ class FO_COMMON_API Building : public UniverseObject { /** \name Structors */ //@{ Building() {} +public: Building(int empire_id, const std::string& building_type, int produced_by_empire_id = ALL_EMPIRES); - template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); +protected: + template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); public: ~Building() {} @@ -83,157 +62,9 @@ class FO_COMMON_API Building : public UniverseObject { int m_produced_by_empire_id = ALL_EMPIRES; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -/** A specification for a building of a certain type. Each building type must - * have a \a unique name string, by which it can be looked up using - * GetBuildingType(...). */ -class FO_COMMON_API BuildingType { -public: - /** \name Structors */ //@{ - BuildingType(const std::string& name, - const std::string& description, - const CommonParams& common_params, - CaptureResult capture_result, - const std::string& icon) : - m_name(name), - m_description(description), - m_production_cost(common_params.production_cost), - m_production_time(common_params.production_time), - m_producible(common_params.producible), - m_capture_result(capture_result), - m_tags(), - m_production_meter_consumption(common_params.production_meter_consumption), - m_production_special_consumption(common_params.production_special_consumption), - m_location(common_params.location), - m_enqueue_location(common_params.enqueue_location), - m_effects(common_params.effects), - m_icon(icon) - { - Init(); - for (const std::string& tag : common_params.tags) - m_tags.insert(boost::to_upper_copy(tag)); - } - - ~BuildingType(); - //@} - - /** \name Accessors */ //@{ - const std::string& Name() const { return m_name; } ///< returns the unique name for this type of building - const std::string& Description() const { return m_description; } ///< returns a text description of this type of building - std::string Dump() const; ///< returns a data file format representation of this object - - bool ProductionCostTimeLocationInvariant() const; ///< returns true if the production cost and time are invariant (does not depend on) the location - float ProductionCost(int empire_id, int location_id) const; ///< returns the number of production points required to build this building at this location by this empire - float PerTurnCost(int empire_id, int location_id) const; ///< returns the maximum number of production points per turn that can be spend on this building - int ProductionTime(int empire_id, int location_id) const; ///< returns the number of turns required to build this building at this location by this empire - - const ValueRef::ValueRefBase* Cost() const { return m_production_cost; } ///< returns the ValueRef that determines ProductionCost() - const ValueRef::ValueRefBase* Time() const { return m_production_time; } ///< returns the ValueRef that determines ProductionTime() - - bool Producible() const { return m_producible; } ///< returns whether this building type is producible by players and appears on the production screen - - const std::map*, Condition::ConditionBase*>>& - ProductionMeterConsumption() const { return m_production_meter_consumption; } - const std::map*, Condition::ConditionBase*>>& - ProductionSpecialConsumption() const{ return m_production_special_consumption; } - - const std::set& Tags() const { return m_tags; } - const Condition::ConditionBase* Location() const { return m_location; } ///< returns the condition that determines the locations where this building can be produced - const Condition::ConditionBase* EnqueueLocation() const { return m_enqueue_location; } ///< returns the condition that determines the locations where this building can be enqueued (ie. put onto the production queue) - - /** Returns the EffectsGroups that encapsulate the effects that buildings ofi - this type have when operational. */ - const std::vector>& Effects() const - { return m_effects; } - - const std::string& Icon() const { return m_icon; } ///< returns the name of the grapic file for this building type - - bool ProductionLocation(int empire_id, int location_id) const; ///< returns true iff the empire with ID empire_id can produce this building at the location with location_id - bool EnqueueLocation(int empire_id, int location_id) const; ///< returns true iff the empire with ID empire_id can enqueue this building at the location with location_id - - /** returns CaptureResult for empire with ID \a to_empire_id capturing from - * empire with IDs \a from_empire_id the planet (or other UniverseObject) - * with id \a location_id on which this type of Building is located (if - * \a as_production_item is false) or which is the location of a Production - * Queue BuildItem for a building of this type (otherwise) */ - CaptureResult GetCaptureResult(int from_empire_id, int to_empire_id, - int location_id, bool as_production_item) const - { return m_capture_result; } - - /** Returns a number, calculated from the contained data, which should be - * different for different contained data, and must be the same for - * the same contained data, and must be the same on different platforms - * and executions of the program and the function. Useful to verify that - * the parsed content is consistent without sending it all between - * clients and server. */ - unsigned int GetCheckSum() const; - //@} - -private: - void Init(); - - std::string m_name; - std::string m_description; - ValueRef::ValueRefBase* m_production_cost; - ValueRef::ValueRefBase* m_production_time; - bool m_producible; - CaptureResult m_capture_result; - std::set m_tags; - std::map*, Condition::ConditionBase*>> - m_production_meter_consumption; - std::map*, Condition::ConditionBase*>> - m_production_special_consumption; - Condition::ConditionBase* m_location; - Condition::ConditionBase* m_enqueue_location; - std::vector> m_effects; - std::string m_icon; -}; - -/** Holds all FreeOrion building types. Types may be looked up by name. */ -class BuildingTypeManager { -public: - typedef std::map>::const_iterator iterator; - - /** \name Accessors */ //@{ - /** returns the building type with the name \a name; you should use the - * free function GetBuildingType(...) instead, mainly to save some typing. */ - const BuildingType* GetBuildingType(const std::string& name) const; - - /** iterator to the first building type */ - iterator begin() const { return m_building_types.begin(); } - - /** iterator to the last + 1th building type */ - iterator end() const { return m_building_types.end(); } - - /** returns the instance of this singleton class; you should use the free - * function GetBuildingTypeManager() instead */ - static BuildingTypeManager& GetBuildingTypeManager(); - - /** Returns a number, calculated from the contained data, which should be - * different for different contained data, and must be the same for - * the same contained data, and must be the same on different platforms - * and executions of the program and the function. Useful to verify that - * the parsed content is consistent without sending it all between - * clients and server. */ - unsigned int GetCheckSum() const; - //@} - -private: - BuildingTypeManager(); - - std::map> m_building_types; - - static BuildingTypeManager* s_instance; -}; - -/** returns the singleton building type manager */ -FO_COMMON_API BuildingTypeManager& GetBuildingTypeManager(); - -/** Returns the BuildingType specification object for a building of - * type \a name. If no such BuildingType exists, 0 is returned instead. */ -FO_COMMON_API const BuildingType* GetBuildingType(const std::string& name); #endif // _Building_h_ diff --git a/universe/BuildingType.cpp b/universe/BuildingType.cpp new file mode 100644 index 00000000000..11f2051b5e5 --- /dev/null +++ b/universe/BuildingType.cpp @@ -0,0 +1,279 @@ +#include "BuildingType.h" + +#include +#include "../util/CheckSums.h" +#include "../util/GameRules.h" +#include "../util/Logger.h" +#include "../Empire/EmpireManager.h" +#include "Condition.h" +#include "Effect.h" +#include "ValueRef.h" + + +namespace { + void AddRules(GameRules& rules) { + // makes all buildings cost 1 PP and take 1 turn to produce + rules.Add("RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION", + "RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION_DESC", + "", false, true); + } + bool temp_bool = RegisterGameRules(&AddRules); +} + + +BuildingType::BuildingType(const std::string& name, + const std::string& description, + CommonParams& common_params, + CaptureResult capture_result, + const std::string& icon) : + m_name(name), + m_description(description), + m_production_cost(std::move(common_params.production_cost)), + m_production_time(std::move(common_params.production_time)), + m_producible(common_params.producible), + m_capture_result(capture_result), + m_production_meter_consumption(std::move(common_params.production_meter_consumption)), + m_production_special_consumption(std::move(common_params.production_special_consumption)), + m_location(std::move(common_params.location)), + m_enqueue_location(std::move(common_params.enqueue_location)), + m_icon(icon) +{ + for (auto&& effect : common_params.effects) + m_effects.emplace_back(std::move(effect)); + Init(); + for (const std::string& tag : common_params.tags) + m_tags.insert(boost::to_upper_copy(tag)); +} + +BuildingType::~BuildingType() +{} + +void BuildingType::Init() { + if (m_production_cost) + m_production_cost->SetTopLevelContent(m_name); + if (m_production_time) + m_production_time->SetTopLevelContent(m_name); + if (m_location) + m_location->SetTopLevelContent(m_name); + if (m_enqueue_location) + m_enqueue_location->SetTopLevelContent(m_name); + for (auto& effect : m_effects) + effect->SetTopLevelContent(m_name); +} + +std::string BuildingType::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "BuildingType\n"; + retval += DumpIndent(ntabs+1) + "name = \"" + m_name + "\"\n"; + retval += DumpIndent(ntabs+1) + "description = \"" + m_description + "\"\n"; + if (m_production_cost) + retval += DumpIndent(ntabs+1) + "buildcost = " + m_production_cost->Dump(ntabs+1) + "\n"; + if (m_production_time) + retval += DumpIndent(ntabs+1) + "buildtime = " + m_production_time->Dump(ntabs+1) + "\n"; + retval += DumpIndent(ntabs+1) + (m_producible ? "Producible" : "Unproducible") + "\n"; + retval += DumpIndent(ntabs+1) + "captureresult = " + + boost::lexical_cast(m_capture_result) + "\n"; + + if (!m_tags.empty()) { + if (m_tags.size() == 1) { + retval += DumpIndent(ntabs+1) + "tags = \"" + *m_tags.begin() + "\"\n"; + } else { + retval += DumpIndent(ntabs+1) + "tags = [ "; + for (const std::string& tag : m_tags) + retval += "\"" + tag + "\" "; + retval += " ]\n"; + } + } + + if (m_location) { + retval += DumpIndent(ntabs+1) + "location = \n"; + retval += m_location->Dump(ntabs+2); + } + if (m_enqueue_location) { + retval += DumpIndent(ntabs+1) + "enqueue location = \n"; + retval += m_enqueue_location->Dump(ntabs+2); + } + + if (m_effects.size() == 1) { + retval += DumpIndent(ntabs+1) + "effectsgroups =\n"; + retval += m_effects[0]->Dump(ntabs+2); + } else { + retval += DumpIndent(ntabs+1) + "effectsgroups = [\n"; + for (auto& effect : m_effects) + retval += effect->Dump(ntabs+2); + retval += DumpIndent(ntabs+1) + "]\n"; + } + retval += DumpIndent(ntabs+1) + "icon = \"" + m_icon + "\"\n"; + return retval; +} + +bool BuildingType::ProductionCostTimeLocationInvariant() const { + // if rule is active, then scripted costs and times are ignored and actual costs are invariant + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION")) + return true; + + // if cost or time are specified and not invariant, result is non-invariance + if (m_production_cost && !(m_production_cost->TargetInvariant() && m_production_cost->SourceInvariant())) + return false; + if (m_production_time && !(m_production_time->TargetInvariant() && m_production_time->SourceInvariant())) + return false; + // if both cost and time are not specified, result is invariance + return true; +} + +float BuildingType::ProductionCost(int empire_id, int location_id) const { + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION") || !m_production_cost) + return 1.0f; + + if (m_production_cost->ConstantExpr()) + return m_production_cost->Eval(); + else if (m_production_cost->SourceInvariant() && m_production_cost->TargetInvariant()) + return m_production_cost->Eval(); + + const auto ARBITRARY_LARGE_COST = 999999.9f; + + auto location = Objects().get(location_id); + if (!location && !m_production_cost->TargetInvariant()) + return ARBITRARY_LARGE_COST; + + auto source = Empires().GetSource(empire_id); + if (!source && !m_production_cost->SourceInvariant()) + return ARBITRARY_LARGE_COST; + + ScriptingContext context(source, location); + + return m_production_cost->Eval(context); +} + +float BuildingType::PerTurnCost(int empire_id, int location_id) const +{ return ProductionCost(empire_id, location_id) / std::max(1, ProductionTime(empire_id, location_id)); } + +int BuildingType::ProductionTime(int empire_id, int location_id) const { + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_BUILDING_PRODUCTION") || !m_production_time) + return 1; + + if (m_production_time->ConstantExpr()) + return m_production_time->Eval(); + else if (m_production_time->SourceInvariant() && m_production_time->TargetInvariant()) + return m_production_time->Eval(); + + const int ARBITRARY_LARGE_TURNS = 9999; + + auto location = Objects().get(location_id); + if (!location && !m_production_time->TargetInvariant()) + return ARBITRARY_LARGE_TURNS; + + auto source = Empires().GetSource(empire_id); + if (!source && !m_production_time->SourceInvariant()) + return ARBITRARY_LARGE_TURNS; + + ScriptingContext context(source, location); + + return m_production_time->Eval(context); +} + +bool BuildingType::ProductionLocation(int empire_id, int location_id) const { + if (!m_location) + return true; + + auto location = Objects().get(location_id); + if (!location) + return false; + + auto source = Empires().GetSource(empire_id); + if (!source) + return false; + + return m_location->Eval(ScriptingContext(source), location); +} + +bool BuildingType::EnqueueLocation(int empire_id, int location_id) const { + if (!m_enqueue_location) + return true; + + auto location = Objects().get(location_id); + if (!location) + return false; + + auto source = Empires().GetSource(empire_id); + if (!source) + return false; + + return m_enqueue_location->Eval(ScriptingContext(source), location); +} + +unsigned int BuildingType::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_description); + CheckSums::CheckSumCombine(retval, m_production_cost); + CheckSums::CheckSumCombine(retval, m_production_time); + CheckSums::CheckSumCombine(retval, m_producible); + CheckSums::CheckSumCombine(retval, m_capture_result); + CheckSums::CheckSumCombine(retval, m_tags); + CheckSums::CheckSumCombine(retval, m_production_meter_consumption); + CheckSums::CheckSumCombine(retval, m_production_special_consumption); + CheckSums::CheckSumCombine(retval, m_location); + CheckSums::CheckSumCombine(retval, m_enqueue_location); + CheckSums::CheckSumCombine(retval, m_effects); + CheckSums::CheckSumCombine(retval, m_icon); + + return retval; +} + + +BuildingTypeManager* BuildingTypeManager::s_instance = nullptr; + +BuildingTypeManager::BuildingTypeManager() { + if (s_instance) + throw std::runtime_error("Attempted to create more than one BuildingTypeManager."); + + // Only update the global pointer on sucessful construction. + s_instance = this; +} + +const BuildingType* BuildingTypeManager::GetBuildingType(const std::string& name) const { + CheckPendingBuildingTypes(); + const auto& it = m_building_types.find(name); + return it != m_building_types.end() ? it->second.get() : nullptr; +} + +BuildingTypeManager::iterator BuildingTypeManager::begin() const { + CheckPendingBuildingTypes(); + return m_building_types.begin(); +} + +BuildingTypeManager::iterator BuildingTypeManager::end() const { + CheckPendingBuildingTypes(); + return m_building_types.end(); +} + +BuildingTypeManager& BuildingTypeManager::GetBuildingTypeManager() { + static BuildingTypeManager manager; + return manager; +} + +unsigned int BuildingTypeManager::GetCheckSum() const { + CheckPendingBuildingTypes(); + unsigned int retval{0}; + for (auto const& name_type_pair : m_building_types) + CheckSums::CheckSumCombine(retval, name_type_pair); + CheckSums::CheckSumCombine(retval, m_building_types.size()); + + + DebugLogger() << "BuildingTypeManager checksum: " << retval; + return retval; +} + +void BuildingTypeManager::SetBuildingTypes(Pending::Pending&& pending_building_types) +{ m_pending_building_types = std::move(pending_building_types); } + +void BuildingTypeManager::CheckPendingBuildingTypes() const +{ Pending::SwapPending(m_pending_building_types, m_building_types); } + + +BuildingTypeManager& GetBuildingTypeManager() +{ return BuildingTypeManager::GetBuildingTypeManager(); } + +const BuildingType* GetBuildingType(const std::string& name) +{ return GetBuildingTypeManager().GetBuildingType(name); } diff --git a/universe/BuildingType.h b/universe/BuildingType.h new file mode 100644 index 00000000000..f4ad5e57c6b --- /dev/null +++ b/universe/BuildingType.h @@ -0,0 +1,209 @@ +#ifndef _BuildingType_h_ +#define _BuildingType_h_ + + +#include "CommonParams.h" +#include "../util/Export.h" +#include "../util/Pending.h" + + +namespace Effect { + class EffectsGroup; +} +namespace Condition { + struct Condition; +} +namespace ValueRef { + template + struct ValueRef; +} + + +//! Class to specify a kind of building. +//! +//! Each building type must have a unique @a name string, by which it can be +//! looked up using GetBuildingType(...). +class FO_COMMON_API BuildingType { +public: + BuildingType(const std::string& name, + const std::string& description, + CommonParams& common_params, + CaptureResult capture_result, + const std::string& icon); + + ~BuildingType(); + + //! Returns the unique name for this type of building + auto Name() const -> const std::string& + { return m_name; } + + //! Returns a text description of this type of building + auto Description() const -> const std::string& + { return m_description; } + + //! Returns a data file format representation of this object + auto Dump(unsigned short ntabs = 0) const -> std::string; + + //! Returns true if the production cost and time are invariant (does not + //! depend on) the location + auto ProductionCostTimeLocationInvariant() const -> bool; + + //! Returns the number of production points required to build this building + //! at this location by this empire + auto ProductionCost(int empire_id, int location_id) const -> float; + + //! Returns the maximum number of production points per turn that can be + //! spend on this building + auto PerTurnCost(int empire_id, int location_id) const -> float; + + //! Returns the number of turns required to build this building at this + //! location by this empire + auto ProductionTime(int empire_id, int location_id) const -> int; + + //! Returns the ValueRef that determines ProductionCost() + auto Cost() const -> const ValueRef::ValueRef* + { return m_production_cost.get(); } + + //! Returns the ValueRef that determines ProductionTime() + auto Time() const -> const ValueRef::ValueRef* + { return m_production_time.get(); } + + //! Returns whether this building type is producible by players and appears + //! on the production screen + auto Producible() const -> bool + { return m_producible; } + + auto ProductionMeterConsumption() const -> const ConsumptionMap& + { return m_production_meter_consumption; } + + auto ProductionSpecialConsumption() const -> const ConsumptionMap& + { return m_production_special_consumption; } + + auto Tags() const -> const std::set& + { return m_tags; } + + //! Returns the condition that determines the locations where this building + //! can be produced + auto Location() const -> const Condition::Condition* + { return m_location.get(); } + + //! Returns a condition that can be used by the UI to further filter (beyond + //! the Location() requirement) where this building will be presented for + //! enqueuing onto the production queue, to avoid clutter in the + //! BuildDesignatorWnd. Example usage: Buildings that are already enqueued + //! at a production location are hidden so they don't appear in the list of + //! available items that can be enqueued/produced (again) at that location. + auto EnqueueLocation() const -> const Condition::Condition* + { return m_enqueue_location.get(); } + + //! Returns the EffectsGroups that encapsulate the effects that buildings of + //! this type have when operational. + auto Effects() const -> const std::vector>& + { return m_effects; } + + //! Returns the name of the grapic file for this building type + auto Icon() const -> const std::string& + { return m_icon; } + + //! Returns true iff the empire with ID empire_id can produce this building + //! at the location with location_id + auto ProductionLocation(int empire_id, int location_id) const -> bool; + + //! Returns true iff the empire with ID empire_id meets the requirements of + //! the EnqueueLocation() UI filter method for this building at the + //! location with location_id + auto EnqueueLocation(int empire_id, int location_id) const -> bool; + + //! Returns CaptureResult for empire with ID @p to_empire_id capturing from + //! empire with IDs @p from_empire_id the planet (or other UniverseObject) + //! with id @p location_id on which this type of Building is located (if + //! @p as_production_item is false) or which is the location of a Production + //! Queue BuildItem for a building of this type (otherwise) + auto GetCaptureResult(int from_empire_id, int to_empire_id, int location_id, bool as_production_item) const -> CaptureResult + { return m_capture_result; } + + //! Returns a number, calculated from the contained data, which should be + //! different for different contained data, and must be the same for + //! the same contained data, and must be the same on different platforms + //! and executions of the program and the function. Useful to verify that + //! the parsed content is consistent without sending it all between + //! clients and server. + auto GetCheckSum() const -> unsigned int; + +private: + void Init(); + + std::string m_name; + std::string m_description; + std::unique_ptr> m_production_cost; + std::unique_ptr> m_production_time; + bool m_producible = true; + CaptureResult m_capture_result; + std::set m_tags; + ConsumptionMap m_production_meter_consumption; + ConsumptionMap m_production_special_consumption; + std::unique_ptr m_location; + std::unique_ptr m_enqueue_location; + std::vector> m_effects; + std::string m_icon; +}; + +//! Holds all FreeOrion BuildingType%s. Types may be looked up by name. +class BuildingTypeManager { +public: + using container_type = std::map>; + using iterator = container_type::const_iterator; + + //! Returns the building type with the name @p name; you should use the + //! free function GetBuildingType(...) instead, mainly to save some typing. + auto GetBuildingType(const std::string& name) const -> const BuildingType*; + + auto NumBuildingTypes() const -> std::size_t { return m_building_types.size(); } + + //! iterator to the first building type + FO_COMMON_API auto begin() const -> iterator; + + //! iterator to the last + 1th building type + FO_COMMON_API auto end() const -> iterator; + + //! Returns the instance of this singleton class; you should use the free + //! function GetBuildingTypeManager() instead + static auto GetBuildingTypeManager() -> BuildingTypeManager&; + + //! Returns a number, calculated from the contained data, which should be + //! different for different contained data, and must be the same for + //! the same contained data, and must be the same on different platforms + //! and executions of the program and the function. Useful to verify that + //! the parsed content is consistent without sending it all between + //! clients and server. + auto GetCheckSum() const -> unsigned int; + + //! Sets building types to the future value of \p pending_building_types. + FO_COMMON_API void SetBuildingTypes(Pending::Pending&& pending_building_types); + +private: + BuildingTypeManager(); + + //! Assigns any m_pending_building_types to m_bulding_types. + void CheckPendingBuildingTypes() const; + + //! Future building type being parsed by parser. + //! Mutable so that it can be assigned to m_building_types when completed. + mutable boost::optional> m_pending_building_types = boost::none; + + //! Map of building types identified by the BuildingType::Name. + //! mutable so that when the parse complete it can be updated. + mutable container_type m_building_types; + + static BuildingTypeManager* s_instance; +}; + +//! Returns the singleton building type manager +FO_COMMON_API auto GetBuildingTypeManager() -> BuildingTypeManager&; + +//! Returns the BuildingType specification object for a building of type +//! @p name. If no such BuildingType exists, nullptr is returned instead. +FO_COMMON_API auto GetBuildingType(const std::string& name) -> const BuildingType*; + + +#endif // _BuildingType_h_ diff --git a/universe/CMakeLists.txt b/universe/CMakeLists.txt index ccc33c03c24..0d7f4c57815 100644 --- a/universe/CMakeLists.txt +++ b/universe/CMakeLists.txt @@ -1,14 +1,21 @@ target_sources(freeorioncommon PUBLIC ${CMAKE_CURRENT_LIST_DIR}/Building.h + ${CMAKE_CURRENT_LIST_DIR}/BuildingType.h + ${CMAKE_CURRENT_LIST_DIR}/CommonParams.h ${CMAKE_CURRENT_LIST_DIR}/Condition.h - ${CMAKE_CURRENT_LIST_DIR}/EffectAccounting.h + ${CMAKE_CURRENT_LIST_DIR}/ConditionAll.h + ${CMAKE_CURRENT_LIST_DIR}/ConditionSource.h + ${CMAKE_CURRENT_LIST_DIR}/Conditions.h ${CMAKE_CURRENT_LIST_DIR}/Effect.h + ${CMAKE_CURRENT_LIST_DIR}/Effects.h ${CMAKE_CURRENT_LIST_DIR}/Encyclopedia.h ${CMAKE_CURRENT_LIST_DIR}/Enums.h ${CMAKE_CURRENT_LIST_DIR}/Field.h + ${CMAKE_CURRENT_LIST_DIR}/FieldType.h ${CMAKE_CURRENT_LIST_DIR}/Fighter.h ${CMAKE_CURRENT_LIST_DIR}/Fleet.h + ${CMAKE_CURRENT_LIST_DIR}/FleetPlan.h ${CMAKE_CURRENT_LIST_DIR}/IDAllocator.h ${CMAKE_CURRENT_LIST_DIR}/Meter.h ${CMAKE_CURRENT_LIST_DIR}/ObjectMap.h @@ -16,27 +23,34 @@ target_sources(freeorioncommon ${CMAKE_CURRENT_LIST_DIR}/PopCenter.h ${CMAKE_CURRENT_LIST_DIR}/Predicates.h ${CMAKE_CURRENT_LIST_DIR}/ResourceCenter.h - ${CMAKE_CURRENT_LIST_DIR}/ShipDesign.h + ${CMAKE_CURRENT_LIST_DIR}/ScriptingContext.h ${CMAKE_CURRENT_LIST_DIR}/Ship.h + ${CMAKE_CURRENT_LIST_DIR}/ShipDesign.h + ${CMAKE_CURRENT_LIST_DIR}/ShipHull.h + ${CMAKE_CURRENT_LIST_DIR}/ShipPart.h ${CMAKE_CURRENT_LIST_DIR}/Special.h ${CMAKE_CURRENT_LIST_DIR}/Species.h ${CMAKE_CURRENT_LIST_DIR}/System.h ${CMAKE_CURRENT_LIST_DIR}/Tech.h ${CMAKE_CURRENT_LIST_DIR}/Universe.h + ${CMAKE_CURRENT_LIST_DIR}/UnlockableItem.h ${CMAKE_CURRENT_LIST_DIR}/Pathfinder.h ${CMAKE_CURRENT_LIST_DIR}/UniverseObject.h ${CMAKE_CURRENT_LIST_DIR}/ValueRef.h - ${CMAKE_CURRENT_LIST_DIR}/ValueRefFwd.h + ${CMAKE_CURRENT_LIST_DIR}/ValueRefs.h PRIVATE ${CMAKE_CURRENT_LIST_DIR}/Building.cpp - ${CMAKE_CURRENT_LIST_DIR}/Condition.cpp - ${CMAKE_CURRENT_LIST_DIR}/EffectAccounting.cpp + ${CMAKE_CURRENT_LIST_DIR}/BuildingType.cpp + ${CMAKE_CURRENT_LIST_DIR}/Conditions.cpp ${CMAKE_CURRENT_LIST_DIR}/Effect.cpp + ${CMAKE_CURRENT_LIST_DIR}/Effects.cpp ${CMAKE_CURRENT_LIST_DIR}/Encyclopedia.cpp ${CMAKE_CURRENT_LIST_DIR}/Enums.cpp ${CMAKE_CURRENT_LIST_DIR}/Field.cpp + ${CMAKE_CURRENT_LIST_DIR}/FieldType.cpp ${CMAKE_CURRENT_LIST_DIR}/Fighter.cpp ${CMAKE_CURRENT_LIST_DIR}/Fleet.cpp + ${CMAKE_CURRENT_LIST_DIR}/FleetPlan.cpp ${CMAKE_CURRENT_LIST_DIR}/IDAllocator.cpp ${CMAKE_CURRENT_LIST_DIR}/Meter.cpp ${CMAKE_CURRENT_LIST_DIR}/ObjectMap.cpp @@ -46,19 +60,15 @@ target_sources(freeorioncommon ${CMAKE_CURRENT_LIST_DIR}/ResourceCenter.cpp ${CMAKE_CURRENT_LIST_DIR}/Ship.cpp ${CMAKE_CURRENT_LIST_DIR}/ShipDesign.cpp + ${CMAKE_CURRENT_LIST_DIR}/ShipHull.cpp + ${CMAKE_CURRENT_LIST_DIR}/ShipPart.cpp ${CMAKE_CURRENT_LIST_DIR}/Special.cpp ${CMAKE_CURRENT_LIST_DIR}/Species.cpp ${CMAKE_CURRENT_LIST_DIR}/System.cpp ${CMAKE_CURRENT_LIST_DIR}/Tech.cpp ${CMAKE_CURRENT_LIST_DIR}/Universe.cpp + ${CMAKE_CURRENT_LIST_DIR}/UnlockableItem.cpp ${CMAKE_CURRENT_LIST_DIR}/Pathfinder.cpp ${CMAKE_CURRENT_LIST_DIR}/UniverseObject.cpp - ${CMAKE_CURRENT_LIST_DIR}/ValueRef.cpp -) - -target_sources(freeoriond - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/UniverseGenerator.h - PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/UniverseGenerator.cpp + ${CMAKE_CURRENT_LIST_DIR}/ValueRefs.cpp ) diff --git a/universe/CommonParams.h b/universe/CommonParams.h new file mode 100644 index 00000000000..64056f36eea --- /dev/null +++ b/universe/CommonParams.h @@ -0,0 +1,76 @@ +#ifndef _CommonParams_h_ +#define _CommonParams_h_ + + +#include +#include +#include +#include +#include + +#include "EnumsFwd.h" + +#include "../util/Export.h" + + +namespace Condition { + struct Condition; +} +namespace Effect { + class EffectsGroup; +} +namespace ValueRef { + template + struct ValueRef; +} + +template +using ConsumptionMap = std::map>, + std::unique_ptr>>; + +//! Common parameters for ShipPart, ShipHull, and BuildingType constructors. +//! +//! Used as temporary storage for parsing to reduce number of sub-items parsed +//! per item. +struct FO_COMMON_API CommonParams { + CommonParams(); + CommonParams(std::unique_ptr>&& production_cost_, + std::unique_ptr>&& production_time_, + bool producible_, + const std::set& tags_, + std::unique_ptr&& location_, + std::vector>&& effects_, + ConsumptionMap&& production_meter_consumption_, + ConsumptionMap&& production_special_consumption_, + std::unique_ptr&& enqueue_location_); + ~CommonParams(); + + std::unique_ptr> production_cost; + std::unique_ptr> production_time; + bool producible = true; + std::set tags; + ConsumptionMap production_meter_consumption; + ConsumptionMap production_special_consumption; + std::unique_ptr location; + std::unique_ptr enqueue_location; + std::vector> effects; +}; + +struct MoreCommonParams { + MoreCommonParams() : + name(), + description(), + exclusions() + {} + MoreCommonParams(const std::string& name_, const std::string& description_, + const std::set& exclusions_) : + name(name_), + description(description_), + exclusions(exclusions_) + {} + std::string name; + std::string description; + std::set exclusions; +}; + +#endif // _CommonParams_h_ diff --git a/universe/Condition.h b/universe/Condition.h index 042de4bbcba..b05b8a144a8 100644 --- a/universe/Condition.h +++ b/universe/Condition.h @@ -1,100 +1,33 @@ #ifndef _Condition_h_ #define _Condition_h_ - -#include "EnumsFwd.h" -#include "ValueRefFwd.h" - #include "../util/Export.h" -#include "../util/CheckSums.h" #include -#include #include #include #include - class UniverseObject; struct ScriptingContext; -/** this namespace holds ConditionBase and its subclasses; these classes - * represent predicates about UniverseObjects used by, for instance, the - * Effect system. */ namespace Condition { -typedef std::vector> ObjectSet; -enum Invariance { - UNKNOWN_INVARIANCE, ///< This condition hasn't yet calculated this invariance type - INVARIANT, ///< This condition is invariant to a particular type of object change - VARIANT ///< This condition's result depends on the state of a particular object -}; +typedef std::vector> ObjectSet; -enum SearchDomain { +enum SearchDomain : int { NON_MATCHES, ///< The Condition will only examine items in the non matches set; those that match the Condition will be inserted into the matches set. MATCHES ///< The Condition will only examine items in the matches set; those that do not match the Condition will be inserted into the nonmatches set. }; -enum SortingMethod { - SORT_MAX, ///< Objects with the largest sort key will be selected - SORT_MIN, ///< Objects with the smallest sort key will be selected - SORT_MODE, ///< Objects with the most common sort key will be selected - SORT_RANDOM ///< Objects will be selected randomly, without consideration of property values -}; - -enum ComparisonType { - INVALID_COMPARISON = -1, - EQUAL, - GREATER_THAN, - GREATER_THAN_OR_EQUAL, - LESS_THAN, - LESS_THAN_OR_EQUAL, - NOT_EQUAL -}; - -enum ContentType { - CONTENT_BUILDING, - CONTENT_SPECIES, - CONTENT_SHIP_HULL, - CONTENT_SHIP_PART, - CONTENT_SPECIAL, - CONTENT_FOCUS -}; - -struct ConditionBase; - -/** Same as ConditionDescription, but returns a string only with conditions that have not been met. */ -FO_COMMON_API std::string ConditionFailedDescription(const std::vector& conditions, - std::shared_ptr candidate_object = nullptr, - std::shared_ptr source_object = nullptr); - -/** Returns a single string which describes a vector of Conditions. If multiple - * conditions are passed, they are treated as if they were contained by an And - * condition. Subconditions within an And (or nested And) are listed as - * lines in a list, with duplicates removed, titled something like "All of...". - * Subconditions within an Or (or nested Ors) are similarly listed as lines in - * a list, with duplicates removed, titled something like "One of...". If a - * candidate object is provided, the returned string will indicate which - * subconditions the candidate matches, and indicate if the overall combination - * of conditions matches the object. */ -FO_COMMON_API std::string ConditionDescription(const std::vector& conditions, - std::shared_ptr candidate_object = nullptr, - std::shared_ptr source_object = nullptr); - /** The base class for all Conditions. */ -struct FO_COMMON_API ConditionBase { - ConditionBase() : - m_root_candidate_invariant(UNKNOWN_INVARIANCE), - m_target_invariant(UNKNOWN_INVARIANCE), - m_source_invariant(UNKNOWN_INVARIANCE) - {} - - virtual ~ConditionBase(); +struct FO_COMMON_API Condition { + Condition() {} + virtual ~Condition(); - virtual bool operator==(const ConditionBase& rhs) const; - - bool operator!=(const ConditionBase& rhs) const + virtual bool operator==(const Condition& rhs) const; + bool operator!=(const Condition& rhs) const { return !(*this == rhs); } virtual void Eval(const ScriptingContext& parent_context, @@ -111,50 +44,47 @@ struct FO_COMMON_API ConditionBase { std::shared_ptr candidate) const; /** Tests single candidate object, returning true iff it matches condition - * with empty ScriptingContext. */ + * with empty ScriptingContext. If this condition is not invariant to */ bool Eval(std::shared_ptr candidate) const; virtual void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const; - /** Returns true iff this condition's evaluation does not reference - * the RootCandidate objects. This requirement ensures that if this - * condition is a subcondition to another Condition or a ValueRef, this - * condition may be evaluated once and its result used to match all local - * candidates to that condition. */ - virtual bool RootCandidateInvariant() const - { return false; } - - /** (Almost) all conditions are varying with local candidates; this is the - * point of evaluating a condition. This funciton is provided for - * consistency with ValueRef, which may not depend on the local candidiate - * of an enclosing condition. */ + //! Returns true iff this condition's evaluation does not reference + //! the RootCandidate objects. This requirement ensures that if this + //! condition is a subcondition to another Condition or a ValueRef, this + //! condition may be evaluated once and its result used to match all local + //! candidates to that condition. + bool RootCandidateInvariant() const + { return m_root_candidate_invariant; } + + //! (Almost) all conditions are varying with local candidates; this is the + //! point of evaluating a condition. This funciton is provided for + //! consistency with ValueRef, which may not depend on the local candidiate + //! of an enclosing condition. bool LocalCandidateInvariant() const { return false; } - /** Returns true iff this condition's evaluation does not reference the - * target object.*/ - virtual bool TargetInvariant() const - { return false; } + //! Returns true iff this condition's evaluation does not reference the + //! target object. + bool TargetInvariant() const + { return m_target_invariant; } - /** Returns true iff this condition's evaluation does not reference the - * source object.*/ - virtual bool SourceInvariant() const - { return false; } + //! Returns true iff this condition's evaluation does not reference the + //! source object. + bool SourceInvariant() const + { return m_source_invariant; } virtual std::string Description(bool negated = false) const = 0; - - virtual std::string Dump() const = 0; - + virtual std::string Dump(unsigned short ntabs = 0) const = 0; virtual void SetTopLevelContent(const std::string& content_name) = 0; - virtual unsigned int GetCheckSum() const { return 0; } protected: - mutable Invariance m_root_candidate_invariant; - mutable Invariance m_target_invariant; - mutable Invariance m_source_invariant; + bool m_root_candidate_invariant = false; + bool m_target_invariant = false; + bool m_source_invariant = false; private: struct MatchHelper; @@ -163,3057 +93,10 @@ struct FO_COMMON_API ConditionBase { virtual bool Match(const ScriptingContext& local_context) const; friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects if the number of objects that match Condition - * \a condition is is >= \a low and < \a high. Matched objects may - * or may not themselves match the condition. */ -struct FO_COMMON_API Number : public ConditionBase { - Number(ValueRef::ValueRefBase* low, ValueRef::ValueRefBase* high, - ConditionBase* condition) : - m_low(low), - m_high(high), - m_condition(condition) - {} - - virtual ~Number(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_low; - ValueRef::ValueRefBase* m_high; - ConditionBase* m_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects if the current game turn is >= \a low and < \a high. */ -struct FO_COMMON_API Turn : public ConditionBase { - explicit Turn(ValueRef::ValueRefBase* low, ValueRef::ValueRefBase* high = nullptr) : - m_low(low), - m_high(high) - {} - - virtual ~Turn(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_low; - ValueRef::ValueRefBase* m_high; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches a specified \a number of objects that match Condition \a condition - * or as many objects as match the condition if the number of objects is less - * than the number requested. If more objects match the condition than are - * requested, the objects are sorted according to the value of the specified - * \a property_name and objects are matched according to whether they have - * the specified \a sorting_type of those property values. For example, - * objects with the largest, smallest or most common property value may be - * selected preferentially. */ -struct FO_COMMON_API SortedNumberOf : public ConditionBase { - /** Sorts randomly, without considering a sort key. */ - SortedNumberOf(ValueRef::ValueRefBase* number, - ConditionBase* condition) : - m_number(number), - m_sort_key(nullptr), - m_sorting_method(SORT_RANDOM), - m_condition(condition) - {} - - /** Sorts according to the specified method, based on the key values - * evaluated for each object. */ - SortedNumberOf(ValueRef::ValueRefBase* number, - ValueRef::ValueRefBase* sort_key_ref, - SortingMethod sorting_method, - ConditionBase* condition) : - m_number(number), - m_sort_key(sort_key_ref), - m_sorting_method(sorting_method), - m_condition(condition) - {} - - virtual ~SortedNumberOf(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_number; - ValueRef::ValueRefBase* m_sort_key; - SortingMethod m_sorting_method; - ConditionBase* m_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects. */ -struct FO_COMMON_API All : public ConditionBase { - All() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches no objects. Currently only has an experimental use for efficient immediate rejection as the top-line condition. - * Essentially the entire point of this Condition is to provide the specialized GetDefaultInitialCandidateObjects() */ -struct FO_COMMON_API None : public ConditionBase { - None() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override - { /* efficient rejection of everything. */ } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that are owned (if \a exclusive == false) or only owned - * (if \a exclusive == true) by an empire that has affilitation type - * \a affilitation with Empire \a empire_id. */ -struct FO_COMMON_API EmpireAffiliation : public ConditionBase { - EmpireAffiliation(ValueRef::ValueRefBase* empire_id, EmpireAffiliationType affiliation) : - m_empire_id(empire_id), - m_affiliation(affiliation) - {} - - explicit EmpireAffiliation(ValueRef::ValueRefBase* empire_id); - - explicit EmpireAffiliation(EmpireAffiliationType affiliation) : - m_empire_id(nullptr), - m_affiliation(affiliation) - {} - - virtual ~EmpireAffiliation(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_empire_id; - EmpireAffiliationType m_affiliation; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches the source object only. */ -struct FO_COMMON_API Source : public ConditionBase { - Source() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches the root candidate object in a condition tree. This is useful - * within a subcondition to match the object actually being matched by the - * whole compound condition, rather than an object just being matched in a - * subcondition in order to evaluate the outer condition. */ -struct FO_COMMON_API RootCandidate : public ConditionBase { - RootCandidate() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - bool RootCandidateInvariant() const override - { return false; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** There is no LocalCandidate condition. To match any local candidate object, - * use the All condition. */ - -/** Matches the target of an effect being executed. */ -struct FO_COMMON_API Target : public ConditionBase { - Target() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return false; } - - bool SourceInvariant() const override - { return true; } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches planets that are a homeworld for any of the species specified in - * \a names. If \a names is empty, matches any planet that is a homeworld for - * any species in the current game Universe. */ -struct FO_COMMON_API Homeworld : public ConditionBase { - Homeworld() : - ConditionBase(), - m_names() - {} - - explicit Homeworld(const std::vector*>& names) : - ConditionBase(), - m_names(names) - {} - - virtual ~Homeworld(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - std::vector*> m_names; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches planets that are an empire's capital. */ -struct FO_COMMON_API Capital : public ConditionBase { - Capital() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches space monsters. */ -struct FO_COMMON_API Monster : public ConditionBase { - Monster() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches armed ships and monsters. */ -struct FO_COMMON_API Armed : public ConditionBase { - Armed() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -/** Matches all objects that are of UniverseObjectType \a type. */ -struct FO_COMMON_API Type : public ConditionBase { - explicit Type(ValueRef::ValueRefBase* type) : - ConditionBase(), - m_type(type) - {} - - virtual ~Type(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_type; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all Building objects that are one of the building types specified - * in \a names. */ -struct FO_COMMON_API Building : public ConditionBase { - explicit Building(const std::vector*>& names) : - ConditionBase(), - m_names(names) - {} - - virtual ~Building(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - std::vector*> m_names; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that have an attached Special named \a name. */ -struct FO_COMMON_API HasSpecial : public ConditionBase { - explicit HasSpecial(const std::string& name); - - explicit HasSpecial(ValueRef::ValueRefBase* name = nullptr) : - ConditionBase(), - m_name(name), - m_capacity_low(nullptr), - m_capacity_high(nullptr), - m_since_turn_low(nullptr), - m_since_turn_high(nullptr) - {} - - HasSpecial(ValueRef::ValueRefBase* name, - ValueRef::ValueRefBase* since_turn_low, - ValueRef::ValueRefBase* since_turn_high = nullptr) : - ConditionBase(), - m_name(name), - m_capacity_low(nullptr), - m_capacity_high(nullptr), - m_since_turn_low(since_turn_low), - m_since_turn_high(since_turn_high) - {} - - HasSpecial(ValueRef::ValueRefBase* name, - ValueRef::ValueRefBase* capacity_low, - ValueRef::ValueRefBase* capacity_high = nullptr) : - ConditionBase(), - m_name(name), - m_capacity_low(capacity_low), - m_capacity_high(capacity_high), - m_since_turn_low(nullptr), - m_since_turn_high(nullptr) - {} - - virtual ~HasSpecial(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_name; - ValueRef::ValueRefBase* m_capacity_low; - ValueRef::ValueRefBase* m_capacity_high; - ValueRef::ValueRefBase* m_since_turn_low; - ValueRef::ValueRefBase* m_since_turn_high; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that have the tag \a tag. */ -struct FO_COMMON_API HasTag : public ConditionBase { - explicit HasTag(const std::string& name); - - explicit HasTag(ValueRef::ValueRefBase* name = nullptr) : - ConditionBase(), - m_name(name) - {} - - virtual ~HasTag(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_name; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that were created on turns within the specified range. */ -struct FO_COMMON_API CreatedOnTurn : public ConditionBase { - CreatedOnTurn(ValueRef::ValueRefBase* low, ValueRef::ValueRefBase* high) : - ConditionBase(), - m_low(low), - m_high(high) - {} - - virtual ~CreatedOnTurn(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_low; - ValueRef::ValueRefBase* m_high; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that contain an object that matches Condition - * \a condition. Container objects are Systems, Planets (which contain - * Buildings), and Fleets (which contain Ships). */ -struct FO_COMMON_API Contains : public ConditionBase { - Contains(ConditionBase* condition) : - ConditionBase(), - m_condition(condition) - {} - - virtual ~Contains(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ConditionBase* m_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that are contained by an object that matches Condition - * \a condition. Container objects are Systems, Planets (which contain - * Buildings), and Fleets (which contain Ships). */ -struct FO_COMMON_API ContainedBy : public ConditionBase { - ContainedBy(ConditionBase* condition) : - ConditionBase(), - m_condition(condition) - {} - - virtual ~ContainedBy(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ConditionBase* m_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that are in the system with the indicated \a system_id */ -struct FO_COMMON_API InSystem : public ConditionBase { - InSystem(ValueRef::ValueRefBase* system_id) : - ConditionBase(), - m_system_id(system_id) - {} - - virtual ~InSystem(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_system_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches the object with the id \a object_id */ -struct FO_COMMON_API ObjectID : public ConditionBase { - ObjectID(ValueRef::ValueRefBase* object_id) : - ConditionBase(), - m_object_id(object_id) - {} - - virtual ~ObjectID(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_object_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all Planet objects that have one of the PlanetTypes in \a types. - * Note that all Building objects which are on matching planets are also - * matched. */ -struct FO_COMMON_API PlanetType : public ConditionBase { - PlanetType(const std::vector*>& types) : - ConditionBase(), - m_types(types) - {} - - virtual ~PlanetType(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - std::vector*> m_types; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all Planet objects that have one of the PlanetSizes in \a sizes. - * Note that all Building objects which are on matching planets are also - * matched. */ -struct FO_COMMON_API PlanetSize : public ConditionBase { - PlanetSize(const std::vector*>& sizes) : - ConditionBase(), - m_sizes(sizes) - {} - - virtual ~PlanetSize(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - std::vector*> m_sizes; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all Planet objects that have one of the PlanetEnvironments in - * \a environments. Note that all Building objects which are on matching - * planets are also matched. */ -struct FO_COMMON_API PlanetEnvironment : public ConditionBase { - PlanetEnvironment(const std::vector*>& environments, - ValueRef::ValueRefBase* species_name_ref = nullptr) : - ConditionBase(), - m_environments(environments), - m_species_name(species_name_ref) - {} - - virtual ~PlanetEnvironment(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - std::vector*> m_environments; - ValueRef::ValueRefBase* m_species_name; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all planets or ships that have one of the species in \a species. - * Note that all Building object which are on matching planets are also - * matched. */ -struct FO_COMMON_API Species : public ConditionBase { - Species(const std::vector*>& names) : - ConditionBase(), - m_names(names) - {} - - Species() : - ConditionBase(), - m_names() - {} - - virtual ~Species(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - std::vector*> m_names; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches planets where the indicated number of the indicated building type - * or ship design are enqueued on the production queue. */ -struct FO_COMMON_API Enqueued : public ConditionBase { - Enqueued(BuildType build_type, - ValueRef::ValueRefBase* name, - ValueRef::ValueRefBase* empire_id = nullptr, - ValueRef::ValueRefBase* low = nullptr, - ValueRef::ValueRefBase* high = nullptr) : - ConditionBase(), - m_build_type(build_type), - m_name(name), - m_design_id(nullptr), - m_empire_id(empire_id), - m_low(low), - m_high(high) - {} - - explicit Enqueued(ValueRef::ValueRefBase* design_id, - ValueRef::ValueRefBase* empire_id = nullptr, - ValueRef::ValueRefBase* low = nullptr, - ValueRef::ValueRefBase* high = nullptr); - - Enqueued(); - - virtual ~Enqueued(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - BuildType m_build_type; - ValueRef::ValueRefBase* m_name; - ValueRef::ValueRefBase* m_design_id; - ValueRef::ValueRefBase* m_empire_id; - ValueRef::ValueRefBase* m_low; - ValueRef::ValueRefBase* m_high; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all ProdCenter objects that have one of the FocusTypes in \a foci. */ -struct FO_COMMON_API FocusType : public ConditionBase { - FocusType(const std::vector*>& names) : - ConditionBase(), - m_names(names) - {} - - virtual ~FocusType(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - std::vector*> m_names; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all System objects that have one of the StarTypes in \a types. Note that all objects - in matching Systems are also matched (Ships, Fleets, Buildings, Planets, etc.). */ -struct FO_COMMON_API StarType : public ConditionBase { - StarType(const std::vector*>& types) : - ConditionBase(), - m_types(types) - {} - - virtual ~StarType(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - std::vector*> m_types; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all ships whose ShipDesign has the hull specified by \a name. */ -struct FO_COMMON_API DesignHasHull : public ConditionBase { - explicit DesignHasHull(ValueRef::ValueRefBase* name) : - ConditionBase(), - m_name(name) - {} - - virtual ~DesignHasHull(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_name; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all ships whose ShipDesign has >= \a low and < \a high of the ship - * part specified by \a name. */ -struct FO_COMMON_API DesignHasPart : public ConditionBase { - DesignHasPart(ValueRef::ValueRefBase* name, ValueRef::ValueRefBase* low = nullptr, - ValueRef::ValueRefBase* high = nullptr) : - ConditionBase(), - m_low(low), - m_high(high), - m_name(name) - {} - - virtual ~DesignHasPart(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_low; - ValueRef::ValueRefBase* m_high; - ValueRef::ValueRefBase* m_name; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches ships whose ShipDesign has >= \a low and < \a high of ship parts of - * the specified \a part_class */ -struct FO_COMMON_API DesignHasPartClass : public ConditionBase { - DesignHasPartClass(ShipPartClass part_class, ValueRef::ValueRefBase* low, - ValueRef::ValueRefBase* high) : - ConditionBase(), - m_low(low), - m_high(high), - m_class(part_class) - {} - - virtual ~DesignHasPartClass(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_low; - ValueRef::ValueRefBase* m_high; - ShipPartClass m_class; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches ships who ShipDesign is a predefined shipdesign with the name - * \a name */ -struct FO_COMMON_API PredefinedShipDesign : public ConditionBase { - explicit PredefinedShipDesign(ValueRef::ValueRefBase* name) : - ConditionBase(), - m_name(name) - {} - - virtual ~PredefinedShipDesign(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_name; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches ships whose design id \a id. */ -struct FO_COMMON_API NumberedShipDesign : public ConditionBase { - NumberedShipDesign(ValueRef::ValueRefBase* design_id) : - ConditionBase(), - m_design_id(design_id) - {} - - virtual ~NumberedShipDesign(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_design_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches ships or buildings produced by the empire with id \a empire_id.*/ -struct FO_COMMON_API ProducedByEmpire : public ConditionBase { - ProducedByEmpire(ValueRef::ValueRefBase* empire_id) : - ConditionBase(), - m_empire_id(empire_id) - {} - - virtual ~ProducedByEmpire(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_empire_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches a given object with a linearly distributed probability of \a chance. */ -struct FO_COMMON_API Chance : public ConditionBase { - Chance(ValueRef::ValueRefBase* chance) : - ConditionBase(), - m_chance(chance) - {} - - virtual ~Chance(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_chance; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that have a meter of type \a meter, and whose current - * value is >= \a low and <= \a high. */ -struct FO_COMMON_API MeterValue : public ConditionBase { - MeterValue(MeterType meter, ValueRef::ValueRefBase* low, - ValueRef::ValueRefBase* high) : - ConditionBase(), - m_meter(meter), - m_low(low), - m_high(high) - {} - - virtual ~MeterValue(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - MeterType m_meter; - ValueRef::ValueRefBase* m_low; - ValueRef::ValueRefBase* m_high; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches ships that have a ship part meter of type \a meter for part \a part - * whose current value is >= low and <= high. */ -struct FO_COMMON_API ShipPartMeterValue : public ConditionBase { - ShipPartMeterValue(ValueRef::ValueRefBase* ship_part_name, - MeterType meter, - ValueRef::ValueRefBase* low, - ValueRef::ValueRefBase* high) : - ConditionBase(), - m_part_name(ship_part_name), - m_meter(meter), - m_low(low), - m_high(high) - {} - - virtual ~ShipPartMeterValue(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_part_name; - MeterType m_meter; - ValueRef::ValueRefBase* m_low; - ValueRef::ValueRefBase* m_high; -}; - -/** Matches all objects if the empire with id \a empire_id has an empire meter - * \a meter whose current value is >= \a low and <= \a high. */ -struct FO_COMMON_API EmpireMeterValue : public ConditionBase { - EmpireMeterValue(const std::string& meter, - ValueRef::ValueRefBase* low, - ValueRef::ValueRefBase* high) : - ConditionBase(), - m_empire_id(nullptr), - m_meter(meter), - m_low(low), - m_high(high) - {} - - EmpireMeterValue(ValueRef::ValueRefBase* empire_id, - const std::string& meter, - ValueRef::ValueRefBase* low, - ValueRef::ValueRefBase* high) : - ConditionBase(), - m_empire_id(empire_id), - m_meter(meter), - m_low(low), - m_high(high) - {} - - virtual ~EmpireMeterValue(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_empire_id; - const std::string m_meter; - ValueRef::ValueRefBase* m_low; - ValueRef::ValueRefBase* m_high; -}; - -/** Matches all objects whose owner's stockpile of \a stockpile is between - * \a low and \a high, inclusive. */ -struct FO_COMMON_API EmpireStockpileValue : public ConditionBase { - EmpireStockpileValue(ResourceType stockpile, ValueRef::ValueRefBase* low, - ValueRef::ValueRefBase* high) : - ConditionBase(), - m_stockpile(stockpile), - m_low(low), - m_high(high) - {} - - virtual ~EmpireStockpileValue(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ResourceType m_stockpile; - ValueRef::ValueRefBase* m_low; - ValueRef::ValueRefBase* m_high; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects whose owner who has tech \a name. */ -struct FO_COMMON_API OwnerHasTech : public ConditionBase { - explicit OwnerHasTech(ValueRef::ValueRefBase* name) : - ConditionBase(), - m_name(name) - {} - - virtual ~OwnerHasTech(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_name; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects whose owner who has the building type \a name available. */ -struct FO_COMMON_API OwnerHasBuildingTypeAvailable : public ConditionBase { - explicit OwnerHasBuildingTypeAvailable(const std::string& name); - - explicit OwnerHasBuildingTypeAvailable(ValueRef::ValueRefBase* name) : - ConditionBase(), - m_name(name) - {} - - virtual ~OwnerHasBuildingTypeAvailable(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_name; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects whose owner who has the ship design \a id available. */ -struct FO_COMMON_API OwnerHasShipDesignAvailable : public ConditionBase { - explicit OwnerHasShipDesignAvailable(int id); - - explicit OwnerHasShipDesignAvailable(ValueRef::ValueRefBase* id) : - ConditionBase(), - m_id(id) - {} - - virtual ~OwnerHasShipDesignAvailable(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects whose owner who has the ship part @a name available. */ -struct FO_COMMON_API OwnerHasShipPartAvailable : public ConditionBase { - explicit OwnerHasShipPartAvailable(const std::string& name); - - explicit OwnerHasShipPartAvailable(ValueRef::ValueRefBase* name) : - ConditionBase(), - m_name(name) - {} - - virtual ~OwnerHasShipPartAvailable(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_name; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that are visible to at least one Empire in \a empire_ids. */ -struct FO_COMMON_API VisibleToEmpire : public ConditionBase { - explicit VisibleToEmpire(ValueRef::ValueRefBase* empire_id) : - ConditionBase(), - m_empire_id(empire_id) - {} - - virtual ~VisibleToEmpire(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_empire_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that are within \a distance units of at least one - * object that meets \a condition. Warning: this Condition can slow things - * down considerably if overused. It is best to use Conditions that yield - * relatively few matches. */ -struct FO_COMMON_API WithinDistance : public ConditionBase { - WithinDistance(ValueRef::ValueRefBase* distance, ConditionBase* condition) : - ConditionBase(), - m_distance(distance), - m_condition(condition) - {} - - virtual ~WithinDistance(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_distance; - ConditionBase* m_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that are within \a jumps starlane jumps of at least one - * object that meets \a condition. Warning: this Condition can slow things - * down considerably if overused. It is best to use Conditions that yield - * relatively few matches. */ -struct FO_COMMON_API WithinStarlaneJumps : public ConditionBase { - WithinStarlaneJumps(ValueRef::ValueRefBase* jumps, ConditionBase* condition) : - ConditionBase(), - m_jumps(jumps), - m_condition(condition) - {} - - virtual ~WithinStarlaneJumps(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_jumps; - ConditionBase* m_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches objects that are in systems that could have starlanes added between - * them and all (not just one) of the systems containing (or that are) one of - * the objects matched by \a condition. "Could have starlanes added" means - * that a lane would be geometrically acceptable, meaning it wouldn't cross - * any other lanes, pass too close to another system, or be too close in angle - * to an existing lane. */ -struct FO_COMMON_API CanAddStarlaneConnection : ConditionBase { - explicit CanAddStarlaneConnection(ConditionBase* condition) : - ConditionBase(), - m_condition(condition) - {} - - virtual ~CanAddStarlaneConnection(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ConditionBase* m_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches systems that have been explored by at least one Empire - * in \a empire_ids. */ -struct FO_COMMON_API ExploredByEmpire : public ConditionBase { - explicit ExploredByEmpire(ValueRef::ValueRefBase* empire_id) : - ConditionBase(), - m_empire_id(empire_id) - {} - - virtual ~ExploredByEmpire(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_empire_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches objects that are moving. ... What does that mean? Departing this - * turn, or were located somewhere else last turn...? */ -struct FO_COMMON_API Stationary : public ConditionBase { - explicit Stationary() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches objects that are aggressive fleets or are in aggressive fleets. */ -struct FO_COMMON_API Aggressive: public ConditionBase { - explicit Aggressive() : - ConditionBase(), - m_aggressive(true) - {} - - explicit Aggressive(bool aggressive) : - ConditionBase(), - m_aggressive(aggressive) - {} - - bool operator==(const ConditionBase& rhs) const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - bool GetAggressive() const - { return m_aggressive; } - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - bool m_aggressive; // false to match passive ships/fleets - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches objects that are in systems that can be fleet supplied by the - * empire with id \a empire_id */ -struct FO_COMMON_API FleetSupplyableByEmpire : public ConditionBase { - explicit FleetSupplyableByEmpire(ValueRef::ValueRefBase* empire_id) : - ConditionBase(), - m_empire_id(empire_id) - {} - - virtual ~FleetSupplyableByEmpire(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_empire_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches objects that are in systems that are connected by resource-sharing - * to at least one object that meets \a condition using the resource-sharing - * network of the empire with id \a empire_id */ -struct FO_COMMON_API ResourceSupplyConnectedByEmpire : public ConditionBase { - ResourceSupplyConnectedByEmpire(ValueRef::ValueRefBase* empire_id, ConditionBase* condition) : - ConditionBase(), - m_empire_id(empire_id), - m_condition(condition) - {} - - virtual ~ResourceSupplyConnectedByEmpire(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_empire_id; - ConditionBase* m_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches objects whose species has the ability to found new colonies. */ -struct FO_COMMON_API CanColonize : public ConditionBase { - explicit CanColonize() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches objects whose species has the ability to produce ships. */ -struct FO_COMMON_API CanProduceShips : public ConditionBase { - CanProduceShips() : ConditionBase() {} - - bool operator==(const ConditionBase& rhs) const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches the objects that have been targeted for bombardment by at least one - * object that matches \a m_by_object_condition. */ -struct FO_COMMON_API OrderedBombarded : public ConditionBase { - OrderedBombarded(ConditionBase* by_object_condition) : - ConditionBase(), - m_by_object_condition(by_object_condition) - {} - - virtual ~OrderedBombarded(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - virtual void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ConditionBase* m_by_object_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects if the comparisons between values of ValueRefs meet the - * specified comparison types. */ -struct FO_COMMON_API ValueTest : public ConditionBase { - ValueTest(ValueRef::ValueRefBase* value_ref1, - ComparisonType comp1, - ValueRef::ValueRefBase* value_ref2, - ComparisonType comp2 = INVALID_COMPARISON, - ValueRef::ValueRefBase* value_ref3 = nullptr); - - ValueTest(ValueRef::ValueRefBase* value_ref1, - ComparisonType comp1, - ValueRef::ValueRefBase* value_ref2, - ComparisonType comp2 = INVALID_COMPARISON, - ValueRef::ValueRefBase* value_ref3 = nullptr); - - ValueTest(ValueRef::ValueRefBase* value_ref1, - ComparisonType comp1, - ValueRef::ValueRefBase* value_ref2, - ComparisonType comp2 = INVALID_COMPARISON, - ValueRef::ValueRefBase* value_ref3 = nullptr); - - virtual ~ValueTest(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_value_ref1 = nullptr; - ValueRef::ValueRefBase* m_value_ref2 = nullptr; - ValueRef::ValueRefBase* m_value_ref3 = nullptr; - ValueRef::ValueRefBase* m_string_value_ref1 = nullptr; - ValueRef::ValueRefBase* m_string_value_ref2 = nullptr; - ValueRef::ValueRefBase* m_string_value_ref3 = nullptr; - ValueRef::ValueRefBase* m_int_value_ref1 = nullptr; - ValueRef::ValueRefBase* m_int_value_ref2 = nullptr; - ValueRef::ValueRefBase* m_int_value_ref3 = nullptr; - - ComparisonType m_compare_type1 = INVALID_COMPARISON; - ComparisonType m_compare_type2 = INVALID_COMPARISON; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches objects that match the location condition of the specified - * content. */ -struct FO_COMMON_API Location : public ConditionBase { -public: - Location(ContentType content_type, ValueRef::ValueRefBase* name1, - ValueRef::ValueRefBase* name2 = nullptr) : - ConditionBase(), - m_name1(name1), - m_name2(name2), - m_content_type(content_type) - {} - - virtual ~Location(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - bool Match(const ScriptingContext& local_context) const override; - - ValueRef::ValueRefBase* m_name1; - ValueRef::ValueRefBase* m_name2; - ContentType m_content_type; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that match every Condition in \a operands. */ -struct FO_COMMON_API And : public ConditionBase { - And(const std::vector& operands) : - ConditionBase(), - m_operands(operands) - {} - - virtual ~And(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - const std::vector& Operands() const - { return m_operands; } - - unsigned int GetCheckSum() const override; - -private: - std::vector m_operands; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that match at least one Condition in \a operands. */ -struct FO_COMMON_API Or : public ConditionBase { - Or(const std::vector& operands) : - ConditionBase(), - m_operands(operands) - {} - - virtual ~Or(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - virtual void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - std::vector m_operands; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches all objects that do not match the Condition \a operand. */ -struct FO_COMMON_API Not : public ConditionBase { - Not(ConditionBase* operand) : - ConditionBase(), - m_operand(operand) - {} - - virtual ~Not(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ConditionBase* m_operand; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Matches whatever its subcondition matches, but has a customized description - * string that is returned by Description() by looking up in the stringtable. */ -struct FO_COMMON_API Described : public ConditionBase { - Described(ConditionBase* condition, const std::string& desc_stringtable_key) : - ConditionBase(), - m_condition(condition), - m_desc_stringtable_key(desc_stringtable_key) - {} - - virtual ~Described(); - - bool operator==(const ConditionBase& rhs) const override; - - void Eval(const ScriptingContext& parent_context, ObjectSet& matches, - ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; - - bool RootCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description(bool negated = false) const override; - - std::string Dump() const override - { return m_condition ? m_condition->Dump() : ""; } - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ConditionBase* m_condition; - std::string m_desc_stringtable_key; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -// template implementations -template -void ConditionBase::serialize(Archive& ar, const unsigned int version) -{} - -template -void Number::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_low) - & BOOST_SERIALIZATION_NVP(m_high) - & BOOST_SERIALIZATION_NVP(m_condition); -} - -template -void Turn::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_low) - & BOOST_SERIALIZATION_NVP(m_high); -} - -template -void SortedNumberOf::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_number) - & BOOST_SERIALIZATION_NVP(m_sort_key) - & BOOST_SERIALIZATION_NVP(m_sorting_method) - & BOOST_SERIALIZATION_NVP(m_condition); -} - -template -void All::serialize(Archive& ar, const unsigned int version) -{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); } - -template -void None::serialize(Archive& ar, const unsigned int version) -{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); } - -template -void EmpireAffiliation::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_empire_id) - & BOOST_SERIALIZATION_NVP(m_affiliation); -} - -template -void Source::serialize(Archive& ar, const unsigned int version) -{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); } - -template -void RootCandidate::serialize(Archive& ar, const unsigned int version) -{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); } - -template -void Target::serialize(Archive& ar, const unsigned int version) -{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); } - -template -void Homeworld::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_names); -} - -template -void Capital::serialize(Archive& ar, const unsigned int version) -{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); } - -template -void Monster::serialize(Archive& ar, const unsigned int version) -{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); } - -template -void Armed::serialize(Archive& ar, const unsigned int version) -{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); } - -template -void Type::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_type); -} - -template -void Building::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_names); -} - -template -void HasSpecial::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_capacity_low) - & BOOST_SERIALIZATION_NVP(m_capacity_high) - & BOOST_SERIALIZATION_NVP(m_since_turn_low) - & BOOST_SERIALIZATION_NVP(m_since_turn_high); -} - -template -void HasTag::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_name); -} - -template -void CreatedOnTurn::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_low) - & BOOST_SERIALIZATION_NVP(m_high); -} - -template -void Contains::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_condition); -} - -template -void ContainedBy::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_condition); -} - -template -void InSystem::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_system_id); -} - -template -void ObjectID::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_object_id); -} - -template -void PlanetType::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_types); -} - -template -void PlanetSize::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_sizes); -} - -template -void PlanetEnvironment::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_environments) - & BOOST_SERIALIZATION_NVP(m_species_name); -} - -template -void Species::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_names); -} - -template -void Enqueued::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_build_type) - & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_design_id) - & BOOST_SERIALIZATION_NVP(m_empire_id) - & BOOST_SERIALIZATION_NVP(m_low) - & BOOST_SERIALIZATION_NVP(m_high); -} - -template -void FocusType::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_names); -} - -template -void StarType::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_types); -} - -template -void DesignHasHull::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_name); -} - -template -void DesignHasPart::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_low) - & BOOST_SERIALIZATION_NVP(m_high) - & BOOST_SERIALIZATION_NVP(m_name); -} - -template -void DesignHasPartClass::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_low) - & BOOST_SERIALIZATION_NVP(m_high) - & BOOST_SERIALIZATION_NVP(m_class); -} - -template -void PredefinedShipDesign::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_name); -} - -template -void NumberedShipDesign::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_design_id); -} - -template -void ProducedByEmpire::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_empire_id); -} - -template -void Chance::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_chance); -} - -template -void MeterValue::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_meter) - & BOOST_SERIALIZATION_NVP(m_low) - & BOOST_SERIALIZATION_NVP(m_high); -} - -template -void EmpireStockpileValue::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_low) - & BOOST_SERIALIZATION_NVP(m_high); -} - -template -void OwnerHasTech::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_name); -} - -template -void OwnerHasBuildingTypeAvailable::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_name); -} - -template -void OwnerHasShipDesignAvailable::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_id); -} - -template -void OwnerHasShipPartAvailable::serialize(Archive& ar, - const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_name); -} - -template -void VisibleToEmpire::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_empire_id); -} - -template -void WithinDistance::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_distance) - & BOOST_SERIALIZATION_NVP(m_condition); -} - -template -void WithinStarlaneJumps::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_jumps) - & BOOST_SERIALIZATION_NVP(m_condition); -} - -template -void CanAddStarlaneConnection::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_condition); -} - -template -void ExploredByEmpire::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_empire_id); -} - -template -void Stationary::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); -} - -template -void Aggressive::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_aggressive); -} - -template -void FleetSupplyableByEmpire::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_empire_id); -} - -template -void ResourceSupplyConnectedByEmpire::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_empire_id) - & BOOST_SERIALIZATION_NVP(m_condition); -} - -template -void CanColonize::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); -} - -template -void CanProduceShips::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase); -} - -template -void OrderedBombarded::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_by_object_condition); -} - -template -void ValueTest::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_value_ref1) - & BOOST_SERIALIZATION_NVP(m_value_ref2) - & BOOST_SERIALIZATION_NVP(m_value_ref3) - & BOOST_SERIALIZATION_NVP(m_string_value_ref1) - & BOOST_SERIALIZATION_NVP(m_string_value_ref2) - & BOOST_SERIALIZATION_NVP(m_string_value_ref3) - & BOOST_SERIALIZATION_NVP(m_int_value_ref1) - & BOOST_SERIALIZATION_NVP(m_int_value_ref2) - & BOOST_SERIALIZATION_NVP(m_int_value_ref3) - & BOOST_SERIALIZATION_NVP(m_compare_type1) - & BOOST_SERIALIZATION_NVP(m_compare_type2); -} - -template -void Location::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_name1) - & BOOST_SERIALIZATION_NVP(m_name2) - & BOOST_SERIALIZATION_NVP(m_content_type); -} - -template -void And::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_operands); -} - -template -void Or::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_operands); -} - -template -void Not::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_operand); -} - -template -void Described::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ConditionBase) - & BOOST_SERIALIZATION_NVP(m_condition) - & BOOST_SERIALIZATION_NVP(m_desc_stringtable_key); } -} // namespace Condition #endif // _Condition_h_ diff --git a/universe/ConditionAll.h b/universe/ConditionAll.h new file mode 100644 index 00000000000..407b54b4b26 --- /dev/null +++ b/universe/ConditionAll.h @@ -0,0 +1,32 @@ +#ifndef _ConditionAll_h_ +#define _ConditionAll_h_ + +#include "Condition.h" + +/** this namespace holds Condition and its subclasses; these classes + * represent predicates about UniverseObjects used by, for instance, the + * Effect system. */ +namespace Condition { + +/** Matches all objects. */ +struct FO_COMMON_API All final : public Condition { + All(); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +} // namespace Condition + +#endif // _ConditionAll_h_ diff --git a/universe/ConditionSource.h b/universe/ConditionSource.h new file mode 100644 index 00000000000..ddd3584275a --- /dev/null +++ b/universe/ConditionSource.h @@ -0,0 +1,34 @@ +#ifndef _ConditionSource_h_ +#define _ConditionSource_h_ + +#include "Condition.h" + +/** this namespace holds Condition and its subclasses; these classes + * represent predicates about UniverseObjects used by, for instance, the + * Effect system. */ +namespace Condition { + +/** Matches the source object only. */ +struct FO_COMMON_API Source final : public Condition { + Source(); + + bool operator==(const Condition& rhs) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +} // namespace Condition + +#endif // _ConditionSource_h_ diff --git a/universe/Condition.cpp b/universe/Conditions.cpp similarity index 66% rename from universe/Condition.cpp rename to universe/Conditions.cpp index 24cc58af6d0..05f55315556 100644 --- a/universe/Condition.cpp +++ b/universe/Conditions.cpp @@ -1,4 +1,4 @@ -#include "Condition.h" +#include "Conditions.h" #include "../util/Logger.h" #include "../util/Random.h" @@ -7,108 +7,118 @@ #include "Pathfinder.h" #include "Universe.h" #include "Building.h" +#include "BuildingType.h" +#include "Fighter.h" #include "Fleet.h" #include "Ship.h" +#include "ShipDesign.h" +#include "ShipPart.h" +#include "ShipHull.h" #include "ObjectMap.h" #include "Planet.h" #include "System.h" #include "Species.h" #include "Special.h" #include "Meter.h" -#include "ValueRef.h" +#include "ValueRefs.h" #include "Enums.h" #include "../Empire/Empire.h" #include "../Empire/EmpireManager.h" #include "../Empire/Supply.h" +#include #include #include #include #include + using boost::io::str; -extern int g_indent; FO_COMMON_API extern const int INVALID_DESIGN_ID; bool UserStringExists(const std::string& str); namespace { - void AddAllObjectsSet(Condition::ObjectSet& condition_non_targets) { - condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingObjects().size()); - std::transform(Objects().ExistingObjects().begin(), Objects().ExistingObjects().end(), + const std::string EMPTY_STRING; + + DeclareThreadSafeLogger(conditions); + + void AddAllObjectsSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) { + condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingObjects().size()); + std::transform(objects.ExistingObjects().begin(), objects.ExistingObjects().end(), std::back_inserter(condition_non_targets), - boost::bind(&std::map>::value_type::second,_1)); + boost::bind(&std::map>::value_type::second, _1)); } - void AddBuildingSet(Condition::ObjectSet& condition_non_targets) { - condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingBuildings().size()); - std::transform(Objects().ExistingBuildings().begin(), Objects().ExistingBuildings().end(), + void AddBuildingSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) { + condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingBuildings().size()); + std::transform(objects.ExistingBuildings().begin(), objects.ExistingBuildings().end(), std::back_inserter(condition_non_targets), - boost::bind(&std::map>::value_type::second,_1)); + boost::bind(&std::map>::value_type::second, _1)); } - void AddFieldSet(Condition::ObjectSet& condition_non_targets) { - condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingFields().size()); - std::transform( Objects().ExistingFields().begin(), Objects().ExistingFields().end(), - std::back_inserter(condition_non_targets), - boost::bind(&std::map>::value_type::second,_1)); + void AddFieldSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) { + condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingFields().size()); + std::transform(objects.ExistingFields().begin(), objects.ExistingFields().end(), + std::back_inserter(condition_non_targets), + boost::bind(&std::map>::value_type::second, _1)); } - void AddFleetSet(Condition::ObjectSet& condition_non_targets) { - condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingFleets().size()); - std::transform(Objects().ExistingFleets().begin(), Objects().ExistingFleets().end(), + void AddFleetSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) { + condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingFleets().size()); + std::transform(objects.ExistingFleets().begin(), objects.ExistingFleets().end(), std::back_inserter(condition_non_targets), - boost::bind(&std::map>::value_type::second,_1)); + boost::bind(&std::map>::value_type::second,_1)); } - void AddPlanetSet(Condition::ObjectSet& condition_non_targets) { - condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingPlanets().size()); - std::transform(Objects().ExistingPlanets().begin(), Objects().ExistingPlanets().end(), + void AddPlanetSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) { + condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingPlanets().size()); + std::transform(objects.ExistingPlanets().begin(), objects.ExistingPlanets().end(), std::back_inserter(condition_non_targets), - boost::bind(&std::map>::value_type::second,_1)); + boost::bind(&std::map>::value_type::second,_1)); } - void AddPopCenterSet(Condition::ObjectSet& condition_non_targets) { - condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingPopCenters().size()); - std::transform(Objects().ExistingPopCenters().begin(), Objects().ExistingPopCenters().end(), + void AddPopCenterSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) { + condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingPopCenters().size()); + std::transform(objects.ExistingPopCenters().begin(), objects.ExistingPopCenters().end(), std::back_inserter(condition_non_targets), - boost::bind(&std::map>::value_type::second,_1)); + boost::bind(&std::map>::value_type::second,_1)); } - void AddResCenterSet(Condition::ObjectSet& condition_non_targets) { - condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingResourceCenters().size()); - std::transform(Objects().ExistingResourceCenters().begin(), Objects().ExistingResourceCenters().end(), + void AddResCenterSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) { + condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingResourceCenters().size()); + std::transform(objects.ExistingResourceCenters().begin(), objects.ExistingResourceCenters().end(), std::back_inserter(condition_non_targets), - boost::bind(&std::map>::value_type::second,_1)); + boost::bind(&std::map>::value_type::second,_1)); } - void AddShipSet(Condition::ObjectSet& condition_non_targets) { - condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingShips().size()); - std::transform(Objects().ExistingShips().begin(), Objects().ExistingShips().end(), + void AddShipSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) { + condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingShips().size()); + std::transform(objects.ExistingShips().begin(), objects.ExistingShips().end(), std::back_inserter(condition_non_targets), - boost::bind(&std::map>::value_type::second,_1)); + boost::bind(&std::map>::value_type::second,_1)); } - void AddSystemSet(Condition::ObjectSet& condition_non_targets) { - condition_non_targets.reserve(condition_non_targets.size() + Objects().ExistingSystems().size()); - std::transform( Objects().ExistingSystems().begin(), Objects().ExistingSystems().end(), - std::back_inserter(condition_non_targets), - boost::bind(&std::map>::value_type::second,_1)); + void AddSystemSet(const ObjectMap& objects, Condition::ObjectSet& condition_non_targets) { + condition_non_targets.reserve(condition_non_targets.size() + objects.ExistingSystems().size()); + std::transform(objects.ExistingSystems().begin(), objects.ExistingSystems().end(), + std::back_inserter(condition_non_targets), + boost::bind(&std::map>::value_type::second,_1)); } - /** Used by 4-parameter ConditionBase::Eval function, and some of its + /** Used by 4-parameter Condition::Eval function, and some of its * overrides, to scan through \a matches or \a non_matches set and apply * \a pred to each object, to test if it should remain in its current set * or be transferred from the \a search_domain specified set into the * other. */ - template + template void EvalImpl(Condition::ObjectSet& matches, Condition::ObjectSet& non_matches, Condition::SearchDomain search_domain, const Pred& pred) { - Condition::ObjectSet& from_set = search_domain == Condition::MATCHES ? matches : non_matches; - Condition::ObjectSet& to_set = search_domain == Condition::MATCHES ? non_matches : matches; - for (Condition::ObjectSet::iterator it = from_set.begin(); it != from_set.end(); ) { + auto& from_set = search_domain == Condition::MATCHES ? matches : non_matches; + auto& to_set = search_domain == Condition::MATCHES ? non_matches : matches; + for (auto it = from_set.begin(); it != from_set.end(); ) { bool match = pred(*it); if ((search_domain == Condition::MATCHES && !match) || (search_domain == Condition::NON_MATCHES && match)) @@ -122,13 +132,13 @@ namespace { } } - std::vector FlattenAndNestedConditions( - const std::vector& input_conditions) + std::vector FlattenAndNestedConditions( + const std::vector& input_conditions) { - std::vector retval; - for (Condition::ConditionBase* condition : input_conditions) { + std::vector retval; + for (Condition::Condition* condition : input_conditions) { if (Condition::And* and_condition = dynamic_cast(condition)) { - std::vector flattened_operands = + std::vector flattened_operands = FlattenAndNestedConditions(and_condition->Operands()); std::copy(flattened_operands.begin(), flattened_operands.end(), std::back_inserter(retval)); } else { @@ -139,13 +149,14 @@ namespace { return retval; } - std::map ConditionDescriptionAndTest(const std::vector& conditions, - const ScriptingContext& parent_context, - std::shared_ptr candidate_object/* = nullptr*/) + std::map ConditionDescriptionAndTest( + const std::vector& conditions, + const ScriptingContext& parent_context, + std::shared_ptr candidate_object/* = nullptr*/) { std::map retval; - std::vector flattened_conditions; + std::vector flattened_conditions; if (conditions.empty()) return retval; else if (conditions.size() > 1 || dynamic_cast(*conditions.begin())) @@ -155,7 +166,7 @@ namespace { else flattened_conditions = conditions; - for (Condition::ConditionBase* condition : flattened_conditions) { + for (Condition::Condition* condition : flattened_conditions) { retval[condition->Description()] = condition->Eval(parent_context, candidate_object); } return retval; @@ -163,18 +174,17 @@ namespace { } namespace Condition { -std::string ConditionFailedDescription(const std::vector& conditions, +std::string ConditionFailedDescription(const std::vector& conditions, std::shared_ptr candidate_object/* = nullptr*/, std::shared_ptr source_object/* = nullptr*/) { if (conditions.empty()) return UserString("NONE"); - ScriptingContext parent_context(source_object); std::string retval; // test candidate against all input conditions, and store descriptions of each - for (std::map::value_type& result : ConditionDescriptionAndTest(conditions, parent_context, candidate_object)) { + for (const auto& result : ConditionDescriptionAndTest(conditions, ScriptingContext(source_object), candidate_object)) { if (!result.second) retval += UserString("FAILED") + " " + result.first +"\n"; } @@ -185,19 +195,18 @@ std::string ConditionFailedDescription(const std::vector& condit return retval; } -std::string ConditionDescription(const std::vector& conditions, +std::string ConditionDescription(const std::vector& conditions, std::shared_ptr candidate_object/* = nullptr*/, std::shared_ptr source_object/* = nullptr*/) { if (conditions.empty()) return UserString("NONE"); - ScriptingContext parent_context(source_object); // test candidate against all input conditions, and store descriptions of each - std::map condition_description_and_test_results = - ConditionDescriptionAndTest(conditions, parent_context, candidate_object); + auto condition_description_and_test_results = + ConditionDescriptionAndTest(conditions, ScriptingContext(source_object), candidate_object); bool all_conditions_match_candidate = true, at_least_one_condition_matches_candidate = false; - for (std::map::value_type& result : condition_description_and_test_results) { + for (const auto& result : condition_description_and_test_results) { all_conditions_match_candidate = all_conditions_match_candidate && result.second; at_least_one_condition_matches_candidate = at_least_one_condition_matches_candidate || result.second; } @@ -213,7 +222,7 @@ std::string ConditionDescription(const std::vector& conditions, } // else just output single condition description and PASS/FAIL text - for (std::map::value_type& result : condition_description_and_test_results) { + for (const auto& result : condition_description_and_test_results) { retval += (result.second ? UserString("PASSED") : UserString("FAILED")); retval += " " + result.first + "\n"; } @@ -230,10 +239,10 @@ std::string ConditionDescription(const std::vector& conditions, } } /////////////////////////////////////////////////////////// -// ConditionBase // +// Condition // /////////////////////////////////////////////////////////// -struct ConditionBase::MatchHelper { - MatchHelper(const ConditionBase* this_, const ScriptingContext& parent_context) : +struct Condition::MatchHelper { + MatchHelper(const Condition* this_, const ScriptingContext& parent_context) : m_this(this_), m_parent_context(parent_context) {} @@ -241,14 +250,13 @@ struct ConditionBase::MatchHelper { bool operator()(std::shared_ptr candidate) const { return m_this->Match(ScriptingContext(m_parent_context, candidate)); } - const ConditionBase* m_this; - const ScriptingContext& m_parent_context; + const Condition* m_this; + const ScriptingContext& m_parent_context; }; -ConditionBase::~ConditionBase() -{} +Condition::~Condition() = default; -bool ConditionBase::operator==(const ConditionBase& rhs) const { +bool Condition::operator==(const Condition& rhs) const { if (this == &rhs) return true; @@ -258,13 +266,13 @@ bool ConditionBase::operator==(const ConditionBase& rhs) const { return true; } -void ConditionBase::Eval(const ScriptingContext& parent_context, - ObjectSet& matches, ObjectSet& non_matches, - SearchDomain search_domain/* = NON_MATCHES*/) const +void Condition::Eval(const ScriptingContext& parent_context, + ObjectSet& matches, ObjectSet& non_matches, + SearchDomain search_domain/* = NON_MATCHES*/) const { EvalImpl(matches, non_matches, search_domain, MatchHelper(this, parent_context)); } -void ConditionBase::Eval(const ScriptingContext& parent_context, - ObjectSet& matches) const +void Condition::Eval(const ScriptingContext& parent_context, + ObjectSet& matches) const { matches.clear(); ObjectSet condition_initial_candidates; @@ -276,8 +284,8 @@ void ConditionBase::Eval(const ScriptingContext& parent_context, Eval(parent_context, matches, condition_initial_candidates); } -bool ConditionBase::Eval(const ScriptingContext& parent_context, - std::shared_ptr candidate) const +bool Condition::Eval(const ScriptingContext& parent_context, + std::shared_ptr candidate) const { if (!candidate) return false; @@ -287,7 +295,7 @@ bool ConditionBase::Eval(const ScriptingContext& parent_context, return non_matches.empty(); // if candidate has been matched, non_matches will now be empty } -bool ConditionBase::Eval(std::shared_ptr candidate) const { +bool Condition::Eval(std::shared_ptr candidate) const { if (!candidate) return false; ObjectSet non_matches, matches; @@ -296,29 +304,42 @@ bool ConditionBase::Eval(std::shared_ptr candidate) const return non_matches.empty(); // if candidate has been matched, non_matches will now be empty } -void ConditionBase::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const -{ AddAllObjectsSet(condition_non_targets); } +void Condition::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const +{ AddAllObjectsSet(parent_context.ContextObjects(), condition_non_targets); } -std::string ConditionBase::Description(bool negated/* = false*/) const +std::string Condition::Description(bool negated/* = false*/) const { return ""; } -std::string ConditionBase::Dump() const +std::string Condition::Dump(unsigned short ntabs) const { return ""; } -bool ConditionBase::Match(const ScriptingContext& local_context) const +bool Condition::Match(const ScriptingContext& local_context) const { return false; } /////////////////////////////////////////////////////////// // Number // /////////////////////////////////////////////////////////// -Number::~Number() { - delete m_low; - delete m_high; - delete m_condition; -} - -bool Number::operator==(const ConditionBase& rhs) const { +Number::Number(std::unique_ptr>&& low, + std::unique_ptr>&& high, + std::unique_ptr&& condition) : + m_low(std::move(low)), + m_high(std::move(high)), + m_condition(std::move(condition)) +{ + auto operands = {m_low.get(), m_high.get()}; + m_root_candidate_invariant = + m_condition->RootCandidateInvariant() && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = + m_condition->TargetInvariant() && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = + m_condition->SourceInvariant() && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +bool Number::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -352,16 +373,14 @@ std::string Number::Description(bool negated/* = false*/) const { % m_condition->Description()); } -std::string Number::Dump() const { - std::string retval = DumpIndent() + "Number"; +std::string Number::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Number"; if (m_low) - retval += " low = " + m_low->Dump(); + retval += " low = " + m_low->Dump(ntabs); if (m_high) - retval += " high = " + m_high->Dump(); + retval += " high = " + m_high->Dump(ntabs); retval += " condition =\n"; - ++g_indent; - retval += m_condition->Dump(); - --g_indent; + retval += m_condition->Dump(ntabs+1); return retval; } @@ -370,23 +389,19 @@ void Number::Eval(const ScriptingContext& parent_context, SearchDomain search_domain/* = NON_MATCHES*/) const { // Number does not have a single valid local candidate to be matched, as it - // will match anything if the proper number of objects match the - // subcondition. So, the local context that is passed to the subcondition - // needs to have a null local candidate. - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); + // will match anything if the proper number of objects match the subcondition. if (!( - (!m_low || m_low->LocalCandidateInvariant()) + (!m_low || m_low->LocalCandidateInvariant()) && (!m_high || m_high->LocalCandidateInvariant()) ) ) { ErrorLogger() << "Condition::Number::Eval has local candidate-dependent ValueRefs, but no valid local candidate!"; } else if ( - !local_context.condition_root_candidate + !parent_context.condition_root_candidate && !( - (!m_low || m_low->RootCandidateInvariant()) + (!m_low || m_low->RootCandidateInvariant()) && (!m_high || m_high->RootCandidateInvariant()) ) ) @@ -394,57 +409,32 @@ void Number::Eval(const ScriptingContext& parent_context, ErrorLogger() << "Condition::Number::Eval has root candidate-dependent ValueRefs, but expects local candidate to be the root candidate, and has no valid local candidate!"; } - if (!local_context.condition_root_candidate && !this->RootCandidateInvariant()) { + if (!parent_context.condition_root_candidate && !this->RootCandidateInvariant()) { // no externally-defined root candidate, so each object matched must // separately act as a root candidate, and sub-condition must be re- // evaluated for each tested object and the number of objects matched // checked for each object being tested - ConditionBase::Eval(local_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } else { - // parameters for number of subcondition objects that needs to be matched - int low = (m_low ? m_low->Eval(local_context) : 0); - int high = (m_high ? m_high->Eval(local_context) : INT_MAX); - - // get set of all UniverseObjects that satisfy m_condition - ObjectSet condition_matches; - // can evaluate subcondition once for all objects being tested by this condition - m_condition->Eval(local_context, condition_matches); - // compare number of objects that satisfy m_condition to the acceptable range of such objects - int matched = condition_matches.size(); - bool in_range = (low <= matched && matched <= high); - - // transfer objects to or from candidate set, according to whether number of matches was within - // the requested range. + // Matching for this condition doesn't need to check each candidate object against + // the number of subcondition matches, so don't need to use EvalImpl + bool in_range = Match(parent_context); + + // transfer objects to or from candidate set, according to whether + // number of matches was within the requested range. if (search_domain == MATCHES && !in_range) { + // move all objects from matches to non_matches non_matches.insert(non_matches.end(), matches.begin(), matches.end()); matches.clear(); - } - if (search_domain == NON_MATCHES && in_range) { + } else if (search_domain == NON_MATCHES && in_range) { + // move all objects from non_matches to matches matches.insert(matches.end(), non_matches.begin(), non_matches.end()); non_matches.clear(); } } } -bool Number::RootCandidateInvariant() const { - return (!m_low || m_low->RootCandidateInvariant()) && - (!m_high || m_high->RootCandidateInvariant()) && - m_condition->RootCandidateInvariant(); -} - -bool Number::TargetInvariant() const { - return (!m_low || m_low->TargetInvariant()) && - (!m_high || m_high->TargetInvariant()) && - m_condition->TargetInvariant(); -} - -bool Number::SourceInvariant() const { - return (!m_low || m_low->SourceInvariant()) && - (!m_high || m_high->SourceInvariant()) && - m_condition->SourceInvariant(); -} - bool Number::Match(const ScriptingContext& local_context) const { // get acceptable range of subcondition matches for candidate int low = (m_low ? std::max(0, m_low->Eval(local_context)) : 0); @@ -484,12 +474,18 @@ unsigned int Number::GetCheckSum() const { /////////////////////////////////////////////////////////// // Turn // /////////////////////////////////////////////////////////// -Turn::~Turn() { - delete m_low; - delete m_high; +Turn::Turn(std::unique_ptr>&& low, + std::unique_ptr>&& high) : + m_low(std::move(low)), + m_high(std::move(high)) +{ + auto operands = {m_low.get(), m_high.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -bool Turn::operator==(const ConditionBase& rhs) const { +bool Turn::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -519,16 +515,12 @@ void Turn::Eval(const ScriptingContext& parent_context, (!m_high || m_high->LocalCandidateInvariant()) && (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { - // evaluate turn limits once, check range, and use result to match or - // reject all the search domain, since the current turn doesn't change - // from object to object, and neither do the range limits. - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - int low = (m_low ? std::max(BEFORE_FIRST_TURN, m_low->Eval(local_context)) : BEFORE_FIRST_TURN); - int high = (m_high ? std::min(m_high->Eval(local_context), IMPOSSIBLY_LARGE_TURN) : IMPOSSIBLY_LARGE_TURN); - int turn = CurrentTurn(); - bool match = (low <= turn && turn <= high); + // Matching for this condition doesn't need to check each candidate object against + // the turn number separately, so don't need to use EvalImpl + bool match = Match(parent_context); + // transfer objects to or from candidate set, according to whether the + // current turn was within the requested range. if (match && search_domain == NON_MATCHES) { // move all objects from non_matches to matches matches.insert(matches.end(), non_matches.begin(), non_matches.end()); @@ -540,19 +532,10 @@ void Turn::Eval(const ScriptingContext& parent_context, } } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool Turn::RootCandidateInvariant() const -{ return (!m_low || m_low->RootCandidateInvariant()) && (!m_high || m_high->RootCandidateInvariant()); } - -bool Turn::TargetInvariant() const -{ return (!m_low || m_low->TargetInvariant()) && (!m_high || m_high->TargetInvariant()); } - -bool Turn::SourceInvariant() const -{ return (!m_low || m_low->SourceInvariant()) && (!m_high || m_high->SourceInvariant()); } - std::string Turn::Description(bool negated/* = false*/) const { std::string low_str; if (m_low) @@ -595,19 +578,19 @@ std::string Turn::Description(bool negated/* = false*/) const { } } -std::string Turn::Dump() const { - std::string retval = DumpIndent() + "Turn"; +std::string Turn::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Turn"; if (m_low) - retval += " low = " + m_low->Dump(); + retval += " low = " + m_low->Dump(ntabs); if (m_high) - retval += " high = " + m_high->Dump(); + retval += " high = " + m_high->Dump(ntabs); retval += "\n"; return retval; } bool Turn::Match(const ScriptingContext& local_context) const { - int low = (m_low ? std::max(0, m_low->Eval(local_context)) : 0); - int high = (m_high ? std::min(m_high->Eval(local_context), IMPOSSIBLY_LARGE_TURN) : IMPOSSIBLY_LARGE_TURN); + int low = (m_low ? std::max(BEFORE_FIRST_TURN, m_low->Eval(local_context)) : BEFORE_FIRST_TURN); + int high = (m_high ? std::min(m_high->Eval(local_context), IMPOSSIBLY_LARGE_TURN) : IMPOSSIBLY_LARGE_TURN); int turn = CurrentTurn(); return (low <= turn && turn <= high); } @@ -633,13 +616,36 @@ unsigned int Turn::GetCheckSum() const { /////////////////////////////////////////////////////////// // SortedNumberOf // /////////////////////////////////////////////////////////// -SortedNumberOf::~SortedNumberOf() { - delete m_number; - delete m_sort_key; - delete m_condition; -} +SortedNumberOf::SortedNumberOf(std::unique_ptr>&& number, + std::unique_ptr&& condition) : + SortedNumberOf(std::move(number), nullptr, SORT_RANDOM, std::move(condition)) +{} -bool SortedNumberOf::operator==(const ConditionBase& rhs) const { +SortedNumberOf::SortedNumberOf(std::unique_ptr>&& number, + std::unique_ptr>&& sort_key_ref, + SortingMethod sorting_method, + std::unique_ptr&& condition) : + m_number(std::move(number)), + m_sort_key(std::move(sort_key_ref)), + m_sorting_method(sorting_method), + m_condition(std::move(condition)) +{ + m_root_candidate_invariant = + (!m_number || m_number->RootCandidateInvariant()) && + (!m_sort_key || m_sort_key->RootCandidateInvariant()) && + (!m_condition || m_condition->RootCandidateInvariant()); + m_target_invariant = + (!m_number || m_number->TargetInvariant()) && + (!m_sort_key || m_sort_key->TargetInvariant()) && + (!m_condition || m_condition->TargetInvariant()); + m_source_invariant = + (!m_number || m_number->SourceInvariant()) && + (!m_sort_key || m_sort_key->SourceInvariant()) && + (!m_condition || m_condition->SourceInvariant()); + +} + +bool SortedNumberOf::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -659,9 +665,8 @@ bool SortedNumberOf::operator==(const ConditionBase& rhs) const { namespace { /** Random number genrator function to use with random_shuffle */ - int CustomRandInt(int max_plus_one) { - return RandSmallInt(0, max_plus_one - 1); - } + int CustomRandInt(int max_plus_one) + { return RandSmallInt(0, max_plus_one - 1); } int (*CRI)(int) = CustomRandInt; /** Transfers the indicated \a number of objects, randomly selected from from_set to to_set */ @@ -683,7 +688,7 @@ namespace { // transfer objects that have been flagged int i = 0; - for (ObjectSet::iterator it = from_set.begin(); it != from_set.end(); ++i) { + for (auto it = from_set.begin(); it != from_set.end(); ++i) { if (transfer_flags[i]) { to_set.push_back(*it); *it = from_set.back(); @@ -699,7 +704,7 @@ namespace { * of \a sort_key evaluated on them, with the largest / smallest / most * common sort keys chosen, or a random selection chosen, depending on the * specified \a sorting_method */ - void TransferSortedObjects(unsigned int number, ValueRef::ValueRefBase* sort_key, + void TransferSortedObjects(unsigned int number, ValueRef::ValueRef* sort_key, const ScriptingContext& context, SortingMethod sorting_method, ObjectSet& from_set, ObjectSet& to_set) { @@ -717,9 +722,9 @@ namespace { // get sort key values for all objects in from_set, and sort by inserting into map std::multimap> sort_key_objects; - for (std::shared_ptr from : from_set) { + for (auto& from : from_set) { float sort_value = sort_key->Eval(ScriptingContext(context, from)); - sort_key_objects.insert(std::make_pair(sort_value, from)); + sort_key_objects.insert({sort_value, from}); } // how many objects to select? @@ -732,9 +737,9 @@ namespace { if (sorting_method == SORT_MIN) { // move (number) objects with smallest sort key (at start of map) // from the from_set into the to_set. - for (std::multimap>::value_type& entry : sort_key_objects) { - std::shared_ptr object_to_transfer = entry.second; - ObjectSet::iterator from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer); + for (const auto& entry : sort_key_objects) { + auto object_to_transfer = entry.second; + auto from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer); if (from_it != from_set.end()) { *from_it = from_set.back(); from_set.pop_back(); @@ -744,14 +749,15 @@ namespace { return; } } + } else if (sorting_method == SORT_MAX) { // move (number) objects with largest sort key (at end of map) // from the from_set into the to_set. - for (std::multimap>::reverse_iterator sorted_it = sort_key_objects.rbegin(); // would use const_reverse_iterator but this causes a compile error in some compilers + for (auto sorted_it = sort_key_objects.rbegin(); // would use const_reverse_iterator but this causes a compile error in some compilers sorted_it != sort_key_objects.rend(); ++sorted_it) { - std::shared_ptr object_to_transfer = sorted_it->second; - ObjectSet::iterator from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer); + auto object_to_transfer = sorted_it->second; + auto from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer); if (from_it != from_set.end()) { *from_it = from_set.back(); from_set.pop_back(); @@ -761,36 +767,36 @@ namespace { return; } } + } else if (sorting_method == SORT_MODE) { // compile histogram of of number of times each sort key occurs std::map histogram; - for (std::multimap>::value_type& entry : sort_key_objects) { + for (const auto& entry : sort_key_objects) { histogram[entry.first]++; } + // invert histogram to index by number of occurances std::multimap inv_histogram; - for (std::map::value_type& entry : histogram) { - inv_histogram.insert(std::make_pair(entry.second, entry.first)); - } + for (const auto& entry : histogram) + inv_histogram.insert({entry.second, entry.first}); + // reverse-loop through inverted histogram to find which sort keys // occurred most frequently, and transfer objects with those sort // keys from from_set to to_set. - for (std::multimap::reverse_iterator inv_hist_it = inv_histogram.rbegin(); // would use const_reverse_iterator but this causes a compile error in some compilers + for (auto inv_hist_it = inv_histogram.rbegin(); // would use const_reverse_iterator but this causes a compile error in some compilers inv_hist_it != inv_histogram.rend(); ++inv_hist_it) { float cur_sort_key = inv_hist_it->second; // get range of objects with the current sort key - std::pair>::const_iterator, - std::multimap>::const_iterator> key_range = - sort_key_objects.equal_range(cur_sort_key); + auto key_range = sort_key_objects.equal_range(cur_sort_key); // loop over range, selecting objects to transfer from from_set to to_set - for (std::multimap>::const_iterator sorted_it = key_range.first; + for (auto sorted_it = key_range.first; sorted_it != key_range.second; ++sorted_it) { - std::shared_ptr object_to_transfer = sorted_it->second; - ObjectSet::iterator from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer); + auto object_to_transfer = sorted_it->second; + auto from_it = std::find(from_set.begin(), from_set.end(), object_to_transfer); if (from_it != from_set.end()) { *from_it = from_set.back(); from_set.pop_back(); @@ -801,8 +807,9 @@ namespace { } } } + } else { - DebugLogger() << "TransferSortedObjects given unknown sort method"; + ErrorLogger() << "TransferSortedObjects given unknown sort method"; } } } @@ -828,7 +835,7 @@ void SortedNumberOf::Eval(const ScriptingContext& parent_context, // SortedNumberOf does not have a valid local candidate to be matched // before the subcondition is evaluated, so the local context that is - // passed to the subcondition needs to have a null local candidate. + // passed to the subcondition should have a null local candidate. std::shared_ptr no_object; ScriptingContext local_context(parent_context, no_object); @@ -864,15 +871,16 @@ void SortedNumberOf::Eval(const ScriptingContext& parent_context, // matches, or those left in matches while the rest are moved into non_matches ObjectSet matched_objects; matched_objects.reserve(number); - TransferSortedObjects(number, m_sort_key, local_context, m_sorting_method, all_subcondition_matches, matched_objects); + TransferSortedObjects(number, m_sort_key.get(), parent_context, m_sorting_method, all_subcondition_matches, matched_objects); // put objects back into matches and non_target sets as output... if (search_domain == NON_MATCHES) { // put matched objects that are in subcondition_matching_non_matches into matches - for (std::shared_ptr matched_object : matched_objects) { + for (auto& matched_object : matched_objects) { // is this matched object in subcondition_matching_non_matches? - ObjectSet::iterator smnt_it = std::find(subcondition_matching_non_matches.begin(), subcondition_matching_non_matches.end(), matched_object); + auto smnt_it = std::find(subcondition_matching_non_matches.begin(), + subcondition_matching_non_matches.end(), matched_object); if (smnt_it != subcondition_matching_non_matches.end()) { // yes; move object to matches *smnt_it = subcondition_matching_non_matches.back(); @@ -893,9 +901,10 @@ void SortedNumberOf::Eval(const ScriptingContext& parent_context, } else { /*(search_domain == MATCHES)*/ // put matched objecs that are in subcondition_matching_matches back into matches - for (std::shared_ptr matched_object : matched_objects) { + for (auto& matched_object : matched_objects) { // is this matched object in subcondition_matching_matches? - ObjectSet::iterator smt_it = std::find(subcondition_matching_matches.begin(), subcondition_matching_matches.end(), matched_object); + auto smt_it = std::find(subcondition_matching_matches.begin(), + subcondition_matching_matches.end(), matched_object); if (smt_it != subcondition_matching_matches.end()) { // yes; move back into matches *smt_it = subcondition_matching_matches.back(); @@ -916,21 +925,6 @@ void SortedNumberOf::Eval(const ScriptingContext& parent_context, } } -bool SortedNumberOf::RootCandidateInvariant() const -{ return ((!m_number || m_number->SourceInvariant()) && - (!m_sort_key || m_sort_key->SourceInvariant()) && - (!m_condition || m_condition->SourceInvariant())); } - -bool SortedNumberOf::TargetInvariant() const -{ return ((!m_number || m_number->SourceInvariant()) && - (!m_sort_key || m_sort_key->SourceInvariant()) && - (!m_condition || m_condition->SourceInvariant())); } - -bool SortedNumberOf::SourceInvariant() const -{ return ((!m_number || m_number->SourceInvariant()) && - (!m_sort_key || m_sort_key->SourceInvariant()) && - (!m_condition || m_condition->SourceInvariant())); } - std::string SortedNumberOf::Description(bool negated/* = false*/) const { std::string number_str = m_number->ConstantExpr() ? m_number->Dump() : m_number->Description(); @@ -944,7 +938,7 @@ std::string SortedNumberOf::Description(bool negated/* = false*/) const { } else { std::string sort_key_str = m_sort_key->ConstantExpr() ? m_sort_key->Dump() : m_sort_key->Description(); - std::string description_str, temp; + std::string description_str; switch (m_sorting_method) { case SORT_MAX: description_str = (!negated) @@ -974,8 +968,8 @@ std::string SortedNumberOf::Description(bool negated/* = false*/) const { } } -std::string SortedNumberOf::Dump() const { - std::string retval = DumpIndent(); +std::string SortedNumberOf::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs); switch (m_sorting_method) { case SORT_RANDOM: retval += "NumberOf"; break; @@ -989,15 +983,13 @@ std::string SortedNumberOf::Dump() const { retval += "??NumberOf??"; break; } - retval += " number = " + m_number->Dump(); + retval += " number = " + m_number->Dump(ntabs); if (m_sort_key) - retval += " sortby = " + m_sort_key->Dump(); + retval += " sortby = " + m_sort_key->Dump(ntabs); retval += " condition =\n"; - ++g_indent; - retval += m_condition->Dump(); - --g_indent; + retval += m_condition->Dump(ntabs+1); return retval; } @@ -1008,7 +1000,7 @@ void SortedNumberOf::GetDefaultInitialCandidateObjects(const ScriptingContext& p if (m_condition) { m_condition->GetDefaultInitialCandidateObjects(parent_context, condition_non_targets); } else { - ConditionBase::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets); + Condition::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets); } } @@ -1037,6 +1029,14 @@ unsigned int SortedNumberOf::GetCheckSum() const { /////////////////////////////////////////////////////////// // All // /////////////////////////////////////////////////////////// +All::All() : + Condition() +{ + m_root_candidate_invariant = true; + m_target_invariant = true; + m_source_invariant = true; +} + void All::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const @@ -1050,8 +1050,8 @@ void All::Eval(const ScriptingContext& parent_context, // match this condition, so should remain in matches set } -bool All::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +bool All::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string All::Description(bool negated/* = false*/) const { return (!negated) @@ -1059,8 +1059,8 @@ std::string All::Description(bool negated/* = false*/) const { : UserString("DESC_ALL_NOT"); } -std::string All::Dump() const -{ return DumpIndent() + "All\n"; } +std::string All::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "All\n"; } unsigned int All::GetCheckSum() const { unsigned int retval{0}; @@ -1072,8 +1072,16 @@ unsigned int All::GetCheckSum() const { } /////////////////////////////////////////////////////////// -// None // +// None // /////////////////////////////////////////////////////////// +None::None() : + Condition() +{ + m_root_candidate_invariant = true; + m_target_invariant = true; + m_source_invariant = true; +} + void None::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const @@ -1086,8 +1094,8 @@ void None::Eval(const ScriptingContext& parent_context, // if search domain is non_matches, no need to do anything since none of them match None. } -bool None::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +bool None::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string None::Description(bool negated/* = false*/) const { return (!negated) @@ -1095,8 +1103,8 @@ std::string None::Description(bool negated/* = false*/) const { : UserString("DESC_NONE_NOT"); } -std::string None::Dump() const -{ return DumpIndent() + "None\n"; } +std::string None::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "None\n"; } unsigned int None::GetCheckSum() const { unsigned int retval{0}; @@ -1110,17 +1118,25 @@ unsigned int None::GetCheckSum() const { /////////////////////////////////////////////////////////// // EmpireAffiliation // /////////////////////////////////////////////////////////// -EmpireAffiliation::EmpireAffiliation(ValueRef::ValueRefBase* empire_id) : - m_empire_id(empire_id), - m_affiliation(AFFIL_SELF) +EmpireAffiliation::EmpireAffiliation(std::unique_ptr>&& empire_id, + EmpireAffiliationType affiliation) : + m_empire_id(std::move(empire_id)), + m_affiliation(affiliation) +{ + m_root_candidate_invariant = !m_empire_id || m_empire_id->RootCandidateInvariant(); + m_target_invariant = !m_empire_id || m_empire_id->TargetInvariant(); + m_source_invariant = !m_empire_id || m_empire_id->SourceInvariant(); +} + +EmpireAffiliation::EmpireAffiliation(std::unique_ptr>&& empire_id) : + EmpireAffiliation(std::move(empire_id), AFFIL_SELF) {} -EmpireAffiliation::~EmpireAffiliation() { - if (m_empire_id) - delete m_empire_id; -} +EmpireAffiliation::EmpireAffiliation(EmpireAffiliationType affiliation) : + EmpireAffiliation(nullptr, affiliation) +{} -bool EmpireAffiliation::operator==(const ConditionBase& rhs) const { +bool EmpireAffiliation::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -1162,7 +1178,7 @@ namespace { break; } - case AFFIL_ALLY: { + case AFFIL_PEACE: { if (m_empire_id == ALL_EMPIRES) return false; if (m_empire_id == candidate->Owner()) @@ -1172,6 +1188,16 @@ namespace { break; } + case AFFIL_ALLY: { + if (m_empire_id == ALL_EMPIRES) + return false; + if (m_empire_id == candidate->Owner()) + return false; + DiplomaticStatus status = Empires().GetDiplomaticStatus(m_empire_id, candidate->Owner()); + return (status >= DIPLO_ALLIED); + break; + } + case AFFIL_ANY: return !candidate->Unowned(); break; @@ -1215,24 +1241,14 @@ void EmpireAffiliation::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate empire id once, and use to check all candidate objects - std::shared_ptr no_object; - int empire_id = m_empire_id ? m_empire_id->Eval(ScriptingContext(parent_context, no_object)) : ALL_EMPIRES; + int empire_id = m_empire_id ? m_empire_id->Eval(parent_context) : ALL_EMPIRES; EvalImpl(matches, non_matches, search_domain, EmpireAffiliationSimpleMatch(empire_id, m_affiliation)); } else { // re-evaluate empire id for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool EmpireAffiliation::RootCandidateInvariant() const -{ return m_empire_id ? m_empire_id->RootCandidateInvariant() : true; } - -bool EmpireAffiliation::TargetInvariant() const -{ return m_empire_id ? m_empire_id->TargetInvariant() : true; } - -bool EmpireAffiliation::SourceInvariant() const -{ return m_empire_id ? m_empire_id->SourceInvariant() : true; } - std::string EmpireAffiliation::Description(bool negated/* = false*/) const { std::string empire_str; if (m_empire_id) { @@ -1266,12 +1282,12 @@ std::string EmpireAffiliation::Description(bool negated/* = false*/) const { } } -std::string EmpireAffiliation::Dump() const { - std::string retval = DumpIndent(); +std::string EmpireAffiliation::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs); if (m_affiliation == AFFIL_SELF) { retval += "OwnedBy"; if (m_empire_id) - retval += " empire = " + m_empire_id->Dump(); + retval += " empire = " + m_empire_id->Dump(ntabs); } else if (m_affiliation == AFFIL_ANY) { retval += "OwnedBy affiliation = AnyEmpire"; @@ -1280,14 +1296,19 @@ std::string EmpireAffiliation::Dump() const { retval += "Unowned"; } else if (m_affiliation == AFFIL_ENEMY) { - retval += "OwnedBy affilition = EnemyOf"; + retval += "OwnedBy affiliation = EnemyOf"; + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs); + + } else if (m_affiliation == AFFIL_PEACE) { + retval += "OwnedBy affiliation = PeaceWith"; if (m_empire_id) - retval += " empire = " + m_empire_id->Dump(); + retval += " empire = " + m_empire_id->Dump(ntabs); } else if (m_affiliation == AFFIL_ALLY) { retval += "OwnedBy affiliation = AllyOf"; if (m_empire_id) - retval += " empire = " + m_empire_id->Dump(); + retval += " empire = " + m_empire_id->Dump(ntabs); } else if (m_affiliation == AFFIL_CAN_SEE) { retval += "OwnedBy affiliation = CanSee"; @@ -1304,7 +1325,7 @@ std::string EmpireAffiliation::Dump() const { } bool EmpireAffiliation::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "EmpireAffiliation::Match passed no candidate object"; return false; @@ -1334,8 +1355,16 @@ unsigned int EmpireAffiliation::GetCheckSum() const { /////////////////////////////////////////////////////////// // Source // /////////////////////////////////////////////////////////// -bool Source::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +Source::Source() : + Condition() +{ + m_root_candidate_invariant = true; + m_target_invariant = true; + m_source_invariant = false; +} + +bool Source::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string Source::Description(bool negated/* = false*/) const { return (!negated) @@ -1343,8 +1372,8 @@ std::string Source::Description(bool negated/* = false*/) const { : UserString("DESC_SOURCE_NOT"); } -std::string Source::Dump() const -{ return DumpIndent() + "Source\n"; } +std::string Source::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "Source\n"; } bool Source::Match(const ScriptingContext& local_context) const { if (!local_context.source) @@ -1357,7 +1386,6 @@ void Source::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_co { if (parent_context.source) condition_non_targets.push_back(parent_context.source); - //DebugLogger() << "ConditionBase::Eval will check at most one source object rather than " << Objects().NumObjects() << " total objects"; } unsigned int Source::GetCheckSum() const { @@ -1372,8 +1400,16 @@ unsigned int Source::GetCheckSum() const { /////////////////////////////////////////////////////////// // RootCandidate // /////////////////////////////////////////////////////////// -bool RootCandidate::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +RootCandidate::RootCandidate() : + Condition() +{ + m_root_candidate_invariant = false; + m_target_invariant = true; + m_source_invariant = true; +} + +bool RootCandidate::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string RootCandidate::Description(bool negated/* = false*/) const { return (!negated) @@ -1381,8 +1417,8 @@ std::string RootCandidate::Description(bool negated/* = false*/) const { : UserString("DESC_ROOT_CANDIDATE_NOT"); } -std::string RootCandidate::Dump() const -{ return DumpIndent() + "RootCandidate\n"; } +std::string RootCandidate::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "RootCandidate\n"; } bool RootCandidate::Match(const ScriptingContext& local_context) const { if (!local_context.condition_root_candidate) @@ -1390,6 +1426,13 @@ bool RootCandidate::Match(const ScriptingContext& local_context) const { return local_context.condition_root_candidate == local_context.condition_local_candidate; } +void RootCandidate::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const +{ + if (parent_context.condition_root_candidate) + condition_non_targets.push_back(parent_context.condition_root_candidate); +} + unsigned int RootCandidate::GetCheckSum() const { unsigned int retval{0}; @@ -1402,8 +1445,16 @@ unsigned int RootCandidate::GetCheckSum() const { /////////////////////////////////////////////////////////// // Target // /////////////////////////////////////////////////////////// -bool Target::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +Target::Target() : + Condition() +{ + m_root_candidate_invariant = true; + m_target_invariant = false; + m_source_invariant = true; +} + +bool Target::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string Target::Description(bool negated/* = false*/) const { return (!negated) @@ -1411,8 +1462,8 @@ std::string Target::Description(bool negated/* = false*/) const { : UserString("DESC_TARGET_NOT"); } -std::string Target::Dump() const -{ return DumpIndent() + "Target\n"; } +std::string Target::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "Target\n"; } bool Target::Match(const ScriptingContext& local_context) const { if (!local_context.effect_target) @@ -1439,12 +1490,21 @@ unsigned int Target::GetCheckSum() const { /////////////////////////////////////////////////////////// // Homeworld // /////////////////////////////////////////////////////////// -Homeworld::~Homeworld() { - for (ValueRef::ValueRefBase* name : m_names) - delete name; +Homeworld::Homeworld() : + Condition(), + m_names() +{} + +Homeworld::Homeworld(std::vector>>&& names) : + Condition(), + m_names(std::move(names)) +{ + m_root_candidate_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->SourceInvariant(); }); } -bool Homeworld::operator==(const ConditionBase& rhs) const { +bool Homeworld::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -1463,8 +1523,9 @@ bool Homeworld::operator==(const ConditionBase& rhs) const { namespace { struct HomeworldSimpleMatch { - HomeworldSimpleMatch(const std::vector& names) : - m_names(names) + HomeworldSimpleMatch(const std::vector& names, const ObjectMap& objects) : + m_names(names), + m_objects(objects) {} bool operator()(std::shared_ptr candidate) const { @@ -1472,11 +1533,10 @@ namespace { return false; // is it a planet or a building on a planet? - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); std::shared_ptr building; - if (!planet && (building = std::dynamic_pointer_cast(candidate))) { - planet = GetPlanet(building->PlanetID()); - } + if (!planet && (building = std::dynamic_pointer_cast(candidate))) + planet = m_objects.get(building->PlanetID()); if (!planet) return false; @@ -1485,10 +1545,10 @@ namespace { if (m_names.empty()) { // match homeworlds for any species - for (SpeciesManager::iterator species_it = manager.begin(); species_it != manager.end(); ++species_it) { - if (const ::Species* species = species_it->second) { - const std::set& homeworld_ids = species->Homeworlds(); - if (homeworld_ids.find(planet_id) != homeworld_ids.end()) + for (auto species_it = manager.begin(); species_it != manager.end(); ++species_it) { + if (const auto& species = species_it->second) { + const auto& homeworld_ids = species->Homeworlds(); + if (homeworld_ids.count(planet_id)) return true; } } @@ -1496,9 +1556,9 @@ namespace { } else { // match any of the species specified for (const std::string& name : m_names) { - if (const ::Species* species = GetSpecies(name)) { - const std::set& homeworld_ids = species->Homeworlds(); - if (homeworld_ids.find(planet_id) != homeworld_ids.end()) + if (auto species = GetSpecies(name)) { + const auto& homeworld_ids = species->Homeworlds(); + if (homeworld_ids.count(planet_id)) return true; } } @@ -1508,6 +1568,7 @@ namespace { } const std::vector& m_names; + const ObjectMap& m_objects; }; } @@ -1518,7 +1579,7 @@ void Homeworld::Eval(const ScriptingContext& parent_context, bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant(); if (simple_eval_safe) { // check each valueref for invariance to local candidate - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (!name->LocalCandidateInvariant()) { simple_eval_safe = false; break; @@ -1528,39 +1589,15 @@ void Homeworld::Eval(const ScriptingContext& parent_context, if (simple_eval_safe) { // evaluate names once, and use to check all candidate objects std::vector names; + names.reserve(m_names.size()); // get all names from valuerefs - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) names.push_back(name->Eval(parent_context)); - } - EvalImpl(matches, non_matches, search_domain, HomeworldSimpleMatch(names)); + EvalImpl(matches, non_matches, search_domain, HomeworldSimpleMatch(names, parent_context.ContextObjects())); } else { // re-evaluate allowed names for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); - } -} - -bool Homeworld::RootCandidateInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->RootCandidateInvariant()) - return false; - } - return true; -} - -bool Homeworld::TargetInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->TargetInvariant()) - return false; + Condition::Eval(parent_context, matches, non_matches, search_domain); } - return true; -} - -bool Homeworld::SourceInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->SourceInvariant()) - return false; - } - return true; } std::string Homeworld::Description(bool negated/* = false*/) const { @@ -1583,14 +1620,14 @@ std::string Homeworld::Description(bool negated/* = false*/) const { % values_str); } -std::string Homeworld::Dump() const { - std::string retval = DumpIndent() + "HomeWorld"; +std::string Homeworld::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "HomeWorld"; if (m_names.size() == 1) { - retval += " name = " + m_names[0]->Dump(); + retval += " name = " + m_names[0]->Dump(ntabs); } else if (!m_names.empty()) { retval += " name = [ "; - for (ValueRef::ValueRefBase* name : m_names) { - retval += name->Dump() + " "; + for (auto& name : m_names) { + retval += name->Dump(ntabs) + " "; } retval += "]"; } @@ -1598,18 +1635,17 @@ std::string Homeworld::Dump() const { } bool Homeworld::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Homeworld::Match passed no candidate object"; return false; } // is it a planet or a building on a planet? - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); std::shared_ptr building; - if (!planet && (building = std::dynamic_pointer_cast(candidate))) { - planet = GetPlanet(building->PlanetID()); - } + if (!planet && (building = std::dynamic_pointer_cast(candidate))) + planet = local_context.ContextObjects().get(building->PlanetID()); if (!planet) return false; @@ -1618,21 +1654,21 @@ bool Homeworld::Match(const ScriptingContext& local_context) const { if (m_names.empty()) { // match homeworlds for any species - for (const std::map::value_type& entry : manager) { - if (const ::Species* species = entry.second) { - const std::set& homeworld_ids = species->Homeworlds(); - if (homeworld_ids.find(planet_id) != homeworld_ids.end()) // is this planet the homeworld for this species? + for (const auto& entry : manager) { + if (const auto& species = entry.second) { + const auto& homeworld_ids = species->Homeworlds(); + if (homeworld_ids.count(planet_id)) return true; } } } else { // match any of the species specified - for (ValueRef::ValueRefBase* name : m_names) { - const std::string& species_name = name->Eval(local_context); - if (const ::Species* species = manager.GetSpecies(species_name)) { - const std::set& homeworld_ids = species->Homeworlds(); - if (homeworld_ids.find(planet_id) != homeworld_ids.end()) // is this planet the homeworld for this species? + for (auto& name : m_names) { + const auto& species_name = name->Eval(local_context); + if (const auto species = manager.GetSpecies(species_name)) { + const auto& homeworld_ids = species->Homeworlds(); + if (homeworld_ids.count(planet_id)) return true; } } @@ -1643,10 +1679,10 @@ bool Homeworld::Match(const ScriptingContext& local_context) const { void Homeworld::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const -{ AddPlanetSet(condition_non_targets); } +{ AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); } void Homeworld::SetTopLevelContent(const std::string& content_name) { - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (name) name->SetTopLevelContent(content_name); } @@ -1665,8 +1701,16 @@ unsigned int Homeworld::GetCheckSum() const { /////////////////////////////////////////////////////////// // Capital // /////////////////////////////////////////////////////////// -bool Capital::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +Capital::Capital() : + Condition() +{ + m_root_candidate_invariant = true; + m_target_invariant = true; + m_source_invariant = true; +} + +bool Capital::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string Capital::Description(bool negated/* = false*/) const { return (!negated) @@ -1674,11 +1718,11 @@ std::string Capital::Description(bool negated/* = false*/) const { : UserString("DESC_CAPITAL_NOT"); } -std::string Capital::Dump() const -{ return DumpIndent() + "Capital\n"; } +std::string Capital::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "Capital\n"; } bool Capital::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Capital::Match passed no candidate object"; return false; @@ -1687,7 +1731,7 @@ bool Capital::Match(const ScriptingContext& local_context) const { // check if any empire's capital's ID is that candidate object's id. // if it is, the candidate object is a capital. - for (const std::map::value_type& entry : Empires()) + for (const auto& entry : Empires()) if (entry.second->CapitalID() == candidate_id) return true; return false; @@ -1695,7 +1739,7 @@ bool Capital::Match(const ScriptingContext& local_context) const { void Capital::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const -{ AddPlanetSet(condition_non_targets); } +{ AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); } unsigned int Capital::GetCheckSum() const { unsigned int retval{0}; @@ -1709,8 +1753,16 @@ unsigned int Capital::GetCheckSum() const { /////////////////////////////////////////////////////////// // Monster // /////////////////////////////////////////////////////////// -bool Monster::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +Monster::Monster() : + Condition() +{ + m_root_candidate_invariant = true; + m_target_invariant = true; + m_source_invariant = true; +} + +bool Monster::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string Monster::Description(bool negated/* = false*/) const { return (!negated) @@ -1718,17 +1770,17 @@ std::string Monster::Description(bool negated/* = false*/) const { : UserString("DESC_MONSTER_NOT"); } -std::string Monster::Dump() const -{ return DumpIndent() + "Monster\n"; } +std::string Monster::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "Monster\n"; } bool Monster::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Monster::Match passed no candidate object"; return false; } - if (std::shared_ptr ship = std::dynamic_pointer_cast(candidate)) + if (auto ship = std::dynamic_pointer_cast(candidate)) if (ship->IsMonster()) return true; @@ -1737,7 +1789,7 @@ bool Monster::Match(const ScriptingContext& local_context) const { void Monster::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const -{ AddShipSet(condition_non_targets); } +{ AddShipSet(parent_context.ContextObjects(), condition_non_targets); } unsigned int Monster::GetCheckSum() const { unsigned int retval{0}; @@ -1751,8 +1803,16 @@ unsigned int Monster::GetCheckSum() const { /////////////////////////////////////////////////////////// // Armed // /////////////////////////////////////////////////////////// -bool Armed::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +Armed::Armed() : + Condition() +{ + m_root_candidate_invariant = true; + m_target_invariant = true; + m_source_invariant = true; +} + +bool Armed::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string Armed::Description(bool negated/* = false*/) const { return (!negated) @@ -1760,18 +1820,18 @@ std::string Armed::Description(bool negated/* = false*/) const { : UserString("DESC_ARMED_NOT"); } -std::string Armed::Dump() const -{ return DumpIndent() + "Armed\n"; } +std::string Armed::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "Armed\n"; } bool Armed::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Armed::Match passed no candidate object"; return false; } - if (std::shared_ptr ship = std::dynamic_pointer_cast(candidate)) - if (ship->IsArmed() || ship->HasFighters()) + if (auto ship = std::dynamic_pointer_cast(candidate)) + if (ship->IsArmed()) return true; return false; @@ -1789,10 +1849,20 @@ unsigned int Armed::GetCheckSum() const { /////////////////////////////////////////////////////////// // Type // /////////////////////////////////////////////////////////// -Type::~Type() -{ delete m_type; } +Type::Type(std::unique_ptr>&& type) : + Condition(), + m_type(std::move(type)) +{ + m_root_candidate_invariant = m_type->RootCandidateInvariant(); + m_target_invariant = m_type->TargetInvariant(); + m_source_invariant = m_type->SourceInvariant(); +} + +Type::Type(UniverseObjectType type) : + Type(std::move(std::make_unique>(type))) +{} -bool Type::operator==(const ConditionBase& rhs) const { +bool Type::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -1822,6 +1892,7 @@ namespace { case OBJ_PLANET: case OBJ_SYSTEM: case OBJ_FIELD: + case OBJ_FIGHTER: return candidate->ObjectType() == m_type; break; case OBJ_POP_CENTER: @@ -1852,19 +1923,10 @@ void Type::Eval(const ScriptingContext& parent_context, EvalImpl(matches, non_matches, search_domain, TypeSimpleMatch(type)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool Type::RootCandidateInvariant() const -{ return m_type->RootCandidateInvariant(); } - -bool Type::TargetInvariant() const -{ return m_type->TargetInvariant(); } - -bool Type::SourceInvariant() const -{ return m_type->SourceInvariant(); } - std::string Type::Description(bool negated/* = false*/) const { std::string value_str = m_type->ConstantExpr() ? UserString(boost::lexical_cast(m_type->Eval())) : @@ -1875,9 +1937,9 @@ std::string Type::Description(bool negated/* = false*/) const { % value_str); } -std::string Type::Dump() const { - std::string retval = DumpIndent(); - if (dynamic_cast*>(m_type)) { +std::string Type::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs); + if (dynamic_cast*>(m_type.get())) { switch (m_type->Eval()) { case OBJ_BUILDING: retval += "Building\n"; break; case OBJ_SHIP: retval += "Ship\n"; break; @@ -1887,16 +1949,17 @@ std::string Type::Dump() const { case OBJ_PROD_CENTER: retval += "ProductionCenter\n"; break; case OBJ_SYSTEM: retval += "System\n"; break; case OBJ_FIELD: retval += "Field\n"; break; + case OBJ_FIGHTER: retval += "Fighter\n"; break; default: retval += "?\n"; break; } } else { - retval += "ObjectType type = " + m_type->Dump() + "\n"; + retval += "ObjectType type = " + m_type->Dump(ntabs) + "\n"; } return retval; } bool Type::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Type::Match passed no candidate object"; return false; @@ -1915,42 +1978,43 @@ void Type::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_cont //std::vector> this_base; switch (m_type->Eval()) { case OBJ_BUILDING: - AddBuildingSet(condition_non_targets); + AddBuildingSet(parent_context.ContextObjects(), condition_non_targets); break; case OBJ_FIELD: - AddFieldSet(condition_non_targets); + AddFieldSet(parent_context.ContextObjects(), condition_non_targets); break; case OBJ_FLEET: - AddFleetSet(condition_non_targets); + AddFleetSet(parent_context.ContextObjects(), condition_non_targets); break; case OBJ_PLANET: - AddPlanetSet(condition_non_targets); + AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); break; case OBJ_POP_CENTER: - AddPopCenterSet(condition_non_targets); + AddPopCenterSet(parent_context.ContextObjects(), condition_non_targets); break; case OBJ_PROD_CENTER: - AddResCenterSet(condition_non_targets); + AddResCenterSet(parent_context.ContextObjects(), condition_non_targets); break; case OBJ_SHIP: - AddShipSet(condition_non_targets); + AddShipSet(parent_context.ContextObjects(), condition_non_targets); break; case OBJ_SYSTEM: - AddSystemSet(condition_non_targets); + AddSystemSet(parent_context.ContextObjects(), condition_non_targets); break; + case OBJ_FIGHTER: // shouldn't exist outside of combat as a separate object default: found_type = false; break; } } if (found_type) { - //if (int(condition_non_targets.size()) < Objects().NumObjects()) { + //if (int(condition_non_targets.size()) < parent_context.ContextObjects().size()) { // DebugLogger() << "Type::GetBaseNonMatches will provide " << condition_non_targets.size() // << " objects of type " << GetType()->Eval() << " rather than " - // << Objects().NumObjects() << " total objects"; + // << parent_context.ContextObjects().size() << " total objects"; //} } else { - ConditionBase::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets); + Condition::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets); } } @@ -1972,13 +2036,16 @@ unsigned int Type::GetCheckSum() const { /////////////////////////////////////////////////////////// // Building // /////////////////////////////////////////////////////////// -Building::~Building() { - for (ValueRef::ValueRefBase* name : m_names) { - delete name; - } +Building::Building(std::vector>>&& names) : + Condition(), + m_names(std::move(names)) +{ + m_root_candidate_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->SourceInvariant(); }); } -bool Building::operator==(const ConditionBase& rhs) const { +bool Building::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -2006,7 +2073,7 @@ namespace { return false; // is it a building? - std::shared_ptr building = std::dynamic_pointer_cast(candidate); + auto building = std::dynamic_pointer_cast(candidate); if (!building) return false; @@ -2015,7 +2082,7 @@ namespace { return true; // is it one of the specified building types? - return std::find(m_names.begin(), m_names.end(), building->BuildingTypeName()) != m_names.end(); + return std::count(m_names.begin(), m_names.end(), building->BuildingTypeName()); } const std::vector& m_names; @@ -2029,7 +2096,7 @@ void Building::Eval(const ScriptingContext& parent_context, bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant(); if (simple_eval_safe) { // check each valueref for invariance to local candidate - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (!name->LocalCandidateInvariant()) { simple_eval_safe = false; break; @@ -2040,38 +2107,14 @@ void Building::Eval(const ScriptingContext& parent_context, // evaluate names once, and use to check all candidate objects std::vector names; // get all names from valuerefs - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { names.push_back(name->Eval(parent_context)); } EvalImpl(matches, non_matches, search_domain, BuildingSimpleMatch(names)); } else { // re-evaluate allowed building types range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); - } -} - -bool Building::RootCandidateInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->RootCandidateInvariant()) - return false; - } - return true; -} - -bool Building::TargetInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->TargetInvariant()) - return false; - } - return true; -} - -bool Building::SourceInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->SourceInvariant()) - return false; + Condition::Eval(parent_context, matches, non_matches, search_domain); } - return true; } std::string Building::Description(bool negated/* = false*/) const { @@ -2094,14 +2137,14 @@ std::string Building::Description(bool negated/* = false*/) const { % values_str); } -std::string Building::Dump() const { - std::string retval = DumpIndent() + "Building name = "; +std::string Building::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Building name = "; if (m_names.size() == 1) { - retval += m_names[0]->Dump() + "\n"; + retval += m_names[0]->Dump(ntabs) + "\n"; } else { retval += "[ "; - for (ValueRef::ValueRefBase* name : m_names) { - retval += name->Dump() + " "; + for (auto& name : m_names) { + retval += name->Dump(ntabs) + " "; } retval += "]\n"; } @@ -2110,24 +2153,24 @@ std::string Building::Dump() const { void Building::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const -{ AddBuildingSet(condition_non_targets); } +{ AddBuildingSet(parent_context.ContextObjects(), condition_non_targets); } bool Building::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Building::Match passed no candidate object"; return false; } // is it a building? - std::shared_ptr building = std::dynamic_pointer_cast(candidate); + auto building = std::dynamic_pointer_cast(candidate); if (building) { // match any building type? if (m_names.empty()) return true; // match one of the specified building types - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (name->Eval(local_context) == building->BuildingTypeName()) return true; } @@ -2137,7 +2180,7 @@ bool Building::Match(const ScriptingContext& local_context) const { } void Building::SetTopLevelContent(const std::string& content_name) { - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (name) name->SetTopLevelContent(content_name); } @@ -2156,24 +2199,59 @@ unsigned int Building::GetCheckSum() const { /////////////////////////////////////////////////////////// // HasSpecial // /////////////////////////////////////////////////////////// +HasSpecial::HasSpecial() : + HasSpecial(nullptr, std::unique_ptr>{}, std::unique_ptr>{}) +{} + HasSpecial::HasSpecial(const std::string& name) : - ConditionBase(), - m_name(new ValueRef::Constant(name)), - m_capacity_low(nullptr), - m_capacity_high(nullptr), - m_since_turn_low(nullptr), - m_since_turn_high(nullptr) + HasSpecial(std::move(std::make_unique>(name)), std::unique_ptr>{}, std::unique_ptr>{}) {} -HasSpecial::~HasSpecial() { - delete m_name; - delete m_capacity_low; - delete m_capacity_high; - delete m_since_turn_low; - delete m_since_turn_high; -} +HasSpecial::HasSpecial(std::unique_ptr>&& name) : + HasSpecial(std::move(name), std::unique_ptr>{}, std::unique_ptr>{}) +{} -bool HasSpecial::operator==(const ConditionBase& rhs) const { +HasSpecial::HasSpecial(std::unique_ptr>&& name, + std::unique_ptr>&& since_turn_low, + std::unique_ptr>&& since_turn_high) : + Condition(), + m_name(std::move(name)), + m_since_turn_low(std::move(since_turn_low)), + m_since_turn_high(std::move(since_turn_high)) +{ + auto operands = {m_since_turn_low.get(), m_since_turn_high.get()}; + m_root_candidate_invariant = + (!m_name || m_name->RootCandidateInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = + (!m_name || m_name->TargetInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = + (!m_name || m_name->SourceInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +HasSpecial::HasSpecial(std::unique_ptr>&& name, + std::unique_ptr>&& capacity_low, + std::unique_ptr>&& capacity_high) : + Condition(), + m_name(std::move(name)), + m_capacity_low(std::move(capacity_low)), + m_capacity_high(std::move(capacity_high)) +{ + auto operands = {m_capacity_low.get(), m_capacity_high.get()}; + m_root_candidate_invariant = + (!m_name || m_name->RootCandidateInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = + (!m_name || m_name->TargetInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = + (!m_name || m_name->SourceInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +bool HasSpecial::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -2207,7 +2285,7 @@ namespace { if (m_name.empty()) return !candidate->Specials().empty(); - std::map>::const_iterator it = candidate->Specials().find(m_name); + auto it = candidate->Specials().find(m_name); if (it == candidate->Specials().end()) return false; @@ -2238,42 +2316,19 @@ void HasSpecial::Eval(const ScriptingContext& parent_context, (!m_since_turn_high || m_since_turn_high->LocalCandidateInvariant()) && (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { - // evaluate turn limits once, pass to simple match for all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - std::string name = (m_name ? m_name->Eval(local_context) : ""); - float low_cap = (m_capacity_low ? m_capacity_low->Eval(local_context) : -FLT_MAX); - float high_cap = (m_capacity_high ? m_capacity_high->Eval(local_context) : FLT_MAX); - int low_turn = (m_since_turn_low ? m_since_turn_low->Eval(local_context) : BEFORE_FIRST_TURN); - int high_turn = (m_since_turn_high ? m_since_turn_high->Eval(local_context) : IMPOSSIBLY_LARGE_TURN); + // evaluate turn limits and capacities once, pass to simple match for all candidates + std::string name = (m_name ? m_name->Eval(parent_context) : ""); + float low_cap = (m_capacity_low ? m_capacity_low->Eval(parent_context) : -FLT_MAX); + float high_cap = (m_capacity_high ? m_capacity_high->Eval(parent_context) : FLT_MAX); + int low_turn = (m_since_turn_low ? m_since_turn_low->Eval(parent_context) : BEFORE_FIRST_TURN); + int high_turn = (m_since_turn_high ? m_since_turn_high->Eval(parent_context) : IMPOSSIBLY_LARGE_TURN); EvalImpl(matches, non_matches, search_domain, HasSpecialSimpleMatch(name, low_cap, high_cap, low_turn, high_turn)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool HasSpecial::RootCandidateInvariant() const -{ return ((!m_name || m_name->RootCandidateInvariant()) && - (!m_capacity_low || m_capacity_low->RootCandidateInvariant()) && - (!m_capacity_high || m_capacity_high->RootCandidateInvariant()) && - (!m_since_turn_low || m_since_turn_low->RootCandidateInvariant()) && - (!m_since_turn_high || m_since_turn_high->RootCandidateInvariant())); } - -bool HasSpecial::TargetInvariant() const -{ return ((!m_name || m_name->TargetInvariant()) && - (!m_capacity_low || m_capacity_low->TargetInvariant()) && - (!m_capacity_high || m_capacity_high->TargetInvariant()) && - (!m_since_turn_low || m_since_turn_low->TargetInvariant()) && - (!m_since_turn_high || m_since_turn_high->TargetInvariant())); } - -bool HasSpecial::SourceInvariant() const -{ return ((!m_name || m_name->SourceInvariant()) && - (!m_capacity_low || m_capacity_low->SourceInvariant()) && - (!m_capacity_high || m_capacity_high->SourceInvariant()) && - (!m_since_turn_low || m_since_turn_low->SourceInvariant()) && - (!m_since_turn_high || m_since_turn_high->SourceInvariant())); } - std::string HasSpecial::Description(bool negated/* = false*/) const { std::string name_str; if (m_name) { @@ -2324,26 +2379,26 @@ std::string HasSpecial::Description(bool negated/* = false*/) const { % name_str); } -std::string HasSpecial::Dump() const { - std::string name_str = (m_name ? m_name->Dump() : ""); +std::string HasSpecial::Dump(unsigned short ntabs) const { + std::string name_str = (m_name ? m_name->Dump(ntabs) : ""); if (m_since_turn_low || m_since_turn_high) { - std::string low_dump = (m_since_turn_low ? m_since_turn_low->Dump() : std::to_string(BEFORE_FIRST_TURN)); - std::string high_dump = (m_since_turn_high ? m_since_turn_high->Dump() : std::to_string(IMPOSSIBLY_LARGE_TURN)); - return DumpIndent() + "HasSpecialSinceTurn name = \"" + name_str + "\" low = " + low_dump + " high = " + high_dump; + std::string low_dump = (m_since_turn_low ? m_since_turn_low->Dump(ntabs) : std::to_string(BEFORE_FIRST_TURN)); + std::string high_dump = (m_since_turn_high ? m_since_turn_high->Dump(ntabs) : std::to_string(IMPOSSIBLY_LARGE_TURN)); + return DumpIndent(ntabs) + "HasSpecialSinceTurn name = \"" + name_str + "\" low = " + low_dump + " high = " + high_dump; } if (m_capacity_low || m_capacity_high) { - std::string low_dump = (m_capacity_low ? m_capacity_low->Dump() : std::to_string(-FLT_MAX)); - std::string high_dump = (m_capacity_high ? m_capacity_high->Dump() : std::to_string(FLT_MAX)); - return DumpIndent() + "HasSpecialCapacity name = \"" + name_str + "\" low = " + low_dump + " high = " + high_dump; + std::string low_dump = (m_capacity_low ? m_capacity_low->Dump(ntabs) : std::to_string(-FLT_MAX)); + std::string high_dump = (m_capacity_high ? m_capacity_high->Dump(ntabs) : std::to_string(FLT_MAX)); + return DumpIndent(ntabs) + "HasSpecialCapacity name = \"" + name_str + "\" low = " + low_dump + " high = " + high_dump; } - return DumpIndent() + "HasSpecial name = \"" + name_str + "\"\n"; + return DumpIndent(ntabs) + "HasSpecial name = \"" + name_str + "\"\n"; } bool HasSpecial::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "HasSpecial::Match passed no candidate object"; return false; @@ -2387,15 +2442,24 @@ unsigned int HasSpecial::GetCheckSum() const { /////////////////////////////////////////////////////////// // HasTag // /////////////////////////////////////////////////////////// +HasTag::HasTag() : + HasTag(std::unique_ptr>{}) +{} + HasTag::HasTag(const std::string& name) : - ConditionBase(), - m_name(new ValueRef::Constant(name)) + HasTag(std::move(std::make_unique>(name))) {} -HasTag::~HasTag() -{ delete m_name; } +HasTag::HasTag(std::unique_ptr>&& name) : + Condition(), + m_name(std::move(name)) +{ + m_root_candidate_invariant = !m_name || m_name->RootCandidateInvariant(); + m_target_invariant = !m_name || m_name->TargetInvariant(); + m_source_invariant = !m_name || m_name->SourceInvariant(); +} -bool HasTag::operator==(const ConditionBase& rhs) const { +bool HasTag::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -2443,29 +2507,18 @@ void HasTag::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant()); if (simple_eval_safe) { // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); if (!m_name) { EvalImpl(matches, non_matches, search_domain, HasTagSimpleMatch()); } else { - std::string name = boost::to_upper_copy(m_name->Eval(local_context)); + std::string name = boost::to_upper_copy(m_name->Eval(parent_context)); EvalImpl(matches, non_matches, search_domain, HasTagSimpleMatch(name)); } } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool HasTag::RootCandidateInvariant() const -{ return !m_name || m_name->RootCandidateInvariant(); } - -bool HasTag::TargetInvariant() const -{ return !m_name || m_name->TargetInvariant(); } - -bool HasTag::SourceInvariant() const -{ return !m_name || m_name->SourceInvariant(); } - std::string HasTag::Description(bool negated/* = false*/) const { std::string name_str; if (m_name) { @@ -2479,16 +2532,16 @@ std::string HasTag::Description(bool negated/* = false*/) const { % name_str); } -std::string HasTag::Dump() const { - std::string retval = DumpIndent() + "HasTag"; +std::string HasTag::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "HasTag"; if (m_name) - retval += " name = " + m_name->Dump(); + retval += " name = " + m_name->Dump(ntabs); retval += "\n"; return retval; } bool HasTag::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "HasTag::Match passed no candidate object"; return false; @@ -2519,12 +2572,19 @@ unsigned int HasTag::GetCheckSum() const { /////////////////////////////////////////////////////////// // CreatedOnTurn // /////////////////////////////////////////////////////////// -CreatedOnTurn::~CreatedOnTurn() { - delete m_low; - delete m_high; +CreatedOnTurn::CreatedOnTurn(std::unique_ptr>&& low, + std::unique_ptr>&& high) : + Condition(), + m_low(std::move(low)), + m_high(std::move(high)) +{ + auto operands = {m_low.get(), m_high.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -bool CreatedOnTurn::operator==(const ConditionBase& rhs) const { +bool CreatedOnTurn::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -2565,26 +2625,15 @@ void CreatedOnTurn::Eval(const ScriptingContext& parent_context, (!m_high || m_high->LocalCandidateInvariant()) && (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - int low = (m_low ? m_low->Eval(local_context) : BEFORE_FIRST_TURN); - int high = (m_high ? m_high->Eval(local_context) : IMPOSSIBLY_LARGE_TURN); + int low = (m_low ? m_low->Eval(parent_context) : BEFORE_FIRST_TURN); + int high = (m_high ? m_high->Eval(parent_context) : IMPOSSIBLY_LARGE_TURN); EvalImpl(matches, non_matches, search_domain, CreatedOnTurnSimpleMatch(low, high)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool CreatedOnTurn::RootCandidateInvariant() const -{ return ((!m_low || m_low->RootCandidateInvariant()) && (!m_high || m_high->RootCandidateInvariant())); } - -bool CreatedOnTurn::TargetInvariant() const -{ return ((!m_low || m_low->TargetInvariant()) && (!m_high || m_high->TargetInvariant())); } - -bool CreatedOnTurn::SourceInvariant() const -{ return ((!m_low || m_low->SourceInvariant()) && (!m_high || m_high->SourceInvariant())); } - std::string CreatedOnTurn::Description(bool negated/* = false*/) const { std::string low_str = (m_low ? (m_low->ConstantExpr() ? std::to_string(m_low->Eval()) : @@ -2601,18 +2650,18 @@ std::string CreatedOnTurn::Description(bool negated/* = false*/) const { % high_str); } -std::string CreatedOnTurn::Dump() const { - std::string retval = DumpIndent() + "CreatedOnTurn"; +std::string CreatedOnTurn::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "CreatedOnTurn"; if (m_low) - retval += " low = " + m_low->Dump(); + retval += " low = " + m_low->Dump(ntabs); if (m_high) - retval += " high = " + m_high->Dump(); + retval += " high = " + m_high->Dump(ntabs); retval += "\n"; return retval; } bool CreatedOnTurn::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "CreatedOnTurn::Match passed no candidate object"; return false; @@ -2643,10 +2692,16 @@ unsigned int CreatedOnTurn::GetCheckSum() const { /////////////////////////////////////////////////////////// // Contains // /////////////////////////////////////////////////////////// -Contains::~Contains() -{ delete m_condition; } +Contains::Contains(std::unique_ptr&& condition) : + Condition(), + m_condition(std::move(condition)) +{ + m_root_candidate_invariant = m_condition->RootCandidateInvariant(); + m_target_invariant = m_condition->TargetInvariant(); + m_source_invariant = m_condition->SourceInvariant(); +} -bool Contains::operator==(const ConditionBase& rhs) const { +bool Contains::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -2674,7 +2729,7 @@ namespace { // for each candidate. m_subcondition_matches_ids.reserve(subcondition_matches.size()); // gather the ids - for (std::shared_ptr obj : subcondition_matches) { + for (auto& obj : subcondition_matches) { if (obj) m_subcondition_matches_ids.push_back(obj->ID()); } @@ -2687,7 +2742,7 @@ namespace { return false; bool match = false; - const std::set& candidate_elements = candidate->ContainedObjectIDs(); // guaranteed O(1) + const auto& candidate_elements = candidate->ContainedObjectIDs(); // guaranteed O(1) // We need to test whether candidate_elements and m_subcondition_matches_ids have a common element. // We choose the strategy that is more efficient by comparing the sizes of both sets. @@ -2695,8 +2750,8 @@ namespace { // candidate_elements is smaller, so we iterate it and look up each candidate element in m_subcondition_matches_ids for (int id : candidate_elements) { // std::lower_bound requires m_subcondition_matches_ids to be sorted - std::vector::const_iterator matching_it = std::lower_bound(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end(), id); - + auto matching_it = std::lower_bound(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end(), id); + if (matching_it != m_subcondition_matches_ids.end() && *matching_it == id) { match = true; break; @@ -2730,7 +2785,7 @@ void Contains::Eval(const ScriptingContext& parent_context, search_domain_size < 2; if (!simple_eval_safe) { // re-evaluate contained objects for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); return; } @@ -2745,13 +2800,13 @@ void Contains::Eval(const ScriptingContext& parent_context, ScriptingContext local_context(parent_context, search_domain == MATCHES ? *matches.begin() : *non_matches.begin()); // initialize subcondition candidates from local candidate's contents - const ObjectMap& objects = Objects(); - ObjectSet subcondition_matches = objects.FindObjects(local_context.condition_local_candidate->ContainedObjectIDs()); + const ObjectMap& objects = parent_context.ContextObjects(); + ObjectSet subcondition_matches = objects.find(local_context.condition_local_candidate->ContainedObjectIDs()); // apply subcondition to candidates if (!subcondition_matches.empty()) { ObjectSet dummy; - m_condition->Eval(local_context, subcondition_matches, dummy, Condition::MATCHES); + m_condition->Eval(local_context, subcondition_matches, dummy, MATCHES); } // move single local candidate as appropriate... @@ -2778,15 +2833,6 @@ void Contains::Eval(const ScriptingContext& parent_context, } } -bool Contains::RootCandidateInvariant() const -{ return m_condition->RootCandidateInvariant(); } - -bool Contains::TargetInvariant() const -{ return m_condition->TargetInvariant(); } - -bool Contains::SourceInvariant() const -{ return m_condition->SourceInvariant(); } - std::string Contains::Description(bool negated/* = false*/) const { return str(FlexibleFormat((!negated) ? UserString("DESC_CONTAINS") @@ -2794,11 +2840,9 @@ std::string Contains::Description(bool negated/* = false*/) const { % m_condition->Description()); } -std::string Contains::Dump() const { - std::string retval = DumpIndent() + "Contains condition =\n"; - ++g_indent; - retval += m_condition->Dump(); - --g_indent; +std::string Contains::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Contains condition =\n"; + retval += m_condition->Dump(ntabs+1); return retval; } @@ -2806,13 +2850,13 @@ void Contains::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_ ObjectSet& condition_non_targets) const { // objects that can contain other objects: systems, fleets, planets - AddSystemSet(condition_non_targets); - AddFleetSet(condition_non_targets); - AddPlanetSet(condition_non_targets); + AddSystemSet(parent_context.ContextObjects(), condition_non_targets); + AddFleetSet(parent_context.ContextObjects(), condition_non_targets); + AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); } bool Contains::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Contains::Match passed no candidate object"; return false; @@ -2823,7 +2867,7 @@ bool Contains::Match(const ScriptingContext& local_context) const { m_condition->Eval(local_context, subcondition_matches); // does candidate object contain any subcondition matches? - for (std::shared_ptr obj : subcondition_matches) + for (auto& obj : subcondition_matches) if (candidate->Contains(obj->ID())) return true; @@ -2848,10 +2892,16 @@ unsigned int Contains::GetCheckSum() const { /////////////////////////////////////////////////////////// // ContainedBy // /////////////////////////////////////////////////////////// -ContainedBy::~ContainedBy() -{ delete m_condition; } +ContainedBy::ContainedBy(std::unique_ptr&& condition) : + Condition(), + m_condition(std::move(condition)) +{ + m_root_candidate_invariant = m_condition->RootCandidateInvariant(); + m_target_invariant = m_condition->TargetInvariant(); + m_source_invariant = m_condition->SourceInvariant(); +} -bool ContainedBy::operator==(const ConditionBase& rhs) const { +bool ContainedBy::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -2879,7 +2929,7 @@ namespace { // executed for each candidate. m_subcondition_matches_ids.reserve(subcondition_matches.size()); // gather the ids - for (std::shared_ptr obj : subcondition_matches) { + for (auto& obj : subcondition_matches) { if (obj) { m_subcondition_matches_ids.push_back(obj->ID()); } } @@ -2907,8 +2957,8 @@ namespace { // candidate_containers is smaller, so we iterate it and look up each candidate container in m_subcondition_matches_ids for (int id : candidate_containers) { // std::lower_bound requires m_subcondition_matches_ids to be sorted - std::vector::const_iterator matching_it = std::lower_bound(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end(), id); - + auto matching_it = std::lower_bound(m_subcondition_matches_ids.begin(), m_subcondition_matches_ids.end(), id); + if (matching_it != m_subcondition_matches_ids.end() && *matching_it == id) { match = true; break; @@ -2943,7 +2993,7 @@ void ContainedBy::Eval(const ScriptingContext& parent_context, if (!simple_eval_safe) { // re-evaluate container objects for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); return; } @@ -2958,19 +3008,19 @@ void ContainedBy::Eval(const ScriptingContext& parent_context, ScriptingContext local_context(parent_context, search_domain == MATCHES ? *matches.begin() : *non_matches.begin()); // initialize subcondition candidates from local candidate's containers - const ObjectMap& objects = Objects(); + const ObjectMap& objects = parent_context.ContextObjects(); std::set container_object_ids; if (local_context.condition_local_candidate->ContainerObjectID() != INVALID_OBJECT_ID) container_object_ids.insert(local_context.condition_local_candidate->ContainerObjectID()); if (local_context.condition_local_candidate->SystemID() != INVALID_OBJECT_ID) container_object_ids.insert(local_context.condition_local_candidate->SystemID()); - ObjectSet subcondition_matches = objects.FindObjects(container_object_ids); + ObjectSet subcondition_matches = objects.find(container_object_ids); // apply subcondition to candidates if (!subcondition_matches.empty()) { ObjectSet dummy; - m_condition->Eval(local_context, subcondition_matches, dummy, Condition::MATCHES); + m_condition->Eval(local_context, subcondition_matches, dummy, MATCHES); } // move single local candidate as appropriate... @@ -2997,15 +3047,6 @@ void ContainedBy::Eval(const ScriptingContext& parent_context, } } -bool ContainedBy::RootCandidateInvariant() const -{ return m_condition->RootCandidateInvariant(); } - -bool ContainedBy::TargetInvariant() const -{ return m_condition->TargetInvariant(); } - -bool ContainedBy::SourceInvariant() const -{ return m_condition->SourceInvariant(); } - std::string ContainedBy::Description(bool negated/* = false*/) const { return str(FlexibleFormat((!negated) ? UserString("DESC_CONTAINED_BY") @@ -3013,11 +3054,9 @@ std::string ContainedBy::Description(bool negated/* = false*/) const { % m_condition->Description()); } -std::string ContainedBy::Dump() const { - std::string retval = DumpIndent() + "ContainedBy condition =\n"; - ++g_indent; - retval += m_condition->Dump(); - --g_indent; +std::string ContainedBy::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "ContainedBy condition =\n"; + retval += m_condition->Dump(ntabs+1); return retval; } @@ -3025,14 +3064,14 @@ void ContainedBy::GetDefaultInitialCandidateObjects(const ScriptingContext& pare ObjectSet& condition_non_targets) const { // objects that can be contained by other objects: fleets, planets, ships, buildings - AddFleetSet(condition_non_targets); - AddPlanetSet(condition_non_targets); - AddShipSet(condition_non_targets); - AddBuildingSet(condition_non_targets); + AddFleetSet(parent_context.ContextObjects(), condition_non_targets); + AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); + AddShipSet(parent_context.ContextObjects(), condition_non_targets); + AddBuildingSet(parent_context.ContextObjects(), condition_non_targets); } bool ContainedBy::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "ContainedBy::Match passed no candidate object"; return false; @@ -3045,7 +3084,7 @@ bool ContainedBy::Match(const ScriptingContext& local_context) const { if (candidate->ContainerObjectID() != INVALID_OBJECT_ID && candidate->ContainerObjectID() != candidate->SystemID()) containers.insert(candidate->ContainerObjectID()); - ObjectSet container_objects = Objects().FindObjects(containers); + ObjectSet container_objects = local_context.ContextObjects().find(containers); if (container_objects.empty()) return false; @@ -3072,10 +3111,16 @@ unsigned int ContainedBy::GetCheckSum() const { /////////////////////////////////////////////////////////// // InSystem // /////////////////////////////////////////////////////////// -InSystem::~InSystem() -{ delete m_system_id; } +InSystem::InSystem(std::unique_ptr>&& system_id) : + Condition(), + m_system_id(std::move(system_id)) +{ + m_root_candidate_invariant = !m_system_id || m_system_id->RootCandidateInvariant(); + m_target_invariant = !m_system_id || m_system_id->TargetInvariant(); + m_source_invariant = !m_system_id || m_system_id->SourceInvariant(); +} -bool InSystem::operator==(const ConditionBase& rhs) const { +bool InSystem::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -3116,30 +3161,20 @@ void InSystem::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate empire id once, and use to check all candidate objects - std::shared_ptr no_object; - int system_id = (m_system_id ? m_system_id->Eval(ScriptingContext(parent_context, no_object)) : INVALID_OBJECT_ID); + int system_id = (m_system_id ? m_system_id->Eval(parent_context) : INVALID_OBJECT_ID); EvalImpl(matches, non_matches, search_domain, InSystemSimpleMatch(system_id)); } else { // re-evaluate empire id for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool InSystem::RootCandidateInvariant() const -{ return !m_system_id || m_system_id->RootCandidateInvariant(); } - -bool InSystem::TargetInvariant() const -{ return !m_system_id || m_system_id->TargetInvariant(); } - -bool InSystem::SourceInvariant() const -{ return !m_system_id || m_system_id->SourceInvariant(); } - std::string InSystem::Description(bool negated/* = false*/) const { std::string system_str; int system_id = INVALID_OBJECT_ID; if (m_system_id && m_system_id->ConstantExpr()) system_id = m_system_id->Eval(); - if (std::shared_ptr system = GetSystem(system_id)) + if (auto system = Objects().get(system_id)) system_str = system->Name(); else if (m_system_id) system_str = m_system_id->Description(); @@ -3157,10 +3192,10 @@ std::string InSystem::Description(bool negated/* = false*/) const { return str(FlexibleFormat(description_str) % system_str); } -std::string InSystem::Dump() const { - std::string retval = DumpIndent() + "InSystem"; +std::string InSystem::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "InSystem"; if (m_system_id) - retval += " id = " + m_system_id->Dump(); + retval += " id = " + m_system_id->Dump(ntabs); retval += "\n"; return retval; } @@ -3170,7 +3205,7 @@ void InSystem::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_ { if (!m_system_id) { // can match objects in any system, or any system - AddAllObjectsSet(condition_non_targets); + AddAllObjectsSet(parent_context.ContextObjects(), condition_non_targets); return; } @@ -3180,19 +3215,18 @@ void InSystem::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_ if (!simple_eval_safe) { // almost anything can be in a system, and can also match the system itself - AddAllObjectsSet(condition_non_targets); + AddAllObjectsSet(parent_context.ContextObjects(), condition_non_targets); return; } // simple case of a single specified system id; can add just objects in that system int system_id = m_system_id->Eval(parent_context); - std::shared_ptr system = GetSystem(system_id); + auto system = parent_context.ContextObjects().get(system_id); if (!system) return; - const ObjectMap& obj_map = Objects(); const std::set& system_object_ids = system->ObjectIDs(); - std::vector> sys_objs = obj_map.FindObjects(system_object_ids); + auto sys_objs = parent_context.ContextObjects().find(system_object_ids); // insert all objects that have the specified system id condition_non_targets.reserve(sys_objs.size() + 1); @@ -3202,7 +3236,7 @@ void InSystem::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_ } bool InSystem::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "InSystem::Match passed no candidate object"; return false; @@ -3229,10 +3263,16 @@ unsigned int InSystem::GetCheckSum() const { /////////////////////////////////////////////////////////// // ObjectID // /////////////////////////////////////////////////////////// -ObjectID::~ObjectID() -{ delete m_object_id; } +ObjectID::ObjectID(std::unique_ptr>&& object_id) : + Condition(), + m_object_id(std::move(object_id)) +{ + m_root_candidate_invariant = !m_object_id || m_object_id->RootCandidateInvariant(); + m_target_invariant = !m_object_id || m_object_id->TargetInvariant(); + m_source_invariant = !m_object_id || m_object_id->SourceInvariant(); +} -bool ObjectID::operator==(const ConditionBase& rhs) const { +bool ObjectID::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -3270,30 +3310,20 @@ void ObjectID::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate empire id once, and use to check all candidate objects - std::shared_ptr no_object; - int object_id = (m_object_id ? m_object_id->Eval(ScriptingContext(parent_context, no_object)) : INVALID_OBJECT_ID); + int object_id = (m_object_id ? m_object_id->Eval(parent_context) : INVALID_OBJECT_ID); EvalImpl(matches, non_matches, search_domain, ObjectIDSimpleMatch(object_id)); } else { // re-evaluate empire id for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool ObjectID::RootCandidateInvariant() const -{ return !m_object_id || m_object_id->RootCandidateInvariant(); } - -bool ObjectID::TargetInvariant() const -{ return !m_object_id || m_object_id->TargetInvariant(); } - -bool ObjectID::SourceInvariant() const -{ return !m_object_id || m_object_id->SourceInvariant(); } - std::string ObjectID::Description(bool negated/* = false*/) const { std::string object_str; int object_id = INVALID_OBJECT_ID; if (m_object_id && m_object_id->ConstantExpr()) object_id = m_object_id->Eval(); - if (std::shared_ptr system = GetSystem(object_id)) + if (auto system = Objects().get(object_id)) object_str = system->Name(); else if (m_object_id) object_str = m_object_id->Description(); @@ -3306,8 +3336,8 @@ std::string ObjectID::Description(bool negated/* = false*/) const { % object_str); } -std::string ObjectID::Dump() const -{ return DumpIndent() + "Object id = " + m_object_id->Dump() + "\n"; } +std::string ObjectID::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "Object id = " + m_object_id->Dump(ntabs) + "\n"; } void ObjectID::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const @@ -3320,23 +3350,21 @@ void ObjectID::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_ (parent_context.condition_root_candidate || RootCandidateInvariant())); if (!simple_eval_safe) { - AddAllObjectsSet(condition_non_targets); + AddAllObjectsSet(parent_context.ContextObjects(), condition_non_targets); return; } // simple case of a single specified id; can add just that object - std::shared_ptr no_object; - int object_id = m_object_id->Eval(ScriptingContext(parent_context, no_object)); + int object_id = m_object_id->Eval(parent_context); if (object_id == INVALID_OBJECT_ID) return; - std::shared_ptr obj = Objects().ExistingObject(object_id); - if (obj) + if (auto obj = parent_context.ContextObjects().ExistingObject(object_id)) condition_non_targets.push_back(obj); } bool ObjectID::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "ObjectID::Match passed no candidate object"; return false; @@ -3363,13 +3391,16 @@ unsigned int ObjectID::GetCheckSum() const { /////////////////////////////////////////////////////////// // PlanetType // /////////////////////////////////////////////////////////// -PlanetType::~PlanetType() { - for (ValueRef::ValueRefBase<::PlanetType>* type : m_types) { - delete type; - } +PlanetType::PlanetType(std::vector>>&& types) : + Condition(), + m_types(std::move(types)) +{ + m_root_candidate_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->SourceInvariant(); }); } -bool PlanetType::operator==(const ConditionBase& rhs) const { +bool PlanetType::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -3388,8 +3419,9 @@ bool PlanetType::operator==(const ConditionBase& rhs) const { namespace { struct PlanetTypeSimpleMatch { - PlanetTypeSimpleMatch(const std::vector< ::PlanetType>& types) : - m_types(types) + PlanetTypeSimpleMatch(const std::vector< ::PlanetType>& types, const ObjectMap& objects) : + m_types(types), + m_objects(objects) {} bool operator()(std::shared_ptr candidate) const { @@ -3397,31 +3429,31 @@ namespace { return false; // is it a planet or on a planet? - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); std::shared_ptr building; - if (!planet && (building = std::dynamic_pointer_cast(candidate))) { - planet = GetPlanet(building->PlanetID()); - } + if (!planet && (building = std::dynamic_pointer_cast(candidate))) + planet = m_objects.get(building->PlanetID()); if (planet) { // is it one of the specified building types? - return std::find(m_types.begin(), m_types.end(), planet->Type()) != m_types.end(); + return std::count(m_types.begin(), m_types.end(), planet->Type()); } return false; } const std::vector< ::PlanetType>& m_types; + const ObjectMap& m_objects; }; } void PlanetType::Eval(const ScriptingContext& parent_context, - ObjectSet& matches, ObjectSet& non_matches, - SearchDomain search_domain/* = NON_MATCHES*/) const + ObjectSet& matches, ObjectSet& non_matches, + SearchDomain search_domain/* = NON_MATCHES*/) const { bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant(); if (simple_eval_safe) { // check each valueref for invariance to local candidate - for (ValueRef::ValueRefBase<::PlanetType>* type : m_types) { + for (auto& type : m_types) { if (!type->LocalCandidateInvariant()) { simple_eval_safe = false; break; @@ -3432,38 +3464,14 @@ void PlanetType::Eval(const ScriptingContext& parent_context, // evaluate types once, and use to check all candidate objects std::vector< ::PlanetType> types; // get all types from valuerefs - for (ValueRef::ValueRefBase<::PlanetType>* type : m_types) { + for (auto& type : m_types) { types.push_back(type->Eval(parent_context)); } - EvalImpl(matches, non_matches, search_domain, PlanetTypeSimpleMatch(types)); + EvalImpl(matches, non_matches, search_domain, PlanetTypeSimpleMatch(types, parent_context.ContextObjects())); } else { // re-evaluate contained objects for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); - } -} - -bool PlanetType::RootCandidateInvariant() const { - for (ValueRef::ValueRefBase<::PlanetType>* type : m_types) { - if (!type->RootCandidateInvariant()) - return false; - } - return true; -} - -bool PlanetType::TargetInvariant() const { - for (ValueRef::ValueRefBase<::PlanetType>* type : m_types) { - if (!type->TargetInvariant()) - return false; - } - return true; -} - -bool PlanetType::SourceInvariant() const { - for (ValueRef::ValueRefBase<::PlanetType>* type : m_types) { - if (!type->SourceInvariant()) - return false; + Condition::Eval(parent_context, matches, non_matches, search_domain); } - return true; } std::string PlanetType::Description(bool negated/* = false*/) const { @@ -3486,14 +3494,14 @@ std::string PlanetType::Description(bool negated/* = false*/) const { % values_str); } -std::string PlanetType::Dump() const { - std::string retval = DumpIndent() + "Planet type = "; +std::string PlanetType::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Planet type = "; if (m_types.size() == 1) { - retval += m_types[0]->Dump() + "\n"; + retval += m_types[0]->Dump(ntabs) + "\n"; } else { retval += "[ "; - for (ValueRef::ValueRefBase<::PlanetType>* type : m_types) { - retval += type->Dump() + " "; + for (auto& type : m_types) { + retval += type->Dump(ntabs) + " "; } retval += "]\n"; } @@ -3503,25 +3511,25 @@ std::string PlanetType::Dump() const { void PlanetType::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const { - AddPlanetSet(condition_non_targets); - AddBuildingSet(condition_non_targets); + AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); + AddBuildingSet(parent_context.ContextObjects(), condition_non_targets); } bool PlanetType::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "PlanetType::Match passed no candidate object"; return false; } - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); std::shared_ptr building; - if (!planet && (building = std::dynamic_pointer_cast(candidate))) { - planet = GetPlanet(building->PlanetID()); - } + if (!planet && (building = std::dynamic_pointer_cast(candidate))) + planet = local_context.ContextObjects().get(building->PlanetID()); + if (planet) { - for (ValueRef::ValueRefBase<::PlanetType>* type : m_types) { - if (type->Eval(ScriptingContext(local_context)) == planet->Type()) + for (auto& type : m_types) { + if (type->Eval(local_context) == planet->Type()) return true; } } @@ -3529,7 +3537,7 @@ bool PlanetType::Match(const ScriptingContext& local_context) const { } void PlanetType::SetTopLevelContent(const std::string& content_name) { - for (ValueRef::ValueRefBase<::PlanetType>* type : m_types) { + for (auto& type : m_types) { if (type) type->SetTopLevelContent(content_name); } @@ -3548,13 +3556,16 @@ unsigned int PlanetType::GetCheckSum() const { /////////////////////////////////////////////////////////// // PlanetSize // /////////////////////////////////////////////////////////// -PlanetSize::~PlanetSize() { - for (ValueRef::ValueRefBase<::PlanetSize>* size : m_sizes) { - delete size; - } +PlanetSize::PlanetSize(std::vector>>&& sizes) : + Condition(), + m_sizes(std::move(sizes)) +{ + m_root_candidate_invariant = boost::algorithm::all_of(m_sizes, [](auto& e){ return e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_sizes, [](auto& e){ return e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_sizes, [](auto& e){ return e->SourceInvariant(); }); } -bool PlanetSize::operator==(const ConditionBase& rhs) const { +bool PlanetSize::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -3573,8 +3584,9 @@ bool PlanetSize::operator==(const ConditionBase& rhs) const { namespace { struct PlanetSizeSimpleMatch { - PlanetSizeSimpleMatch(const std::vector< ::PlanetSize>& sizes) : - m_sizes(sizes) + PlanetSizeSimpleMatch(const std::vector< ::PlanetSize>& sizes, const ObjectMap& objects) : + m_sizes(sizes), + m_objects(objects) {} bool operator()(std::shared_ptr candidate) const { @@ -3582,14 +3594,13 @@ namespace { return false; // is it a planet or on a planet? TODO: This concept should be generalized and factored out. - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); std::shared_ptr building; - if (!planet && (building = std::dynamic_pointer_cast(candidate))) { - planet = GetPlanet(building->PlanetID()); - } + if (!planet && (building = std::dynamic_pointer_cast(candidate))) + planet = m_objects.get(building->PlanetID()); if (planet) { // is it one of the specified building types? - for (::PlanetSize size : m_sizes) { + for (auto size : m_sizes) { if (planet->Size() == size) return true; } @@ -3599,6 +3610,7 @@ namespace { } const std::vector< ::PlanetSize>& m_sizes; + const ObjectMap& m_objects; }; } @@ -3609,7 +3621,7 @@ void PlanetSize::Eval(const ScriptingContext& parent_context, bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant(); if (simple_eval_safe) { // check each valueref for invariance to local candidate - for (ValueRef::ValueRefBase<::PlanetSize>* size : m_sizes) { + for (auto& size : m_sizes) { if (!size->LocalCandidateInvariant()) { simple_eval_safe = false; break; @@ -3620,38 +3632,14 @@ void PlanetSize::Eval(const ScriptingContext& parent_context, // evaluate types once, and use to check all candidate objects std::vector< ::PlanetSize> sizes; // get all types from valuerefs - for (ValueRef::ValueRefBase<::PlanetSize>* size : m_sizes) { + for (auto& size : m_sizes) { sizes.push_back(size->Eval(parent_context)); } - EvalImpl(matches, non_matches, search_domain, PlanetSizeSimpleMatch(sizes)); + EvalImpl(matches, non_matches, search_domain, PlanetSizeSimpleMatch(sizes, parent_context.ContextObjects())); } else { // re-evaluate contained objects for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); - } -} - -bool PlanetSize::RootCandidateInvariant() const { - for (ValueRef::ValueRefBase<::PlanetSize>* size : m_sizes) { - if (!size->RootCandidateInvariant()) - return false; - } - return true; -} - -bool PlanetSize::TargetInvariant() const { - for (ValueRef::ValueRefBase<::PlanetSize>* size : m_sizes) { - if (!size->TargetInvariant()) - return false; - } - return true; -} - -bool PlanetSize::SourceInvariant() const { - for (ValueRef::ValueRefBase<::PlanetSize>* size : m_sizes) { - if (!size->SourceInvariant()) - return false; + Condition::Eval(parent_context, matches, non_matches, search_domain); } - return true; } std::string PlanetSize::Description(bool negated/* = false*/) const { @@ -3674,14 +3662,14 @@ std::string PlanetSize::Description(bool negated/* = false*/) const { % values_str); } -std::string PlanetSize::Dump() const { - std::string retval = DumpIndent() + "Planet size = "; +std::string PlanetSize::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Planet size = "; if (m_sizes.size() == 1) { - retval += m_sizes[0]->Dump() + "\n"; + retval += m_sizes[0]->Dump(ntabs) + "\n"; } else { retval += "[ "; - for (ValueRef::ValueRefBase<::PlanetSize>* size : m_sizes) { - retval += size->Dump() + " "; + for (auto& size : m_sizes) { + retval += size->Dump(ntabs) + " "; } retval += "]\n"; } @@ -3691,24 +3679,24 @@ std::string PlanetSize::Dump() const { void PlanetSize::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const { - AddPlanetSet(condition_non_targets); - AddBuildingSet(condition_non_targets); + AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); + AddBuildingSet(parent_context.ContextObjects(), condition_non_targets); } bool PlanetSize::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "PlanetSize::Match passed no candidate object"; return false; } - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); std::shared_ptr building; - if (!planet && (building = std::dynamic_pointer_cast(candidate))) { - planet = GetPlanet(building->PlanetID()); - } + if (!planet && (building = std::dynamic_pointer_cast(candidate))) + planet = local_context.ContextObjects().get(building->PlanetID()); + if (planet) { - for (ValueRef::ValueRefBase<::PlanetSize>* size : m_sizes) { + for (auto& size : m_sizes) { if (size->Eval(local_context) == planet->Size()) return true; } @@ -3717,7 +3705,7 @@ bool PlanetSize::Match(const ScriptingContext& local_context) const { } void PlanetSize::SetTopLevelContent(const std::string& content_name) { - for (ValueRef::ValueRefBase<::PlanetSize>* size : m_sizes) { + for (auto& size : m_sizes) { if (size) size->SetTopLevelContent(content_name); } @@ -3736,14 +3724,24 @@ unsigned int PlanetSize::GetCheckSum() const { /////////////////////////////////////////////////////////// // PlanetEnvironment // /////////////////////////////////////////////////////////// -PlanetEnvironment::~PlanetEnvironment() { - for (ValueRef::ValueRefBase<::PlanetEnvironment>* environment : m_environments) { - delete environment; - } - delete m_species_name; -} - -bool PlanetEnvironment::operator==(const ConditionBase& rhs) const { +PlanetEnvironment::PlanetEnvironment(std::vector>>&& environments, + std::unique_ptr>&& species_name_ref) : + Condition(), + m_environments(std::move(environments)), + m_species_name(std::move(species_name_ref)) +{ + m_root_candidate_invariant = + (!m_species_name || m_species_name->RootCandidateInvariant()) && + boost::algorithm::all_of(m_environments, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = + (!m_species_name || m_species_name->TargetInvariant()) && + boost::algorithm::all_of(m_environments, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = + (!m_species_name || m_species_name->SourceInvariant()) && + boost::algorithm::all_of(m_environments, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +bool PlanetEnvironment::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -3765,9 +3763,11 @@ bool PlanetEnvironment::operator==(const ConditionBase& rhs) const { namespace { struct PlanetEnvironmentSimpleMatch { PlanetEnvironmentSimpleMatch(const std::vector< ::PlanetEnvironment>& environments, + const ObjectMap& objects, const std::string& species = "") : m_environments(environments), - m_species(species) + m_species(species), + m_objects(objects) {} bool operator()(std::shared_ptr candidate) const { @@ -3775,14 +3775,13 @@ namespace { return false; // is it a planet or on a planet? TODO: factor out - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); std::shared_ptr building; - if (!planet && (building = std::dynamic_pointer_cast(candidate))) { - planet = GetPlanet(building->PlanetID()); - } + if (!planet && (building = std::dynamic_pointer_cast(candidate))) + planet = m_objects.get(building->PlanetID()); if (planet) { // is it one of the specified building types? - for (::PlanetEnvironment environment : m_environments) { + for (auto environment : m_environments) { if (planet->EnvironmentForSpecies(m_species) == environment) return true; } @@ -3793,18 +3792,19 @@ namespace { const std::vector< ::PlanetEnvironment>& m_environments; const std::string& m_species; + const ObjectMap& m_objects; }; } void PlanetEnvironment::Eval(const ScriptingContext& parent_context, - ObjectSet& matches, ObjectSet& non_matches, - SearchDomain search_domain/* = NON_MATCHES*/) const + ObjectSet& matches, ObjectSet& non_matches, + SearchDomain search_domain/* = NON_MATCHES*/) const { bool simple_eval_safe = ((!m_species_name || m_species_name->LocalCandidateInvariant()) && (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // check each valueref for invariance to local candidate - for (ValueRef::ValueRefBase<::PlanetEnvironment>* environment : m_environments) { + for (auto& environment : m_environments) { if (!environment->LocalCandidateInvariant()) { simple_eval_safe = false; break; @@ -3815,47 +3815,17 @@ void PlanetEnvironment::Eval(const ScriptingContext& parent_context, // evaluate types once, and use to check all candidate objects std::vector< ::PlanetEnvironment> environments; // get all types from valuerefs - for (ValueRef::ValueRefBase<::PlanetEnvironment>* environment : m_environments) { + for (auto& environment : m_environments) { environments.push_back(environment->Eval(parent_context)); } std::string species_name; if (m_species_name) species_name = m_species_name->Eval(parent_context); - EvalImpl(matches, non_matches, search_domain, PlanetEnvironmentSimpleMatch(environments, species_name)); + EvalImpl(matches, non_matches, search_domain, PlanetEnvironmentSimpleMatch(environments, parent_context.ContextObjects(), species_name)); } else { // re-evaluate contained objects for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); - } -} - -bool PlanetEnvironment::RootCandidateInvariant() const { - if (m_species_name && !m_species_name->RootCandidateInvariant()) - return false; - for (ValueRef::ValueRefBase<::PlanetEnvironment>* environment : m_environments) { - if (!environment->RootCandidateInvariant()) - return false; + Condition::Eval(parent_context, matches, non_matches, search_domain); } - return true; -} - -bool PlanetEnvironment::TargetInvariant() const { - if (m_species_name && !m_species_name->TargetInvariant()) - return false; - for (ValueRef::ValueRefBase<::PlanetEnvironment>* environment : m_environments) { - if (!environment->TargetInvariant()) - return false; - } - return true; -} - -bool PlanetEnvironment::SourceInvariant() const { - if (m_species_name && !m_species_name->SourceInvariant()) - return false; - for (ValueRef::ValueRefBase<::PlanetEnvironment>* environment : m_environments) { - if (!environment->SourceInvariant()) - return false; - } - return true; } std::string PlanetEnvironment::Description(bool negated/* = false*/) const { @@ -3887,19 +3857,19 @@ std::string PlanetEnvironment::Description(bool negated/* = false*/) const { % species_str); } -std::string PlanetEnvironment::Dump() const { - std::string retval = DumpIndent() + "Planet environment = "; +std::string PlanetEnvironment::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Planet environment = "; if (m_environments.size() == 1) { - retval += m_environments[0]->Dump(); + retval += m_environments[0]->Dump(ntabs); } else { retval += "[ "; - for (ValueRef::ValueRefBase<::PlanetEnvironment>* environment : m_environments) { - retval += environment->Dump() + " "; + for (auto& environment : m_environments) { + retval += environment->Dump(ntabs) + " "; } retval += "]"; } if (m_species_name) - retval += " species = " + m_species_name->Dump(); + retval += " species = " + m_species_name->Dump(ntabs); retval += "\n"; return retval; } @@ -3907,23 +3877,22 @@ std::string PlanetEnvironment::Dump() const { void PlanetEnvironment::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const { - AddPlanetSet(condition_non_targets); - AddBuildingSet(condition_non_targets); + AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); + AddBuildingSet(parent_context.ContextObjects(), condition_non_targets); } bool PlanetEnvironment::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "PlanetEnvironment::Match passed no candidate object"; return false; } // is it a planet or on a planet? TODO: factor out - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); std::shared_ptr building; - if (!planet && (building = std::dynamic_pointer_cast(candidate))) { - planet = GetPlanet(building->PlanetID()); - } + if (!planet && (building = std::dynamic_pointer_cast(candidate))) + planet = local_context.ContextObjects().get(building->PlanetID()); if (!planet) return false; @@ -3931,8 +3900,8 @@ bool PlanetEnvironment::Match(const ScriptingContext& local_context) const { if (m_species_name) species_name = m_species_name->Eval(local_context); - ::PlanetEnvironment env_for_planets_species = planet->EnvironmentForSpecies(species_name); - for (ValueRef::ValueRefBase<::PlanetEnvironment>* environment : m_environments) { + auto env_for_planets_species = planet->EnvironmentForSpecies(species_name); + for (auto& environment : m_environments) { if (environment->Eval(local_context) == env_for_planets_species) return true; } @@ -3942,7 +3911,7 @@ bool PlanetEnvironment::Match(const ScriptingContext& local_context) const { void PlanetEnvironment::SetTopLevelContent(const std::string& content_name) { if (m_species_name) m_species_name->SetTopLevelContent(content_name); - for (ValueRef::ValueRefBase<::PlanetEnvironment>* environment : m_environments) { + for (auto& environment : m_environments) { if (environment) environment->SetTopLevelContent(content_name); } @@ -3960,14 +3929,22 @@ unsigned int PlanetEnvironment::GetCheckSum() const { } /////////////////////////////////////////////////////////// -// Species // +// Species // /////////////////////////////////////////////////////////// -Species::~Species() { - for (ValueRef::ValueRefBase* name : m_names) - delete name; +Species::Species(std::vector>>&& names) : + Condition(), + m_names(std::move(names)) +{ + m_root_candidate_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->SourceInvariant(); }); } -bool Species::operator==(const ConditionBase& rhs) const { +Species::Species() : + Species(std::vector>>{}) +{} + +bool Species::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -3986,8 +3963,9 @@ bool Species::operator==(const ConditionBase& rhs) const { namespace { struct SpeciesSimpleMatch { - SpeciesSimpleMatch(const std::vector& names) : - m_names(names) + SpeciesSimpleMatch(const std::vector& names, const ObjectMap& objects) : + m_names(names), + m_objects(objects) {} bool operator()(std::shared_ptr candidate) const { @@ -3995,29 +3973,30 @@ namespace { return false; // is it a population centre? - if (std::shared_ptr pop = std::dynamic_pointer_cast(candidate)) { + if (auto pop = std::dynamic_pointer_cast(candidate)) { const std::string& species_name = pop->SpeciesName(); // if the popcenter has a species and that species is one of those specified... - return !species_name.empty() && (m_names.empty() || (std::find(m_names.begin(), m_names.end(), species_name) != m_names.end())); + return !species_name.empty() && (m_names.empty() || std::count(m_names.begin(), m_names.end(), species_name)); } // is it a ship? - if (std::shared_ptr ship = std::dynamic_pointer_cast(candidate)) { + if (auto ship = std::dynamic_pointer_cast(candidate)) { // if the ship has a species and that species is one of those specified... const std::string& species_name = ship->SpeciesName(); - return !species_name.empty() && (m_names.empty() || (std::find(m_names.begin(), m_names.end(), species_name) != m_names.end())); + return !species_name.empty() && (m_names.empty() || std::count(m_names.begin(), m_names.end(), species_name)); } // is it a building on a planet? - if (std::shared_ptr building = std::dynamic_pointer_cast(candidate)) { - std::shared_ptr planet = GetPlanet(building->PlanetID()); + if (auto building = std::dynamic_pointer_cast(candidate)) { + auto planet = m_objects.get(building->PlanetID()); const std::string& species_name = planet->SpeciesName(); // if the planet (which IS a popcenter) has a species and that species is one of those specified... - return !species_name.empty() && (m_names.empty() || (std::find(m_names.begin(), m_names.end(), species_name) != m_names.end())); + return !species_name.empty() && (m_names.empty() || std::count(m_names.begin(), m_names.end(), species_name)); } return false; } const std::vector& m_names; + const ObjectMap& m_objects; }; } @@ -4028,7 +4007,7 @@ void Species::Eval(const ScriptingContext& parent_context, bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant(); if (simple_eval_safe) { // check each valueref for invariance to local candidate - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (!name->LocalCandidateInvariant()) { simple_eval_safe = false; break; @@ -4039,38 +4018,14 @@ void Species::Eval(const ScriptingContext& parent_context, // evaluate names once, and use to check all candidate objects std::vector names; // get all names from valuerefs - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { names.push_back(name->Eval(parent_context)); } - EvalImpl(matches, non_matches, search_domain, SpeciesSimpleMatch(names)); + EvalImpl(matches, non_matches, search_domain, SpeciesSimpleMatch(names, parent_context.ContextObjects())); } else { // re-evaluate allowed building types range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); - } -} - -bool Species::RootCandidateInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->RootCandidateInvariant()) - return false; - } - return true; -} - -bool Species::TargetInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->TargetInvariant()) - return false; - } - return true; -} - -bool Species::SourceInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->SourceInvariant()) - return false; + Condition::Eval(parent_context, matches, non_matches, search_domain); } - return true; } std::string Species::Description(bool negated/* = false*/) const { @@ -4095,16 +4050,16 @@ std::string Species::Description(bool negated/* = false*/) const { % values_str); } -std::string Species::Dump() const { - std::string retval = DumpIndent() + "Species"; +std::string Species::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Species"; if (m_names.empty()) { // do nothing else } else if (m_names.size() == 1) { - retval += " name = " + m_names[0]->Dump() + "\n"; + retval += " name = " + m_names[0]->Dump(ntabs) + "\n"; } else { retval += " name = [ "; - for (ValueRef::ValueRefBase* name : m_names) { - retval += name->Dump() + " "; + for (auto& name : m_names) { + retval += name->Dump(ntabs) + " "; } retval += "]\n"; } @@ -4114,43 +4069,42 @@ std::string Species::Dump() const { void Species::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const { - AddPlanetSet(condition_non_targets); - AddBuildingSet(condition_non_targets); - AddShipSet(condition_non_targets); + AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); + AddBuildingSet(parent_context.ContextObjects(), condition_non_targets); + AddShipSet(parent_context.ContextObjects(), condition_non_targets); } bool Species::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Species::Match passed no candidate object"; return false; } // is it a planet or a building on a planet? TODO: factor out - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); std::shared_ptr building; - if (!planet && (building = std::dynamic_pointer_cast(candidate))) { - planet = GetPlanet(building->PlanetID()); - } + if (!planet && (building = std::dynamic_pointer_cast(candidate))) + planet = local_context.ContextObjects().get(building->PlanetID()); if (planet) { if (m_names.empty()) { return !planet->SpeciesName().empty(); // match any species name } else { // match only specified species names - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (name->Eval(local_context) == planet->SpeciesName()) return true; } } } // is it a ship? - std::shared_ptr ship = std::dynamic_pointer_cast(candidate); + auto ship = std::dynamic_pointer_cast(candidate); if (ship) { if (m_names.empty()) { return !ship->SpeciesName().empty(); // match any species name } else { // match only specified species names - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (name->Eval(local_context) == ship->SpeciesName()) return true; } @@ -4160,7 +4114,7 @@ bool Species::Match(const ScriptingContext& local_context) const { } void Species::SetTopLevelContent(const std::string& content_name) { - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (name) name->SetTopLevelContent(content_name); } @@ -4179,35 +4133,52 @@ unsigned int Species::GetCheckSum() const { /////////////////////////////////////////////////////////// // Enqueued // /////////////////////////////////////////////////////////// -Enqueued::Enqueued(ValueRef::ValueRefBase* design_id, ValueRef::ValueRefBase* empire_id, - ValueRef::ValueRefBase* low, ValueRef::ValueRefBase* high) : - ConditionBase(), +Enqueued::Enqueued(std::unique_ptr>&& design_id, + std::unique_ptr>&& empire_id, + std::unique_ptr>&& low, + std::unique_ptr>&& high) : + Condition(), m_build_type(BT_SHIP), - m_name(), - m_design_id(design_id), - m_empire_id(empire_id), - m_low(low), - m_high(high) -{} + m_design_id(std::move(design_id)), + m_empire_id(std::move(empire_id)), + m_low(std::move(low)), + m_high(std::move(high)) +{ + auto operands = {m_design_id.get(), m_empire_id.get(), m_low.get(), m_high.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} Enqueued::Enqueued() : - ConditionBase(), - m_build_type(BT_NOT_BUILDING), - m_name(), - m_design_id(0), - m_empire_id(0), - m_low(0), - m_high(0) + Enqueued(BT_NOT_BUILDING, nullptr, nullptr, nullptr) {} -Enqueued::~Enqueued() { - delete m_name; - delete m_design_id; - delete m_low; - delete m_high; -} - -bool Enqueued::operator==(const ConditionBase& rhs) const { +Enqueued::Enqueued(BuildType build_type, + std::unique_ptr>&& name, + std::unique_ptr>&& empire_id, + std::unique_ptr>&& low, + std::unique_ptr>&& high) : + Condition(), + m_build_type(build_type), + m_name(std::move(name)), + m_empire_id(std::move(empire_id)), + m_low(std::move(low)), + m_high(std::move(high)) +{ + auto operands = {m_empire_id.get(), m_low.get(), m_high.get()}; + m_root_candidate_invariant = + (!m_name || m_name->RootCandidateInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = + (!m_name || m_name->TargetInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = + (!m_name || m_name->SourceInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +bool Enqueued::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -4232,7 +4203,7 @@ namespace { const std::string& name = "", int design_id = INVALID_DESIGN_ID) { int retval = 0; - for (const ProductionQueue::Element& element : queue) { + for (const auto& element : queue) { if (!(build_type == INVALID_BUILD_TYPE || build_type == element.item.build_type)) continue; if (location_id != element.location) @@ -4277,7 +4248,7 @@ namespace { int count = 0; if (m_empire_id == ALL_EMPIRES) { - for (std::map::value_type& item : Empires()) { + for (auto& item : Empires()) { const Empire* empire = item.second; count += NumberOnQueue(empire->GetProductionQueue(), m_build_type, candidate->ID(), m_name, m_design_id); @@ -4316,6 +4287,7 @@ void Enqueued::Eval(const ScriptingContext& parent_context, (m_high && !m_high->LocalCandidateInvariant())) { simple_eval_safe = false; } } + if (simple_eval_safe) { // evaluate valuerefs once, and use to check all candidate objects std::string name = (m_name ? m_name->Eval(parent_context) : ""); @@ -4332,44 +4304,16 @@ void Enqueued::Eval(const ScriptingContext& parent_context, if (!m_low && !m_high) low = 1; + // need to test each candidate separately using EvalImpl and EnqueuedSimpleMatch + // because the test checks that something is enqueued at the candidate location EvalImpl(matches, non_matches, search_domain, EnqueuedSimpleMatch(m_build_type, name, design_id, empire_id, low, high)); } else { // re-evaluate allowed building types range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool Enqueued::RootCandidateInvariant() const { - if ((m_name && !m_name->RootCandidateInvariant()) || - (m_design_id && !m_design_id->RootCandidateInvariant()) || - (m_empire_id && !m_empire_id->RootCandidateInvariant()) || - (m_low && !m_low->RootCandidateInvariant()) || - (m_high && !m_high->RootCandidateInvariant())) - { return false; } - return true; -} - -bool Enqueued::TargetInvariant() const { - if ((m_name && !m_name->TargetInvariant()) || - (m_design_id && !m_design_id->TargetInvariant()) || - (m_empire_id && !m_empire_id->TargetInvariant()) || - (m_low && !m_low->TargetInvariant()) || - (m_high && !m_high->TargetInvariant())) - { return false; } - return true; -} - -bool Enqueued::SourceInvariant() const { - if ((m_name && !m_name->SourceInvariant()) || - (m_design_id && !m_design_id->SourceInvariant()) || - (m_empire_id && !m_empire_id->SourceInvariant()) || - (m_low && !m_low->SourceInvariant()) || - (m_high && !m_high->SourceInvariant())) - { return false; } - return true; -} - std::string Enqueued::Description(bool negated/* = false*/) const { std::string empire_str; if (m_empire_id) { @@ -4425,32 +4369,32 @@ std::string Enqueued::Description(bool negated/* = false*/) const { % what_str); } -std::string Enqueued::Dump() const { - std::string retval = DumpIndent() + "Enqueued"; +std::string Enqueued::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Enqueued"; if (m_build_type == BT_BUILDING) { retval += " type = Building"; if (m_name) - retval += " name = " + m_name->Dump(); + retval += " name = " + m_name->Dump(ntabs); } else if (m_build_type == BT_SHIP) { retval += " type = Ship"; if (m_name) - retval += " design = " + m_name->Dump(); + retval += " design = " + m_name->Dump(ntabs); else if (m_design_id) - retval += " design = " + m_design_id->Dump(); + retval += " design = " + m_design_id->Dump(ntabs); } if (m_empire_id) - retval += " empire = " + m_empire_id->Dump(); + retval += " empire = " + m_empire_id->Dump(ntabs); if (m_low) - retval += " low = " + m_low->Dump(); + retval += " low = " + m_low->Dump(ntabs); if (m_high) - retval += " high = " + m_high->Dump(); + retval += " high = " + m_high->Dump(ntabs); retval += "\n"; return retval; } bool Enqueued::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Enqueued::Match passed no candidate object"; return false; @@ -4466,7 +4410,7 @@ bool Enqueued::Match(const ScriptingContext& local_context) const { void Enqueued::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const { - AddPlanetSet(condition_non_targets); + AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); } void Enqueued::SetTopLevelContent(const std::string& content_name) { @@ -4499,13 +4443,16 @@ unsigned int Enqueued::GetCheckSum() const { /////////////////////////////////////////////////////////// // FocusType // /////////////////////////////////////////////////////////// -FocusType::~FocusType() { - for (ValueRef::ValueRefBase* name : m_names) { - delete name; - } +FocusType::FocusType(std::vector>>&& names) : + Condition(), + m_names(std::move(names)) +{ + m_root_candidate_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_names, [](auto& e){ return e->SourceInvariant(); }); } -bool FocusType::operator==(const ConditionBase& rhs) const { +bool FocusType::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -4524,8 +4471,9 @@ bool FocusType::operator==(const ConditionBase& rhs) const { namespace { struct FocusTypeSimpleMatch { - FocusTypeSimpleMatch(const std::vector& names) : - m_names(names) + FocusTypeSimpleMatch(const std::vector& names, const ObjectMap& objects) : + m_names(names), + m_objects(objects) {} bool operator()(std::shared_ptr candidate) const { @@ -4533,21 +4481,22 @@ namespace { return false; // is it a ResourceCenter or a Building on a Planet (that is a ResourceCenter) - std::shared_ptr res_center = std::dynamic_pointer_cast(candidate); + auto res_center = std::dynamic_pointer_cast(candidate); std::shared_ptr building; if (!res_center && (building = std::dynamic_pointer_cast(candidate))) { - if (std::shared_ptr planet = GetPlanet(building->PlanetID())) + if (auto planet = m_objects.get(building->PlanetID())) res_center = std::dynamic_pointer_cast(planet); } if (res_center) { return !res_center->Focus().empty() && - (std::find(m_names.begin(), m_names.end(), res_center->Focus()) != m_names.end()); + std::count(m_names.begin(), m_names.end(), res_center->Focus()); } return false; } const std::vector& m_names; + const ObjectMap& m_objects; }; } @@ -4558,7 +4507,7 @@ void FocusType::Eval(const ScriptingContext& parent_context, bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant(); if (simple_eval_safe) { // check each valueref for invariance to local candidate - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (!name->LocalCandidateInvariant()) { simple_eval_safe = false; break; @@ -4569,40 +4518,16 @@ void FocusType::Eval(const ScriptingContext& parent_context, // evaluate names once, and use to check all candidate objects std::vector names; // get all names from valuerefs - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { names.push_back(name->Eval(parent_context)); } - EvalImpl(matches, non_matches, search_domain, FocusTypeSimpleMatch(names)); + EvalImpl(matches, non_matches, search_domain, FocusTypeSimpleMatch(names, parent_context.ContextObjects())); } else { // re-evaluate allowed building types range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool FocusType::RootCandidateInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->RootCandidateInvariant()) - return false; - } - return true; -} - -bool FocusType::TargetInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->TargetInvariant()) - return false; - } - return true; -} - -bool FocusType::SourceInvariant() const { - for (ValueRef::ValueRefBase* name : m_names) { - if (!name->SourceInvariant()) - return false; - } - return true; -} - std::string FocusType::Description(bool negated/* = false*/) const { std::string values_str; for (unsigned int i = 0; i < m_names.size(); ++i) { @@ -4623,14 +4548,14 @@ std::string FocusType::Description(bool negated/* = false*/) const { % values_str); } -std::string FocusType::Dump() const { - std::string retval = DumpIndent() + "Focus name = "; +std::string FocusType::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Focus name = "; if (m_names.size() == 1) { - retval += m_names[0]->Dump() + "\n"; + retval += m_names[0]->Dump(ntabs) + "\n"; } else { retval += "[ "; - for (ValueRef::ValueRefBase* name : m_names) { - retval += name->Dump() + " "; + for (auto& name : m_names) { + retval += name->Dump(ntabs) + " "; } retval += "]\n"; } @@ -4638,21 +4563,21 @@ std::string FocusType::Dump() const { } bool FocusType::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "FocusType::Match passed no candidate object"; return false; } // is it a ResourceCenter or a Building on a Planet (that is a ResourceCenter) - std::shared_ptr res_center = std::dynamic_pointer_cast(candidate); + auto res_center = std::dynamic_pointer_cast(candidate); std::shared_ptr building; if (!res_center && (building = std::dynamic_pointer_cast(candidate))) { - if (std::shared_ptr planet = GetPlanet(building->PlanetID())) + if (auto planet = local_context.ContextObjects().get(building->PlanetID())) res_center = std::dynamic_pointer_cast(planet); } if (res_center) { - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (name->Eval(local_context) == res_center->Focus()) return true; } @@ -4663,12 +4588,12 @@ bool FocusType::Match(const ScriptingContext& local_context) const { void FocusType::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const { - AddPlanetSet(condition_non_targets); - AddBuildingSet(condition_non_targets); + AddPlanetSet(parent_context.ContextObjects(), condition_non_targets); + AddBuildingSet(parent_context.ContextObjects(), condition_non_targets); } void FocusType::SetTopLevelContent(const std::string& content_name) { - for (ValueRef::ValueRefBase* name : m_names) { + for (auto& name : m_names) { if (name) name->SetTopLevelContent(content_name); } @@ -4687,13 +4612,16 @@ unsigned int FocusType::GetCheckSum() const { /////////////////////////////////////////////////////////// // StarType // /////////////////////////////////////////////////////////// -StarType::~StarType() { - for (ValueRef::ValueRefBase<::StarType>* type : m_types) { - delete type; - } +StarType::StarType(std::vector>>&& types) : + Condition(), + m_types(std::move(types)) +{ + m_root_candidate_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_types, [](auto& e){ return e->SourceInvariant(); }); } -bool StarType::operator==(const ConditionBase& rhs) const { +bool StarType::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -4712,22 +4640,24 @@ bool StarType::operator==(const ConditionBase& rhs) const { namespace { struct StarTypeSimpleMatch { - StarTypeSimpleMatch(const std::vector< ::StarType>& types) : - m_types(types) + StarTypeSimpleMatch(const std::vector< ::StarType>& types, const ObjectMap& objects) : + m_types(types), + m_objects(objects) {} bool operator()(std::shared_ptr candidate) const { if (!candidate) return false; - std::shared_ptr system = GetSystem(candidate->SystemID()); + std::shared_ptr system = m_objects.get(candidate->SystemID()); if (system || (system = std::dynamic_pointer_cast(candidate))) - return !m_types.empty() && (std::find(m_types.begin(), m_types.end(), system->GetStarType()) != m_types.end()); + return !m_types.empty() && std::count(m_types.begin(), m_types.end(), system->GetStarType()); return false; } const std::vector< ::StarType>& m_types; + const ObjectMap& m_objects; }; } @@ -4738,7 +4668,7 @@ void StarType::Eval(const ScriptingContext& parent_context, bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant(); if (simple_eval_safe) { // check each valueref for invariance to local candidate - for (ValueRef::ValueRefBase<::StarType>* type : m_types) { + for (auto& type : m_types) { if (!type->LocalCandidateInvariant()) { simple_eval_safe = false; break; @@ -4749,38 +4679,14 @@ void StarType::Eval(const ScriptingContext& parent_context, // evaluate types once, and use to check all candidate objects std::vector< ::StarType> types; // get all types from valuerefs - for (ValueRef::ValueRefBase<::StarType>* type : m_types) { + for (auto& type : m_types) { types.push_back(type->Eval(parent_context)); } - EvalImpl(matches, non_matches, search_domain, StarTypeSimpleMatch(types)); + EvalImpl(matches, non_matches, search_domain, StarTypeSimpleMatch(types, parent_context.ContextObjects())); } else { // re-evaluate contained objects for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); - } -} - -bool StarType::RootCandidateInvariant() const { - for (ValueRef::ValueRefBase<::StarType>* type : m_types) { - if (!type->RootCandidateInvariant()) - return false; - } - return true; -} - -bool StarType::TargetInvariant() const { - for (ValueRef::ValueRefBase<::StarType>* type : m_types) { - if (!type->TargetInvariant()) - return false; - } - return true; -} - -bool StarType::SourceInvariant() const { - for (ValueRef::ValueRefBase<::StarType>* type : m_types) { - if (!type->SourceInvariant()) - return false; + Condition::Eval(parent_context, matches, non_matches, search_domain); } - return true; } std::string StarType::Description(bool negated/* = false*/) const { @@ -4803,14 +4709,14 @@ std::string StarType::Description(bool negated/* = false*/) const { % values_str); } -std::string StarType::Dump() const { - std::string retval = DumpIndent() + "Star type = "; +std::string StarType::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Star type = "; if (m_types.size() == 1) { - retval += m_types[0]->Dump() + "\n"; + retval += m_types[0]->Dump(ntabs) + "\n"; } else { retval += "[ "; - for (ValueRef::ValueRefBase<::StarType>* type : m_types) { - retval += type->Dump() + " "; + for (auto& type : m_types) { + retval += type->Dump(ntabs) + " "; } retval += "]\n"; } @@ -4818,15 +4724,15 @@ std::string StarType::Dump() const { } bool StarType::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "StarType::Match passed no candidate object"; return false; } - std::shared_ptr system = GetSystem(candidate->SystemID()); + std::shared_ptr system = local_context.ContextObjects().get(candidate->SystemID()); if (system || (system = std::dynamic_pointer_cast(candidate))) { - for (ValueRef::ValueRefBase<::StarType>* type : m_types) { + for (auto& type : m_types) { if (type->Eval(local_context) == system->GetStarType()) return true; } @@ -4835,7 +4741,7 @@ bool StarType::Match(const ScriptingContext& local_context) const { } void StarType::SetTopLevelContent(const std::string& content_name) { - for (ValueRef::ValueRefBase<::StarType>* type : m_types) { + for (auto& type : m_types) { if (type) (type)->SetTopLevelContent(content_name); } @@ -4854,10 +4760,16 @@ unsigned int StarType::GetCheckSum() const { /////////////////////////////////////////////////////////// // DesignHasHull // /////////////////////////////////////////////////////////// -DesignHasHull::~DesignHasHull() -{ delete m_name; } +DesignHasHull::DesignHasHull(std::unique_ptr>&& name) : + Condition(), + m_name(std::move(name)) +{ + m_root_candidate_invariant = !m_name || m_name->RootCandidateInvariant(); + m_target_invariant = !m_name || m_name->TargetInvariant(); + m_source_invariant = !m_name || m_name->SourceInvariant(); +} -bool DesignHasHull::operator==(const ConditionBase& rhs) const { +bool DesignHasHull::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -4881,7 +4793,7 @@ namespace { return false; // is it a ship? - std::shared_ptr ship = std::dynamic_pointer_cast(candidate); + auto ship = std::dynamic_pointer_cast(candidate); if (!ship) return false; // with a valid design? @@ -4904,25 +4816,17 @@ void DesignHasHull::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant()); if (simple_eval_safe) { // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - std::string name = (m_name ? m_name->Eval(local_context) : ""); + std::string name = (m_name ? m_name->Eval(parent_context) : ""); + + // need to test each candidate separately using EvalImpl and because the + // design of the candidate object is tested EvalImpl(matches, non_matches, search_domain, DesignHasHullSimpleMatch(name)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool DesignHasHull::RootCandidateInvariant() const -{ return (!m_name || m_name->RootCandidateInvariant()); } - -bool DesignHasHull::TargetInvariant() const -{ return (!m_name || m_name->TargetInvariant()); } - -bool DesignHasHull::SourceInvariant() const -{ return (!m_name || m_name->SourceInvariant()); } - std::string DesignHasHull::Description(bool negated/* = false*/) const { std::string name_str; if (m_name) { @@ -4936,16 +4840,16 @@ std::string DesignHasHull::Description(bool negated/* = false*/) const { % name_str); } -std::string DesignHasHull::Dump() const { - std::string retval = DumpIndent() + "DesignHasHull"; +std::string DesignHasHull::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "DesignHasHull"; if (m_name) - retval += " name = " + m_name->Dump(); + retval += " name = " + m_name->Dump(ntabs); retval += "\n"; return retval; } bool DesignHasHull::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "DesignHasHull::Match passed no candidate object"; return false; @@ -4957,9 +4861,9 @@ bool DesignHasHull::Match(const ScriptingContext& local_context) const { } void DesignHasHull::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, - ObjectSet& condition_non_targets) const + ObjectSet& condition_non_targets) const { - AddShipSet(condition_non_targets); + AddShipSet(parent_context.ContextObjects(), condition_non_targets); } void DesignHasHull::SetTopLevelContent(const std::string& content_name) { @@ -4980,13 +4884,27 @@ unsigned int DesignHasHull::GetCheckSum() const { /////////////////////////////////////////////////////////// // DesignHasPart // /////////////////////////////////////////////////////////// -DesignHasPart::~DesignHasPart() { - delete m_name; - delete m_low; - delete m_high; -} - -bool DesignHasPart::operator==(const ConditionBase& rhs) const { +DesignHasPart::DesignHasPart(std::unique_ptr>&& name, + std::unique_ptr>&& low, + std::unique_ptr>&& high) : + Condition(), + m_low(std::move(low)), + m_high(std::move(high)), + m_name(std::move(name)) +{ + auto operands = {m_low.get(), m_high.get()}; + m_root_candidate_invariant = + (!m_name || m_name->RootCandidateInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = + (!m_name || m_name->TargetInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = + (!m_name || m_name->SourceInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +bool DesignHasPart::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -5003,20 +4921,29 @@ bool DesignHasPart::operator==(const ConditionBase& rhs) const { namespace { struct DesignHasPartSimpleMatch { - DesignHasPartSimpleMatch(int low, int high, const std::string& name) : + DesignHasPartSimpleMatch(int low, int high, const std::string& name, const ObjectMap& objects) : m_low(low), m_high(high), - m_name(name) + m_name(name), + m_objects(objects) {} bool operator()(std::shared_ptr candidate) const { if (!candidate) return false; - // is it a ship? - std::shared_ptr ship = std::dynamic_pointer_cast(candidate); + std::shared_ptr ship = nullptr; + if (auto fighter = std::dynamic_pointer_cast(candidate)) { + // it is a fighter + ship = m_objects.get(fighter->LaunchedFrom()); + } else { + ship = std::dynamic_pointer_cast(candidate); + } + + // is it a ship if (!ship) return false; + // with a valid design? const ShipDesign* design = ship->Design(); if (!design) @@ -5035,6 +4962,7 @@ namespace { int m_low; int m_high; const std::string& m_name; + const ObjectMap& m_objects; }; } @@ -5048,33 +4976,19 @@ void DesignHasPart::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant()); if (simple_eval_safe) { // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - std::string name = (m_name ? m_name->Eval(local_context) : ""); - int low = (m_low ? std::max(0, m_low->Eval(local_context)) : 1); - int high = (m_high ? std::min(m_high->Eval(local_context), INT_MAX) : INT_MAX); - EvalImpl(matches, non_matches, search_domain, DesignHasPartSimpleMatch(low, high, name)); + std::string name = (m_name ? m_name->Eval(parent_context) : ""); + int low = (m_low ? std::max(0, m_low->Eval(parent_context)) : 1); + int high = (m_high ? std::min(m_high->Eval(parent_context), INT_MAX) : INT_MAX); + + // need to test each candidate separately using EvalImpl and because the + // design of the candidate object is tested + EvalImpl(matches, non_matches, search_domain, DesignHasPartSimpleMatch(low, high, name, parent_context.ContextObjects())); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool DesignHasPart::RootCandidateInvariant() const -{ return (!m_low || m_low->RootCandidateInvariant()) && - (!m_high || m_high->RootCandidateInvariant()) && - (!m_name || m_name->RootCandidateInvariant()); } - -bool DesignHasPart::TargetInvariant() const -{ return (!m_low || m_low->TargetInvariant()) && - (!m_high || m_high->TargetInvariant()) && - (!m_name || m_name->TargetInvariant()); } - -bool DesignHasPart::SourceInvariant() const -{ return (!m_low || m_low->SourceInvariant()) && - (!m_high || m_high->SourceInvariant()) && - (!m_name || m_name->SourceInvariant()); } - std::string DesignHasPart::Description(bool negated/* = false*/) const { std::string low_str = "1"; if (m_low) { @@ -5102,20 +5016,20 @@ std::string DesignHasPart::Description(bool negated/* = false*/) const { % name_str); } -std::string DesignHasPart::Dump() const { - std::string retval = DumpIndent() + "DesignHasPart"; +std::string DesignHasPart::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "DesignHasPart"; if (m_low) - retval += "low = " + m_low->Dump(); + retval += "low = " + m_low->Dump(ntabs); if (m_high) - retval += " high = " + m_high->Dump(); + retval += " high = " + m_high->Dump(ntabs); if (m_name) - retval += " name = " + m_name->Dump(); + retval += " name = " + m_name->Dump(ntabs); retval += "\n"; return retval; } bool DesignHasPart::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "DesignHasPart::Match passed no candidate object"; return false; @@ -5125,13 +5039,13 @@ bool DesignHasPart::Match(const ScriptingContext& local_context) const { int high = (m_high ? std::min(m_high->Eval(local_context), IMPOSSIBLY_LARGE_TURN) : IMPOSSIBLY_LARGE_TURN); std::string name = (m_name ? m_name->Eval(local_context) : ""); - return DesignHasPartSimpleMatch(low, high, name)(candidate); + return DesignHasPartSimpleMatch(low, high, name, local_context.ContextObjects())(candidate); } void DesignHasPart::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const { - AddShipSet(condition_non_targets); + AddShipSet(parent_context.ContextObjects(), condition_non_targets); } void DesignHasPart::SetTopLevelContent(const std::string& content_name) { @@ -5158,12 +5072,21 @@ unsigned int DesignHasPart::GetCheckSum() const { /////////////////////////////////////////////////////////// // DesignHasPartClass // /////////////////////////////////////////////////////////// -DesignHasPartClass::~DesignHasPartClass() { - delete m_low; - delete m_high; +DesignHasPartClass::DesignHasPartClass(ShipPartClass part_class, + std::unique_ptr>&& low, + std::unique_ptr>&& high) : + Condition(), + m_low(std::move(low)), + m_high(std::move(high)), + m_class(std::move(part_class)) +{ + auto operands = {m_low.get(), m_high.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -bool DesignHasPartClass::operator==(const ConditionBase& rhs) const { +bool DesignHasPartClass::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -5193,7 +5116,7 @@ namespace { return false; // is it a ship? - std::shared_ptr ship = std::dynamic_pointer_cast(candidate); + auto ship = std::dynamic_pointer_cast(candidate); if (!ship) return false; // with a valid design? @@ -5204,8 +5127,8 @@ namespace { int count = 0; for (const std::string& name : design->Parts()) { - if (const PartType* part_type = GetPartType(name)) { - if (part_type->Class() == m_part_class) + if (const ShipPart* ship_part = GetShipPart(name)) { + if (ship_part->Class() == m_part_class) ++count; } } @@ -5227,26 +5150,18 @@ void DesignHasPartClass::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant()); if (simple_eval_safe) { // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - int low = (m_low ? std::max(0, m_low->Eval(local_context)) : 1); - int high = (m_high ? std::min(m_high->Eval(local_context), INT_MAX) : INT_MAX); + int low = (m_low ? std::max(0, m_low->Eval(parent_context)) : 1); + int high = (m_high ? std::min(m_high->Eval(parent_context), INT_MAX) : INT_MAX); + + // need to test each candidate separately using EvalImpl and because the + // design of the candidate object is tested EvalImpl(matches, non_matches, search_domain, DesignHasPartClassSimpleMatch(low, high, m_class)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool DesignHasPartClass::RootCandidateInvariant() const -{ return (!m_low || m_low->RootCandidateInvariant()) && (!m_high || m_high->RootCandidateInvariant()); } - -bool DesignHasPartClass::TargetInvariant() const -{ return (!m_low || m_low->TargetInvariant()) && (!m_high || m_high->TargetInvariant()); } - -bool DesignHasPartClass::SourceInvariant() const -{ return (!m_low || m_low->SourceInvariant()) && (!m_high || m_high->SourceInvariant()); } - std::string DesignHasPartClass::Description(bool negated/* = false*/) const { std::string low_str = "1"; if (m_low) { @@ -5268,19 +5183,19 @@ std::string DesignHasPartClass::Description(bool negated/* = false*/) const { % UserString(boost::lexical_cast(m_class))); } -std::string DesignHasPartClass::Dump() const { - std::string retval = DumpIndent() + "DesignHasPartClass"; +std::string DesignHasPartClass::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "DesignHasPartClass"; if (m_low) - retval += " low = " + m_low->Dump(); + retval += " low = " + m_low->Dump(ntabs); if (m_high) - retval += " high = " + m_high->Dump(); + retval += " high = " + m_high->Dump(ntabs); retval += " class = " + UserString(boost::lexical_cast(m_class)); retval += "\n"; return retval; } bool DesignHasPartClass::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "DesignHasPartClass::Match passed no candidate object"; return false; @@ -5295,7 +5210,7 @@ bool DesignHasPartClass::Match(const ScriptingContext& local_context) const { void DesignHasPartClass::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, ObjectSet& condition_non_targets) const { - AddShipSet(condition_non_targets); + AddShipSet(parent_context.ContextObjects(), condition_non_targets); } void DesignHasPartClass::SetTopLevelContent(const std::string& content_name) { @@ -5320,10 +5235,16 @@ unsigned int DesignHasPartClass::GetCheckSum() const { /////////////////////////////////////////////////////////// // PredefinedShipDesign // /////////////////////////////////////////////////////////// -PredefinedShipDesign::~PredefinedShipDesign() -{ delete m_name; } +PredefinedShipDesign::PredefinedShipDesign(std::unique_ptr>&& name) : + Condition(), + m_name(std::move(name)) +{ + m_root_candidate_invariant = !m_name || m_name->RootCandidateInvariant(); + m_target_invariant = !m_name || m_name->TargetInvariant(); + m_source_invariant = !m_name || m_name->SourceInvariant(); +} -bool PredefinedShipDesign::operator==(const ConditionBase& rhs) const { +bool PredefinedShipDesign::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -5349,7 +5270,7 @@ namespace { {} bool operator()(std::shared_ptr candidate) const { - std::shared_ptr ship = std::dynamic_pointer_cast(candidate); + auto ship = std::dynamic_pointer_cast(candidate); if (!ship) return false; const ShipDesign* candidate_design = ship->Design(); @@ -5380,30 +5301,20 @@ void PredefinedShipDesign::Eval(const ScriptingContext& parent_context, bool simple_eval_safe = (!m_name || m_name->LocalCandidateInvariant()) && (parent_context.condition_root_candidate || RootCandidateInvariant()); if (simple_eval_safe) { - // evaluate number limits once, use to match all candidates + // testing each candidate to see if its design is predefined or is a + // particular named predefined design if (!m_name) { EvalImpl(matches, non_matches, search_domain, PredefinedShipDesignSimpleMatch()); } else { - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - std::string name = m_name->Eval(local_context); + std::string name = m_name->Eval(parent_context); EvalImpl(matches, non_matches, search_domain, PredefinedShipDesignSimpleMatch(name)); } } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool PredefinedShipDesign::RootCandidateInvariant() const -{ return !m_name || m_name->RootCandidateInvariant(); } - -bool PredefinedShipDesign::TargetInvariant() const -{ return !m_name || m_name->TargetInvariant(); } - -bool PredefinedShipDesign::SourceInvariant() const -{ return !m_name || m_name->SourceInvariant(); } - std::string PredefinedShipDesign::Description(bool negated/* = false*/) const { std::string name_str; if (m_name) { @@ -5417,16 +5328,16 @@ std::string PredefinedShipDesign::Description(bool negated/* = false*/) const { % name_str); } -std::string PredefinedShipDesign::Dump() const { - std::string retval = DumpIndent() + "PredefinedShipDesign"; +std::string PredefinedShipDesign::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "PredefinedShipDesign"; if (m_name) - retval += " name = " + m_name->Dump(); + retval += " name = " + m_name->Dump(ntabs); retval += "\n"; return retval; } bool PredefinedShipDesign::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "PredefinedShipDesign::Match passed no candidate object"; return false; @@ -5457,10 +5368,16 @@ unsigned int PredefinedShipDesign::GetCheckSum() const { /////////////////////////////////////////////////////////// // NumberedShipDesign // /////////////////////////////////////////////////////////// -NumberedShipDesign::~NumberedShipDesign() -{ delete m_design_id; } +NumberedShipDesign::NumberedShipDesign(std::unique_ptr>&& design_id) : + Condition(), + m_design_id(std::move(design_id)) +{ + m_root_candidate_invariant = !m_design_id || m_design_id->RootCandidateInvariant(); + m_target_invariant = !m_design_id || m_design_id->TargetInvariant(); + m_source_invariant = !m_design_id || m_design_id->SourceInvariant(); +} -bool NumberedShipDesign::operator==(const ConditionBase& rhs) const { +bool NumberedShipDesign::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -5484,7 +5401,7 @@ namespace { return false; if (m_design_id == INVALID_DESIGN_ID) return false; - if (std::shared_ptr ship = std::dynamic_pointer_cast(candidate)) + if (auto ship = std::dynamic_pointer_cast(candidate)) if (ship->DesignID() == m_design_id) return true; return false; @@ -5502,25 +5419,17 @@ void NumberedShipDesign::Eval(const ScriptingContext& parent_context, (m_design_id->LocalCandidateInvariant() && (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { - // evaluate empire id once, and use to check all candidate objects - std::shared_ptr no_object; - int design_id = m_design_id->Eval(ScriptingContext(parent_context, no_object)); + // evaluate design id once, and use to check all candidate objects + int design_id = m_design_id->Eval(parent_context); + + // design of the candidate objects is tested, so need to check each separately EvalImpl(matches, non_matches, search_domain, NumberedShipDesignSimpleMatch(design_id)); } else { // re-evaluate design id for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool NumberedShipDesign::RootCandidateInvariant() const -{ return !m_design_id || m_design_id->RootCandidateInvariant(); } - -bool NumberedShipDesign::TargetInvariant() const -{ return !m_design_id || m_design_id->TargetInvariant(); } - -bool NumberedShipDesign::SourceInvariant() const -{ return !m_design_id || m_design_id->SourceInvariant(); } - std::string NumberedShipDesign::Description(bool negated/* = false*/) const { std::string id_str = m_design_id->ConstantExpr() ? std::to_string(m_design_id->Eval()) : @@ -5532,11 +5441,11 @@ std::string NumberedShipDesign::Description(bool negated/* = false*/) const { % id_str); } -std::string NumberedShipDesign::Dump() const -{ return DumpIndent() + "NumberedShipDesign design_id = " + m_design_id->Dump(); } +std::string NumberedShipDesign::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "NumberedShipDesign design_id = " + m_design_id->Dump(ntabs); } bool NumberedShipDesign::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "NumberedShipDesign::Match passed no candidate object"; return false; @@ -5562,10 +5471,16 @@ unsigned int NumberedShipDesign::GetCheckSum() const { /////////////////////////////////////////////////////////// // ProducedByEmpire // /////////////////////////////////////////////////////////// -ProducedByEmpire::~ProducedByEmpire() -{ delete m_empire_id; } +ProducedByEmpire::ProducedByEmpire(std::unique_ptr>&& empire_id) : + Condition(), + m_empire_id(std::move(empire_id)) +{ + m_root_candidate_invariant = !m_empire_id || m_empire_id->RootCandidateInvariant(); + m_target_invariant = !m_empire_id || m_empire_id->TargetInvariant(); + m_source_invariant = !m_empire_id || m_empire_id->SourceInvariant(); +} -bool ProducedByEmpire::operator==(const ConditionBase& rhs) const { +bool ProducedByEmpire::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -5588,9 +5503,9 @@ namespace { if (!candidate) return false; - if (std::shared_ptr ship = std::dynamic_pointer_cast(candidate)) + if (auto ship = std::dynamic_pointer_cast(candidate)) return ship->ProducedByEmpireID() == m_empire_id; - else if (std::shared_ptr building = std::dynamic_pointer_cast(candidate)) + else if (auto building = std::dynamic_pointer_cast(candidate)) return building->ProducedByEmpireID() == m_empire_id; return false; } @@ -5608,24 +5523,14 @@ void ProducedByEmpire::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate empire id once, and use to check all candidate objects - std::shared_ptr no_object; - int empire_id = m_empire_id->Eval(ScriptingContext(parent_context, no_object)); + int empire_id = m_empire_id->Eval(parent_context); EvalImpl(matches, non_matches, search_domain, ProducedByEmpireSimpleMatch(empire_id)); } else { // re-evaluate empire id for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool ProducedByEmpire::RootCandidateInvariant() const -{ return !m_empire_id || m_empire_id->RootCandidateInvariant(); } - -bool ProducedByEmpire::TargetInvariant() const -{ return !m_empire_id || m_empire_id->TargetInvariant(); } - -bool ProducedByEmpire::SourceInvariant() const -{ return !m_empire_id || m_empire_id->SourceInvariant(); } - std::string ProducedByEmpire::Description(bool negated/* = false*/) const { std::string empire_str; if (m_empire_id) { @@ -5644,11 +5549,11 @@ std::string ProducedByEmpire::Description(bool negated/* = false*/) const { % empire_str); } -std::string ProducedByEmpire::Dump() const -{ return DumpIndent() + "ProducedByEmpire empire_id = " + m_empire_id->Dump(); } +std::string ProducedByEmpire::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "ProducedByEmpire empire_id = " + m_empire_id->Dump(ntabs); } bool ProducedByEmpire::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "ProducedByEmpire::Match passed no candidate object"; return false; @@ -5675,10 +5580,16 @@ unsigned int ProducedByEmpire::GetCheckSum() const { /////////////////////////////////////////////////////////// // Chance // /////////////////////////////////////////////////////////// -Chance::~Chance() -{ delete m_chance; } +Chance::Chance(std::unique_ptr>&& chance) : + Condition(), + m_chance(std::move(chance)) +{ + m_root_candidate_invariant = !m_chance || m_chance->RootCandidateInvariant(); + m_target_invariant = !m_chance || m_chance->TargetInvariant(); + m_source_invariant = !m_chance || m_chance->SourceInvariant(); +} -bool Chance::operator==(const ConditionBase& rhs) const { +bool Chance::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -5713,26 +5624,16 @@ void Chance::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate empire id once, and use to check all candidate objects - std::shared_ptr no_object; - float chance = std::max(0.0, std::min(1.0, m_chance->Eval(ScriptingContext(parent_context, no_object)))); + float chance = std::max(0.0, std::min(1.0, m_chance->Eval(parent_context))); + // chance is tested independently for each candidate object EvalImpl(matches, non_matches, search_domain, ChanceSimpleMatch(chance)); } else { // re-evaluate empire id for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool Chance::RootCandidateInvariant() const -{ return !m_chance || m_chance->RootCandidateInvariant(); } - -bool Chance::TargetInvariant() const -{ return !m_chance || m_chance->TargetInvariant(); } - -bool Chance::SourceInvariant() const -{ return !m_chance || m_chance->SourceInvariant(); } - std::string Chance::Description(bool negated/* = false*/) const { - std::string value_str; if (m_chance->ConstantExpr()) { return str(FlexibleFormat((!negated) ? UserString("DESC_CHANCE_PERCENTAGE") @@ -5746,8 +5647,8 @@ std::string Chance::Description(bool negated/* = false*/) const { } } -std::string Chance::Dump() const -{ return DumpIndent() + "Random probability = " + m_chance->Dump() + "\n"; } +std::string Chance::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "Random probability = " + m_chance->Dump(ntabs) + "\n"; } bool Chance::Match(const ScriptingContext& local_context) const { float chance = std::max(0.0, std::min(m_chance->Eval(local_context), 1.0)); @@ -5772,12 +5673,21 @@ unsigned int Chance::GetCheckSum() const { /////////////////////////////////////////////////////////// // MeterValue // /////////////////////////////////////////////////////////// -MeterValue::~MeterValue() { - delete m_low; - delete m_high; +MeterValue::MeterValue(MeterType meter, + std::unique_ptr>&& low, + std::unique_ptr>&& high) : + Condition(), + m_meter(meter), + m_low(std::move(low)), + m_high(std::move(high)) +{ + auto operands = {m_low.get(), m_high.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -bool MeterValue::operator==(const ConditionBase& rhs) const { +bool MeterValue::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -5827,24 +5737,36 @@ namespace { case METER_TARGET_RESEARCH: return "TargetResearch"; break; case METER_TARGET_TRADE: return "TargetTrade"; break; case METER_TARGET_CONSTRUCTION: return "TargetConstruction"; break; + case METER_TARGET_HAPPINESS: return "TargetHappiness"; break; + case METER_MAX_CAPACITY: return "MaxCapacity"; break; + case METER_MAX_SECONDARY_STAT: return "MaxSecondaryStat"; break; case METER_MAX_FUEL: return "MaxFuel"; break; case METER_MAX_SHIELD: return "MaxShield"; break; case METER_MAX_STRUCTURE: return "MaxStructure"; break; case METER_MAX_DEFENSE: return "MaxDefense"; break; + case METER_MAX_SUPPLY: return "MaxSupply"; break; + case METER_MAX_STOCKPILE: return "MaxStockpile"; break; + case METER_MAX_TROOPS: return "MaxTroops"; break; case METER_POPULATION: return "Population"; break; case METER_INDUSTRY: return "Industry"; break; case METER_RESEARCH: return "Research"; break; case METER_TRADE: return "Trade"; break; case METER_CONSTRUCTION: return "Construction"; break; + case METER_HAPPINESS: return "Happiness"; break; + case METER_CAPACITY: return "Capacity"; break; + case METER_SECONDARY_STAT: return "SecondaryStat"; break; case METER_FUEL: return "Fuel"; break; case METER_SHIELD: return "Shield"; break; case METER_STRUCTURE: return "Structure"; break; case METER_DEFENSE: return "Defense"; break; case METER_SUPPLY: return "Supply"; break; + case METER_STOCKPILE: return "Stockpile"; break; + case METER_TROOPS: return "Troops"; break; + case METER_REBEL_TROOPS: return "RebelTroops"; break; + case METER_SIZE: return "Size"; break; case METER_STEALTH: return "Stealth"; break; case METER_DETECTION: return "Detection"; break; case METER_SPEED: return "Speed"; break; - case METER_CAPACITY: return "Capacity"; break; default: return "?Meter?"; break; } } @@ -5859,26 +5781,15 @@ void MeterValue::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - float low = (m_low ? m_low->Eval(local_context) : -Meter::LARGE_VALUE); - float high = (m_high ? m_high->Eval(local_context) : Meter::LARGE_VALUE); + float low = (m_low ? m_low->Eval(parent_context) : -Meter::LARGE_VALUE); + float high = (m_high ? m_high->Eval(parent_context) : Meter::LARGE_VALUE); EvalImpl(matches, non_matches, search_domain, MeterValueSimpleMatch(low, high, m_meter)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool MeterValue::RootCandidateInvariant() const -{ return (!m_low || m_low->RootCandidateInvariant()) && (!m_high || m_high->RootCandidateInvariant()); } - -bool MeterValue::TargetInvariant() const -{ return (!m_low || m_low->TargetInvariant()) && (!m_high || m_high->TargetInvariant()); } - -bool MeterValue::SourceInvariant() const -{ return (!m_low || m_low->SourceInvariant()) && (!m_high || m_high->SourceInvariant()); } - std::string MeterValue::Description(bool negated/* = false*/) const { std::string low_str = (m_low ? (m_low->ConstantExpr() ? std::to_string(m_low->Eval()) : @@ -5911,19 +5822,19 @@ std::string MeterValue::Description(bool negated/* = false*/) const { } } -std::string MeterValue::Dump() const { - std::string retval = DumpIndent(); +std::string MeterValue::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs); retval += MeterTypeDumpString(m_meter); if (m_low) - retval += " low = " + m_low->Dump(); + retval += " low = " + m_low->Dump(ntabs); if (m_high) - retval += " high = " + m_high->Dump(); + retval += " high = " + m_high->Dump(ntabs); retval += "\n"; return retval; } bool MeterValue::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "MeterValue::Match passed no candidate object"; return false; @@ -5955,13 +5866,29 @@ unsigned int MeterValue::GetCheckSum() const { /////////////////////////////////////////////////////////// // ShipPartMeterValue // /////////////////////////////////////////////////////////// -ShipPartMeterValue::~ShipPartMeterValue() { - delete m_part_name; - delete m_low; - delete m_high; -} - -bool ShipPartMeterValue::operator==(const ConditionBase& rhs) const { +ShipPartMeterValue::ShipPartMeterValue(std::unique_ptr>&& ship_part_name, + MeterType meter, + std::unique_ptr>&& low, + std::unique_ptr>&& high) : + Condition(), + m_part_name(std::move(ship_part_name)), + m_meter(meter), + m_low(std::move(low)), + m_high(std::move(high)) +{ + auto operands = {m_low.get(), m_high.get()}; + m_root_candidate_invariant = + (!m_part_name || m_part_name->RootCandidateInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = + (!m_part_name || m_part_name->TargetInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = + (!m_part_name || m_part_name->SourceInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +bool ShipPartMeterValue::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -5992,7 +5919,7 @@ namespace { bool operator()(std::shared_ptr candidate) const { if (!candidate) return false; - std::shared_ptr ship = std::dynamic_pointer_cast(candidate); + auto ship = std::dynamic_pointer_cast(candidate); if (!ship) return false; const Meter* meter = ship->GetPartMeter(m_meter, m_part_name); @@ -6019,36 +5946,16 @@ void ShipPartMeterValue::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - float low = (m_low ? m_low->Eval(local_context) : -Meter::LARGE_VALUE); - float high = (m_high ? m_high->Eval(local_context) : Meter::LARGE_VALUE); - std::string part_name = (m_part_name ? m_part_name->Eval(local_context) : ""); + float low = (m_low ? m_low->Eval(parent_context) : -Meter::LARGE_VALUE); + float high = (m_high ? m_high->Eval(parent_context) : Meter::LARGE_VALUE); + std::string part_name = (m_part_name ? m_part_name->Eval(parent_context) : ""); EvalImpl(matches, non_matches, search_domain, ShipPartMeterValueSimpleMatch(part_name, m_meter, low, high)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool ShipPartMeterValue::RootCandidateInvariant() const { - return ((!m_part_name || m_part_name->RootCandidateInvariant()) && - (!m_low || m_low->RootCandidateInvariant()) && - (!m_high || m_high->RootCandidateInvariant())); -} - -bool ShipPartMeterValue::TargetInvariant() const { - return ((!m_part_name || m_part_name->TargetInvariant()) && - (!m_low || m_low->TargetInvariant()) && - (!m_high || m_high->TargetInvariant())); -} - -bool ShipPartMeterValue::SourceInvariant() const { - return ((!m_part_name || m_part_name->SourceInvariant()) && - (!m_low || m_low->SourceInvariant()) && - (!m_high || m_high->SourceInvariant())); -} - std::string ShipPartMeterValue::Description(bool negated/* = false*/) const { std::string low_str; if (m_low) @@ -6078,21 +5985,21 @@ std::string ShipPartMeterValue::Description(bool negated/* = false*/) const { % high_str); } -std::string ShipPartMeterValue::Dump() const { - std::string retval = DumpIndent(); +std::string ShipPartMeterValue::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs); retval += MeterTypeDumpString(m_meter); if (m_part_name) - retval += " part = " + m_part_name->Dump(); + retval += " part = " + m_part_name->Dump(ntabs); if (m_low) - retval += " low = " + m_low->Dump(); + retval += " low = " + m_low->Dump(ntabs); if (m_high) - retval += " high = " + m_high->Dump(); + retval += " high = " + m_high->Dump(ntabs); retval += "\n"; return retval; } bool ShipPartMeterValue::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "ShipPartMeterValue::Match passed no candidate object"; return false; @@ -6128,13 +6035,35 @@ unsigned int ShipPartMeterValue::GetCheckSum() const { /////////////////////////////////////////////////////////// // EmpireMeterValue // /////////////////////////////////////////////////////////// -EmpireMeterValue::~EmpireMeterValue() { - delete m_empire_id; - delete m_low; - delete m_high; -} +EmpireMeterValue::EmpireMeterValue(const std::string& meter, + std::unique_ptr>&& low, + std::unique_ptr>&& high) : + EmpireMeterValue(nullptr, meter, std::move(low), std::move(high)) +{} -bool EmpireMeterValue::operator==(const ConditionBase& rhs) const { +EmpireMeterValue::EmpireMeterValue(std::unique_ptr>&& empire_id, + const std::string& meter, + std::unique_ptr>&& low, + std::unique_ptr>&& high) : + Condition(), + m_empire_id(std::move(empire_id)), + m_meter(meter), + m_low(std::move(low)), + m_high(std::move(high)) +{ + auto operands = {m_low.get(), m_high.get()}; + m_root_candidate_invariant = + (!m_empire_id || m_empire_id->RootCandidateInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = + (!m_empire_id || m_empire_id->TargetInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = + (!m_empire_id || m_empire_id->SourceInvariant()) && + boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +bool EmpireMeterValue::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -6154,34 +6083,6 @@ bool EmpireMeterValue::operator==(const ConditionBase& rhs) const { return true; } -namespace { - struct EmpireMeterValueSimpleMatch { - EmpireMeterValueSimpleMatch(int empire_id, float low, float high, const std::string& meter) : - m_empire_id(empire_id), - m_low(low), - m_high(high), - m_meter(meter) - {} - - bool operator()(std::shared_ptr candidate) const { - if (!candidate) - return false; - const Empire* empire = GetEmpire(m_empire_id); - if (!empire) - return false; - const Meter* meter = empire->GetMeter(m_meter); - if (!meter) - return false; - float meter_current = meter->Current(); - return (m_low <= meter_current && meter_current <= m_high); - } - - int m_empire_id; - float m_low; - float m_high; - std::string m_meter; - }; -} void EmpireMeterValue::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, @@ -6192,37 +6093,30 @@ void EmpireMeterValue::Eval(const ScriptingContext& parent_context, (!m_high || m_high->LocalCandidateInvariant()) && (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { - // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - int empire_id = m_empire_id->Eval(local_context); // if m_empire_id not set, default to local candidate's owner, which is not target invariant - float low = (m_low ? m_low->Eval(local_context) : -Meter::LARGE_VALUE); - float high = (m_high ? m_high->Eval(local_context) : Meter::LARGE_VALUE); - EvalImpl(matches, non_matches, search_domain, EmpireMeterValueSimpleMatch(empire_id, low, high, m_meter)); + // If m_empire_id is specified (not null), and all parameters are + // local-candidate-invariant, then matching for this condition doesn't + // need to check each candidate object separately for matching, so + // don't need to use EvalImpl and can instead do a simpler transfer + bool match = Match(parent_context); + + // transfer objects to or from candidate set, according to whether the + // specified empire meter was in the requested range + if (match && search_domain == NON_MATCHES) { + // move all objects from non_matches to matches + matches.insert(matches.end(), non_matches.begin(), non_matches.end()); + non_matches.clear(); + } else if (!match && search_domain == MATCHES) { + // move all objects from matches to non_matches + non_matches.insert(non_matches.end(), matches.begin(), matches.end()); + matches.clear(); + } + } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool EmpireMeterValue::RootCandidateInvariant() const { - return (!m_empire_id || m_empire_id->RootCandidateInvariant()) && - (!m_low || m_low->RootCandidateInvariant()) && - (!m_high || m_high->RootCandidateInvariant()); -} - -bool EmpireMeterValue::TargetInvariant() const { - return (!m_empire_id || m_empire_id->TargetInvariant()) && - (!m_low || m_low->TargetInvariant()) && - (!m_high || m_high->TargetInvariant()); -} - -bool EmpireMeterValue::SourceInvariant() const { - return (!m_empire_id || m_empire_id->SourceInvariant()) && - (!m_low || m_low->SourceInvariant()) && - (!m_high || m_high->SourceInvariant()); -} - std::string EmpireMeterValue::Description(bool negated/* = false*/) const { std::string empire_str; if (m_empire_id) { @@ -6251,31 +6145,56 @@ std::string EmpireMeterValue::Description(bool negated/* = false*/) const { % empire_str); } -std::string EmpireMeterValue::Dump() const { - std::string retval = DumpIndent() + "EmpireMeterValue"; +std::string EmpireMeterValue::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "EmpireMeterValue"; if (m_empire_id) - retval += " empire = " + m_empire_id->Dump(); + retval += " empire = " + m_empire_id->Dump(ntabs); retval += " meter = " + m_meter; if (m_low) - retval += " low = " + m_low->Dump(); + retval += " low = " + m_low->Dump(ntabs); if (m_high) - retval += " high = " + m_high->Dump(); + retval += " high = " + m_high->Dump(ntabs); retval += "\n"; return retval; } bool EmpireMeterValue::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; - if (!candidate) { - ErrorLogger() << "EmpireMeterValue::Match passed no candidate object"; + int empire_id = ALL_EMPIRES; + auto candidate = local_context.condition_local_candidate; + // if m_empire_id not set, default to candidate object's owner + if (!m_empire_id && !candidate) { + ErrorLogger() << "EmpireMeterValue::Match passed no candidate object but expects one due to having no empire id valueref specified and thus wanting to use the local candidate's owner as the empire id"; + return false; + + } else if (m_empire_id && !candidate && !m_empire_id->LocalCandidateInvariant()) { + ErrorLogger() << "EmpireMeterValue::Match passed no candidate object but but empire id valueref references the local candidate"; + return false; + + } else if (!m_empire_id && candidate) { + // default to candidate's owner if no empire id valueref is specified + empire_id = candidate->Owner(); + + } else if (m_empire_id) { + // either candidate exists or m_empire_id is local-candidate-invariant (or both) + empire_id = m_empire_id->Eval(local_context); + + } else { + ErrorLogger() << "EmpireMeterValue::Match reached unexpected default case for candidate and empire id valueref existance"; return false; } - int empire_id = (m_empire_id ? m_empire_id->Eval(local_context) : candidate->Owner()); - if (empire_id == ALL_EMPIRES) + + const Empire* empire = GetEmpire(empire_id); + if (!empire) return false; - float low = (m_low ? m_low->Eval(local_context) : -Meter::LARGE_VALUE); + const Meter* meter = empire->GetMeter(m_meter); + if (!meter) + return false; + + float meter_current = meter->Current(); + float low = (m_low ? m_low->Eval(local_context) : -Meter::LARGE_VALUE); float high = (m_high ? m_high->Eval(local_context) : Meter::LARGE_VALUE); - return EmpireMeterValueSimpleMatch(empire_id, low, high, m_meter)(candidate); + + return (low <= meter_current && meter_current <= high); } void EmpireMeterValue::SetTopLevelContent(const std::string& content_name) { @@ -6303,12 +6222,21 @@ unsigned int EmpireMeterValue::GetCheckSum() const { /////////////////////////////////////////////////////////// // EmpireStockpileValue // /////////////////////////////////////////////////////////// -EmpireStockpileValue::~EmpireStockpileValue() { - delete m_low; - delete m_high; +EmpireStockpileValue::EmpireStockpileValue(ResourceType stockpile, + std::unique_ptr>&& low, + std::unique_ptr>&& high) : + Condition(), + m_stockpile(stockpile), + m_low(std::move(low)), + m_high(std::move(high)) +{ + auto operands = {m_low.get(), m_high.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -bool EmpireStockpileValue::operator==(const ConditionBase& rhs) const { +bool EmpireStockpileValue::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -6325,67 +6253,42 @@ bool EmpireStockpileValue::operator==(const ConditionBase& rhs) const { return true; } -namespace { - struct EmpireStockpileValueSimpleMatch { - EmpireStockpileValueSimpleMatch(float low, float high, ResourceType stockpile) : - m_low(low), - m_high(high), - m_stockpile(stockpile) - {} - - bool operator()(std::shared_ptr candidate) const { - if (!candidate) - return false; - - if (candidate->Unowned()) - return false; - - const Empire* empire = GetEmpire(candidate->Owner()); - if (!empire) - return false; - - if (m_stockpile == RE_TRADE) { - float amount = empire->ResourceStockpile(m_stockpile); - return (m_low <= amount && amount <= m_high); - } - - return false; - } - - float m_low; - float m_high; - ResourceType m_stockpile; - }; -} - void EmpireStockpileValue::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - bool simple_eval_safe = (m_low->LocalCandidateInvariant() && m_high->LocalCandidateInvariant() && + // if m_empire_id not set, the local candidate's owner is used, which is not target invariant + bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) && + (!m_low || m_low->LocalCandidateInvariant()) && + (!m_high || m_high->LocalCandidateInvariant()) && (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { - // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - float low = m_low->Eval(local_context); - float high = m_high->Eval(local_context); - EvalImpl(matches, non_matches, search_domain, EmpireStockpileValueSimpleMatch(low, high, m_stockpile)); - } else { - // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + // If m_empire_id is specified (not null), and all parameters are + // local-candidate-invariant, then matching for this condition doesn't + // need to check each candidate object separately for matching, so + // don't need to use EvalImpl and can instead do a simpler transfer + bool match = Match(parent_context); + + // transfer objects to or from candidate set, according to whether the + // specified empire meter was in the requested range + if (match && search_domain == NON_MATCHES) { + // move all objects from non_matches to matches + matches.insert(matches.end(), non_matches.begin(), non_matches.end()); + non_matches.clear(); + } else if (!match && search_domain == MATCHES) { + // move all objects from matches to non_matches + non_matches.insert(non_matches.end(), matches.begin(), matches.end()); + matches.clear(); + } + + } else { + // re-evaluate all parameters for each candidate object. + // could optimize further by only re-evaluating the local-candidate + // variants. + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool EmpireStockpileValue::RootCandidateInvariant() const -{ return (m_low->RootCandidateInvariant() && m_high->RootCandidateInvariant()); } - -bool EmpireStockpileValue::TargetInvariant() const -{ return (m_low->TargetInvariant() && m_high->TargetInvariant()); } - -bool EmpireStockpileValue::SourceInvariant() const -{ return (m_low->SourceInvariant() && m_high->SourceInvariant()); } - std::string EmpireStockpileValue::Description(bool negated/* = false*/) const { std::string low_str = m_low->ConstantExpr() ? std::to_string(m_low->Eval()) : @@ -6401,31 +6304,66 @@ std::string EmpireStockpileValue::Description(bool negated/* = false*/) const { % high_str); } -std::string EmpireStockpileValue::Dump() const { - std::string retval = DumpIndent(); +std::string EmpireStockpileValue::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs); switch (m_stockpile) { case RE_TRADE: retval += "OwnerTradeStockpile"; break; case RE_RESEARCH: retval += "OwnerResearchStockpile"; break; case RE_INDUSTRY: retval += "OwnerIndustryStockpile"; break; - default: retval += "?"; break; + default: retval += "?"; break; } - retval += " low = " + m_low->Dump() + " high = " + m_high->Dump() + "\n"; + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs); + if (m_low) + retval += " low = " + m_low->Dump(ntabs); + if (m_high) + retval += " high = " + m_high->Dump(ntabs); + retval += "\n"; return retval; } bool EmpireStockpileValue::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; - if (!candidate) { - ErrorLogger() << "EmpireStockpileValue::Match passed no candidate object"; + int empire_id = ALL_EMPIRES; + auto candidate = local_context.condition_local_candidate; + // if m_empire_id not set, default to candidate object's owner + if (!m_empire_id && !candidate) { + ErrorLogger() << "EmpireStockpileValue::Match passed no candidate object but expects one due to having no empire id valueref specified and thus wanting to use the local candidate's owner as the empire id"; + return false; + + } else if (m_empire_id && !candidate && !m_empire_id->LocalCandidateInvariant()) { + ErrorLogger() << "EmpireStockpileValue::Match passed no candidate object but but empire id valueref references the local candidate"; + return false; + + } else if (!m_empire_id && candidate) { + // default to candidate's owner if no empire id valueref is specified + empire_id = candidate->Owner(); + + } else if (m_empire_id) { + // either candidate exists or m_empire_id is local-candidate-invariant (or both) + empire_id = m_empire_id->Eval(local_context); + + } else { + ErrorLogger() << "EmpireStockpileValue::Match reached unexpected default case for candidate and empire id valueref existance"; return false; } - float low = m_low->Eval(local_context); - float high = m_high->Eval(local_context); - return EmpireStockpileValueSimpleMatch(low, high, m_stockpile)(candidate); + const Empire* empire = GetEmpire(empire_id); + if (!empire) + return false; + + try { + float low = (m_low ? m_low->Eval(local_context) : -Meter::LARGE_VALUE); + float high = (m_high ? m_high->Eval(local_context) : Meter::LARGE_VALUE); + float amount = empire->ResourceStockpile(m_stockpile); + return (low <= amount && amount <= high); + } catch (...) { + return false; + } } void EmpireStockpileValue::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); if (m_low) m_low->SetTopLevelContent(content_name); if (m_high) @@ -6436,6 +6374,7 @@ unsigned int EmpireStockpileValue::GetCheckSum() const { unsigned int retval{0}; CheckSums::CheckSumCombine(retval, "Condition::EmpireStockpileValue"); + CheckSums::CheckSumCombine(retval, m_empire_id); CheckSums::CheckSumCombine(retval, m_stockpile); CheckSums::CheckSumCombine(retval, m_low); CheckSums::CheckSumCombine(retval, m_high); @@ -6447,10 +6386,28 @@ unsigned int EmpireStockpileValue::GetCheckSum() const { /////////////////////////////////////////////////////////// // OwnerHasTech // /////////////////////////////////////////////////////////// -OwnerHasTech::~OwnerHasTech() -{ delete m_name; } +OwnerHasTech::OwnerHasTech(std::unique_ptr>&& empire_id, + std::unique_ptr>&& name) : + Condition(), + m_name(std::move(name)), + m_empire_id(std::move(empire_id)) +{ + m_root_candidate_invariant = + (!m_empire_id || m_empire_id->RootCandidateInvariant()) && + (!m_name || m_name->RootCandidateInvariant()); + m_target_invariant = + (!m_empire_id || m_empire_id->TargetInvariant()) && + (!m_name || m_name->TargetInvariant()); + m_source_invariant = + (!m_empire_id || m_empire_id->SourceInvariant()) && + (!m_name || m_name->SourceInvariant()); +} + +OwnerHasTech::OwnerHasTech(std::unique_ptr>&& name) : + OwnerHasTech(nullptr, std::move(name)) +{} -bool OwnerHasTech::operator==(const ConditionBase& rhs) const { +bool OwnerHasTech::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -6458,6 +6415,9 @@ bool OwnerHasTech::operator==(const ConditionBase& rhs) const { const OwnerHasTech& rhs_ = static_cast(rhs); + if (m_empire_id != rhs_.m_empire_id) + return false; + CHECK_COND_VREF_MEMBER(m_name) return true; @@ -6465,7 +6425,8 @@ bool OwnerHasTech::operator==(const ConditionBase& rhs) const { namespace { struct OwnerHasTechSimpleMatch { - OwnerHasTechSimpleMatch(const std::string& name) : + OwnerHasTechSimpleMatch(int empire_id, const std::string& name) : + m_empire_id(empire_id), m_name(name) {} @@ -6473,15 +6434,21 @@ namespace { if (!candidate) return false; - if (candidate->Unowned()) - return false; + int actual_empire_id = m_empire_id; + if (m_empire_id == ALL_EMPIRES) { + if (candidate->Unowned()) + return false; + actual_empire_id = candidate->Owner(); + } - if (const Empire* empire = GetEmpire(candidate->Owner())) - return empire->TechResearched(m_name); + const Empire* empire = GetEmpire(actual_empire_id); + if (!empire) + return false; - return false; + return empire->TechResearched(m_name); } + int m_empire_id; std::string m_name; }; } @@ -6490,29 +6457,21 @@ void OwnerHasTech::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - bool simple_eval_safe = (!m_name || m_name->LocalCandidateInvariant()) && - (parent_context.condition_root_candidate || RootCandidateInvariant()); + // if m_empire_id not set, the local candidate's owner is used, which is not target invariant + bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) && + (!m_name || m_name->LocalCandidateInvariant()) && + (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - std::string name = m_name ? m_name->Eval(local_context) : ""; - EvalImpl(matches, non_matches, search_domain, OwnerHasTechSimpleMatch(name)); + int empire_id = m_empire_id->Eval(parent_context); // check above should ensure m_empire_id is non-null + std::string name = m_name ? m_name->Eval(parent_context) : ""; + EvalImpl(matches, non_matches, search_domain, OwnerHasTechSimpleMatch(empire_id, name)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool OwnerHasTech::RootCandidateInvariant() const -{ return !m_name || m_name->RootCandidateInvariant(); } - -bool OwnerHasTech::TargetInvariant() const -{ return !m_name || m_name->TargetInvariant(); } - -bool OwnerHasTech::SourceInvariant() const -{ return !m_name || m_name->SourceInvariant(); } - std::string OwnerHasTech::Description(bool negated/* = false*/) const { std::string name_str; if (m_name) { @@ -6526,26 +6485,34 @@ std::string OwnerHasTech::Description(bool negated/* = false*/) const { % name_str); } -std::string OwnerHasTech::Dump() const { - std::string retval = DumpIndent() + "OwnerHasTech"; +std::string OwnerHasTech::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "OwnerHasTech"; + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs); if (m_name) - retval += " name = " + m_name->Dump(); + retval += " name = " + m_name->Dump(ntabs); retval += "\n"; return retval; } bool OwnerHasTech::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "OwnerHasTech::Match passed no candidate object"; return false; } + int empire_id = (m_empire_id ? m_empire_id->Eval(local_context) : candidate->Owner()); + if (empire_id == ALL_EMPIRES) + return false; std::string name = m_name ? m_name->Eval(local_context) : ""; - return OwnerHasTechSimpleMatch(name)(candidate); + + return OwnerHasTechSimpleMatch(empire_id, name)(candidate); } void OwnerHasTech::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); if (m_name) m_name->SetTopLevelContent(content_name); } @@ -6554,6 +6521,7 @@ unsigned int OwnerHasTech::GetCheckSum() const { unsigned int retval{0}; CheckSums::CheckSumCombine(retval, "Condition::OwnerHasTech"); + CheckSums::CheckSumCombine(retval, m_empire_id); CheckSums::CheckSumCombine(retval, m_name); TraceLogger() << "GetCheckSum(OwnerHasTech): retval: " << retval; @@ -6563,15 +6531,33 @@ unsigned int OwnerHasTech::GetCheckSum() const { /////////////////////////////////////////////////////////// // OwnerHasBuildingTypeAvailable // /////////////////////////////////////////////////////////// +OwnerHasBuildingTypeAvailable::OwnerHasBuildingTypeAvailable( + std::unique_ptr>&& empire_id, + std::unique_ptr>&& name) : + Condition(), + m_name(std::move(name)), + m_empire_id(std::move(empire_id)) +{ + m_root_candidate_invariant = + (!m_empire_id || m_empire_id->RootCandidateInvariant()) && + (!m_name || m_name->RootCandidateInvariant()); + m_target_invariant = + (!m_empire_id || m_empire_id->TargetInvariant()) && + (!m_name || m_name->TargetInvariant()); + m_source_invariant = + (!m_empire_id || m_empire_id->SourceInvariant()) && + (!m_name || m_name->SourceInvariant()); +} + OwnerHasBuildingTypeAvailable::OwnerHasBuildingTypeAvailable(const std::string& name) : - ConditionBase(), - m_name(new ValueRef::Constant(name)) + OwnerHasBuildingTypeAvailable(nullptr, std::move(std::make_unique>(name))) {} -OwnerHasBuildingTypeAvailable::~OwnerHasBuildingTypeAvailable() -{ delete m_name; } +OwnerHasBuildingTypeAvailable::OwnerHasBuildingTypeAvailable(std::unique_ptr>&& name) : + OwnerHasBuildingTypeAvailable(nullptr, std::move(name)) +{} -bool OwnerHasBuildingTypeAvailable::operator==(const ConditionBase& rhs) const { +bool OwnerHasBuildingTypeAvailable::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -6579,6 +6565,9 @@ bool OwnerHasBuildingTypeAvailable::operator==(const ConditionBase& rhs) const { const OwnerHasBuildingTypeAvailable& rhs_ = static_cast(rhs); + if (m_empire_id != rhs_.m_empire_id) + return false; + CHECK_COND_VREF_MEMBER(m_name) return true; @@ -6586,7 +6575,8 @@ bool OwnerHasBuildingTypeAvailable::operator==(const ConditionBase& rhs) const { namespace { struct OwnerHasBuildingTypeAvailableSimpleMatch { - OwnerHasBuildingTypeAvailableSimpleMatch(const std::string& name) : + OwnerHasBuildingTypeAvailableSimpleMatch(int empire_id, const std::string& name) : + m_empire_id(empire_id), m_name(name) {} @@ -6594,15 +6584,21 @@ namespace { if (!candidate) return false; - if (candidate->Unowned()) - return false; + int actual_empire_id = m_empire_id; + if (m_empire_id == ALL_EMPIRES) { + if (candidate->Unowned()) + return false; + actual_empire_id = candidate->Owner(); + } - if (const Empire* empire = GetEmpire(candidate->Owner())) - return empire->BuildingTypeAvailable(m_name); + const Empire* empire = GetEmpire(actual_empire_id); + if (!empire) + return false; - return false; + return empire->BuildingTypeAvailable(m_name); } + int m_empire_id; std::string m_name; }; } @@ -6611,29 +6607,21 @@ void OwnerHasBuildingTypeAvailable::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - bool simple_eval_safe = (!m_name || m_name->LocalCandidateInvariant()) && - (parent_context.condition_root_candidate || RootCandidateInvariant()); + // if m_empire_id not set, the local candidate's owner is used, which is not target invariant + bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) && + (!m_name || m_name->LocalCandidateInvariant()) && + (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - std::string name = m_name ? m_name->Eval(local_context) : ""; - EvalImpl(matches, non_matches, search_domain, OwnerHasBuildingTypeAvailableSimpleMatch(name)); + int empire_id = m_empire_id->Eval(parent_context); // check above should ensure m_empire_id is non-null + std::string name = m_name ? m_name->Eval(parent_context) : ""; + EvalImpl(matches, non_matches, search_domain, OwnerHasBuildingTypeAvailableSimpleMatch(empire_id, name)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool OwnerHasBuildingTypeAvailable::RootCandidateInvariant() const -{ return !m_name || m_name->RootCandidateInvariant(); } - -bool OwnerHasBuildingTypeAvailable::TargetInvariant() const -{ return !m_name || m_name->TargetInvariant(); } - -bool OwnerHasBuildingTypeAvailable::SourceInvariant() const -{ return !m_name || m_name->SourceInvariant(); } - std::string OwnerHasBuildingTypeAvailable::Description(bool negated/* = false*/) const { // used internally for a tooltip where context is apparent, so don't need // to name builing type here @@ -6642,26 +6630,34 @@ std::string OwnerHasBuildingTypeAvailable::Description(bool negated/* = false*/) : UserString("DESC_OWNER_HAS_BUILDING_TYPE_NOT"); } -std::string OwnerHasBuildingTypeAvailable::Dump() const { - std::string retval= DumpIndent() + "OwnerHasBuildingTypeAvailable"; +std::string OwnerHasBuildingTypeAvailable::Dump(unsigned short ntabs) const { + std::string retval= DumpIndent(ntabs) + "OwnerHasBuildingTypeAvailable"; + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs); if (m_name) - retval += " name = " + m_name->Dump(); + retval += " name = " + m_name->Dump(ntabs); retval += "\n"; return retval; } bool OwnerHasBuildingTypeAvailable::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "OwnerHasTech::Match passed no candidate object"; return false; } + int empire_id = (m_empire_id ? m_empire_id->Eval(local_context) : candidate->Owner()); + if (empire_id == ALL_EMPIRES) + return false; std::string name = m_name ? m_name->Eval(local_context) : ""; - return OwnerHasBuildingTypeAvailableSimpleMatch(name)(candidate); + + return OwnerHasBuildingTypeAvailableSimpleMatch(empire_id, name)(candidate); } void OwnerHasBuildingTypeAvailable::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); if (m_name) m_name->SetTopLevelContent(content_name); } @@ -6670,6 +6666,7 @@ unsigned int OwnerHasBuildingTypeAvailable::GetCheckSum() const { unsigned int retval{0}; CheckSums::CheckSumCombine(retval, "Condition::OwnerHasBuildingTypeAvailable"); + CheckSums::CheckSumCombine(retval, m_empire_id); CheckSums::CheckSumCombine(retval, m_name); TraceLogger() << "GetCheckSum(OwnerHasBuildingTypeAvailable): retval: " << retval; @@ -6679,15 +6676,28 @@ unsigned int OwnerHasBuildingTypeAvailable::GetCheckSum() const { /////////////////////////////////////////////////////////// // OwnerHasShipDesignAvailable // /////////////////////////////////////////////////////////// -OwnerHasShipDesignAvailable::OwnerHasShipDesignAvailable(int id) : - ConditionBase(), - m_id(new ValueRef::Constant(id)) +OwnerHasShipDesignAvailable::OwnerHasShipDesignAvailable( + std::unique_ptr>&& empire_id, + std::unique_ptr>&& design_id) : + Condition(), + m_id(std::move(design_id)), + m_empire_id(std::move(empire_id)) +{ + auto operands = {m_id.get(), m_empire_id.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +OwnerHasShipDesignAvailable::OwnerHasShipDesignAvailable(int design_id) : + OwnerHasShipDesignAvailable(nullptr, std::move(std::make_unique>(design_id))) {} -OwnerHasShipDesignAvailable::~OwnerHasShipDesignAvailable() -{ delete m_id; } +OwnerHasShipDesignAvailable::OwnerHasShipDesignAvailable(std::unique_ptr>&& design_id) : + OwnerHasShipDesignAvailable(nullptr, std::move(design_id)) +{} -bool OwnerHasShipDesignAvailable::operator==(const ConditionBase& rhs) const { +bool OwnerHasShipDesignAvailable::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -6695,6 +6705,9 @@ bool OwnerHasShipDesignAvailable::operator==(const ConditionBase& rhs) const { const OwnerHasShipDesignAvailable& rhs_ = static_cast(rhs); + if (m_empire_id != rhs_.m_empire_id) + return false; + CHECK_COND_VREF_MEMBER(m_id) return true; @@ -6702,23 +6715,30 @@ bool OwnerHasShipDesignAvailable::operator==(const ConditionBase& rhs) const { namespace { struct OwnerHasShipDesignAvailableSimpleMatch { - OwnerHasShipDesignAvailableSimpleMatch(int id) : - m_id(id) + OwnerHasShipDesignAvailableSimpleMatch(int empire_id, int design_id) : + m_empire_id(empire_id), + m_id(design_id) {} bool operator()(std::shared_ptr candidate) const { if (!candidate) return false; - if (candidate->Unowned()) - return false; + int actual_empire_id = m_empire_id; + if (m_empire_id == ALL_EMPIRES) { + if (candidate->Unowned()) + return false; + actual_empire_id = candidate->Owner(); + } - if (const Empire* empire = GetEmpire(candidate->Owner())) - return empire->ShipDesignAvailable(m_id); + const Empire* empire = GetEmpire(actual_empire_id); + if (!empire) + return false; - return false; + return empire->ShipDesignAvailable(m_id); } + int m_empire_id; int m_id; }; } @@ -6727,29 +6747,21 @@ void OwnerHasShipDesignAvailable::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - bool simple_eval_safe = (!m_id || m_id->LocalCandidateInvariant()) && - (parent_context.condition_root_candidate || RootCandidateInvariant()); + // if m_empire_id not set, the local candidate's owner is used, which is not target invariant + bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) && + (!m_id || m_id->LocalCandidateInvariant()) && + (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - int id = m_id ? m_id->Eval(local_context) : INVALID_DESIGN_ID; - EvalImpl(matches, non_matches, search_domain, OwnerHasShipDesignAvailableSimpleMatch(id)); + int empire_id = m_empire_id->Eval(parent_context); // check above should ensure m_empire_id is non-null + int design_id = m_id ? m_id->Eval(parent_context) : INVALID_DESIGN_ID; + EvalImpl(matches, non_matches, search_domain, OwnerHasShipDesignAvailableSimpleMatch(empire_id, design_id)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool OwnerHasShipDesignAvailable::RootCandidateInvariant() const -{ return !m_id || m_id->RootCandidateInvariant(); } - -bool OwnerHasShipDesignAvailable::TargetInvariant() const -{ return !m_id || m_id->TargetInvariant(); } - -bool OwnerHasShipDesignAvailable::SourceInvariant() const -{ return !m_id || m_id->SourceInvariant(); } - std::string OwnerHasShipDesignAvailable::Description(bool negated/* = false*/) const { // used internally for a tooltip where context is apparent, so don't need // to specify design here @@ -6758,26 +6770,34 @@ std::string OwnerHasShipDesignAvailable::Description(bool negated/* = false*/) c : UserString("DESC_OWNER_HAS_SHIP_DESIGN_NOT"); } -std::string OwnerHasShipDesignAvailable::Dump() const { - std::string retval = DumpIndent() + "OwnerHasShipDesignAvailable"; +std::string OwnerHasShipDesignAvailable::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "OwnerHasShipDesignAvailable"; + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs); if (m_id) - retval += " id = " + m_id->Dump(); + retval += " id = " + m_id->Dump(ntabs); retval += "\n"; return retval; } bool OwnerHasShipDesignAvailable::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "OwnerHasTech::Match passed no candidate object"; return false; } - int id = m_id ? m_id->Eval(local_context) : INVALID_DESIGN_ID; - return OwnerHasShipDesignAvailableSimpleMatch(id)(candidate); + int empire_id = (m_empire_id ? m_empire_id->Eval(local_context) : candidate->Owner()); + if (empire_id == ALL_EMPIRES) + return false; + int design_id = m_id ? m_id->Eval(local_context) : INVALID_DESIGN_ID; + + return OwnerHasShipDesignAvailableSimpleMatch(empire_id, design_id)(candidate); } void OwnerHasShipDesignAvailable::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); if (m_id) m_id->SetTopLevelContent(content_name); } @@ -6786,6 +6806,7 @@ unsigned int OwnerHasShipDesignAvailable::GetCheckSum() const { unsigned int retval{0}; CheckSums::CheckSumCombine(retval, "Condition::OwnerHasShipDesignAvailable"); + CheckSums::CheckSumCombine(retval, m_empire_id); CheckSums::CheckSumCombine(retval, m_id); TraceLogger() << "GetCheckSum(OwnerHasShipDesignAvailable): retval: " << retval; @@ -6795,15 +6816,33 @@ unsigned int OwnerHasShipDesignAvailable::GetCheckSum() const { /////////////////////////////////////////////////////////// // OwnerHasShipPartAvailable // /////////////////////////////////////////////////////////// +OwnerHasShipPartAvailable::OwnerHasShipPartAvailable( + std::unique_ptr>&& empire_id, + std::unique_ptr>&& name) : + Condition(), + m_name(std::move(name)), + m_empire_id(std::move(empire_id)) +{ + m_root_candidate_invariant = + (!m_empire_id || m_empire_id->RootCandidateInvariant()) && + (!m_name || m_name->RootCandidateInvariant()); + m_target_invariant = + (!m_empire_id || m_empire_id->TargetInvariant()) && + (!m_name || m_name->TargetInvariant()); + m_source_invariant = + (!m_empire_id || m_empire_id->TargetInvariant()) && + (!m_name || m_name->TargetInvariant()); +} + OwnerHasShipPartAvailable::OwnerHasShipPartAvailable(const std::string& name) : - ConditionBase(), - m_name(new ValueRef::Constant(name)) + OwnerHasShipPartAvailable(nullptr, std::move(std::make_unique>(name))) {} -OwnerHasShipPartAvailable::~OwnerHasShipPartAvailable() -{ delete m_name; } +OwnerHasShipPartAvailable::OwnerHasShipPartAvailable(std::unique_ptr>&& name) : + OwnerHasShipPartAvailable(nullptr, std::move(name)) +{} -bool OwnerHasShipPartAvailable::operator==(const ConditionBase& rhs) const { +bool OwnerHasShipPartAvailable::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -6812,6 +6851,9 @@ bool OwnerHasShipPartAvailable::operator==(const ConditionBase& rhs) const { const OwnerHasShipPartAvailable& rhs_ = static_cast(rhs); + if (m_empire_id != rhs_.m_empire_id) + return false; + CHECK_COND_VREF_MEMBER(m_name) return true; @@ -6819,7 +6861,8 @@ bool OwnerHasShipPartAvailable::operator==(const ConditionBase& rhs) const { namespace { struct OwnerHasShipPartAvailableSimpleMatch { - OwnerHasShipPartAvailableSimpleMatch(const std::string& name) : + OwnerHasShipPartAvailableSimpleMatch(int empire_id, const std::string& name) : + m_empire_id(empire_id), m_name(name) {} @@ -6827,15 +6870,21 @@ namespace { if (!candidate) return false; - if (candidate->Unowned()) - return false; + int actual_empire_id = m_empire_id; + if (m_empire_id == ALL_EMPIRES) { + if (candidate->Unowned()) + return false; + actual_empire_id = candidate->Owner(); + } - if (const Empire* empire = GetEmpire(candidate->Owner())) - return empire->ShipPartAvailable(m_name); + const Empire* empire = GetEmpire(actual_empire_id); + if (!empire) + return false; - return false; + return empire->ShipPartAvailable(m_name); } + int m_empire_id; std::string m_name; }; } @@ -6844,58 +6893,55 @@ void OwnerHasShipPartAvailable::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - bool simple_eval_safe = (!m_name || m_name->LocalCandidateInvariant()) && - (parent_context.condition_root_candidate || - RootCandidateInvariant()); + // if m_empire_id not set, the local candidate's owner is used, which is not target invariant + bool simple_eval_safe = ((m_empire_id && m_empire_id->LocalCandidateInvariant()) && + (!m_name || m_name->LocalCandidateInvariant()) && + (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate number limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - std::string name = m_name ? m_name->Eval(local_context) : ""; - EvalImpl(matches, non_matches, search_domain, - OwnerHasShipPartAvailableSimpleMatch(name)); + int empire_id = m_empire_id->Eval(parent_context); // check above should ensure m_empire_id is non-null + std::string name = m_name ? m_name->Eval(parent_context) : ""; + EvalImpl(matches, non_matches, search_domain, OwnerHasShipPartAvailableSimpleMatch(empire_id, name)); } else { // re-evaluate allowed turn range for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool OwnerHasShipPartAvailable::RootCandidateInvariant() const -{ return !m_name || m_name->RootCandidateInvariant(); } - -bool OwnerHasShipPartAvailable::TargetInvariant() const -{ return !m_name || m_name->TargetInvariant(); } - -bool OwnerHasShipPartAvailable::SourceInvariant() const -{ return !m_name || m_name->SourceInvariant(); } - std::string OwnerHasShipPartAvailable::Description(bool negated/* = false*/) const { return (!negated) ? UserString("DESC_OWNER_HAS_SHIP_PART") : UserString("DESC_OWNER_HAS_SHIP_PART_NOT"); } -std::string OwnerHasShipPartAvailable::Dump() const { - std::string retval = DumpIndent() + "OwnerHasShipPartAvailable"; +std::string OwnerHasShipPartAvailable::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "OwnerHasShipPartAvailable"; + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs); if (m_name) - retval += " name = " + m_name->Dump(); + retval += " name = " + m_name->Dump(ntabs); retval += "\n"; return retval; } bool OwnerHasShipPartAvailable::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = - local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "OwnerHasShipPart::Match passed no candidate object"; return false; } + int empire_id = (m_empire_id ? m_empire_id->Eval(local_context) : candidate->Owner()); + if (empire_id == ALL_EMPIRES) + return false; std::string name = m_name ? m_name->Eval(local_context) : ""; - return OwnerHasShipPartAvailableSimpleMatch(name)(candidate); + + return OwnerHasShipPartAvailableSimpleMatch(empire_id, name)(candidate); } void OwnerHasShipPartAvailable::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); if (m_name) m_name->SetTopLevelContent(content_name); } @@ -6904,6 +6950,7 @@ unsigned int OwnerHasShipPartAvailable::GetCheckSum() const { unsigned int retval{0}; CheckSums::CheckSumCombine(retval, "Condition::OwnerHasShipPartAvailable"); + CheckSums::CheckSumCombine(retval, m_empire_id); CheckSums::CheckSumCombine(retval, m_name); TraceLogger() << "GetCheckSum(OwnerHasShipPartAvailable): retval: " << retval; @@ -6913,10 +6960,16 @@ unsigned int OwnerHasShipPartAvailable::GetCheckSum() const { /////////////////////////////////////////////////////////// // VisibleToEmpire // /////////////////////////////////////////////////////////// -VisibleToEmpire::~VisibleToEmpire() -{ delete m_empire_id; } +VisibleToEmpire::VisibleToEmpire(std::unique_ptr>&& empire_id) : + Condition(), + m_empire_id(std::move(empire_id)) +{ + m_root_candidate_invariant = !m_empire_id || m_empire_id->RootCandidateInvariant(); + m_target_invariant = !m_empire_id || m_empire_id->TargetInvariant(); + m_source_invariant = !m_empire_id || m_empire_id->SourceInvariant(); +} -bool VisibleToEmpire::operator==(const ConditionBase& rhs) const { +bool VisibleToEmpire::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -6931,17 +6984,33 @@ bool VisibleToEmpire::operator==(const ConditionBase& rhs) const { namespace { struct VisibleToEmpireSimpleMatch { - VisibleToEmpireSimpleMatch(int empire_id) : - m_empire_id(empire_id) + VisibleToEmpireSimpleMatch(int empire_id, + const Universe::EmpireObjectVisibilityMap& vis_map) : + m_empire_id(empire_id), + vis_map(vis_map) {} bool operator()(std::shared_ptr candidate) const { if (!candidate) return false; - return candidate->GetVisibility(m_empire_id) != VIS_NO_VISIBILITY; + + // if override is empty, use universe state + if (vis_map.empty()) + return candidate->GetVisibility(m_empire_id) > VIS_NO_VISIBILITY; + + // if override specified, get visibility info from it + auto empire_it = vis_map.find(m_empire_id); + if (empire_it == vis_map.end()) + return false; + const auto& object_map = empire_it->second; + auto object_it = object_map.find(candidate->ID()); + if (object_it == object_map.end()) + return false; + return object_it->second > VIS_NO_VISIBILITY; } int m_empire_id; + const Universe::EmpireObjectVisibilityMap& vis_map; }; } @@ -6954,24 +7023,17 @@ void VisibleToEmpire::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate empire id once, and use to check all candidate objects - std::shared_ptr no_object; - int empire_id = m_empire_id->Eval(ScriptingContext(parent_context, no_object)); - EvalImpl(matches, non_matches, search_domain, VisibleToEmpireSimpleMatch(empire_id)); + int empire_id = m_empire_id->Eval(parent_context); + + // need to check visibility of each candidate object separately + EvalImpl(matches, non_matches, search_domain, + VisibleToEmpireSimpleMatch(empire_id, parent_context.combat_info.empire_object_visibility)); } else { // re-evaluate empire id for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool VisibleToEmpire::RootCandidateInvariant() const -{ return m_empire_id->RootCandidateInvariant(); } - -bool VisibleToEmpire::TargetInvariant() const -{ return m_empire_id->TargetInvariant(); } - -bool VisibleToEmpire::SourceInvariant() const -{ return m_empire_id->SourceInvariant(); } - std::string VisibleToEmpire::Description(bool negated/* = false*/) const { std::string empire_str; if (m_empire_id) { @@ -6990,17 +7052,23 @@ std::string VisibleToEmpire::Description(bool negated/* = false*/) const { % empire_str); } -std::string VisibleToEmpire::Dump() const -{ return DumpIndent() + "VisibleToEmpire empire_id = " + m_empire_id->Dump() + "\n"; } +std::string VisibleToEmpire::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "VisibleToEmpire"; + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs); + retval += "\n"; + return retval; +} bool VisibleToEmpire::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "VisibleToEmpire::Match passed no candidate object"; return false; } - return candidate->GetVisibility(m_empire_id->Eval(local_context)) != VIS_NO_VISIBILITY; + int empire_id = m_empire_id->Eval(local_context); + return VisibleToEmpireSimpleMatch(empire_id, local_context.combat_info.empire_object_visibility)(candidate); } void VisibleToEmpire::SetTopLevelContent(const std::string& content_name) { @@ -7021,12 +7089,24 @@ unsigned int VisibleToEmpire::GetCheckSum() const { /////////////////////////////////////////////////////////// // WithinDistance // /////////////////////////////////////////////////////////// -WithinDistance::~WithinDistance() { - delete m_distance; - delete m_condition; -} - -bool WithinDistance::operator==(const ConditionBase& rhs) const { +WithinDistance::WithinDistance(std::unique_ptr>&& distance, + std::unique_ptr&& condition) : + Condition(), + m_distance(std::move(distance)), + m_condition(std::move(condition)) +{ + m_root_candidate_invariant = + m_distance->RootCandidateInvariant() && + m_condition->RootCandidateInvariant(); + m_target_invariant = + m_distance->TargetInvariant() && + m_condition->TargetInvariant(); + m_source_invariant = + m_distance->SourceInvariant() && + m_condition->SourceInvariant(); +} + +bool WithinDistance::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -7052,7 +7132,7 @@ namespace { return false; // is candidate object close enough to any of the passed-in objects? - for (std::shared_ptr obj : m_from_objects) { + for (auto& obj : m_from_objects) { double delta_x = candidate->X() - obj->X(); double delta_y = candidate->Y() - obj->Y(); if (delta_x*delta_x + delta_y*delta_y <= m_distance2) @@ -7071,36 +7151,26 @@ void WithinDistance::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - bool simple_eval_safe = m_distance->LocalCandidateInvariant() - && (parent_context.condition_root_candidate || RootCandidateInvariant()); + bool simple_eval_safe = m_distance->LocalCandidateInvariant() && + (parent_context.condition_root_candidate || RootCandidateInvariant()); if (simple_eval_safe) { - // evaluate contained objects once and check for all candidates - - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); + // evaluate contained objects and distance once and check for all candidates + TraceLogger(conditions) << "WithinDistance::Eval simple case"; // get subcondition matches ObjectSet subcondition_matches; - m_condition->Eval(local_context, subcondition_matches); - - double distance = m_distance->Eval(local_context); + m_condition->Eval(parent_context, subcondition_matches); + double distance = m_distance->Eval(parent_context); + // need to check locations (with respect to subcondition matches) of candidates separately EvalImpl(matches, non_matches, search_domain, WithinDistanceSimpleMatch(subcondition_matches, distance)); } else { // re-evaluate contained objects for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + TraceLogger(conditions) << "WithinDistance::Eval full case"; + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool WithinDistance::RootCandidateInvariant() const -{ return m_distance->RootCandidateInvariant() && m_condition->RootCandidateInvariant(); } - -bool WithinDistance::TargetInvariant() const -{ return m_distance->TargetInvariant() && m_condition->TargetInvariant(); } - -bool WithinDistance::SourceInvariant() const -{ return m_distance->SourceInvariant() && m_condition->SourceInvariant(); } - std::string WithinDistance::Description(bool negated/* = false*/) const { std::string value_str = m_distance->ConstantExpr() ? std::to_string(m_distance->Eval()) : @@ -7112,16 +7182,14 @@ std::string WithinDistance::Description(bool negated/* = false*/) const { % m_condition->Description()); } -std::string WithinDistance::Dump() const { - std::string retval = DumpIndent() + "WithinDistance distance = " + m_distance->Dump() + " condition =\n"; - ++g_indent; - retval += m_condition->Dump(); - --g_indent; +std::string WithinDistance::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "WithinDistance distance = " + m_distance->Dump(ntabs) + " condition =\n"; + retval += m_condition->Dump(ntabs+1); return retval; } bool WithinDistance::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "WithinDistance::Match passed no candidate object"; return false; @@ -7157,12 +7225,18 @@ unsigned int WithinDistance::GetCheckSum() const { /////////////////////////////////////////////////////////// // WithinStarlaneJumps // /////////////////////////////////////////////////////////// -WithinStarlaneJumps::~WithinStarlaneJumps() { - delete m_jumps; - delete m_condition; +WithinStarlaneJumps::WithinStarlaneJumps(std::unique_ptr>&& jumps, + std::unique_ptr&& condition) : + Condition(), + m_jumps(std::move(jumps)), + m_condition(std::move(condition)) +{ + m_root_candidate_invariant = m_jumps->RootCandidateInvariant() && m_condition->RootCandidateInvariant(); + m_target_invariant = m_jumps->TargetInvariant() && m_condition->TargetInvariant(); + m_source_invariant = m_jumps->SourceInvariant() && m_condition->SourceInvariant(); } -bool WithinStarlaneJumps::operator==(const ConditionBase& rhs) const { +bool WithinStarlaneJumps::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -7180,36 +7254,25 @@ void WithinStarlaneJumps::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - bool simple_eval_safe = m_jumps->LocalCandidateInvariant() - && (parent_context.condition_root_candidate || RootCandidateInvariant()); + bool simple_eval_safe = m_jumps->LocalCandidateInvariant() && + (parent_context.condition_root_candidate || RootCandidateInvariant()); if (simple_eval_safe) { - // evaluate contained objects once and check for all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); + // evaluate contained objects and jumps limit once and check for all candidates // get subcondition matches ObjectSet subcondition_matches; - m_condition->Eval(local_context, subcondition_matches); - int jump_limit = m_jumps->Eval(local_context); - ObjectSet &from_set(search_domain == Condition::MATCHES ? matches : non_matches); + m_condition->Eval(parent_context, subcondition_matches); + int jump_limit = m_jumps->Eval(parent_context); + ObjectSet &from_set(search_domain == MATCHES ? matches : non_matches); std::tie(matches, non_matches) = GetPathfinder()->WithinJumpsOfOthers(jump_limit, from_set, subcondition_matches); } else { // re-evaluate contained objects for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool WithinStarlaneJumps::RootCandidateInvariant() const -{ return m_jumps->RootCandidateInvariant() && m_condition->RootCandidateInvariant(); } - -bool WithinStarlaneJumps::TargetInvariant() const -{ return m_jumps->TargetInvariant() && m_condition->TargetInvariant(); } - -bool WithinStarlaneJumps::SourceInvariant() const -{ return m_jumps->SourceInvariant() && m_condition->SourceInvariant(); } - std::string WithinStarlaneJumps::Description(bool negated/* = false*/) const { std::string value_str = m_jumps->ConstantExpr() ? std::to_string(m_jumps->Eval()) : m_jumps->Description(); return str(FlexibleFormat((!negated) @@ -7219,16 +7282,14 @@ std::string WithinStarlaneJumps::Description(bool negated/* = false*/) const { % m_condition->Description()); } -std::string WithinStarlaneJumps::Dump() const { - std::string retval = DumpIndent() + "WithinStarlaneJumps jumps = " + m_jumps->Dump() + " condition =\n"; - ++g_indent; - retval += m_condition->Dump(); - --g_indent; +std::string WithinStarlaneJumps::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "WithinStarlaneJumps jumps = " + m_jumps->Dump(ntabs) + " condition =\n"; + retval += m_condition->Dump(ntabs+1); return retval; } bool WithinStarlaneJumps::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "WithinStarlaneJumps::Match passed no candidate object"; return false; @@ -7275,10 +7336,16 @@ unsigned int WithinStarlaneJumps::GetCheckSum() const { /////////////////////////////////////////////////////////// // CanAddStarlaneConnection // /////////////////////////////////////////////////////////// -CanAddStarlaneConnection::~CanAddStarlaneConnection() -{ delete m_condition; } +CanAddStarlaneConnection::CanAddStarlaneConnection(std::unique_ptr&& condition) : + Condition(), + m_condition(std::move(condition)) +{ + m_root_candidate_invariant = m_condition->RootCandidateInvariant(); + m_target_invariant = m_condition->TargetInvariant(); + m_source_invariant = m_condition->SourceInvariant(); +} -bool CanAddStarlaneConnection::operator==(const ConditionBase& rhs) const { +bool CanAddStarlaneConnection::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -7320,7 +7387,8 @@ namespace { dy2 /= mag; - const float MAX_LANE_DOT_PRODUCT = 0.98f; // magic limit copied from CullAngularlyTooCloseLanes in UniverseGenerator + const float MAX_LANE_DOT_PRODUCT = 0.87f; // magic limit adjusted to allow no more than 12 starlanes from a system + // arccos(0.87) = 0.515594 rad = 29.5 degrees float dp = (dx1 * dx2) + (dy1 * dy2); //TraceLogger() << "systems: " << sys1->UniverseObject::Name() << " " << lane1_sys2->UniverseObject::Name() << " " << lane2_sys2->UniverseObject::Name() << " dp: " << dp << std::endl; @@ -7388,7 +7456,7 @@ namespace { // d = |1O| cos(a) = (1O).(12) / |12| // d = -(O1).(12 / |12|) - const float MIN_PERP_DIST = 10; // magic limit, in units of universe units (uu) + const float MIN_PERP_DIST = 20; // magic limit, in units of universe units (uu) float perp_dist = std::abs(v_o1_x*v_12_x + v_o1_y*v_12_y); @@ -7464,24 +7532,23 @@ namespace { } bool LaneCrossesExistingLane(std::shared_ptr lane_end_sys1, - std::shared_ptr lane_end_sys2) + std::shared_ptr lane_end_sys2, + const ObjectMap& objects) { if (!lane_end_sys1 || !lane_end_sys2 || lane_end_sys1 == lane_end_sys2) return true; - const ObjectMap& objects = Objects(); - // loop over all existing lanes in all systems, checking if a lane // beween the specified systems would cross any of the existing lanes - for (std::shared_ptr system : objects.FindObjects()) { + for (auto& system : objects.all()) { if (system == lane_end_sys1 || system == lane_end_sys2) continue; - const std::map& sys_existing_lanes = system->StarlanesWormholes(); + const auto& sys_existing_lanes = system->StarlanesWormholes(); // check all existing lanes of currently-being-checked system - for (const std::map::value_type& lane : sys_existing_lanes) { - std::shared_ptr lane_end_sys3 = GetSystem(lane.first); + for (const auto& lane : sys_existing_lanes) { + auto lane_end_sys3 = objects.get(lane.first); if (!lane_end_sys3) continue; // don't need to check against existing lanes that include one @@ -7501,17 +7568,15 @@ namespace { } bool LaneTooCloseToOtherSystem(std::shared_ptr lane_end_sys1, - std::shared_ptr lane_end_sys2) + std::shared_ptr lane_end_sys2, + const ObjectMap& objects) { if (!lane_end_sys1 || !lane_end_sys2 || lane_end_sys1 == lane_end_sys2) return true; - const ObjectMap& objects = Objects(); - std::vector> systems = objects.FindObjects(); - // loop over all existing systems, checking if each is too close to a // lane between the specified lane endpoints - for (std::shared_ptr system : systems) { + for (auto& system : objects.all()) { if (system == lane_end_sys1 || system == lane_end_sys2) continue; @@ -7523,14 +7588,15 @@ namespace { } struct CanAddStarlaneConnectionSimpleMatch { - CanAddStarlaneConnectionSimpleMatch(const ObjectSet& destination_objects) : - m_destination_systems() + CanAddStarlaneConnectionSimpleMatch(const ObjectSet& destination_objects, const ObjectMap& objects) : + m_destination_systems(), + m_objects(objects) { // get (one of each of) set of systems that are or that contain any // destination objects std::set> dest_systems; - for (std::shared_ptr obj : destination_objects) { - if (std::shared_ptr sys = GetSystem(obj->SystemID())) + for (auto& obj : destination_objects) { + if (auto sys = m_objects.get(obj->SystemID())) dest_systems.insert(sys); } std::copy(dest_systems.begin(), dest_systems.end(), std::inserter(m_destination_systems, m_destination_systems.end())); @@ -7541,22 +7607,22 @@ namespace { return false; // get system from candidate - std::shared_ptr candidate_sys = std::dynamic_pointer_cast(candidate); + auto candidate_sys = std::dynamic_pointer_cast(candidate); if (!candidate_sys) - candidate_sys = GetSystem(candidate->SystemID()); + candidate_sys = m_objects.get(candidate->SystemID()); if (!candidate_sys) return false; // check if candidate is one of the destination systems - for (std::shared_ptr destination : m_destination_systems) { + for (auto& destination : m_destination_systems) { if (candidate_sys->ID() == destination->ID()) return false; } // check if candidate already has a lane to any of the destination systems - for (std::shared_ptr destination : m_destination_systems) { + for (auto& destination : m_destination_systems) { if (candidate_sys->HasStarlaneTo(destination->ID())) return false; } @@ -7564,13 +7630,13 @@ namespace { // check if any of the proposed lanes are too close to any already- // present lanes of the candidate system //TraceLogger() << "... Checking lanes of candidate system: " << candidate->UniverseObject::Name() << std::endl; - for (const std::map::value_type& lane : candidate_sys->StarlanesWormholes()) { - std::shared_ptr candidate_existing_lane_end_sys = GetSystem(lane.first); + for (const auto& lane : candidate_sys->StarlanesWormholes()) { + auto candidate_existing_lane_end_sys = m_objects.get(lane.first); if (!candidate_existing_lane_end_sys) continue; // check this existing lane against potential lanes to all destination systems - for (std::shared_ptr dest_sys : m_destination_systems) { + for (auto& dest_sys : m_destination_systems) { if (LanesAngularlyTooClose(candidate_sys, candidate_existing_lane_end_sys, dest_sys)) { //TraceLogger() << " ... ... can't add lane from candidate: " << candidate_sys->UniverseObject::Name() << " to " << dest_sys->UniverseObject::Name() << " due to existing lane to " << candidate_existing_lane_end_sys->UniverseObject::Name() << std::endl; return false; @@ -7582,11 +7648,11 @@ namespace { // check if any of the proposed lanes are too close to any already- // present lanes of any of the destination systems //TraceLogger() << "... Checking lanes of destination systems:" << std::endl; - for (std::shared_ptr dest_sys : m_destination_systems) { + for (auto& dest_sys : m_destination_systems) { // check this destination system's existing lanes against a lane // to the candidate system - for (const std::map::value_type& dest_lane : dest_sys->StarlanesWormholes()) { - std::shared_ptr dest_lane_end_sys = GetSystem(dest_lane.first); + for (const auto& dest_lane : dest_sys->StarlanesWormholes()) { + auto dest_lane_end_sys = m_objects.get(dest_lane.first); if (!dest_lane_end_sys) continue; @@ -7600,16 +7666,16 @@ namespace { // check if any of the proposed lanes are too close to eachother //TraceLogger() << "... Checking proposed lanes against eachother" << std::endl; - for (std::vector>::const_iterator it1 = m_destination_systems.begin(); + for (auto it1 = m_destination_systems.begin(); it1 != m_destination_systems.end(); ++it1) { - std::shared_ptr dest_sys1 = *it1; + auto dest_sys1 = *it1; // don't need to check a lane in both directions, so start at one past it1 - std::vector>::const_iterator it2 = it1; + auto it2 = it1; ++it2; for (; it2 != m_destination_systems.end(); ++it2) { - std::shared_ptr dest_sys2 = *it2; + auto dest_sys2 = *it2; if (LanesAngularlyTooClose(candidate_sys, dest_sys1, dest_sys2)) { //TraceLogger() << " ... ... can't add lane from candidate: " << candidate_sys->UniverseObject::Name() << " to " << dest_sys1->UniverseObject::Name() << " and also to " << dest_sys2->UniverseObject::Name() << std::endl; return false; @@ -7621,8 +7687,8 @@ namespace { // check that the proposed lanes are not too close to any existing // system they are not connected to //TraceLogger() << "... Checking proposed lanes for proximity to other systems" < dest_sys : m_destination_systems) { - if (LaneTooCloseToOtherSystem(candidate_sys, dest_sys)) { + for (auto& dest_sys : m_destination_systems) { + if (LaneTooCloseToOtherSystem(candidate_sys, dest_sys, m_objects)) { //TraceLogger() << " ... ... can't add lane from candidate: " << candidate_sys->Name() << " to " << dest_sys->Name() << " due to proximity to another system." << std::endl; return false; } @@ -7631,8 +7697,8 @@ namespace { // check that there are no lanes already existing that cross the proposed lanes //TraceLogger() << "... Checking for potential lanes crossing existing lanes" << std::endl; - for (std::shared_ptr dest_sys : m_destination_systems) { - if (LaneCrossesExistingLane(candidate_sys, dest_sys)) { + for (auto& dest_sys : m_destination_systems) { + if (LaneCrossesExistingLane(candidate_sys, dest_sys, m_objects)) { //TraceLogger() << " ... ... can't add lane from candidate: " << candidate_sys->Name() << " to " << dest_sys->Name() << " due to crossing an existing lane." << std::endl; return false; } @@ -7642,6 +7708,7 @@ namespace { } std::vector> m_destination_systems; + const ObjectMap& m_objects; }; } @@ -7652,45 +7719,32 @@ void CanAddStarlaneConnection::Eval(const ScriptingContext& parent_context, bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant(); if (simple_eval_safe) { // evaluate contained objects once and check for all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); // get subcondition matches ObjectSet subcondition_matches; - m_condition->Eval(local_context, subcondition_matches); + m_condition->Eval(parent_context, subcondition_matches); - EvalImpl(matches, non_matches, search_domain, CanAddStarlaneConnectionSimpleMatch(subcondition_matches)); + EvalImpl(matches, non_matches, search_domain, CanAddStarlaneConnectionSimpleMatch(subcondition_matches, parent_context.ContextObjects())); } else { // re-evaluate contained objects for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool CanAddStarlaneConnection::RootCandidateInvariant() const -{ return m_condition->RootCandidateInvariant(); } - -bool CanAddStarlaneConnection::TargetInvariant() const -{ return m_condition->TargetInvariant(); } - -bool CanAddStarlaneConnection::SourceInvariant() const -{ return m_condition->SourceInvariant(); } - std::string CanAddStarlaneConnection::Description(bool negated/* = false*/) const { return str(FlexibleFormat((!negated) ? UserString("DESC_CAN_ADD_STARLANE_CONNECTION") : UserString("DESC_CAN_ADD_STARLANE_CONNECTION_NOT")) % m_condition->Description()); } -std::string CanAddStarlaneConnection::Dump() const { - std::string retval = DumpIndent() + "CanAddStarlanesTo condition =\n"; - ++g_indent; - retval += m_condition->Dump(); - --g_indent; +std::string CanAddStarlaneConnection::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "CanAddStarlanesTo condition =\n"; + retval += m_condition->Dump(ntabs+1); return retval; } bool CanAddStarlaneConnection::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "CanAddStarlaneConnection::Match passed no candidate object"; return false; @@ -7700,7 +7754,7 @@ bool CanAddStarlaneConnection::Match(const ScriptingContext& local_context) cons ObjectSet subcondition_matches; m_condition->Eval(local_context, subcondition_matches); - return CanAddStarlaneConnectionSimpleMatch(subcondition_matches)(candidate); + return CanAddStarlaneConnectionSimpleMatch(subcondition_matches, local_context.ContextObjects())(candidate); } void CanAddStarlaneConnection::SetTopLevelContent(const std::string& content_name) { @@ -7721,10 +7775,16 @@ unsigned int CanAddStarlaneConnection::GetCheckSum() const { /////////////////////////////////////////////////////////// // ExploredByEmpire // /////////////////////////////////////////////////////////// -ExploredByEmpire::~ExploredByEmpire() -{ delete m_empire_id; } +ExploredByEmpire::ExploredByEmpire(std::unique_ptr>&& empire_id) : + Condition(), + m_empire_id(std::move(empire_id)) +{ + m_root_candidate_invariant = m_empire_id->RootCandidateInvariant(); + m_target_invariant = m_empire_id->TargetInvariant(); + m_source_invariant = m_empire_id->SourceInvariant(); +} -bool ExploredByEmpire::operator==(const ConditionBase& rhs) const { +bool ExploredByEmpire::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -7766,24 +7826,16 @@ void ExploredByEmpire::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate empire id once, and use to check all candidate objects - std::shared_ptr no_object; - int empire_id = m_empire_id->Eval(ScriptingContext(parent_context, no_object)); + int empire_id = m_empire_id->Eval(parent_context); + + // need to check each candidate separately to test if it has been explored EvalImpl(matches, non_matches, search_domain, ExploredByEmpireSimpleMatch(empire_id)); } else { // re-evaluate empire id for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool ExploredByEmpire::RootCandidateInvariant() const -{ return m_empire_id->RootCandidateInvariant(); } - -bool ExploredByEmpire::TargetInvariant() const -{ return m_empire_id->TargetInvariant(); } - -bool ExploredByEmpire::SourceInvariant() const -{ return m_empire_id->SourceInvariant(); } - std::string ExploredByEmpire::Description(bool negated/* = false*/) const { std::string empire_str; if (m_empire_id) { @@ -7802,11 +7854,11 @@ std::string ExploredByEmpire::Description(bool negated/* = false*/) const { % empire_str); } -std::string ExploredByEmpire::Dump() const -{ return DumpIndent() + "ExploredByEmpire empire_id = " + m_empire_id->Dump(); } +std::string ExploredByEmpire::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "ExploredByEmpire empire_id = " + m_empire_id->Dump(ntabs); } bool ExploredByEmpire::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "ExploredByEmpire::Match passed no candidate object"; return false; @@ -7833,8 +7885,16 @@ unsigned int ExploredByEmpire::GetCheckSum() const { /////////////////////////////////////////////////////////// // Stationary // /////////////////////////////////////////////////////////// -bool Stationary::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +Stationary::Stationary() : + Condition() +{ + m_root_candidate_invariant = true; + m_target_invariant = true; + m_source_invariant = true; +} + +bool Stationary::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string Stationary::Description(bool negated/* = false*/) const { return (!negated) @@ -7842,11 +7902,11 @@ std::string Stationary::Description(bool negated/* = false*/) const { : UserString("DESC_STATIONARY_NOT"); } -std::string Stationary::Dump() const -{ return DumpIndent() + "Stationary\n"; } +std::string Stationary::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "Stationary\n"; } bool Stationary::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Stationary::Match passed no candidate object"; return false; @@ -7855,10 +7915,10 @@ bool Stationary::Match(const ScriptingContext& local_context) const { // the only objects that can move are fleets and the ships in them. so, // attempt to cast the candidate object to a fleet or ship, and if it's a ship // get the fleet of that ship - std::shared_ptr fleet = std::dynamic_pointer_cast(candidate); + auto fleet = std::dynamic_pointer_cast(candidate); if (!fleet) - if (std::shared_ptr ship = std::dynamic_pointer_cast(candidate)) - fleet = GetFleet(ship->FleetID()); + if (auto ship = std::dynamic_pointer_cast(candidate)) + fleet = local_context.ContextObjects().get(ship->FleetID()); if (fleet) { // if a fleet is available, it is "moving", or not stationary, if it's @@ -7886,8 +7946,21 @@ unsigned int Stationary::GetCheckSum() const { /////////////////////////////////////////////////////////// // Aggressive // /////////////////////////////////////////////////////////// -bool Aggressive::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +Aggressive::Aggressive() : + Aggressive(true) +{} + +Aggressive::Aggressive(bool aggressive) : + Condition(), + m_aggressive(aggressive) +{ + m_root_candidate_invariant = true; + m_target_invariant = true; + m_source_invariant = true; +} + +bool Aggressive::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string Aggressive::Description(bool negated/* = false*/) const { if (m_aggressive) @@ -7900,11 +7973,11 @@ std::string Aggressive::Description(bool negated/* = false*/) const { : UserString("DESC_PASSIVE_NOT"); } -std::string Aggressive::Dump() const -{ return DumpIndent() + (m_aggressive ? "Aggressive\n" : "Passive\n"); } +std::string Aggressive::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + (m_aggressive ? "Aggressive\n" : "Passive\n"); } bool Aggressive::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Aggressive::Match passed no candidate object"; return false; @@ -7913,10 +7986,10 @@ bool Aggressive::Match(const ScriptingContext& local_context) const { // the only objects that can be aggressive are fleets and the ships in them. // so, attempt to cast the candidate object to a fleet or ship, and if it's // a ship get the fleet of that ship - std::shared_ptr fleet = std::dynamic_pointer_cast(candidate); + auto fleet = std::dynamic_pointer_cast(candidate); if (!fleet) - if (std::shared_ptr ship = std::dynamic_pointer_cast(candidate)) - fleet = GetFleet(ship->FleetID()); + if (auto ship = std::dynamic_pointer_cast(candidate)) + fleet = local_context.ContextObjects().get(ship->FleetID()); if (!fleet) return false; @@ -7937,10 +8010,16 @@ unsigned int Aggressive::GetCheckSum() const { /////////////////////////////////////////////////////////// // FleetSupplyableByEmpire // /////////////////////////////////////////////////////////// -FleetSupplyableByEmpire::~FleetSupplyableByEmpire() -{ delete m_empire_id; } +FleetSupplyableByEmpire::FleetSupplyableByEmpire(std::unique_ptr>&& empire_id) : + Condition(), + m_empire_id(std::move(empire_id)) +{ + m_root_candidate_invariant = m_empire_id->RootCandidateInvariant(); + m_target_invariant = m_empire_id->TargetInvariant(); + m_source_invariant = m_empire_id->SourceInvariant(); +} -bool FleetSupplyableByEmpire::operator==(const ConditionBase& rhs) const { +bool FleetSupplyableByEmpire::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -7968,11 +8047,11 @@ namespace { return false; const SupplyManager& supply = GetSupplyManager(); - const std::map>& empire_supplyable_systems = supply.FleetSupplyableSystemIDs(); - std::map>::const_iterator it = empire_supplyable_systems.find(m_empire_id); + const auto& empire_supplyable_systems = supply.FleetSupplyableSystemIDs(); + auto it = empire_supplyable_systems.find(m_empire_id); if (it == empire_supplyable_systems.end()) return false; - return it->second.find(candidate->SystemID()) != it->second.end(); + return it->second.count(candidate->SystemID()); } int m_empire_id; @@ -7988,24 +8067,14 @@ void FleetSupplyableByEmpire::Eval(const ScriptingContext& parent_context, (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate empire id once, and use to check all candidate objects - std::shared_ptr no_object; - int empire_id = m_empire_id->Eval(ScriptingContext(parent_context, no_object)); + int empire_id = m_empire_id->Eval(parent_context); EvalImpl(matches, non_matches, search_domain, FleetSupplyableSimpleMatch(empire_id)); } else { // re-evaluate empire id for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool FleetSupplyableByEmpire::RootCandidateInvariant() const -{ return m_empire_id->RootCandidateInvariant(); } - -bool FleetSupplyableByEmpire::TargetInvariant() const -{ return m_empire_id->TargetInvariant(); } - -bool FleetSupplyableByEmpire::SourceInvariant() const -{ return m_empire_id->SourceInvariant(); } - std::string FleetSupplyableByEmpire::Description(bool negated/* = false*/) const { std::string empire_str; if (m_empire_id) { @@ -8024,11 +8093,11 @@ std::string FleetSupplyableByEmpire::Description(bool negated/* = false*/) const % empire_str); } -std::string FleetSupplyableByEmpire::Dump() const -{ return DumpIndent() + "ResupplyableBy empire_id = " + m_empire_id->Dump(); } +std::string FleetSupplyableByEmpire::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "ResupplyableBy empire_id = " + m_empire_id->Dump(ntabs); } bool FleetSupplyableByEmpire::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "FleetSupplyableByEmpire::Match passed no candidate object"; return false; @@ -8057,12 +8126,19 @@ unsigned int FleetSupplyableByEmpire::GetCheckSum() const { /////////////////////////////////////////////////////////// // ResourceSupplyConnectedByEmpire // /////////////////////////////////////////////////////////// -ResourceSupplyConnectedByEmpire::~ResourceSupplyConnectedByEmpire() { - delete m_empire_id; - delete m_condition; +ResourceSupplyConnectedByEmpire::ResourceSupplyConnectedByEmpire( + std::unique_ptr>&& empire_id, + std::unique_ptr&& condition) : + Condition(), + m_empire_id(std::move(empire_id)), + m_condition(std::move(condition)) +{ + m_root_candidate_invariant = m_empire_id->RootCandidateInvariant() && m_condition->RootCandidateInvariant(); + m_target_invariant = m_empire_id->TargetInvariant() && m_condition->TargetInvariant(); + m_source_invariant = m_empire_id->SourceInvariant() && m_condition->SourceInvariant(); } -bool ResourceSupplyConnectedByEmpire::operator==(const ConditionBase& rhs) const { +bool ResourceSupplyConnectedByEmpire::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -8077,9 +8153,10 @@ bool ResourceSupplyConnectedByEmpire::operator==(const ConditionBase& rhs) const namespace { struct ResourceSupplySimpleMatch { - ResourceSupplySimpleMatch(int empire_id, const ObjectSet& from_objects) : + ResourceSupplySimpleMatch(int empire_id, const ObjectSet& from_objects, const ObjectMap& objects) : m_empire_id(empire_id), - m_from_objects(from_objects) + m_from_objects(from_objects), + m_objects(objects) {} bool operator()(std::shared_ptr candidate) const { @@ -8087,16 +8164,47 @@ namespace { return false; if (m_from_objects.empty()) return false; - const std::set>& groups = GetSupplyManager().ResourceSupplyGroups(m_empire_id); + const auto& groups = GetSupplyManager().ResourceSupplyGroups(m_empire_id); if (groups.empty()) return false; // is candidate object connected to a subcondition matching object by resource supply? - for (std::shared_ptr from_object : m_from_objects) { - for (const std::set& group : groups) { - if (group.find(from_object->SystemID()) != group.end()) { - // found resource sharing group containing test object. Does it also contain candidate? - if (group.find(candidate->SystemID()) != group.end()) + // first check if candidate object is (or is a building on) a blockaded planet + // "isolated" objects are anything not in a non-blockaded system + bool is_isolated = true; + for (const auto& group : groups) { + if (group.count(candidate->SystemID())) { + is_isolated = false; + break; + } + } + if (is_isolated) { + // planets are still supply-connected to themselves even if blockaded + auto candidate_planet = std::dynamic_pointer_cast(candidate); + std::shared_ptr building; + if (!candidate_planet && (building = std::dynamic_pointer_cast(candidate))) + candidate_planet = m_objects.get(building->PlanetID()); + if (candidate_planet) { + int candidate_planet_id = candidate_planet->ID(); + // can only match if the from_object is (or is on) the same planet + for (auto& from_object : m_from_objects) { + auto from_obj_planet = std::dynamic_pointer_cast(from_object); + std::shared_ptr from_building; + if (!from_obj_planet && (from_building = std::dynamic_pointer_cast(from_object))) + from_obj_planet = m_objects.get(from_building->PlanetID()); + if (from_obj_planet && from_obj_planet->ID() == candidate_planet_id) + return true; + } + } + // candidate is isolated, but did not match planet for any test object + return false; + } + // candidate is not blockaded, so check for system group matches + for (auto& from_object : m_from_objects) { + for (const auto& group : groups) { + if (group.count(from_object->SystemID())) { + // found resource sharing group containing test object system. Does it also contain candidate? + if (group.count(candidate->SystemID())) return true; // test object and candidate object are in same resourse sharing group else // test object is not in resource sharing group with candidate @@ -8113,6 +8221,7 @@ namespace { int m_empire_id; const ObjectSet& m_from_objects; + const ObjectMap& m_objects; }; } @@ -8125,33 +8234,21 @@ void ResourceSupplyConnectedByEmpire::Eval(const ScriptingContext& parent_contex (parent_context.condition_root_candidate || RootCandidateInvariant())); if (simple_eval_safe) { // evaluate contained objects once and check for all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); // get objects to be considering for matching against subcondition ObjectSet subcondition_matches; - m_condition->Eval(local_context, subcondition_matches); + m_condition->Eval(parent_context, subcondition_matches); + int empire_id = m_empire_id->Eval(parent_context); - int empire_id = m_empire_id->Eval(local_context); - - EvalImpl(matches, non_matches, search_domain, ResourceSupplySimpleMatch(empire_id, subcondition_matches)); + EvalImpl(matches, non_matches, search_domain, ResourceSupplySimpleMatch(empire_id, subcondition_matches, parent_context.ContextObjects())); } else { // re-evaluate empire id for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool ResourceSupplyConnectedByEmpire::RootCandidateInvariant() const -{ return m_empire_id->RootCandidateInvariant() && m_condition->RootCandidateInvariant(); } - -bool ResourceSupplyConnectedByEmpire::TargetInvariant() const -{ return m_empire_id->TargetInvariant() && m_condition->TargetInvariant(); } - -bool ResourceSupplyConnectedByEmpire::SourceInvariant() const -{ return m_empire_id->SourceInvariant() && m_condition->SourceInvariant(); } - bool ResourceSupplyConnectedByEmpire::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "ResourceSupplyConnectedByEmpire::Match passed no candidate object"; return false; @@ -8162,7 +8259,7 @@ bool ResourceSupplyConnectedByEmpire::Match(const ScriptingContext& local_contex m_condition->Eval(local_context, subcondition_matches); int empire_id = m_empire_id->Eval(local_context); - return ResourceSupplySimpleMatch(empire_id, subcondition_matches)(candidate); + return ResourceSupplySimpleMatch(empire_id, subcondition_matches, local_context.ContextObjects())(candidate); } std::string ResourceSupplyConnectedByEmpire::Description(bool negated/* = false*/) const { @@ -8184,12 +8281,10 @@ std::string ResourceSupplyConnectedByEmpire::Description(bool negated/* = false* % m_condition->Description()); } -std::string ResourceSupplyConnectedByEmpire::Dump() const { - std::string retval = DumpIndent() + "ResourceSupplyConnectedBy empire_id = " + m_empire_id->Dump() + - " condition = \n"; - ++g_indent; - retval += m_condition->Dump(); - --g_indent; +std::string ResourceSupplyConnectedByEmpire::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "ResourceSupplyConnectedBy empire_id = " + + m_empire_id->Dump(ntabs) + " condition = \n"; + retval += m_condition->Dump(ntabs+1); return retval; } @@ -8214,8 +8309,16 @@ unsigned int ResourceSupplyConnectedByEmpire::GetCheckSum() const { /////////////////////////////////////////////////////////// // CanColonize // /////////////////////////////////////////////////////////// -bool CanColonize::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +CanColonize::CanColonize() : + Condition() +{ + m_root_candidate_invariant = true; + m_target_invariant = true; + m_source_invariant = true; +} + +bool CanColonize::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string CanColonize::Description(bool negated/* = false*/) const { return str(FlexibleFormat((!negated) @@ -8223,11 +8326,11 @@ std::string CanColonize::Description(bool negated/* = false*/) const { : UserString("DESC_CAN_COLONIZE_NOT"))); } -std::string CanColonize::Dump() const -{ return DumpIndent() + "CanColonize\n"; } +std::string CanColonize::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "CanColonize\n"; } bool CanColonize::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "CanColonize::Match passed no candidate object"; return false; @@ -8236,7 +8339,7 @@ bool CanColonize::Match(const ScriptingContext& local_context) const { // is it a ship, a planet, or a building on a planet? std::string species_name; if (candidate->ObjectType() == OBJ_PLANET) { - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); if (!planet) { ErrorLogger() << "CanColonize couldn't cast supposedly planet candidate"; return false; @@ -8244,12 +8347,12 @@ bool CanColonize::Match(const ScriptingContext& local_context) const { species_name = planet->SpeciesName(); } else if (candidate->ObjectType() == OBJ_BUILDING) { - std::shared_ptr building = std::dynamic_pointer_cast(candidate); + auto building = std::dynamic_pointer_cast(candidate); if (!building) { ErrorLogger() << "CanColonize couldn't cast supposedly building candidate"; return false; } - std::shared_ptr planet = GetPlanet(building->PlanetID()); + auto planet = local_context.ContextObjects().get(building->PlanetID()); if (!planet) { ErrorLogger() << "CanColonize couldn't get building's planet"; return false; @@ -8257,7 +8360,7 @@ bool CanColonize::Match(const ScriptingContext& local_context) const { species_name = planet->SpeciesName(); } else if (candidate->ObjectType() == OBJ_SHIP) { - std::shared_ptr ship = std::dynamic_pointer_cast(candidate); + auto ship = std::dynamic_pointer_cast(candidate); if (!ship) { ErrorLogger() << "CanColonize couldn't cast supposedly ship candidate"; return false; @@ -8267,7 +8370,7 @@ bool CanColonize::Match(const ScriptingContext& local_context) const { if (species_name.empty()) return false; - const ::Species* species = GetSpecies(species_name); + auto species = GetSpecies(species_name); if (!species) { ErrorLogger() << "CanColonize couldn't get species: " << species_name; return false; @@ -8287,8 +8390,16 @@ unsigned int CanColonize::GetCheckSum() const { /////////////////////////////////////////////////////////// // CanProduceShips // /////////////////////////////////////////////////////////// -bool CanProduceShips::operator==(const ConditionBase& rhs) const -{ return ConditionBase::operator==(rhs); } +CanProduceShips::CanProduceShips() : + Condition() +{ + m_root_candidate_invariant = true; + m_target_invariant = true; + m_source_invariant = true; +} + +bool CanProduceShips::operator==(const Condition& rhs) const +{ return Condition::operator==(rhs); } std::string CanProduceShips::Description(bool negated/* = false*/) const { return str(FlexibleFormat((!negated) @@ -8296,11 +8407,11 @@ std::string CanProduceShips::Description(bool negated/* = false*/) const { : UserString("DESC_CAN_PRODUCE_SHIPS_NOT"))); } -std::string CanProduceShips::Dump() const -{ return DumpIndent() + "CanColonize\n"; } +std::string CanProduceShips::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "CanColonize\n"; } bool CanProduceShips::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "CanProduceShips::Match passed no candidate object"; return false; @@ -8309,7 +8420,7 @@ bool CanProduceShips::Match(const ScriptingContext& local_context) const { // is it a ship, a planet, or a building on a planet? std::string species_name; if (candidate->ObjectType() == OBJ_PLANET) { - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); if (!planet) { ErrorLogger() << "CanProduceShips couldn't cast supposedly planet candidate"; return false; @@ -8317,12 +8428,12 @@ bool CanProduceShips::Match(const ScriptingContext& local_context) const { species_name = planet->SpeciesName(); } else if (candidate->ObjectType() == OBJ_BUILDING) { - std::shared_ptr building = std::dynamic_pointer_cast(candidate); + auto building = std::dynamic_pointer_cast(candidate); if (!building) { ErrorLogger() << "CanProduceShips couldn't cast supposedly building candidate"; return false; } - std::shared_ptr planet = GetPlanet(building->PlanetID()); + auto planet = local_context.ContextObjects().get(building->PlanetID()); if (!planet) { ErrorLogger() << "CanProduceShips couldn't get building's planet"; return false; @@ -8330,7 +8441,7 @@ bool CanProduceShips::Match(const ScriptingContext& local_context) const { species_name = planet->SpeciesName(); } else if (candidate->ObjectType() == OBJ_SHIP) { - std::shared_ptr ship = std::dynamic_pointer_cast(candidate); + auto ship = std::dynamic_pointer_cast(candidate); if (!ship) { ErrorLogger() << "CanProduceShips couldn't cast supposedly ship candidate"; return false; @@ -8340,7 +8451,7 @@ bool CanProduceShips::Match(const ScriptingContext& local_context) const { if (species_name.empty()) return false; - const ::Species* species = GetSpecies(species_name); + auto species = GetSpecies(species_name); if (!species) { ErrorLogger() << "CanProduceShips couldn't get species: " << species_name; return false; @@ -8360,10 +8471,16 @@ unsigned int CanProduceShips::GetCheckSum() const { /////////////////////////////////////////////////////////// // OrderedBombarded // /////////////////////////////////////////////////////////// -OrderedBombarded::~OrderedBombarded() -{ delete m_by_object_condition; } +OrderedBombarded::OrderedBombarded(std::unique_ptr&& by_object_condition) : + Condition(), + m_by_object_condition(std::move(by_object_condition)) +{ + m_root_candidate_invariant = m_by_object_condition->RootCandidateInvariant(); + m_target_invariant = m_by_object_condition->TargetInvariant(); + m_source_invariant = m_by_object_condition->SourceInvariant(); +} -bool OrderedBombarded::operator==(const ConditionBase& rhs) const { +bool OrderedBombarded::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -8387,7 +8504,7 @@ namespace { return false; if (m_by_objects.empty()) return false; - std::shared_ptr planet = std::dynamic_pointer_cast(candidate); + auto planet = std::dynamic_pointer_cast(candidate); if (!planet) return false; int planet_id = planet->ID(); @@ -8395,8 +8512,8 @@ namespace { return false; // check if any of the by_objects is ordered to bombard the candidate planet - for (std::shared_ptr obj : m_by_objects) { - std::shared_ptr ship = std::dynamic_pointer_cast(obj); + for (auto& obj : m_by_objects) { + auto ship = std::dynamic_pointer_cast(obj); if (!ship) continue; if (ship->OrderedBombardPlanet() == planet_id) @@ -8416,29 +8533,18 @@ void OrderedBombarded::Eval(const ScriptingContext& parent_context, bool simple_eval_safe = parent_context.condition_root_candidate || RootCandidateInvariant(); if (simple_eval_safe) { // evaluate contained objects once and check for all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); // get subcondition matches ObjectSet subcondition_matches; - m_by_object_condition->Eval(local_context, subcondition_matches); + m_by_object_condition->Eval(parent_context, subcondition_matches); EvalImpl(matches, non_matches, search_domain, OrderedBombardedSimpleMatch(subcondition_matches)); } else { // re-evaluate contained objects for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool OrderedBombarded::RootCandidateInvariant() const -{ return m_by_object_condition->RootCandidateInvariant(); } - -bool OrderedBombarded::TargetInvariant() const -{ return m_by_object_condition->TargetInvariant(); } - -bool OrderedBombarded::SourceInvariant() const -{ return m_by_object_condition->SourceInvariant(); } - std::string OrderedBombarded::Description(bool negated/* = false*/) const { std::string by_str; if (m_by_object_condition) @@ -8450,11 +8556,11 @@ std::string OrderedBombarded::Description(bool negated/* = false*/) const { % by_str); } -std::string OrderedBombarded::Dump() const -{ return DumpIndent() + "OrderedBombarded by_object = " + m_by_object_condition->Dump(); } +std::string OrderedBombarded::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "OrderedBombarded by_object = " + m_by_object_condition->Dump(ntabs); } bool OrderedBombarded::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "OrderedBombarded::Match passed no candidate object"; return false; @@ -8487,7 +8593,7 @@ unsigned int OrderedBombarded::GetCheckSum() const { /////////////////////////////////////////////////////////// namespace { bool Comparison(float val1, ComparisonType comp, float val2) { - switch(comp) { + switch (comp) { case EQUAL: return val1 == val2; case GREATER_THAN: return val1 > val2; case GREATER_THAN_OR_EQUAL: return val1 >= val2; @@ -8501,7 +8607,7 @@ namespace { bool Comparison(const std::string& val1, ComparisonType comp, const std::string& val2) { - switch(comp) { + switch (comp) { case EQUAL: return val1 == val2; case NOT_EQUAL: return val1 != val2; case INVALID_COMPARISON: @@ -8510,7 +8616,7 @@ namespace { } std::string CompareTypeString(ComparisonType comp) { - switch(comp) { + switch (comp) { case EQUAL: return "="; case GREATER_THAN: return ">"; case GREATER_THAN_OR_EQUAL: return ">="; @@ -8523,66 +8629,61 @@ namespace { } } -ValueTest::ValueTest(ValueRef::ValueRefBase* value_ref1, +ValueTest::ValueTest(std::unique_ptr>&& value_ref1, ComparisonType comp1, - ValueRef::ValueRefBase* value_ref2, + std::unique_ptr>&& value_ref2, ComparisonType comp2, - ValueRef::ValueRefBase* value_ref3) : - ConditionBase(), - m_value_ref1(value_ref1), - m_value_ref2(value_ref2), - m_value_ref3(value_ref3), + std::unique_ptr>&& value_ref3) : + Condition(), + m_value_ref1(std::move(value_ref1)), + m_value_ref2(std::move(value_ref2)), + m_value_ref3(std::move(value_ref3)), m_compare_type1(comp1), m_compare_type2(comp2) { - //DebugLogger() << "ValueTest(double)"; + auto operands = {m_value_ref1.get(), m_value_ref2.get(), m_value_ref3.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -ValueTest::ValueTest(ValueRef::ValueRefBase* value_ref1, +ValueTest::ValueTest(std::unique_ptr>&& value_ref1, ComparisonType comp1, - ValueRef::ValueRefBase* value_ref2, + std::unique_ptr>&& value_ref2, ComparisonType comp2, - ValueRef::ValueRefBase* value_ref3) : - ConditionBase(), - m_string_value_ref1(value_ref1), - m_string_value_ref2(value_ref2), - m_string_value_ref3(value_ref3), + std::unique_ptr>&& value_ref3) : + Condition(), + m_string_value_ref1(std::move(value_ref1)), + m_string_value_ref2(std::move(value_ref2)), + m_string_value_ref3(std::move(value_ref3)), m_compare_type1(comp1), m_compare_type2(comp2) { - /*DebugLogger() << "String ValueTest(" << value_ref1->Dump() << " " - << CompareTypeString(comp1) << " " - << value_ref2->Dump() << ")";*/ + auto operands = {m_string_value_ref1.get(), m_string_value_ref2.get(), m_string_value_ref3.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -ValueTest::ValueTest(ValueRef::ValueRefBase* value_ref1, +ValueTest::ValueTest(std::unique_ptr>&& value_ref1, ComparisonType comp1, - ValueRef::ValueRefBase* value_ref2, + std::unique_ptr>&& value_ref2, ComparisonType comp2, - ValueRef::ValueRefBase* value_ref3) : - ConditionBase(), - m_int_value_ref1(value_ref1), - m_int_value_ref2(value_ref2), - m_int_value_ref3(value_ref3), + std::unique_ptr>&& value_ref3) : + Condition(), + m_int_value_ref1(std::move(value_ref1)), + m_int_value_ref2(std::move(value_ref2)), + m_int_value_ref3(std::move(value_ref3)), m_compare_type1(comp1), m_compare_type2(comp2) { - //DebugLogger() << "ValueTest(double)"; -} - -ValueTest::~ValueTest() { - delete m_value_ref1; - delete m_value_ref2; - delete m_value_ref3; - delete m_string_value_ref1; - delete m_string_value_ref2; - delete m_string_value_ref3; - delete m_int_value_ref1; - delete m_int_value_ref2; - delete m_int_value_ref3; + auto operands = {m_int_value_ref1.get(), m_int_value_ref2.get(), m_int_value_ref3.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -bool ValueTest::operator==(const ConditionBase& rhs) const { +bool ValueTest::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -8625,51 +8726,7 @@ void ValueTest::Eval(const ScriptingContext& parent_context, if (simple_eval_safe) { // evaluate value and range limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - bool passed = false; - - if (m_value_ref1) { - float val1, val2, val3; - passed = /*m_value_ref1 &&*/ m_value_ref2; - if (passed) { - val1 = m_value_ref1->Eval(local_context); - val2 = m_value_ref2->Eval(local_context); - passed = Comparison(val1, m_compare_type1, val2); - } - if (passed && m_value_ref3) { - val3 = m_value_ref3->Eval(local_context); - passed = Comparison(val2, m_compare_type2, val3); - } - - } else if (m_string_value_ref1) { - std::string val1, val2, val3; - passed = /*m_string_value_ref1 &&*/ m_string_value_ref2; - if (passed) { - val1 = m_string_value_ref1->Eval(local_context); - val2 = m_string_value_ref2->Eval(local_context); - passed = Comparison(val1, m_compare_type1, val2); - } - if (passed && m_string_value_ref3) { - val3 = m_string_value_ref3->Eval(local_context); - passed = Comparison(val2, m_compare_type2, val3); - } - - } else if (m_int_value_ref1) { - int val1, val2, val3; - passed = /*m_int_value_ref1 &&*/ m_int_value_ref2; - if (passed) { - val1 = m_int_value_ref1->Eval(local_context); - val2 = m_int_value_ref2->Eval(local_context); - passed = Comparison(val1, m_compare_type1, val2); - } - if (passed && m_int_value_ref3) { - val3 = m_int_value_ref3->Eval(local_context); - passed = Comparison(val2, m_compare_type2, val3); - } - } else { - // passed remains false - } + bool passed = Match(parent_context); // transfer objects to or from candidate set, according to whether the value comparisons were true if (search_domain == MATCHES && !passed) { @@ -8681,47 +8738,10 @@ void ValueTest::Eval(const ScriptingContext& parent_context, non_matches.clear(); } - } else { // re-evaluate value and ranges for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); - } -} - -bool ValueTest::RootCandidateInvariant() const { - return (!m_value_ref1 || m_value_ref1->RootCandidateInvariant()) && - (!m_value_ref2 || m_value_ref2->RootCandidateInvariant()) && - (!m_value_ref3 || m_value_ref3->RootCandidateInvariant()) && - (!m_string_value_ref1 || m_string_value_ref1->RootCandidateInvariant()) && - (!m_string_value_ref2 || m_string_value_ref2->RootCandidateInvariant()) && - (!m_string_value_ref3 || m_string_value_ref3->RootCandidateInvariant()) && - (!m_int_value_ref1 || m_int_value_ref1->RootCandidateInvariant()) && - (!m_int_value_ref2 || m_int_value_ref2->RootCandidateInvariant()) && - (!m_int_value_ref3 || m_int_value_ref3->RootCandidateInvariant()); -} - -bool ValueTest::TargetInvariant() const { - return (!m_value_ref1 || m_value_ref1->TargetInvariant()) && - (!m_value_ref2 || m_value_ref2->TargetInvariant()) && - (!m_value_ref3 || m_value_ref3->TargetInvariant()) && - (!m_string_value_ref1 || m_string_value_ref1->TargetInvariant()) && - (!m_string_value_ref2 || m_string_value_ref2->TargetInvariant()) && - (!m_string_value_ref3 || m_string_value_ref3->TargetInvariant()) && - (!m_int_value_ref1 || m_int_value_ref1->TargetInvariant()) && - (!m_int_value_ref2 || m_int_value_ref2->TargetInvariant()) && - (!m_int_value_ref3 || m_int_value_ref3->TargetInvariant()); -} - -bool ValueTest::SourceInvariant() const { - return (!m_value_ref1 || m_value_ref1->SourceInvariant()) && - (!m_value_ref2 || m_value_ref2->SourceInvariant()) && - (!m_value_ref3 || m_value_ref3->SourceInvariant()) && - (!m_string_value_ref1 || m_string_value_ref1->SourceInvariant()) && - (!m_string_value_ref2 || m_string_value_ref2->SourceInvariant()) && - (!m_string_value_ref3 || m_string_value_ref3->SourceInvariant()) && - (!m_int_value_ref1 || m_int_value_ref1->SourceInvariant()) && - (!m_int_value_ref2 || m_int_value_ref2->SourceInvariant()) && - (!m_int_value_ref3 || m_int_value_ref3->SourceInvariant()); + Condition::Eval(parent_context, matches, non_matches, search_domain); + } } std::string ValueTest::Description(bool negated/* = false*/) const { @@ -8762,48 +8782,46 @@ std::string ValueTest::Description(bool negated/* = false*/) const { % composed_comparison); } -std::string ValueTest::Dump() const { - std::string retval = DumpIndent() + "("; +std::string ValueTest::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "("; if (m_value_ref1) - retval += m_value_ref1->Dump(); + retval += m_value_ref1->Dump(ntabs); else if (m_string_value_ref1) - retval += m_string_value_ref1->Dump(); + retval += m_string_value_ref1->Dump(ntabs); else if (m_int_value_ref1) - retval += m_int_value_ref1->Dump(); + retval += m_int_value_ref1->Dump(ntabs); if (m_compare_type1 != INVALID_COMPARISON) retval += " " + CompareTypeString(m_compare_type1); if (m_value_ref2) - retval += " " + m_value_ref2->Dump(); + retval += " " + m_value_ref2->Dump(ntabs); else if (m_string_value_ref2) - retval += m_string_value_ref2->Dump(); + retval += " " + m_string_value_ref2->Dump(ntabs); else if (m_int_value_ref2) - retval += m_int_value_ref2->Dump(); + retval += " " + m_int_value_ref2->Dump(ntabs); if (m_compare_type2 != INVALID_COMPARISON) retval += " " + CompareTypeString(m_compare_type2); if (m_value_ref3) - retval += " " + m_value_ref3->Dump(); + retval += " " + m_value_ref3->Dump(ntabs); else if (m_string_value_ref3) - retval += m_string_value_ref3->Dump(); + retval += " " + m_string_value_ref3->Dump(ntabs); else if (m_int_value_ref3) - retval += m_int_value_ref3->Dump(); + retval += " " + m_int_value_ref3->Dump(ntabs); retval += ")\n"; return retval; } bool ValueTest::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; - if (!candidate) { - ErrorLogger() << "ValueTest::Match passed no candidate object"; - return false; - } if (m_compare_type1 == INVALID_COMPARISON) return false; + // simple evaluation should have only local-candidate-invariation sub-value-refs + // base class evalulation should have defined local candidate + if (m_value_ref1) { if (!m_value_ref2) return false; @@ -8833,6 +8851,7 @@ bool ValueTest::Match(const ScriptingContext& local_context) const { std::string val3 = m_string_value_ref3->Eval(local_context); return Comparison(val2, m_compare_type1, val3); + } else if (m_int_value_ref1) { if (!m_int_value_ref2) return false; @@ -8848,6 +8867,7 @@ bool ValueTest::Match(const ScriptingContext& local_context) const { int val3 = m_int_value_ref3->Eval(local_context); return Comparison(val2, m_compare_type1, val3); } + return false; } @@ -8896,7 +8916,7 @@ unsigned int ValueTest::GetCheckSum() const { // Location // /////////////////////////////////////////////////////////// namespace { - const ConditionBase* GetLocationCondition(ContentType content_type, + const Condition* GetLocationCondition(ContentType content_type, const std::string& name1, const std::string& name2) { @@ -8904,37 +8924,36 @@ namespace { return nullptr; switch (content_type) { case CONTENT_BUILDING: { - if (const BuildingType* bt = GetBuildingType(name1)) + if (auto bt = GetBuildingType(name1)) return bt->Location(); break; } case CONTENT_SPECIES: { - const ::Species* s = GetSpecies(name1); - if (!s) - return nullptr; - return s->Location(); + if (auto s = GetSpecies(name1)) + return s->Location(); + break; } case CONTENT_SHIP_HULL: { - if (const HullType* h = GetHullType(name1)) + if (auto h = GetShipHull(name1)) return h->Location(); break; } case CONTENT_SHIP_PART: { - if (const PartType* p = GetPartType(name1)) + if (auto p = GetShipPart(name1)) return p->Location(); break; } case CONTENT_SPECIAL: { - if (const Special* s = GetSpecial(name1)) + if (auto s = GetSpecial(name1)) return s->Location(); break; } - case CONTENT_FOCUS : { + case CONTENT_FOCUS: { if (name2.empty()) return nullptr; // get species, then focus from that species - if (const ::Species* s = GetSpecies(name1)) { - for (const ::FocusType& f : s->Foci()) { + if (auto s = GetSpecies(name1)) { + for (auto& f : s->Foci()) { if (f.Name() == name2) return f.Location(); } @@ -8946,14 +8965,35 @@ namespace { } return nullptr; } + + const std::string& GetContentTypeName(ContentType content_type) { + switch (content_type) { + case CONTENT_BUILDING: return UserString("UIT_BUILDING"); break; + case CONTENT_SPECIES: return UserString("ENC_SPECIES"); break; + case CONTENT_SHIP_HULL: return UserString("UIT_SHIP_HULL"); break; + case CONTENT_SHIP_PART: return UserString("UIT_SHIP_PART"); break; + case CONTENT_SPECIAL: return UserString("ENC_SPECIAL"); break; + case CONTENT_FOCUS: return UserString("PLANETARY_FOCUS_TITLE"); break; + default: return EMPTY_STRING; break; + } + } } -Location::~Location() { - delete m_name1; - delete m_name2; +Location::Location(ContentType content_type, + std::unique_ptr>&& name1, + std::unique_ptr>&& name2) : + Condition(), + m_name1(std::move(name1)), + m_name2(std::move(name2)), + m_content_type(content_type) +{ + auto operands = {m_name1.get(), m_name2.get()}; + m_root_candidate_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -bool Location::operator==(const ConditionBase& rhs) const { +bool Location::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -8980,14 +9020,12 @@ void Location::Eval(const ScriptingContext& parent_context, if (simple_eval_safe) { // evaluate value and range limits once, use to match all candidates - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - std::string name1 = (m_name1 ? m_name1->Eval(local_context) : ""); - std::string name2 = (m_name2 ? m_name2->Eval(local_context) : ""); + std::string name1 = (m_name1 ? m_name1->Eval(parent_context) : ""); + std::string name2 = (m_name2 ? m_name2->Eval(parent_context) : ""); // get condition from content, apply to matches / non_matches - const ConditionBase* condition = GetLocationCondition(m_content_type, name1, name2); + const auto condition = GetLocationCondition(m_content_type, name1, name2); if (condition && condition != this) { condition->Eval(parent_context, matches, non_matches, search_domain); } else { @@ -9002,25 +9040,10 @@ void Location::Eval(const ScriptingContext& parent_context, } else { // re-evaluate value and ranges for each candidate object - ConditionBase::Eval(parent_context, matches, non_matches, search_domain); + Condition::Eval(parent_context, matches, non_matches, search_domain); } } -bool Location::RootCandidateInvariant() const { - return (!m_name1 || m_name1->RootCandidateInvariant()) && - (!m_name2 || m_name2->RootCandidateInvariant()); -} - -bool Location::TargetInvariant() const { - return (!m_name1 || m_name1->TargetInvariant()) && - (!m_name2 || m_name2->TargetInvariant()); -} - -bool Location::SourceInvariant() const { - return (!m_name1 || m_name1->SourceInvariant()) && - (!m_name2 || m_name2->SourceInvariant()); -} - std::string Location::Description(bool negated/* = false*/) const { std::string name1_str; if (m_name1) @@ -9030,19 +9053,18 @@ std::string Location::Description(bool negated/* = false*/) const { if (m_name2) name2_str = m_name2->Description(); - std::string content_type_str; - // todo: get content type as string + std::string content_type_str = GetContentTypeName(m_content_type); + std::string name_str = (m_content_type == CONTENT_FOCUS ? name2_str : name1_str); return str(FlexibleFormat((!negated) ? UserString("DESC_LOCATION") : UserString("DESC_LOCATION_NOT")) % content_type_str - % name1_str - % name2_str); + % name_str); } -std::string Location::Dump() const { - std::string retval = DumpIndent() + "Location content_type = "; +std::string Location::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Location content_type = "; switch (m_content_type) { case CONTENT_BUILDING: retval += "Building"; break; @@ -9055,14 +9077,14 @@ std::string Location::Dump() const { } if (m_name1) - retval += " name1 = " + m_name1->Dump(); + retval += " name1 = " + m_name1->Dump(ntabs); if (m_name2) - retval += " name2 = " + m_name2->Dump(); + retval += " name2 = " + m_name2->Dump(ntabs); return retval; } bool Location::Match(const ScriptingContext& local_context) const { - std::shared_ptr candidate = local_context.condition_local_candidate; + auto candidate = local_context.condition_local_candidate; if (!candidate) { ErrorLogger() << "Location::Match passed no candidate object"; return false; @@ -9071,7 +9093,7 @@ bool Location::Match(const ScriptingContext& local_context) const { std::string name1 = (m_name1 ? m_name1->Eval(local_context) : ""); std::string name2 = (m_name2 ? m_name2->Eval(local_context) : ""); - const ConditionBase* condition = GetLocationCondition(m_content_type, name1, name2); + const auto condition = GetLocationCondition(m_content_type, name1, name2); if (!condition || condition == this) return false; @@ -9099,15 +9121,195 @@ unsigned int Location::GetCheckSum() const { return retval; } +/////////////////////////////////////////////////////////// +// CombatTarget // +/////////////////////////////////////////////////////////// +namespace { + const Condition* GetCombatTargetCondition( + ContentType content_type, const std::string& name) + { + if (name.empty()) + return nullptr; + switch (content_type) { + case CONTENT_SPECIES: { + if (auto s = GetSpecies(name)) + return s->CombatTargets(); + break; + } + case CONTENT_SHIP_PART: { + if (auto p = GetShipPart(name)) + return p->CombatTargets(); + break; + } + case CONTENT_BUILDING: + case CONTENT_SHIP_HULL: + case CONTENT_SPECIAL: + case CONTENT_FOCUS: + default: + return nullptr; + } + return nullptr; + } +} + +CombatTarget::CombatTarget(ContentType content_type, + std::unique_ptr>&& name) : + Condition(), + m_name(std::move(name)), + m_content_type(content_type) +{ + m_root_candidate_invariant = !m_name || m_name->RootCandidateInvariant(); + m_target_invariant = !m_name|| m_name->TargetInvariant(); + m_source_invariant = !m_name || m_name->SourceInvariant(); +} + +bool CombatTarget::operator==(const Condition& rhs) const { + if (this == &rhs) + return true; + if (typeid(*this) != typeid(rhs)) + return false; + + const CombatTarget& rhs_ = static_cast(rhs); + + if (m_content_type != rhs_.m_content_type) + return false; + + CHECK_COND_VREF_MEMBER(m_name) + + return true; +} + +void CombatTarget::Eval(const ScriptingContext& parent_context, + ObjectSet& matches, ObjectSet& non_matches, + SearchDomain search_domain/* = NON_MATCHES*/) const +{ + bool simple_eval_safe = ((!m_name || m_name->LocalCandidateInvariant()) && + (parent_context.condition_root_candidate || RootCandidateInvariant())); + + if (simple_eval_safe) { + // evaluate value and range limits once, use to match all candidates + + std::string name = (m_name ? m_name->Eval(parent_context) : ""); + + // get condition from content, apply to matches / non_matches + const auto condition = GetCombatTargetCondition(m_content_type, name); + if (condition && condition != this) { + condition->Eval(parent_context, matches, non_matches, search_domain); + } else { + // if somehow in a cyclical loop because some content's location + // was defined as CombatTarget or if there is no available combat + // targetting condition (eg. in valid content type, or name of + // a bit of content that doesn't exist), match nothing + if (search_domain == MATCHES) { + non_matches.insert(non_matches.end(), matches.begin(), matches.end()); + matches.clear(); + } + } + + } else { + // re-evaluate value and ranges for each candidate object + Condition::Eval(parent_context, matches, non_matches, search_domain); + } +} + +std::string CombatTarget::Description(bool negated/* = false*/) const { + std::string name_str; + if (m_name) + name_str = m_name->Description(); + + std::string content_type_str = GetContentTypeName(m_content_type); + + return str(FlexibleFormat((!negated) + ? UserString("DESC_COMBAT_TARGET") + : UserString("DESC_COMBAT_TARGET_NOT")) + % content_type_str + % name_str); +} + +std::string CombatTarget::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "CombatTarget content_type = "; + + switch (m_content_type) { + case CONTENT_BUILDING: retval += "Building"; break; + case CONTENT_FOCUS: retval += "Focus"; break; + case CONTENT_SHIP_HULL: retval += "Hull"; break; + case CONTENT_SHIP_PART: retval += "Part"; break; + case CONTENT_SPECIAL: retval += "Special"; break; + case CONTENT_SPECIES: retval += "Species"; break; + default: retval += "???"; + } + + if (m_name) + retval += " name = " + m_name->Dump(ntabs); + return retval; +} + +bool CombatTarget::Match(const ScriptingContext& local_context) const { + auto candidate = local_context.condition_local_candidate; + if (!candidate) { + ErrorLogger() << "CombatTarget::Match passed no candidate object"; + return false; + } + + std::string name = (m_name ? m_name->Eval(local_context) : ""); + + const auto condition = GetCombatTargetCondition(m_content_type, name); + if (!condition || condition == this) + return false; + + // other Conditions' Match functions not directly callable, so can't do any + // better than just calling Eval for each candidate... + return condition->Eval(local_context, candidate); +} + +void CombatTarget::SetTopLevelContent(const std::string& content_name) { + if (m_name) + m_name->SetTopLevelContent(content_name); +} + +unsigned int CombatTarget::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "Condition::CombatTarget"); + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_content_type); + + TraceLogger() << "GetCheckSum(CombatTarget): retval: " << retval; + return retval; +} + /////////////////////////////////////////////////////////// // And // /////////////////////////////////////////////////////////// -And::~And() { - for (ConditionBase* operand : m_operands) - delete operand; +And::And(std::vector>&& operands) : + Condition(), + m_operands(std::move(operands)) +{ + m_root_candidate_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -bool And::operator==(const ConditionBase& rhs) const { +And::And(std::unique_ptr&& operand1, std::unique_ptr&& operand2, + std::unique_ptr&& operand3, std::unique_ptr&& operand4) : + Condition() +{ + // would prefer to initialize the vector m_operands in the initializer list, but this is difficult with non-copyable unique_ptr parameters + if (operand1) + m_operands.push_back(std::move(operand1)); + if (operand2) + m_operands.push_back(std::move(operand2)); + if (operand3) + m_operands.push_back(std::move(operand3)); + if (operand4) + m_operands.push_back(std::move(operand4)); + + m_root_candidate_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +bool And::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -9127,32 +9329,44 @@ bool And::operator==(const ConditionBase& rhs) const { void And::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - if (m_operands.empty()) { ErrorLogger() << "And::Eval given no operands!"; return; } - for (ConditionBase* operand : m_operands) { + for (auto& operand : m_operands) { if (!operand) { ErrorLogger() << "And::Eval given null operand!"; return; } } + auto ObjList = [](const ObjectSet& objs) -> std::string { + std::stringstream ss; + for (const auto& obj : objs) + ss << obj->Name() << " (" << std::to_string(obj->ID()) << ") "; + return ss.str(); + }; + + TraceLogger(conditions) << "And::Eval searching " << (search_domain == MATCHES ? "matches" : "non_matches") + << " with input matches (" << matches.size() << "): " << ObjList(matches) + << " and input non_matches(" << non_matches.size() << "): " << ObjList(non_matches); + if (search_domain == NON_MATCHES) { ObjectSet partly_checked_non_matches; partly_checked_non_matches.reserve(non_matches.size()); // move items in non_matches set that pass first operand condition into // partly_checked_non_matches set - m_operands[0]->Eval(local_context, partly_checked_non_matches, non_matches, NON_MATCHES); + m_operands[0]->Eval(parent_context, partly_checked_non_matches, non_matches, NON_MATCHES); + TraceLogger(conditions) << "Subcondition: " << m_operands[0]->Dump() + <<"\npartly_checked_non_matches (" << partly_checked_non_matches.size() << "): " << ObjList(partly_checked_non_matches); // move items that don't pass one of the other conditions back to non_matches for (unsigned int i = 1; i < m_operands.size(); ++i) { if (partly_checked_non_matches.empty()) break; - m_operands[i]->Eval(local_context, partly_checked_non_matches, non_matches, MATCHES); + m_operands[i]->Eval(parent_context, partly_checked_non_matches, non_matches, MATCHES); + TraceLogger(conditions) << "Subcondition: " << m_operands[i]->Dump() + <<"\npartly_checked_non_matches (" << partly_checked_non_matches.size() << "): " << ObjList(partly_checked_non_matches); } // merge items that passed all operand conditions into matches @@ -9165,82 +9379,56 @@ void And::Eval(const ScriptingContext& parent_context, ObjectSet& matches, // check all operand conditions on all objects in the matches set, moving those // that don't pass a condition to the non-matches set - for (ConditionBase* operand : m_operands) { + for (auto& operand : m_operands) { if (matches.empty()) break; - operand->Eval(local_context, matches, non_matches, MATCHES); + operand->Eval(parent_context, matches, non_matches, MATCHES); + TraceLogger(conditions) << "Subcondition: " << operand->Dump() + <<"\nremaining matches (" << matches.size() << "): " << ObjList(matches); } // items already in non_matches set are not checked, and remain in non_matches set // even if they pass all operand conditions } -} - -bool And::RootCandidateInvariant() const { - if (m_root_candidate_invariant != UNKNOWN_INVARIANCE) - return m_root_candidate_invariant == INVARIANT; - - for (ConditionBase* operand : m_operands) { - if (!operand->RootCandidateInvariant()) { - m_root_candidate_invariant = VARIANT; - return false; - } - } - m_root_candidate_invariant = INVARIANT; - return true; -} - -bool And::TargetInvariant() const { - if (m_target_invariant != UNKNOWN_INVARIANCE) - return m_target_invariant == INVARIANT; - - for (ConditionBase* operand : m_operands) { - if (!operand->TargetInvariant()) { - m_target_invariant = VARIANT; - return false; - } - } - m_target_invariant = INVARIANT; - return true; -} - -bool And::SourceInvariant() const { - if (m_source_invariant != UNKNOWN_INVARIANCE) - return m_source_invariant == INVARIANT; - - for (ConditionBase* operand : m_operands) { - if (!operand->SourceInvariant()) { - m_source_invariant = VARIANT; - return false; - } - } - m_source_invariant = INVARIANT; - return true; + TraceLogger(conditions) << "And::Eval final matches (" << matches.size() << "): " << ObjList(matches) + << " and non_matches (" << non_matches.size() << "): " << ObjList(non_matches); } std::string And::Description(bool negated/* = false*/) const { + std::string values_str; if (m_operands.size() == 1) { - return m_operands[0]->Description(); + values_str += (!negated) + ? UserString("DESC_AND_BEFORE_SINGLE_OPERAND") + : UserString("DESC_NOT_AND_BEFORE_SINGLE_OPERAND"); + // Pushing the negation to the enclosed conditions + values_str += m_operands[0]->Description(negated); + values_str += (!negated) + ? UserString("DESC_AND_AFTER_SINGLE_OPERAND") + : UserString("DESC_NOT_AND_AFTER_SINGLE_OPERAND"); } else { - // TODO: use per-operand-type connecting language - std::string values_str; + values_str += (!negated) + ? UserString("DESC_AND_BEFORE_OPERANDS") + : UserString("DESC_NOT_AND_BEFORE_OPERANDS"); for (unsigned int i = 0; i < m_operands.size(); ++i) { - values_str += m_operands[i]->Description(); + // Pushing the negation to the enclosed conditions + values_str += m_operands[i]->Description(negated); if (i != m_operands.size() - 1) { - values_str += UserString("DESC_AND_BETWEEN_OPERANDS"); + values_str += (!negated) + ? UserString("DESC_AND_BETWEEN_OPERANDS") + : UserString("DESC_NOT_AND_BETWEEN_OPERANDS"); } } - return values_str; + values_str += (!negated) + ? UserString("DESC_AND_AFTER_OPERANDS") + : UserString("DESC_NOT_AND_AFTER_OPERANDS"); } + return values_str; } -std::string And::Dump() const { - std::string retval = DumpIndent() + "And [\n"; - ++g_indent; - for (ConditionBase* operand : m_operands) { - retval += operand->Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; +std::string And::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "And [\n"; + for (auto& operand : m_operands) + retval += operand->Dump(ntabs+1); + retval += DumpIndent(ntabs) + "]\n"; return retval; } @@ -9248,12 +9436,12 @@ void And::GetDefaultInitialCandidateObjects(const ScriptingContext& parent_conte if (!m_operands.empty()) { m_operands[0]->GetDefaultInitialCandidateObjects(parent_context, condition_non_targets); // gets condition_non_targets from first operand condition } else { - ConditionBase::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets); + Condition::GetDefaultInitialCandidateObjects(parent_context, condition_non_targets); } } void And::SetTopLevelContent(const std::string& content_name) { - for (ConditionBase* operand : m_operands) { + for (auto& operand : m_operands) { operand->SetTopLevelContent(content_name); } } @@ -9268,15 +9456,47 @@ unsigned int And::GetCheckSum() const { return retval; } +const std::vector And::Operands() const { + std::vector retval(m_operands.size()); + std::transform(m_operands.begin(), m_operands.end(), retval.begin(), + [](const std::unique_ptr& xx) {return xx.get();}); + return retval; +} + /////////////////////////////////////////////////////////// // Or // /////////////////////////////////////////////////////////// -Or::~Or() { - for (ConditionBase* operand : m_operands) - delete operand; +Or::Or(std::vector>&& operands) : + Condition(), + m_operands(std::move(operands)) +{ + m_root_candidate_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->SourceInvariant(); }); } -bool Or::operator==(const ConditionBase& rhs) const { +Or::Or(std::unique_ptr&& operand1, + std::unique_ptr&& operand2, + std::unique_ptr&& operand3, + std::unique_ptr&& operand4) : + Condition() +{ + // would prefer to initialize the vector m_operands in the initializer list, but this is difficult with non-copyable unique_ptr parameters + if (operand1) + m_operands.push_back(std::move(operand1)); + if (operand2) + m_operands.push_back(std::move(operand2)); + if (operand3) + m_operands.push_back(std::move(operand3)); + if (operand4) + m_operands.push_back(std::move(operand4)); + + m_root_candidate_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +bool Or::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -9296,14 +9516,11 @@ bool Or::operator==(const ConditionBase& rhs) const { void Or::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - if (m_operands.empty()) { ErrorLogger() << "Or::Eval given no operands!"; return; } - for (ConditionBase* operand : m_operands) { + for (auto& operand : m_operands) { if (!operand) { ErrorLogger() << "Or::Eval given null operand!"; return; @@ -9315,9 +9532,9 @@ void Or::Eval(const ScriptingContext& parent_context, ObjectSet& matches, // if a non-candidate item matches an operand condition, move the item to the // matches set. - for (ConditionBase* operand : m_operands) { + for (auto& operand : m_operands) { if (non_matches.empty()) break; - operand->Eval(local_context, matches, non_matches, NON_MATCHES); + operand->Eval(parent_context, matches, non_matches, NON_MATCHES); } // items already in matches set are not checked and remain in the @@ -9329,12 +9546,12 @@ void Or::Eval(const ScriptingContext& parent_context, ObjectSet& matches, // move items in matches set the fail the first operand condition into // partly_checked_matches set - m_operands[0]->Eval(local_context, matches, partly_checked_matches, MATCHES); + m_operands[0]->Eval(parent_context, matches, partly_checked_matches, MATCHES); // move items that pass any of the other conditions back into matches - for (ConditionBase* operand : m_operands) { + for (auto& operand : m_operands) { if (partly_checked_matches.empty()) break; - operand->Eval(local_context, matches, partly_checked_matches, NON_MATCHES); + operand->Eval(parent_context, matches, partly_checked_matches, NON_MATCHES); } // merge items that failed all operand conditions into non_matches @@ -9346,77 +9563,48 @@ void Or::Eval(const ScriptingContext& parent_context, ObjectSet& matches, } } -bool Or::RootCandidateInvariant() const { - if (m_root_candidate_invariant != UNKNOWN_INVARIANCE) - return m_root_candidate_invariant == INVARIANT; - - for (ConditionBase* operand : m_operands) { - if (!operand->RootCandidateInvariant()) { - m_root_candidate_invariant = VARIANT; - return false; - } - } - m_root_candidate_invariant = INVARIANT; - return true; -} - -bool Or::TargetInvariant() const { - if (m_target_invariant != UNKNOWN_INVARIANCE) - return m_target_invariant == INVARIANT; - - for (ConditionBase* operand : m_operands) { - if (!operand->TargetInvariant()) { - m_target_invariant = VARIANT; - return false; - } - } - m_target_invariant = INVARIANT; - return true; -} - -bool Or::SourceInvariant() const { - if (m_source_invariant != UNKNOWN_INVARIANCE) - return m_source_invariant == INVARIANT; - - for (ConditionBase* operand : m_operands) { - if (!operand->SourceInvariant()) { - m_source_invariant = VARIANT; - return false; - } - } - m_source_invariant = INVARIANT; - return true; -} - std::string Or::Description(bool negated/* = false*/) const { + std::string values_str; if (m_operands.size() == 1) { - return m_operands[0]->Description(); + values_str += (!negated) + ? UserString("DESC_OR_BEFORE_SINGLE_OPERAND") + : UserString("DESC_NOT_OR_BEFORE_SINGLE_OPERAND"); + // Pushing the negation to the enclosed conditions + values_str += m_operands[0]->Description(negated); + values_str += (!negated) + ? UserString("DESC_OR_AFTER_SINGLE_OPERAND") + : UserString("DESC_NOT_OR_AFTER_SINGLE_OPERAND"); } else { // TODO: use per-operand-type connecting language - std::string values_str; + values_str += (!negated) + ? UserString("DESC_OR_BEFORE_OPERANDS") + : UserString("DESC_NOT_OR_BEFORE_OPERANDS"); for (unsigned int i = 0; i < m_operands.size(); ++i) { - values_str += m_operands[i]->Description(); + // Pushing the negation to the enclosed conditions + values_str += m_operands[i]->Description(negated); if (i != m_operands.size() - 1) { - values_str += UserString("DESC_OR_BETWEEN_OPERANDS"); + values_str += (!negated) + ? UserString("DESC_OR_BETWEEN_OPERANDS") + : UserString("DESC_NOT_OR_BETWEEN_OPERANDS"); } } - return values_str; + values_str += (!negated) + ? UserString("DESC_OR_AFTER_OPERANDS") + : UserString("DESC_NOT_OR_AFTER_OPERANDS"); } + return values_str; } -std::string Or::Dump() const { - std::string retval = DumpIndent() + "Or [\n"; - ++g_indent; - for (ConditionBase* operand : m_operands) { - retval += operand->Dump(); - } - --g_indent; - retval += "\n" + DumpIndent() + "]\n"; +std::string Or::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Or [\n"; + for (auto& operand : m_operands) + retval += operand->Dump(ntabs+1); + retval += "\n" + DumpIndent(ntabs) + "]\n"; return retval; } void Or::SetTopLevelContent(const std::string& content_name) { - for (ConditionBase* operand : m_operands) { + for (auto& operand : m_operands) { operand->SetTopLevelContent(content_name); } } @@ -9434,10 +9622,16 @@ unsigned int Or::GetCheckSum() const { /////////////////////////////////////////////////////////// // Not // /////////////////////////////////////////////////////////// -Not::~Not() -{ delete m_operand; } +Not::Not(std::unique_ptr&& operand) : + Condition(), + m_operand(std::move(operand)) +{ + m_root_candidate_invariant = m_operand->RootCandidateInvariant(); + m_target_invariant = m_operand->TargetInvariant(); + m_source_invariant = m_operand->SourceInvariant(); +} -bool Not::operator==(const ConditionBase& rhs) const { +bool Not::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -9453,9 +9647,6 @@ bool Not::operator==(const ConditionBase& rhs) const { void Not::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - if (!m_operand) { ErrorLogger() << "Not::Eval found no subcondition to evaluate!"; return; @@ -9464,43 +9655,20 @@ void Not::Eval(const ScriptingContext& parent_context, ObjectSet& matches, Objec if (search_domain == NON_MATCHES) { // search non_matches set for items that don't meet the operand // condition, and move those to the matches set - m_operand->Eval(local_context, non_matches, matches, MATCHES); // swapping order of matches and non_matches set parameters and MATCHES / NON_MATCHES search domain effects NOT on requested search domain + m_operand->Eval(parent_context, non_matches, matches, MATCHES); // swapping order of matches and non_matches set parameters and MATCHES / NON_MATCHES search domain effects NOT on requested search domain } else { // search matches set for items that meet the operand condition // condition, and move those to the non_matches set - m_operand->Eval(local_context, non_matches, matches, NON_MATCHES); + m_operand->Eval(parent_context, non_matches, matches, NON_MATCHES); } } -bool Not::RootCandidateInvariant() const { - if (m_root_candidate_invariant != UNKNOWN_INVARIANCE) - return m_root_candidate_invariant == INVARIANT; - m_root_candidate_invariant = m_operand->RootCandidateInvariant() ? INVARIANT: VARIANT; - return m_root_candidate_invariant == INVARIANT; -} - -bool Not::TargetInvariant() const { - if (m_target_invariant != UNKNOWN_INVARIANCE) - return m_target_invariant == INVARIANT; - m_target_invariant = m_operand->TargetInvariant() ? INVARIANT: VARIANT; - return m_target_invariant == INVARIANT; -} - -bool Not::SourceInvariant() const { - if (m_source_invariant != UNKNOWN_INVARIANCE) - return m_source_invariant == INVARIANT; - m_source_invariant = m_operand->SourceInvariant() ? INVARIANT: VARIANT; - return m_source_invariant == INVARIANT; -} - std::string Not::Description(bool negated/* = false*/) const -{ return m_operand->Description(true); } +{ return m_operand->Description(!negated); } -std::string Not::Dump() const { - std::string retval = DumpIndent() + "Not\n"; - ++g_indent; - retval += m_operand->Dump(); - --g_indent; +std::string Not::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Not\n"; + retval += m_operand->Dump(ntabs+1); return retval; } @@ -9519,13 +9687,218 @@ unsigned int Not::GetCheckSum() const { return retval; } +/////////////////////////////////////////////////////////// +// OrderedAlternativesOf +/////////////////////////////////////////////////////////// +void FCMoveContent(ObjectSet& from_set, ObjectSet& to_set) { + to_set.insert(to_set.end(), + std::make_move_iterator(from_set.begin()), + std::make_move_iterator(from_set.end())); + from_set.clear(); +} + +OrderedAlternativesOf::OrderedAlternativesOf( + std::vector>&& operands) : + Condition(), + m_operands(std::move(operands)) +{ + m_root_candidate_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->RootCandidateInvariant(); }); + m_target_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->TargetInvariant(); }); + m_source_invariant = boost::algorithm::all_of(m_operands, [](auto& e){ return !e || e->SourceInvariant(); }); +} + +bool OrderedAlternativesOf::operator==(const Condition& rhs) const { + if (this == &rhs) + return true; + if (typeid(*this) != typeid(rhs)) + return false; + + const OrderedAlternativesOf& rhs_ = static_cast(rhs); + + if (m_operands.size() != rhs_.m_operands.size()) + return false; + for (unsigned int i = 0; i < m_operands.size(); ++i) { + CHECK_COND_VREF_MEMBER(m_operands.at(i)) + } + + return true; +} + +void OrderedAlternativesOf::Eval(const ScriptingContext& parent_context, + ObjectSet& matches, ObjectSet& non_matches, + SearchDomain search_domain/* = NON_MATCHES*/) const +{ + if (m_operands.empty()) { + ErrorLogger() << "OrderedAlternativesOf::Eval given no operands!"; + return; + } + for (auto& operand : m_operands) { + if (!operand) { + ErrorLogger() << "OrderedAlternativesOf::Eval given null operand!"; + return; + } + } + + // OrderedAlternativesOf [ A B C ] matches all candidates which match the topmost condition. + // If any candidate matches A, then all candidates that match A are matched, + // or if no candidate matches A but any candidate matches B, then all candidates that match B are matched, + // or if no candidate matches A or B but any candidate matches C, then all candidates that match C are matched. + // If no candidate matches A, B, or C, then nothing is matched. + // + // Not OrderedAlternativesOf [ A B C ] finds the topmost condition which has matches and then matches its non-matches. + // If any candidate matches A, then all candidates that do not match A are matched, + // or if no candidates match A but any candidate matches B, then all candidates that do not match B are matched, + // or if no candidate matches A or B but any candidate matches C, then all candidates that do not match C are matched. + // If no candidate matches A, B, or C, then all candidates are matched. + if (search_domain == NON_MATCHES) { + // Check each operand condition on objects in the input matches and non_matches sets, until an operand condition matches an object. + // If an operand condition is selected, apply it to the input non_matches set, moving matching candidates to matches. + // If no operand condition is selected, because no candidate is matched by any operand condition, then do nothing. + ObjectSet temp_objects; + temp_objects.reserve(std::max(matches.size(),non_matches.size())); + + for (auto& operand : m_operands) { + operand->Eval(parent_context, temp_objects, non_matches, NON_MATCHES); + if (!temp_objects.empty()) { + // Select the current operand condition. Apply it to the NON_MATCHES candidate set. + // We alread did the application, so we use the results + matches.reserve(temp_objects.size() + matches.size()); + FCMoveContent(temp_objects, matches); + return; + } + // Check if the operand condition matches an object in the other input set + operand->Eval(parent_context, matches, temp_objects, MATCHES); + if (!matches.empty()) { + // Select the current operand condition. Apply it to the NON_MATCHES candidate set. + // We already did the application before, but there were no matches. + // restore state before checking the operand + FCMoveContent(temp_objects, matches); + return; + } + + // restore state before checking the operand + FCMoveContent(temp_objects, matches); + // try the next operand + } + + // No operand condition was selected. State is restored. Nothing should be moved to matches input set + } else /*(search_domain == MATCHES)*/ { + // Check each operand condition on objects in the input matches and non_matches sets, until an operand condition matches an object. + // If an operand condition is selected, apply it to the input matches set, moving non-matching candidates to non_matches. + // If no operand condition is selected, because no candidate is matched by any operand condition, then move all of the input matches into non_matches. + ObjectSet temp_objects; + temp_objects.reserve(std::max(matches.size(),non_matches.size())); + + for (auto& operand : m_operands) { + // Apply the current operand optimistically. Select it if there are any matching objects in the input sets + operand->Eval(parent_context, temp_objects, matches, NON_MATCHES); + // temp_objects are objects from input matches set which also match the operand + // matches are objects from input matches set which do not match the operand + if (!temp_objects.empty()) { + // Select and apply this operand. Objects in matches do not match this condition. + non_matches.reserve(matches.size() + non_matches.size()); + FCMoveContent(matches, non_matches); + FCMoveContent(temp_objects, matches); + return; + } + // Select this operand if there are matching objects in the non_matches input set. + operand->Eval(parent_context, temp_objects, non_matches, NON_MATCHES); + if (!temp_objects.empty()) { + // Select and apply this operand. But no matching objects exist in the matches input set, + // so all objects need to be moved into the non_matches set + non_matches.reserve(matches.size() + non_matches.size() + temp_objects.size()); + FCMoveContent(matches, non_matches); + FCMoveContent(temp_objects, non_matches); + return; + } + + // Operand was not selected. Restore state before. Try next operand. + FCMoveContent(temp_objects, matches); + } + + // No operand condition was selected. Objects in matches input set do not match, so move those to non_matches input set. + non_matches.reserve(matches.size() + non_matches.size()); + FCMoveContent(matches, non_matches); + } +} + +std::string OrderedAlternativesOf::Description(bool negated/* = false*/) const { + std::string values_str; + if (m_operands.size() == 1) { + values_str += (!negated) + ? UserString("DESC_ORDERED_ALTERNATIVES_BEFORE_SINGLE_OPERAND") + : UserString("DESC_NOT_ORDERED_ALTERNATIVES_BEFORE_SINGLE_OPERAND"); + // Pushing the negation of matches to the enclosed conditions + values_str += m_operands[0]->Description(negated); + values_str += (!negated) + ? UserString("DESC_ORDERED_ALTERNATIVES_AFTER_SINGLE_OPERAND") + : UserString("DESC_NOT_ORDERED_ALTERNATIVES_AFTER_SINGLE_OPERAND"); + } else { + // TODO: use per-operand-type connecting language + values_str += (!negated) + ? UserString("DESC_ORDERED_ALTERNATIVES_BEFORE_OPERANDS") + : UserString("DESC_NOT_ORDERED_ALTERNATIVES_BEFORE_OPERANDS"); + for (unsigned int i = 0; i < m_operands.size(); ++i) { + // Pushing the negation of matches to the enclosed conditions + values_str += m_operands[i]->Description(negated); + if (i != m_operands.size() - 1) { + values_str += (!negated) + ? UserString("DESC_ORDERED_ALTERNATIVES_BETWEEN_OPERANDS") + : UserString("DESC_NOT_ORDERED_ALTERNATIVES_BETWEEN_OPERANDS"); + } + } + values_str += (!negated) + ? UserString("DESC_ORDERED_ALTERNATIVES_AFTER_OPERANDS") + : UserString("DESC_NOT_ORDERED_ALTERNATIVES_AFTER_OPERANDS"); + } + return values_str; +} + +std::string OrderedAlternativesOf::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "OrderedAlternativesOf [\n"; + for (auto& operand : m_operands) + retval += operand->Dump(ntabs+1); + retval += DumpIndent(ntabs) + "]\n"; + return retval; +} + +void OrderedAlternativesOf::SetTopLevelContent(const std::string& content_name) { + for (auto& operand : m_operands) { + operand->SetTopLevelContent(content_name); + } +} + +unsigned int OrderedAlternativesOf::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "Condition::OrderedAlternativesOf"); + CheckSums::CheckSumCombine(retval, m_operands); + + TraceLogger() << "GetCheckSum(OrderedAlternativesOf): retval: " << retval; + return retval; +} + +const std::vector OrderedAlternativesOf::Operands() const { + std::vector retval(m_operands.size()); + std::transform(m_operands.begin(), m_operands.end(), retval.begin(), + [](const std::unique_ptr& xx) {return xx.get();}); + return retval; +} + /////////////////////////////////////////////////////////// // Described // /////////////////////////////////////////////////////////// -Described::~Described() -{ delete m_condition; } +Described::Described(std::unique_ptr&& condition, const std::string& desc_stringtable_key) : + Condition(), + m_condition(std::move(condition)), + m_desc_stringtable_key(desc_stringtable_key) +{ + m_root_candidate_invariant = !m_condition || m_condition->RootCandidateInvariant(); + m_target_invariant = !m_condition || m_condition->TargetInvariant(); + m_source_invariant = !m_condition || m_condition->SourceInvariant(); +} -bool Described::operator==(const ConditionBase& rhs) const { +bool Described::operator==(const Condition& rhs) const { if (this == &rhs) return true; if (typeid(*this) != typeid(rhs)) @@ -9544,14 +9917,10 @@ bool Described::operator==(const ConditionBase& rhs) const { void Described::Eval(const ScriptingContext& parent_context, ObjectSet& matches, ObjectSet& non_matches, SearchDomain search_domain/* = NON_MATCHES*/) const { - std::shared_ptr no_object; - ScriptingContext local_context(parent_context, no_object); - if (!m_condition) { ErrorLogger() << "Described::Eval found no subcondition to evaluate!"; return; } - return m_condition->Eval(parent_context, matches, non_matches, search_domain); } @@ -9563,15 +9932,6 @@ std::string Described::Description(bool negated/* = false*/) const { return ""; } -bool Described::RootCandidateInvariant() const -{ return !m_condition || m_condition->RootCandidateInvariant(); } - -bool Described::TargetInvariant() const -{ return !m_condition || m_condition->TargetInvariant(); } - -bool Described::SourceInvariant() const -{ return !m_condition || m_condition->SourceInvariant(); } - void Described::SetTopLevelContent(const std::string& content_name) { if (m_condition) m_condition->SetTopLevelContent(content_name); diff --git a/universe/Conditions.h b/universe/Conditions.h new file mode 100644 index 00000000000..88935dd11bd --- /dev/null +++ b/universe/Conditions.h @@ -0,0 +1,2125 @@ +#ifndef _Conditions_h_ +#define _Conditions_h_ + + +#include "EnumsFwd.h" +#include "Condition.h" +#include "ConditionSource.h" +#include "ConditionAll.h" + +#include "../util/Export.h" +#include "../util/CheckSums.h" + +#include + +#include +#include +#include + + +namespace ValueRef { + template + struct ValueRef; +} + +/** this namespace holds Condition and its subclasses; these classes + * represent predicates about UniverseObjects used by, for instance, the + * Effect system. */ +namespace Condition { + +enum SortingMethod : int { + SORT_MAX, ///< Objects with the largest sort key will be selected + SORT_MIN, ///< Objects with the smallest sort key will be selected + SORT_MODE, ///< Objects with the most common sort key will be selected + SORT_RANDOM ///< Objects will be selected randomly, without consideration of property values +}; + +enum ComparisonType : int { + INVALID_COMPARISON = -1, + EQUAL, + GREATER_THAN, + GREATER_THAN_OR_EQUAL, + LESS_THAN, + LESS_THAN_OR_EQUAL, + NOT_EQUAL +}; + +enum ContentType : int { + CONTENT_BUILDING, + CONTENT_SPECIES, + CONTENT_SHIP_HULL, + CONTENT_SHIP_PART, + CONTENT_SPECIAL, + CONTENT_FOCUS +}; + +/** Same as ConditionDescription, but returns a string only with conditions that have not been met. */ +FO_COMMON_API std::string ConditionFailedDescription(const std::vector& conditions, + std::shared_ptr candidate_object = nullptr, + std::shared_ptr source_object = nullptr); + +/** Returns a single string which describes a vector of Conditions. If multiple + * conditions are passed, they are treated as if they were contained by an And + * condition. Subconditions within an And (or nested And) are listed as + * lines in a list, with duplicates removed, titled something like "All of...". + * Subconditions within an Or (or nested Ors) are similarly listed as lines in + * a list, with duplicates removed, titled something like "One of...". If a + * candidate object is provided, the returned string will indicate which + * subconditions the candidate matches, and indicate if the overall combination + * of conditions matches the object. */ +FO_COMMON_API std::string ConditionDescription(const std::vector& conditions, + std::shared_ptr candidate_object = nullptr, + std::shared_ptr source_object = nullptr); + +/** Matches all objects if the number of objects that match Condition + * \a condition is is >= \a low and < \a high. Matched objects may + * or may not themselves match the condition. */ +struct FO_COMMON_API Number final : public Condition { + Number(std::unique_ptr>&& low, + std::unique_ptr>&& high, + std::unique_ptr&& condition); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_low; + std::unique_ptr> m_high; + std::unique_ptr m_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects if the current game turn is >= \a low and < \a high. */ +struct FO_COMMON_API Turn final : public Condition { + explicit Turn(std::unique_ptr>&& low, + std::unique_ptr>&& high = nullptr); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_low; + std::unique_ptr> m_high; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches a specified \a number of objects that match Condition \a condition + * or as many objects as match the condition if the number of objects is less + * than the number requested. If more objects match the condition than are + * requested, the objects are sorted according to the value of the specified + * \a property_name and objects are matched according to whether they have + * the specified \a sorting_type of those property values. For example, + * objects with the largest, smallest or most common property value may be + * selected preferentially. */ +struct FO_COMMON_API SortedNumberOf final : public Condition { + /** Sorts randomly, without considering a sort key. */ + SortedNumberOf(std::unique_ptr>&& number, + std::unique_ptr&& condition); + + /** Sorts according to the specified method, based on the key values + * evaluated for each object. */ + SortedNumberOf(std::unique_ptr>&& number, + std::unique_ptr>&& sort_key_ref, + SortingMethod sorting_method, + std::unique_ptr&& condition); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_number; + std::unique_ptr> m_sort_key; + SortingMethod m_sorting_method; + std::unique_ptr m_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches no objects. Currently only has an experimental use for efficient immediate rejection as the top-line condition. + * Essentially the entire point of this Condition is to provide the specialized GetDefaultInitialCandidateObjects() */ +struct FO_COMMON_API None final : public Condition { + None(); + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override + { /* efficient rejection of everything. */ } + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that are owned (if \a exclusive == false) or only owned + * (if \a exclusive == true) by an empire that has affilitation type + * \a affilitation with Empire \a empire_id. */ +struct FO_COMMON_API EmpireAffiliation final : public Condition { + EmpireAffiliation(std::unique_ptr>&& empire_id, EmpireAffiliationType affiliation); + explicit EmpireAffiliation(std::unique_ptr>&& empire_id); + explicit EmpireAffiliation(EmpireAffiliationType affiliation); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_empire_id; + EmpireAffiliationType m_affiliation; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches the root candidate object in a condition tree. This is useful + * within a subcondition to match the object actually being matched by the + * whole compound condition, rather than an object just being matched in a + * subcondition in order to evaluate the outer condition. */ +struct FO_COMMON_API RootCandidate final : public Condition { + RootCandidate(); + bool operator==(const Condition& rhs) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** There is no LocalCandidate condition. To match any local candidate object, + * use the All condition. */ + +/** Matches the target of an effect being executed. */ +struct FO_COMMON_API Target final : public Condition { + Target(); + bool operator==(const Condition& rhs) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches planets that are a homeworld for any of the species specified in + * \a names. If \a names is empty, matches any planet that is a homeworld for + * any species in the current game Universe. */ +struct FO_COMMON_API Homeworld final : public Condition { + Homeworld(); + explicit Homeworld(std::vector>>&& names); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::vector>> m_names; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches planets that are an empire's capital. */ +struct FO_COMMON_API Capital final : public Condition { + Capital(); + bool operator==(const Condition& rhs) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches space monsters. */ +struct FO_COMMON_API Monster final : public Condition { + Monster(); + bool operator==(const Condition& rhs) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches armed ships and monsters. */ +struct FO_COMMON_API Armed final : public Condition { + Armed(); + bool operator==(const Condition& rhs) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that are of UniverseObjectType \a type. */ +struct FO_COMMON_API Type final : public Condition { + explicit Type(std::unique_ptr>&& type); + explicit Type(UniverseObjectType type); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_type; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all Building objects that are one of the building types specified + * in \a names. */ +struct FO_COMMON_API Building final : public Condition { + explicit Building(std::vector>>&& names); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::vector>> m_names; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that have an attached Special named \a name. */ +struct FO_COMMON_API HasSpecial final : public Condition { + explicit HasSpecial(); + explicit HasSpecial(const std::string& name); + explicit HasSpecial(std::unique_ptr>&& name); + explicit HasSpecial(ValueRef::ValueRef* name); + HasSpecial(std::unique_ptr>&& name, + std::unique_ptr>&& since_turn_low, + std::unique_ptr>&& since_turn_high = nullptr); + HasSpecial(std::unique_ptr>&& name, + std::unique_ptr>&& capacity_low, + std::unique_ptr>&& capacity_high = nullptr); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_name; + std::unique_ptr> m_capacity_low; + std::unique_ptr> m_capacity_high; + std::unique_ptr> m_since_turn_low; + std::unique_ptr> m_since_turn_high; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that have the tag \a tag. */ +struct FO_COMMON_API HasTag final : public Condition { + HasTag(); + explicit HasTag(const std::string& name); + explicit HasTag(std::unique_ptr>&& name); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_name; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that were created on turns within the specified range. */ +struct FO_COMMON_API CreatedOnTurn final : public Condition { + CreatedOnTurn(std::unique_ptr>&& low, + std::unique_ptr>&& high); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_low; + std::unique_ptr> m_high; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that contain an object that matches Condition + * \a condition. Container objects are Systems, Planets (which contain + * Buildings), and Fleets (which contain Ships). */ +struct FO_COMMON_API Contains final : public Condition { + Contains(std::unique_ptr&& condition); + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr m_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that are contained by an object that matches Condition + * \a condition. Container objects are Systems, Planets (which contain + * Buildings), and Fleets (which contain Ships). */ +struct FO_COMMON_API ContainedBy final : public Condition { + ContainedBy(std::unique_ptr&& condition); + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr m_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that are in the system with the indicated \a system_id */ +struct FO_COMMON_API InSystem final : public Condition { + InSystem(std::unique_ptr>&& system_id); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_system_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches the object with the id \a object_id */ +struct FO_COMMON_API ObjectID final : public Condition { + ObjectID(std::unique_ptr>&& object_id); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_object_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all Planet objects that have one of the PlanetTypes in \a types. + * Note that all Building objects which are on matching planets are also + * matched. */ +struct FO_COMMON_API PlanetType final : public Condition { + PlanetType(std::vector>>&& types); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::vector>> m_types; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all Planet objects that have one of the PlanetSizes in \a sizes. + * Note that all Building objects which are on matching planets are also + * matched. */ +struct FO_COMMON_API PlanetSize final : public Condition { + PlanetSize(std::vector>>&& sizes); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::vector>> m_sizes; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all Planet objects that have one of the PlanetEnvironments in + * \a environments. Note that all Building objects which are on matching + * planets are also matched. */ +struct FO_COMMON_API PlanetEnvironment final : public Condition { + PlanetEnvironment(std::vector>>&& environments, + std::unique_ptr>&& species_name_ref = nullptr); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::vector>> m_environments; + std::unique_ptr> m_species_name; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all planets or ships that have one of the species in \a species. + * Note that all Building object which are on matching planets are also + * matched. */ +struct FO_COMMON_API Species final : public Condition { + explicit Species(std::vector>>&& names); + Species(); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::vector>> m_names; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches planets where the indicated number of the indicated building type + * or ship design are enqueued on the production queue. */ +struct FO_COMMON_API Enqueued final : public Condition { + Enqueued(BuildType build_type, + std::unique_ptr>&& name, + std::unique_ptr>&& empire_id = nullptr, + std::unique_ptr>&& low = nullptr, + std::unique_ptr>&& high = nullptr); + explicit Enqueued(std::unique_ptr>&& design_id, + std::unique_ptr>&& empire_id = nullptr, + std::unique_ptr>&& low = nullptr, + std::unique_ptr>&& high = nullptr); + Enqueued(); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + BuildType m_build_type; + std::unique_ptr> m_name; + std::unique_ptr> m_design_id; + std::unique_ptr> m_empire_id; + std::unique_ptr> m_low; + std::unique_ptr> m_high; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all ProdCenter objects that have one of the FocusTypes in \a foci. */ +struct FO_COMMON_API FocusType final : public Condition { + FocusType(std::vector>>&& names); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::vector>> m_names; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all System objects that have one of the StarTypes in \a types. Note that all objects + in matching Systems are also matched (Ships, Fleets, Buildings, Planets, etc.). */ +struct FO_COMMON_API StarType final : public Condition { + StarType(std::vector>>&& types); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::vector>> m_types; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all ships whose ShipDesign has the hull specified by \a name. */ +struct FO_COMMON_API DesignHasHull final : public Condition { + explicit DesignHasHull(std::unique_ptr>&& name); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_name; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all ships whose ShipDesign has >= \a low and < \a high of the ship + * part specified by \a name. */ +struct FO_COMMON_API DesignHasPart final : public Condition { + DesignHasPart(std::unique_ptr>&& name, + std::unique_ptr>&& low = nullptr, + std::unique_ptr>&& high = nullptr); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_low; + std::unique_ptr> m_high; + std::unique_ptr> m_name; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches ships whose ShipDesign has >= \a low and < \a high of ship parts of + * the specified \a part_class */ +struct FO_COMMON_API DesignHasPartClass final : public Condition { + DesignHasPartClass(ShipPartClass part_class, + std::unique_ptr>&& low, + std::unique_ptr>&& high); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_low; + std::unique_ptr> m_high; + ShipPartClass m_class; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches ships who ShipDesign is a predefined shipdesign with the name + * \a name */ +struct FO_COMMON_API PredefinedShipDesign final : public Condition { + explicit PredefinedShipDesign(std::unique_ptr>&& name); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_name; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches ships whose design id \a id. */ +struct FO_COMMON_API NumberedShipDesign final : public Condition { + NumberedShipDesign(std::unique_ptr>&& design_id); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_design_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches ships or buildings produced by the empire with id \a empire_id.*/ +struct FO_COMMON_API ProducedByEmpire final : public Condition { + ProducedByEmpire(std::unique_ptr>&& empire_id); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches a given object with a linearly distributed probability of \a chance. */ +struct FO_COMMON_API Chance final : public Condition { + Chance(std::unique_ptr>&& chance); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_chance; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that have a meter of type \a meter, and whose current + * value is >= \a low and <= \a high. */ +struct FO_COMMON_API MeterValue final : public Condition { + MeterValue(MeterType meter, + std::unique_ptr>&& low, + std::unique_ptr>&& high); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + MeterType m_meter; + std::unique_ptr> m_low; + std::unique_ptr> m_high; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches ships that have a ship part meter of type \a meter for part \a part + * whose current value is >= low and <= high. */ +struct FO_COMMON_API ShipPartMeterValue final : public Condition { + ShipPartMeterValue(std::unique_ptr>&& ship_part_name, + MeterType meter, + std::unique_ptr>&& low, + std::unique_ptr>&& high); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_part_name; + MeterType m_meter; + std::unique_ptr> m_low; + std::unique_ptr> m_high; +}; + +/** Matches all objects if the empire with id \a empire_id has an empire meter + * \a meter whose current value is >= \a low and <= \a high. */ +struct FO_COMMON_API EmpireMeterValue final : public Condition { + EmpireMeterValue(const std::string& meter, + std::unique_ptr>&& low, + std::unique_ptr>&& high); + EmpireMeterValue(std::unique_ptr>&& empire_id, + const std::string& meter, + std::unique_ptr>&& low, + std::unique_ptr>&& high); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_empire_id; + const std::string m_meter; + std::unique_ptr> m_low; + std::unique_ptr> m_high; +}; + +/** Matches all objects whose owner's stockpile of \a stockpile is between + * \a low and \a high, inclusive. */ +struct FO_COMMON_API EmpireStockpileValue final : public Condition { + EmpireStockpileValue(ResourceType stockpile, + std::unique_ptr>&& low, + std::unique_ptr>&& high); + EmpireStockpileValue(std::unique_ptr>&& empire_id, + ResourceType stockpile, + std::unique_ptr>&& low, + std::unique_ptr>&& high); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_empire_id; + ResourceType m_stockpile; + std::unique_ptr> m_low; + std::unique_ptr> m_high; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects whose owner who has tech \a name. */ +struct FO_COMMON_API OwnerHasTech final : public Condition { + OwnerHasTech(std::unique_ptr>&& empire_id, + std::unique_ptr>&& name); + explicit OwnerHasTech(std::unique_ptr>&& name); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_name; + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects whose owner who has the building type \a name available. */ +struct FO_COMMON_API OwnerHasBuildingTypeAvailable final : public Condition { + OwnerHasBuildingTypeAvailable(std::unique_ptr>&& empire_id, + std::unique_ptr>&& name); + explicit OwnerHasBuildingTypeAvailable(const std::string& name); + explicit OwnerHasBuildingTypeAvailable(std::unique_ptr>&& name); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_name; + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects whose owner who has the ship design \a id available. */ +struct FO_COMMON_API OwnerHasShipDesignAvailable final : public Condition { + OwnerHasShipDesignAvailable(std::unique_ptr>&& empire_id, + std::unique_ptr>&& design_id); + explicit OwnerHasShipDesignAvailable(int design_id); + explicit OwnerHasShipDesignAvailable(std::unique_ptr>&& design_id); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_id; + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects whose owner who has the ship part @a name available. */ +struct FO_COMMON_API OwnerHasShipPartAvailable final : public Condition { + OwnerHasShipPartAvailable(std::unique_ptr>&& empire_id, + std::unique_ptr>&& name); + explicit OwnerHasShipPartAvailable(const std::string& name); + explicit OwnerHasShipPartAvailable(std::unique_ptr>&& name); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_name; + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that are visible to at least one Empire in \a empire_ids. */ +struct FO_COMMON_API VisibleToEmpire final : public Condition { + explicit VisibleToEmpire(std::unique_ptr>&& empire_id); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that are within \a distance units of at least one + * object that meets \a condition. Warning: this Condition can slow things + * down considerably if overused. It is best to use Conditions that yield + * relatively few matches. */ +struct FO_COMMON_API WithinDistance final : public Condition { + WithinDistance(std::unique_ptr>&& distance, + std::unique_ptr&& condition); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_distance; + std::unique_ptr m_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that are within \a jumps starlane jumps of at least one + * object that meets \a condition. Warning: this Condition can slow things + * down considerably if overused. It is best to use Conditions that yield + * relatively few matches. */ +struct FO_COMMON_API WithinStarlaneJumps final : public Condition { + WithinStarlaneJumps(std::unique_ptr>&& jumps, + std::unique_ptr&& condition); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_jumps; + std::unique_ptr m_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches objects that are in systems that could have starlanes added between + * them and all (not just one) of the systems containing (or that are) one of + * the objects matched by \a condition. "Could have starlanes added" means + * that a lane would be geometrically acceptable, meaning it wouldn't cross + * any other lanes, pass too close to another system, or be too close in angle + * to an existing lane. */ +struct FO_COMMON_API CanAddStarlaneConnection : Condition { + explicit CanAddStarlaneConnection(std::unique_ptr&& condition); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr m_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches systems that have been explored by at least one Empire + * in \a empire_ids. */ +struct FO_COMMON_API ExploredByEmpire final : public Condition { + explicit ExploredByEmpire(std::unique_ptr>&& empire_id); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches objects that are moving. ... What does that mean? Departing this + * turn, or were located somewhere else last turn...? */ +struct FO_COMMON_API Stationary final : public Condition { + explicit Stationary(); + + bool operator==(const Condition& rhs) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches objects that are aggressive fleets or are in aggressive fleets. */ +struct FO_COMMON_API Aggressive final : public Condition { + explicit Aggressive(); + explicit Aggressive(bool aggressive); + + bool operator==(const Condition& rhs) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + bool GetAggressive() const + { return m_aggressive; } + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + bool m_aggressive; // false to match passive ships/fleets + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches objects that are in systems that can be fleet supplied by the + * empire with id \a empire_id */ +struct FO_COMMON_API FleetSupplyableByEmpire final : public Condition { + explicit FleetSupplyableByEmpire(std::unique_ptr>&& empire_id); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches objects that are in systems that are connected by resource-sharing + * to at least one object that meets \a condition using the resource-sharing + * network of the empire with id \a empire_id */ +struct FO_COMMON_API ResourceSupplyConnectedByEmpire final : public Condition { + ResourceSupplyConnectedByEmpire(std::unique_ptr>&& empire_id, + std::unique_ptr&& condition); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_empire_id; + std::unique_ptr m_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches objects whose species has the ability to found new colonies. */ +struct FO_COMMON_API CanColonize final : public Condition { + explicit CanColonize(); + + bool operator==(const Condition& rhs) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches objects whose species has the ability to produce ships. */ +struct FO_COMMON_API CanProduceShips final : public Condition { + CanProduceShips(); + + bool operator==(const Condition& rhs) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override + {} + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches the objects that have been targeted for bombardment by at least one + * object that matches \a m_by_object_condition. */ +struct FO_COMMON_API OrderedBombarded final : public Condition { + explicit OrderedBombarded(std::unique_ptr&& by_object_condition); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + virtual void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr m_by_object_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects if the comparisons between values of ValueRefs meet the + * specified comparison types. */ +struct FO_COMMON_API ValueTest final : public Condition { + ValueTest(std::unique_ptr>&& value_ref1, + ComparisonType comp1, + std::unique_ptr>&& value_ref2, + ComparisonType comp2 = INVALID_COMPARISON, + std::unique_ptr>&& value_ref3 = nullptr); + + ValueTest(std::unique_ptr>&& value_ref1, + ComparisonType comp1, + std::unique_ptr>&& value_ref2, + ComparisonType comp2 = INVALID_COMPARISON, + std::unique_ptr>&& value_ref3 = nullptr); + + ValueTest(std::unique_ptr>&& value_ref1, + ComparisonType comp1, + std::unique_ptr>&& value_ref2, + ComparisonType comp2 = INVALID_COMPARISON, + std::unique_ptr>&& value_ref3 = nullptr); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_value_ref1; + std::unique_ptr> m_value_ref2; + std::unique_ptr> m_value_ref3; + std::unique_ptr> m_string_value_ref1; + std::unique_ptr> m_string_value_ref2; + std::unique_ptr> m_string_value_ref3; + std::unique_ptr> m_int_value_ref1; + std::unique_ptr> m_int_value_ref2; + std::unique_ptr> m_int_value_ref3; + + ComparisonType m_compare_type1 = INVALID_COMPARISON; + ComparisonType m_compare_type2 = INVALID_COMPARISON; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches objects that match the location condition of the specified + * content. */ +struct FO_COMMON_API Location final : public Condition { +public: + Location(ContentType content_type, + std::unique_ptr>&& name1, + std::unique_ptr>&& name2 = nullptr); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_name1; + std::unique_ptr> m_name2; + ContentType m_content_type; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches objects that match the combat targeting condition of the specified + * content. */ +struct FO_COMMON_API CombatTarget final : public Condition { +public: + CombatTarget(ContentType content_type, + std::unique_ptr>&& name); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + bool Match(const ScriptingContext& local_context) const override; + + std::unique_ptr> m_name; + ContentType m_content_type; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + + +/** Matches all objects that match every Condition in \a operands. */ +struct FO_COMMON_API And final : public Condition { + explicit And(std::vector>&& operands); + And(std::unique_ptr&& operand1, + std::unique_ptr&& operand2, + std::unique_ptr&& operand3 = nullptr, + std::unique_ptr&& operand4 = nullptr); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + void GetDefaultInitialCandidateObjects(const ScriptingContext& parent_context, + ObjectSet& condition_non_targets) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + const std::vector Operands() const; + unsigned int GetCheckSum() const override; + +private: + std::vector> m_operands; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that match at least one Condition in \a operands. */ +struct FO_COMMON_API Or final : public Condition { + explicit Or(std::vector>&& operands); + Or(std::unique_ptr&& operand1, + std::unique_ptr&& operand2, + std::unique_ptr&& operand3 = nullptr, + std::unique_ptr&& operand4 = nullptr); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + virtual void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::vector> m_operands; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches all objects that do not match the Condition \a operand. */ +struct FO_COMMON_API Not final : public Condition { + explicit Not(std::unique_ptr&& operand); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr m_operand; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Tests conditions in \a operands in order, to find the first condition that + * matches at least one candidate object. Matches all objects that match that + * condaition, ignoring any conditions listed later. If no candidate matches + * any of the conditions, it matches nothing. */ +struct FO_COMMON_API OrderedAlternativesOf final : public Condition { + explicit OrderedAlternativesOf(std::vector>&& operands); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + const std::vector Operands() const; + unsigned int GetCheckSum() const override; + +private: + std::vector> m_operands; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Matches whatever its subcondition matches, but has a customized description + * string that is returned by Description() by looking up in the stringtable. */ +struct FO_COMMON_API Described final : public Condition { + Described(std::unique_ptr&& condition, const std::string& desc_stringtable_key); + + bool operator==(const Condition& rhs) const override; + void Eval(const ScriptingContext& parent_context, ObjectSet& matches, + ObjectSet& non_matches, SearchDomain search_domain = NON_MATCHES) const override; + std::string Description(bool negated = false) const override; + std::string Dump(unsigned short ntabs = 0) const override + { return m_condition ? m_condition->Dump(ntabs) : ""; } + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr m_condition; + std::string m_desc_stringtable_key; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +// template implementations +template +void Condition::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_NVP(m_root_candidate_invariant) + & BOOST_SERIALIZATION_NVP(m_target_invariant) + & BOOST_SERIALIZATION_NVP(m_source_invariant); +} + +template +void Number::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_low) + & BOOST_SERIALIZATION_NVP(m_high) + & BOOST_SERIALIZATION_NVP(m_condition); +} + +template +void Turn::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_low) + & BOOST_SERIALIZATION_NVP(m_high); +} + +template +void SortedNumberOf::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_number) + & BOOST_SERIALIZATION_NVP(m_sort_key) + & BOOST_SERIALIZATION_NVP(m_sorting_method) + & BOOST_SERIALIZATION_NVP(m_condition); +} + +template +void All::serialize(Archive& ar, const unsigned int version) +{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); } + +template +void None::serialize(Archive& ar, const unsigned int version) +{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); } + +template +void EmpireAffiliation::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_empire_id) + & BOOST_SERIALIZATION_NVP(m_affiliation); +} + +template +void Source::serialize(Archive& ar, const unsigned int version) +{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); } + +template +void RootCandidate::serialize(Archive& ar, const unsigned int version) +{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); } + +template +void Target::serialize(Archive& ar, const unsigned int version) +{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); } + +template +void Homeworld::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_names); +} + +template +void Capital::serialize(Archive& ar, const unsigned int version) +{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); } + +template +void Monster::serialize(Archive& ar, const unsigned int version) +{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); } + +template +void Armed::serialize(Archive& ar, const unsigned int version) +{ ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); } + +template +void Type::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_type); +} + +template +void Building::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_names); +} + +template +void HasSpecial::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_capacity_low) + & BOOST_SERIALIZATION_NVP(m_capacity_high) + & BOOST_SERIALIZATION_NVP(m_since_turn_low) + & BOOST_SERIALIZATION_NVP(m_since_turn_high); +} + +template +void HasTag::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_name); +} + +template +void CreatedOnTurn::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_low) + & BOOST_SERIALIZATION_NVP(m_high); +} + +template +void Contains::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_condition); +} + +template +void ContainedBy::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_condition); +} + +template +void InSystem::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_system_id); +} + +template +void ObjectID::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_object_id); +} + +template +void PlanetType::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_types); +} + +template +void PlanetSize::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_sizes); +} + +template +void PlanetEnvironment::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_environments) + & BOOST_SERIALIZATION_NVP(m_species_name); +} + +template +void Species::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_names); +} + +template +void Enqueued::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_build_type) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_design_id) + & BOOST_SERIALIZATION_NVP(m_empire_id) + & BOOST_SERIALIZATION_NVP(m_low) + & BOOST_SERIALIZATION_NVP(m_high); +} + +template +void FocusType::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_names); +} + +template +void StarType::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_types); +} + +template +void DesignHasHull::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_name); +} + +template +void DesignHasPart::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_low) + & BOOST_SERIALIZATION_NVP(m_high) + & BOOST_SERIALIZATION_NVP(m_name); +} + +template +void DesignHasPartClass::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_low) + & BOOST_SERIALIZATION_NVP(m_high) + & BOOST_SERIALIZATION_NVP(m_class); +} + +template +void PredefinedShipDesign::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_name); +} + +template +void NumberedShipDesign::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_design_id); +} + +template +void ProducedByEmpire::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void Chance::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_chance); +} + +template +void MeterValue::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_meter) + & BOOST_SERIALIZATION_NVP(m_low) + & BOOST_SERIALIZATION_NVP(m_high); +} + +template +void EmpireStockpileValue::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_low) + & BOOST_SERIALIZATION_NVP(m_high) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void OwnerHasTech::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void OwnerHasBuildingTypeAvailable::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void OwnerHasShipDesignAvailable::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_id) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void OwnerHasShipPartAvailable::serialize(Archive& ar, + const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void VisibleToEmpire::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void WithinDistance::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_distance) + & BOOST_SERIALIZATION_NVP(m_condition); +} + +template +void WithinStarlaneJumps::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_jumps) + & BOOST_SERIALIZATION_NVP(m_condition); +} + +template +void CanAddStarlaneConnection::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_condition); +} + +template +void ExploredByEmpire::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void Stationary::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); +} + +template +void Aggressive::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_aggressive); +} + +template +void FleetSupplyableByEmpire::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void ResourceSupplyConnectedByEmpire::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_empire_id) + & BOOST_SERIALIZATION_NVP(m_condition); +} + +template +void CanColonize::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); +} + +template +void CanProduceShips::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition); +} + +template +void OrderedBombarded::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_by_object_condition); +} + +template +void ValueTest::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_value_ref1) + & BOOST_SERIALIZATION_NVP(m_value_ref2) + & BOOST_SERIALIZATION_NVP(m_value_ref3) + & BOOST_SERIALIZATION_NVP(m_string_value_ref1) + & BOOST_SERIALIZATION_NVP(m_string_value_ref2) + & BOOST_SERIALIZATION_NVP(m_string_value_ref3) + & BOOST_SERIALIZATION_NVP(m_int_value_ref1) + & BOOST_SERIALIZATION_NVP(m_int_value_ref2) + & BOOST_SERIALIZATION_NVP(m_int_value_ref3) + & BOOST_SERIALIZATION_NVP(m_compare_type1) + & BOOST_SERIALIZATION_NVP(m_compare_type2); +} + +template +void Location::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_name1) + & BOOST_SERIALIZATION_NVP(m_name2) + & BOOST_SERIALIZATION_NVP(m_content_type); +} + +template +void CombatTarget::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_content_type); +} + +template +void And::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_operands); +} + +template +void Or::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_operands); +} + +template +void Not::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_operand); +} + +template +void OrderedAlternativesOf::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_operands); +} + +template +void Described::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Condition) + & BOOST_SERIALIZATION_NVP(m_condition) + & BOOST_SERIALIZATION_NVP(m_desc_stringtable_key); +} +} // namespace Condition + +#endif // _Conditions_h_ diff --git a/universe/Effect.cpp b/universe/Effect.cpp index 7f356a90dd5..80127a8980b 100644 --- a/universe/Effect.cpp +++ b/universe/Effect.cpp @@ -1,4039 +1,72 @@ #include "Effect.h" -#include "../util/Logger.h" -#include "../util/OptionsDB.h" -#include "../util/Random.h" -#include "../util/Directories.h" -#include "../util/i18n.h" -#include "../util/SitRepEntry.h" -#include "../Empire/EmpireManager.h" -#include "../Empire/Empire.h" -#include "ValueRef.h" -#include "Condition.h" -#include "Pathfinder.h" -#include "Universe.h" #include "UniverseObject.h" -#include "Building.h" -#include "Planet.h" -#include "System.h" -#include "Field.h" -#include "Fleet.h" -#include "Ship.h" -#include "Tech.h" -#include "Species.h" +#include "ObjectMap.h" #include "Enums.h" -#include - -#include -#include - -namespace { - DeclareThreadSafeLogger(effects); -} - -using boost::io::str; - -extern int g_indent; -FO_COMMON_API extern const int INVALID_DESIGN_ID; - -namespace { - /** creates a new fleet at a specified \a x and \a y location within the - * Universe, and and inserts \a ship into it. Used when a ship has been - * moved by the MoveTo effect separately from the fleet that previously - * held it. All ships need to be within fleets. */ - std::shared_ptr CreateNewFleet(double x, double y, std::shared_ptr ship) { - Universe& universe = GetUniverse(); - if (!ship) - return nullptr; - - auto fleet = universe.InsertNew("", x, y, ship->Owner()); - - std::vector ship_ids; - ship_ids.push_back(ship->ID()); - fleet->Rename(fleet->GenerateFleetName()); - fleet->GetMeter(METER_STEALTH)->SetCurrent(Meter::LARGE_VALUE); - - fleet->AddShip(ship->ID()); - ship->SetFleetID(fleet->ID()); - fleet->SetAggressive(fleet->HasArmedShips() || fleet->HasFighterShips()); - - return fleet; - } - - /** Creates a new fleet at \a system and inserts \a ship into it. Used - * when a ship has been moved by the MoveTo effect separately from the - * fleet that previously held it. Also used by CreateShip effect to give - * the new ship a fleet. All ships need to be within fleets. */ - std::shared_ptr CreateNewFleet(std::shared_ptr system, std::shared_ptr ship) { - if (!system || !ship) - return nullptr; - - // remove ship from old fleet / system, put into new system if necessary - if (ship->SystemID() != system->ID()) { - if (std::shared_ptr old_system = GetSystem(ship->SystemID())) { - old_system->Remove(ship->ID()); - ship->SetSystem(INVALID_OBJECT_ID); - } - system->Insert(ship); - } - - if (ship->FleetID() != INVALID_OBJECT_ID) { - if (std::shared_ptr old_fleet = GetFleet(ship->FleetID())) { - old_fleet->RemoveShip(ship->ID()); - } - } - - // create new fleet for ship, and put it in new system - std::shared_ptr fleet = CreateNewFleet(system->X(), system->Y(), ship); - system->Insert(fleet); - - return fleet; - } - - /** Explores the system with the specified \a system_id for the owner of - * the specified \a target_object. Used when moving objects into a system - * with the MoveTo effect, as otherwise the system wouldn't get explored, - * and objects being moved into unexplored systems might disappear for - * players or confuse the AI. */ - void ExploreSystem(int system_id, std::shared_ptr target_object) { - if (!target_object) - return; - if (Empire* empire = GetEmpire(target_object->Owner())) - empire->AddExploredSystem(system_id); - } - - /** Resets the previous and next systems of \a fleet and recalcultes / - * resets the fleet's move route. Used after a fleet has been moved with - * the MoveTo effect, as its previous route was assigned based on its - * previous location, and may not be valid for its new location. */ - void UpdateFleetRoute(std::shared_ptr fleet, int new_next_system, int new_previous_system) { - if (!fleet) { - ErrorLogger() << "UpdateFleetRoute passed a null fleet pointer"; - return; - } - - std::shared_ptr next_system = GetSystem(new_next_system); - if (!next_system) { - ErrorLogger() << "UpdateFleetRoute couldn't get new next system with id: " << new_next_system; - return; - } - - if (new_previous_system != INVALID_OBJECT_ID && !GetSystem(new_previous_system)) { - ErrorLogger() << "UpdateFleetRoute couldn't get new previous system with id: " << new_previous_system; - } - - fleet->SetNextAndPreviousSystems(new_next_system, new_previous_system); - - - // recalculate route from the shortest path between first system on path and final destination - int start_system = fleet->SystemID(); - if (start_system == INVALID_OBJECT_ID) - start_system = new_next_system; - - int dest_system = fleet->FinalDestinationID(); - - std::pair, double> route_pair = GetPathfinder()->ShortestPath(start_system, dest_system, fleet->Owner()); - - // if shortest path is empty, the route may be impossible or trivial, so just set route to move fleet - // to the next system that it was just set to move to anyway. - if (route_pair.first.empty()) - route_pair.first.push_back(new_next_system); - - - // set fleet with newly recalculated route - fleet->SetRoute(route_pair.first); - } - - std::string GenerateSystemName() { - static std::vector star_names = UserStringList("STAR_NAMES"); - - // pick a name for the system - for (const std::string& star_name : star_names) { - // does an existing system have this name? - bool dupe = false; - for (std::shared_ptr system : Objects().FindObjects()) { - if (system->Name() == star_name) { - dupe = true; - break; // another systme has this name. skip to next potential name. - } - } - if (!dupe) - return star_name; // no systems have this name yet. use it. - } - return ""; // fallback to empty name. - } -} - -namespace Effect { -/////////////////////////////////////////////////////////// -// EffectsGroup // -/////////////////////////////////////////////////////////// -EffectsGroup::~EffectsGroup() { - delete m_scope; - delete m_activation; - for (EffectBase* effect : m_effects) { - delete effect; - } -} - -void EffectsGroup::Execute(const TargetsCauses& targets_causes, AccountingMap* accounting_map, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) const -{ - // execute each effect of the group one by one, unless filtered by flags - for (EffectBase* effect : m_effects) - { - effect->Execute(targets_causes, accounting_map, - only_meter_effects, only_appearance_effects, - include_empire_meter_effects, only_generate_sitrep_effects); - } -} - -const std::string& EffectsGroup::GetDescription() const -{ return m_description; } - -std::string EffectsGroup::Dump() const { - std::string retval = DumpIndent() + "EffectsGroup\n"; - ++g_indent; - retval += DumpIndent() + "scope =\n"; - ++g_indent; - retval += m_scope->Dump(); - --g_indent; - if (m_activation) { - retval += DumpIndent() + "activation =\n"; - ++g_indent; - retval += m_activation->Dump(); - --g_indent; - } - if (!m_stacking_group.empty()) - retval += DumpIndent() + "stackinggroup = \"" + m_stacking_group + "\"\n"; - if (m_effects.size() == 1) { - retval += DumpIndent() + "effects =\n"; - ++g_indent; - retval += m_effects[0]->Dump(); - --g_indent; - } else { - retval += DumpIndent() + "effects = [\n"; - ++g_indent; - for (EffectBase* effect : m_effects) { - retval += effect->Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; - } - --g_indent; - return retval; -} - -bool EffectsGroup::HasMeterEffects() const { - for (EffectBase* effect : m_effects) { - if (effect->IsMeterEffect()) - return true; - } - return false; -} - -bool EffectsGroup::HasAppearanceEffects() const { - for (EffectBase* effect : m_effects) { - if (effect->IsAppearanceEffect()) - return true; - } - return false; -} - -bool EffectsGroup::HasSitrepEffects() const { - for (EffectBase* effect : m_effects) { - if (effect->IsSitrepEffect()) - return true; - } - return false; -} - -void EffectsGroup::SetTopLevelContent(const std::string& content_name) { - if (m_scope) - m_scope->SetTopLevelContent(content_name); - if (m_activation) - m_activation->SetTopLevelContent(content_name); - for (EffectBase* effect : m_effects) { - effect->SetTopLevelContent(content_name); - } -} - -unsigned int EffectsGroup::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "EffectsGroup"); - CheckSums::CheckSumCombine(retval, m_scope); - CheckSums::CheckSumCombine(retval, m_activation); - CheckSums::CheckSumCombine(retval, m_stacking_group); - CheckSums::CheckSumCombine(retval, m_effects); - CheckSums::CheckSumCombine(retval, m_accounting_label); - CheckSums::CheckSumCombine(retval, m_priority); - CheckSums::CheckSumCombine(retval, m_description); - - TraceLogger() << "GetCheckSum(EffectsGroup): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// Dump function // -/////////////////////////////////////////////////////////// -std::string Dump(const std::vector>& effects_groups) { - std::stringstream retval; - - for (std::shared_ptr effects_group : effects_groups) { - retval << "\n" << effects_group->Dump(); - } - - return retval.str(); -} - - -/////////////////////////////////////////////////////////// -// EffectBase // -/////////////////////////////////////////////////////////// -EffectBase::~EffectBase() -{} - -void EffectBase::Execute(const TargetsCauses& targets_causes, AccountingMap* accounting_map, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) const -{ - if (only_appearance_effects || only_meter_effects || only_generate_sitrep_effects) - return; // overrides should catch all these effects - - // apply this effect for each source causing it - ScriptingContext source_context; - for (const std::pair& targets_entry : targets_causes) { - source_context.source = GetUniverseObject(targets_entry.first.source_object_id); - Execute(source_context, targets_entry.second.target_set); - } -} - -void EffectBase::Execute(const ScriptingContext& context, const TargetSet& targets) const { - if (targets.empty()) - return; - - // execute effects on targets - ScriptingContext local_context = context; - - for (std::shared_ptr target : targets) { - local_context.effect_target = target; - this->Execute(local_context); - } -} - -unsigned int EffectBase::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "EffectBase"); - - TraceLogger() << "GetCheckSum(EffectsGroup): retval: " << retval; - return retval; -} - -/////////////////////////////////////////////////////////// -// NoOp // -/////////////////////////////////////////////////////////// -NoOp::NoOp() -{} - -void NoOp::Execute(const ScriptingContext& context) const -{} - -std::string NoOp::Dump() const -{ return DumpIndent() + "NoOp\n"; } - -unsigned int NoOp::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "NoOp"); - - TraceLogger() << "GetCheckSum(NoOp): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetMeter // -/////////////////////////////////////////////////////////// -SetMeter::SetMeter(MeterType meter, ValueRef::ValueRefBase* value) : - m_meter(meter), - m_value(value), - m_accounting_label() -{} - -SetMeter::SetMeter(MeterType meter, ValueRef::ValueRefBase* value, const std::string& accounting_label) : - m_meter(meter), - m_value(value), - m_accounting_label(accounting_label) -{} - -SetMeter::~SetMeter() -{ delete m_value; } - -void SetMeter::Execute(const ScriptingContext& context) const { - if (!context.effect_target) return; - Meter* m = context.effect_target->GetMeter(m_meter); - if (!m) return; - - float val = m_value->Eval(ScriptingContext(context, m->Current())); - m->SetCurrent(val); -} - -void SetMeter::Execute(const TargetsCauses& targets_causes, AccountingMap* accounting_map, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) const -{ - if (only_appearance_effects || only_generate_sitrep_effects) - return; - - // apply this effect for each source causing it - for (const std::pair& targets_entry : targets_causes) { - const SourcedEffectsGroup& sourced_effects_group = targets_entry.first; - int source_id = sourced_effects_group.source_object_id; - std::shared_ptr source = GetUniverseObject(source_id); - ScriptingContext source_context(source); - const TargetsAndCause& targets_and_cause = targets_entry.second; - TargetSet targets = targets_and_cause.target_set; - - - TraceLogger(effects) << "\n\nExecute SetMeter effect: \n" << Dump(); - TraceLogger(effects) << "SetMeter execute targets before: "; - for (std::shared_ptr target : targets) - TraceLogger(effects) << " ... " << target->Dump(); - - if (!accounting_map) { - // without accounting, can do default batch execute - Execute(source_context, targets); - - } else if (!accounting_map) { - // process each target separately in order to do effect accounting for each - for (std::shared_ptr target : targets) { - Execute(ScriptingContext(source, target)); - } - - } else { - // accounting info for this effect on this meter, starting with non-target-dependent info - AccountingInfo info; - if (accounting_map) { - info.cause_type = targets_and_cause.effect_cause.cause_type; - info.specific_cause = targets_and_cause.effect_cause.specific_cause; - info.custom_label = (m_accounting_label.empty() ? targets_and_cause.effect_cause.custom_label : m_accounting_label); - info.source_id = source_id; - } - - // process each target separately in order to do effect accounting for each - for (std::shared_ptr target : targets) { - // get Meter for this effect and target - const Meter* meter = target->GetMeter(m_meter); - if (!meter) - continue; // some objects might match target conditions, but not actually have the relevant meter. In that case, don't need to do accounting. - - // record pre-effect meter values... - - // accounting info for this effect on this meter of this target - info.running_meter_total = meter->Current(); - - // actually execute effect to modify meter - Execute(ScriptingContext(source, target)); - - // update for meter change and new total - info.meter_change = meter->Current() - info.running_meter_total; - info.running_meter_total = meter->Current(); - - // add accounting for this effect to end of vector - (*accounting_map)[target->ID()][m_meter].push_back(info); - } - } - - TraceLogger(effects) << "SetMeter execute targets after: "; - for (std::shared_ptr target : targets) - TraceLogger(effects) << " ... " << target->Dump(); - } -} - -void SetMeter::Execute(const ScriptingContext& context, const TargetSet& targets) const { - if (targets.empty()) - return; - if (m_value->TargetInvariant()) { - // meter value does not depend on target, so handle with single ValueRef evaluation - float val = m_value->Eval(context); - for (std::shared_ptr target : targets) { - Meter* m = target->GetMeter(m_meter); - if (!m) continue; - m->SetCurrent(val); - } - return; - } else if (m_value->SimpleIncrement()) { - // meter value is a consistent constant increment for each target, so handle with - // deep inspection single ValueRef evaluation - ValueRef::Operation* op = dynamic_cast*>(m_value); - if (!op) { - ErrorLogger() << "SetMeter::Execute couldn't cast simple increment ValueRef to an Operation. Reverting to standard execute."; - EffectBase::Execute(context, targets); - return; - } - // RHS should be a ConstantExpr - float increment = op->RHS()->Eval(); - if (op->GetOpType() == ValueRef::PLUS) { - // do nothing to modify increment - } else if (op->GetOpType() == ValueRef::MINUS) { - increment = -increment; - } else { - ErrorLogger() << "SetMeter::Execute got invalid increment optype (not PLUS or MINUS). Reverting to standard execute."; - EffectBase::Execute(context, targets); - return; - } - //DebugLogger() << "simple increment: " << increment; - // increment all target meters... - for (std::shared_ptr target : targets) { - Meter* m = target->GetMeter(m_meter); - if (!m) continue; - m->AddToCurrent(increment); - } - return; - } - - // meter value depends on target non-trivially, so handle with default case of per-target ValueRef evaluation - EffectBase::Execute(context, targets); -} - -std::string SetMeter::Dump() const { - std::string retval = DumpIndent() + "Set"; - switch (m_meter) { - case METER_TARGET_POPULATION: retval += "TargetPopulation"; break; - case METER_TARGET_INDUSTRY: retval += "TargetIndustry"; break; - case METER_TARGET_RESEARCH: retval += "TargetResearch"; break; - case METER_TARGET_TRADE: retval += "TargetTrade"; break; - case METER_TARGET_CONSTRUCTION: retval += "TargetConstruction"; break; - case METER_TARGET_HAPPINESS: retval += "TargetHappiness"; break; - - case METER_MAX_CAPACITY: retval += "MaxCapacity"; break; - - case METER_MAX_FUEL: retval += "MaxFuel"; break; - case METER_MAX_SHIELD: retval += "MaxShield"; break; - case METER_MAX_STRUCTURE: retval += "MaxStructure"; break; - case METER_MAX_DEFENSE: retval += "MaxDefense"; break; - case METER_MAX_SUPPLY: retval += "MaxSupply"; break; - case METER_MAX_TROOPS: retval += "MaxTroops"; break; - - case METER_POPULATION: retval += "Population"; break; - case METER_INDUSTRY: retval += "Industry"; break; - case METER_RESEARCH: retval += "Research"; break; - case METER_TRADE: retval += "Trade"; break; - case METER_CONSTRUCTION: retval += "Construction"; break; - case METER_HAPPINESS: retval += "Happiness"; break; - - case METER_CAPACITY: retval += "Capacity"; break; - - case METER_FUEL: retval += "Fuel"; break; - case METER_SHIELD: retval += "Shield"; break; - case METER_STRUCTURE: retval += "Structure"; break; - case METER_DEFENSE: retval += "Defense"; break; - case METER_SUPPLY: retval += "Supply"; break; - case METER_TROOPS: retval += "Troops"; break; - - case METER_REBEL_TROOPS: retval += "RebelTroops"; break; - case METER_SIZE: retval += "Size"; break; - case METER_STEALTH: retval += "Stealth"; break; - case METER_DETECTION: retval += "Detection"; break; - case METER_SPEED: retval += "Speed"; break; - - default: retval += "?"; break; - } - retval += " value = " + m_value->Dump() + "\n"; - return retval; -} - -void SetMeter::SetTopLevelContent(const std::string& content_name) { - if (m_value) - m_value->SetTopLevelContent(content_name); -} - -unsigned int SetMeter::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetMeter"); - CheckSums::CheckSumCombine(retval, m_meter); - CheckSums::CheckSumCombine(retval, m_value); - CheckSums::CheckSumCombine(retval, m_accounting_label); - - TraceLogger() << "GetCheckSum(SetMeter): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetShipPartMeter // -/////////////////////////////////////////////////////////// -SetShipPartMeter::SetShipPartMeter(MeterType meter, - ValueRef::ValueRefBase* part_name, - ValueRef::ValueRefBase* value) : - m_part_name(part_name), - m_meter(meter), - m_value(value) -{} - -SetShipPartMeter::~SetShipPartMeter() { - delete m_value; - delete m_part_name; -} - -void SetShipPartMeter::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - DebugLogger() << "SetShipPartMeter::Execute passed null target pointer"; - return; - } - - if (!m_part_name || !m_value) { - ErrorLogger() << "SetShipPartMeter::Execute missing part name or value ValueRefs"; - return; - } - - std::shared_ptr ship = std::dynamic_pointer_cast(context.effect_target); - if (!ship) { - ErrorLogger() << "SetShipPartMeter::Execute acting on non-ship target:"; - //context.effect_target->Dump(); - return; - } - - std::string part_name = m_part_name->Eval(context); - - // get meter, evaluate new value, assign - Meter* meter = ship->GetPartMeter(m_meter, part_name); - if (!meter) - return; - - double val = m_value->Eval(ScriptingContext(context, meter->Current())); - meter->SetCurrent(val); -} - -void SetShipPartMeter::Execute(const TargetsCauses& targets_causes, AccountingMap* accounting_map, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) const -{ - if (only_appearance_effects || only_generate_sitrep_effects) - return; - - // apply this effect for each source causing it - for (const std::pair& targets_entry : targets_causes) { - const SourcedEffectsGroup& sourced_effects_group = targets_entry.first; - int source_id = sourced_effects_group.source_object_id; - std::shared_ptr source = GetUniverseObject(source_id); - ScriptingContext source_context(source); - const TargetsAndCause& targets_and_cause = targets_entry.second; - TargetSet targets = targets_and_cause.target_set; - - TraceLogger(effects) << "\n\nExecute SetShipPartMeter effect: \n" << Dump(); - TraceLogger(effects) << "SetShipPartMeter execute targets before: "; - for (std::shared_ptr target : targets) - TraceLogger(effects) << " ... " << target->Dump(); - - Execute(source_context, targets); - - TraceLogger(effects) << "SetShipPartMeter execute targets after: "; - for (std::shared_ptr target : targets) - TraceLogger(effects) << " ... " << target->Dump(); - } -} - -void SetShipPartMeter::Execute(const ScriptingContext& context, const TargetSet& targets) const { - if (targets.empty()) - return; - if (!m_part_name || !m_value) { - ErrorLogger() << "SetShipPartMeter::Execute missing part name or value ValueRefs"; - return; - } - std::string part_name = m_part_name->Eval(context); - - if (m_value->TargetInvariant()) { - // meter value does not depend on target, so handle with single ValueRef evaluation - float val = m_value->Eval(context); - for (std::shared_ptr target : targets) { - if (target->ObjectType() != OBJ_SHIP) - continue; - std::shared_ptr ship = std::dynamic_pointer_cast(target); - if (!ship) - continue; - Meter* m = ship->GetPartMeter(m_meter, part_name); - if (!m) continue; - m->SetCurrent(val); - } - return; - - } else if (m_value->SimpleIncrement()) { - // meter value is a consistent constant increment for each target, so handle with - // deep inspection single ValueRef evaluation - ValueRef::Operation* op = dynamic_cast*>(m_value); - if (!op) { - ErrorLogger() << "SetShipPartMeter::Execute couldn't cast simple increment ValueRef to an Operation..."; - return; - } - // RHS should be a ConstantExpr - float increment = op->RHS()->Eval(); - if (op->GetOpType() == ValueRef::PLUS) { - // do nothing to modify increment - } else if (op->GetOpType() == ValueRef::MINUS) { - increment = -increment; - } else { - ErrorLogger() << "SetShipPartMeter::Execute got invalid increment optype (not PLUS or MINUS)"; - return; - } - //DebugLogger() << "simple increment: " << increment; - // increment all target meters... - for (std::shared_ptr target : targets) { - if (target->ObjectType() != OBJ_SHIP) - continue; - std::shared_ptr ship = std::dynamic_pointer_cast(target); - if (!ship) - continue; - Meter* m = ship->GetPartMeter(m_meter, part_name); - if (!m) continue; - m->AddToCurrent(increment); - } - return; - } - - //DebugLogger() << "complicated meter adjustment..."; - // meter value depends on target non-trivially, so handle with default case of per-target ValueRef evaluation - EffectBase::Execute(context, targets); -} - -std::string SetShipPartMeter::Dump() const { - std::string retval = DumpIndent(); - switch (m_meter) { - case METER_CAPACITY: retval += "SetCapacity"; break; - case METER_MAX_CAPACITY: retval += "SetMaxCapacity"; break; - case METER_SECONDARY_STAT: retval += "SetSecondaryStat"; break; - case METER_MAX_SECONDARY_STAT: retval += "SetMaxSecondaryStat";break; - default: retval += "Set???"; break; - } - - if (m_part_name) - retval += " partname = " + m_part_name->Dump(); - - retval += " value = " + m_value->Dump(); - - return retval; -} - -void SetShipPartMeter::SetTopLevelContent(const std::string& content_name) { - if (m_value) - m_value->SetTopLevelContent(content_name); - if (m_part_name) - m_part_name->SetTopLevelContent(content_name); -} - -unsigned int SetShipPartMeter::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetShipPartMeter"); - CheckSums::CheckSumCombine(retval, m_part_name); - CheckSums::CheckSumCombine(retval, m_meter); - CheckSums::CheckSumCombine(retval, m_value); - - TraceLogger() << "GetCheckSum(SetShipPartMeter): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetEmpireMeter // -/////////////////////////////////////////////////////////// -SetEmpireMeter::SetEmpireMeter(const std::string& meter, ValueRef::ValueRefBase* value) : - m_empire_id(new ValueRef::Variable(ValueRef::EFFECT_TARGET_REFERENCE, std::vector(1, "Owner"))), - m_meter(meter), - m_value(value) -{} - -SetEmpireMeter::SetEmpireMeter(ValueRef::ValueRefBase* empire_id, const std::string& meter, - ValueRef::ValueRefBase* value) : - m_empire_id(empire_id), - m_meter(meter), - m_value(value) -{} - -SetEmpireMeter::~SetEmpireMeter() { - delete m_empire_id; - delete m_value; -} - -void SetEmpireMeter::Execute(const ScriptingContext& context) const { - int empire_id = m_empire_id->Eval(context); - - Empire* empire = GetEmpire(empire_id); - if (!empire) { - DebugLogger() << "SetEmpireMeter::Execute unable to find empire with id " << empire_id; - return; - } - - Meter* meter = empire->GetMeter(m_meter); - if (!meter) { - DebugLogger() << "SetEmpireMeter::Execute empire " << empire->Name() << " doesn't have a meter named " << m_meter; - return; - } - - double value = m_value->Eval(ScriptingContext(context, meter->Current())); - - meter->SetCurrent(value); -} - -void SetEmpireMeter::Execute(const TargetsCauses& targets_causes, AccountingMap* accounting_map, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) const -{ - if (only_appearance_effects || only_generate_sitrep_effects) - return; - if (!include_empire_meter_effects) - return; - - // apply this effect for each source causing it - ScriptingContext source_context; - for (const std::pair& targets_entry : targets_causes) { - source_context.source = GetUniverseObject(targets_entry.first.source_object_id); - EffectBase::Execute(source_context, targets_entry.second.target_set); - } -} - -std::string SetEmpireMeter::Dump() const -{ return DumpIndent() + "SetEmpireMeter meter = " + m_meter + " empire = " + m_empire_id->Dump() + " value = " + m_value->Dump(); } - -void SetEmpireMeter::SetTopLevelContent(const std::string& content_name) { - if (m_empire_id) - m_empire_id->SetTopLevelContent(content_name); - if (m_value) - m_value->SetTopLevelContent(content_name); -} - -unsigned int SetEmpireMeter::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetEmpireMeter"); - CheckSums::CheckSumCombine(retval, m_empire_id); - CheckSums::CheckSumCombine(retval, m_meter); - CheckSums::CheckSumCombine(retval, m_value); - - TraceLogger() << "GetCheckSum(SetEmpireMeter): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetEmpireStockpile // -/////////////////////////////////////////////////////////// -SetEmpireStockpile::SetEmpireStockpile(ResourceType stockpile, - ValueRef::ValueRefBase* value) : - m_empire_id(new ValueRef::Variable(ValueRef::EFFECT_TARGET_REFERENCE, std::vector(1, "Owner"))), - m_stockpile(stockpile), - m_value(value) -{} - -SetEmpireStockpile::SetEmpireStockpile(ValueRef::ValueRefBase* empire_id, - ResourceType stockpile, - ValueRef::ValueRefBase* value) : - m_empire_id(empire_id), - m_stockpile(stockpile), - m_value(value) -{} - -SetEmpireStockpile::~SetEmpireStockpile() { - delete m_empire_id; - delete m_value; -} - -void SetEmpireStockpile::Execute(const ScriptingContext& context) const { - int empire_id = m_empire_id->Eval(context); - - Empire* empire = GetEmpire(empire_id); - if (!empire) { - DebugLogger() << "SetEmpireStockpile::Execute couldn't find an empire with id " << empire_id; - return; - } - - double value = m_value->Eval(ScriptingContext(context, empire->ResourceStockpile(m_stockpile))); - empire->SetResourceStockpile(m_stockpile, value); -} - -std::string SetEmpireStockpile::Dump() const { - std::string retval = DumpIndent(); - switch (m_stockpile) { - case RE_TRADE: retval += "SetEmpireTradeStockpile"; break; - default: retval += "?"; break; - } - retval += " empire = " + m_empire_id->Dump() + " value = " + m_value->Dump() + "\n"; - return retval; -} - -void SetEmpireStockpile::SetTopLevelContent(const std::string& content_name) { - if (m_empire_id) - m_empire_id->SetTopLevelContent(content_name); - if (m_value) - m_value->SetTopLevelContent(content_name); -} - -unsigned int SetEmpireStockpile::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetEmpireStockpile"); - CheckSums::CheckSumCombine(retval, m_empire_id); - CheckSums::CheckSumCombine(retval, m_stockpile); - CheckSums::CheckSumCombine(retval, m_value); - - TraceLogger() << "GetCheckSum(SetEmpireStockpile): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetEmpireCapital // -/////////////////////////////////////////////////////////// -SetEmpireCapital::SetEmpireCapital() : - m_empire_id(new ValueRef::Variable(ValueRef::EFFECT_TARGET_REFERENCE, std::vector(1, "Owner"))) -{} - -SetEmpireCapital::SetEmpireCapital(ValueRef::ValueRefBase* empire_id) : - m_empire_id(empire_id) -{} - -SetEmpireCapital::~SetEmpireCapital() -{ delete m_empire_id; } - -void SetEmpireCapital::Execute(const ScriptingContext& context) const { - int empire_id = m_empire_id->Eval(context); - - Empire* empire = GetEmpire(empire_id); - if (!empire) - return; - - std::shared_ptr planet = std::dynamic_pointer_cast(context.effect_target); - if (!planet) - return; - - empire->SetCapitalID(planet->ID()); -} - -std::string SetEmpireCapital::Dump() const -{ return DumpIndent() + "SetEmpireCapital empire = " + m_empire_id->Dump() + "\n"; } - -void SetEmpireCapital::SetTopLevelContent(const std::string& content_name) { - if (m_empire_id) - m_empire_id->SetTopLevelContent(content_name); -} - -unsigned int SetEmpireCapital::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetEmpireCapital"); - CheckSums::CheckSumCombine(retval, m_empire_id); - - TraceLogger() << "GetCheckSum(SetEmpireCapital): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetPlanetType // -/////////////////////////////////////////////////////////// -SetPlanetType::SetPlanetType(ValueRef::ValueRefBase* type) : - m_type(type) -{} - -SetPlanetType::~SetPlanetType() -{ delete m_type; } - -void SetPlanetType::Execute(const ScriptingContext& context) const { - if (std::shared_ptr p = std::dynamic_pointer_cast(context.effect_target)) { - PlanetType type = m_type->Eval(ScriptingContext(context, p->Type())); - p->SetType(type); - if (type == PT_ASTEROIDS) - p->SetSize(SZ_ASTEROIDS); - else if (type == PT_GASGIANT) - p->SetSize(SZ_GASGIANT); - else if (p->Size() == SZ_ASTEROIDS) - p->SetSize(SZ_TINY); - else if (p->Size() == SZ_GASGIANT) - p->SetSize(SZ_HUGE); - } -} - -std::string SetPlanetType::Dump() const -{ return DumpIndent() + "SetPlanetType type = " + m_type->Dump() + "\n"; } - -void SetPlanetType::SetTopLevelContent(const std::string& content_name) { - if (m_type) - m_type->SetTopLevelContent(content_name); -} - -unsigned int SetPlanetType::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetPlanetType"); - CheckSums::CheckSumCombine(retval, m_type); - - TraceLogger() << "GetCheckSum(SetPlanetType): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetPlanetSize // -/////////////////////////////////////////////////////////// -SetPlanetSize::SetPlanetSize(ValueRef::ValueRefBase* size) : - m_size(size) -{} - -SetPlanetSize::~SetPlanetSize() -{ delete m_size; } - -void SetPlanetSize::Execute(const ScriptingContext& context) const { - if (std::shared_ptr p = std::dynamic_pointer_cast(context.effect_target)) { - PlanetSize size = m_size->Eval(ScriptingContext(context, p->Size())); - p->SetSize(size); - if (size == SZ_ASTEROIDS) - p->SetType(PT_ASTEROIDS); - else if (size == SZ_GASGIANT) - p->SetType(PT_GASGIANT); - else if (p->Type() == PT_ASTEROIDS || p->Type() == PT_GASGIANT) - p->SetType(PT_BARREN); - } -} - -std::string SetPlanetSize::Dump() const -{ return DumpIndent() + "SetPlanetSize size = " + m_size->Dump() + "\n"; } - -void SetPlanetSize::SetTopLevelContent(const std::string& content_name) { - if (m_size) - m_size->SetTopLevelContent(content_name); -} - -unsigned int SetPlanetSize::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetPlanetSize"); - CheckSums::CheckSumCombine(retval, m_size); - - TraceLogger() << "GetCheckSum(SetPlanetSize): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetSpecies // -/////////////////////////////////////////////////////////// -SetSpecies::SetSpecies(ValueRef::ValueRefBase* species) : - m_species_name(species) -{} - -SetSpecies::~SetSpecies() -{ delete m_species_name; } - -void SetSpecies::Execute(const ScriptingContext& context) const { - if (std::shared_ptr planet = std::dynamic_pointer_cast(context.effect_target)) { - std::string species_name = m_species_name->Eval(ScriptingContext(context, planet->SpeciesName())); - planet->SetSpecies(species_name); - - // ensure non-empty and permissible focus setting for new species - std::string initial_focus = planet->Focus(); - std::vector available_foci = planet->AvailableFoci(); - - // leave current focus unchanged if available. - for (const std::string& available_focus : available_foci) { - if (available_focus == initial_focus) { - return; - } - } - - // need to set new focus - std::string new_focus; - - const Species* species = GetSpecies(species_name); - std::string preferred_focus; - if (species) - preferred_focus = species->PreferredFocus(); - - // chose preferred focus if available. otherwise use any available focus - bool preferred_available = false; - for (const std::string& available_focus : available_foci) { - if (available_focus == preferred_focus) { - preferred_available = true; - break; - } - } - - if (preferred_available) { - new_focus = preferred_focus; - } else if (!available_foci.empty()) { - new_focus = *available_foci.begin(); - } - - planet->SetFocus(new_focus); - - } else if (std::shared_ptr ship = std::dynamic_pointer_cast(context.effect_target)) { - std::string species_name = m_species_name->Eval(ScriptingContext(context, ship->SpeciesName())); - ship->SetSpecies(species_name); - } -} - -std::string SetSpecies::Dump() const -{ return DumpIndent() + "SetSpecies name = " + m_species_name->Dump() + "\n"; } - -void SetSpecies::SetTopLevelContent(const std::string& content_name) { - if (m_species_name) - m_species_name->SetTopLevelContent(content_name); -} - -unsigned int SetSpecies::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetSpecies"); - CheckSums::CheckSumCombine(retval, m_species_name); - - TraceLogger() << "GetCheckSum(SetSpecies): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetOwner // -/////////////////////////////////////////////////////////// -SetOwner::SetOwner(ValueRef::ValueRefBase* empire_id) : - m_empire_id(empire_id) -{} - -SetOwner::~SetOwner() -{ delete m_empire_id; } - -void SetOwner::Execute(const ScriptingContext& context) const { - if (!context.effect_target) - return; - int initial_owner = context.effect_target->Owner(); - - int empire_id = m_empire_id->Eval(ScriptingContext(context, initial_owner)); - if (initial_owner == empire_id) - return; - - context.effect_target->SetOwner(empire_id); - - if (std::shared_ptr ship = std::dynamic_pointer_cast(context.effect_target)) { - // assigning ownership of a ship requires updating the containing - // fleet, or splitting ship off into a new fleet at the same location - std::shared_ptr fleet = GetFleet(ship->FleetID()); - if (!fleet) - return; - if (fleet->Owner() == empire_id) - return; - - // move ship into new fleet - std::shared_ptr new_fleet; - if (std::shared_ptr system = GetSystem(ship->SystemID())) - new_fleet = CreateNewFleet(system, ship); - else - new_fleet = CreateNewFleet(ship->X(), ship->Y(), ship); - if (new_fleet) { - new_fleet->SetNextAndPreviousSystems(fleet->NextSystemID(), fleet->PreviousSystemID()); - } - - // if old fleet is empty, destroy it. Don't reassign ownership of fleet - // in case that would reval something to the recipient that shouldn't be... - if (fleet->Empty()) - GetUniverse().EffectDestroy(fleet->ID(), INVALID_OBJECT_ID); // no particular source destroyed the fleet in this case - } -} - -std::string SetOwner::Dump() const -{ return DumpIndent() + "SetOwner empire = " + m_empire_id->Dump() + "\n"; } - -void SetOwner::SetTopLevelContent(const std::string& content_name) { - if (m_empire_id) - m_empire_id->SetTopLevelContent(content_name); -} - -unsigned int SetOwner::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetOwner"); - CheckSums::CheckSumCombine(retval, m_empire_id); - - TraceLogger() << "GetCheckSum(SetOwner): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetSpeciesEmpireOpinion // -/////////////////////////////////////////////////////////// -SetSpeciesEmpireOpinion::SetSpeciesEmpireOpinion(ValueRef::ValueRefBase* species_name, - ValueRef::ValueRefBase* empire_id, - ValueRef::ValueRefBase* opinion) : - m_species_name(species_name), - m_empire_id(empire_id), - m_opinion(opinion) -{} - -SetSpeciesEmpireOpinion::~SetSpeciesEmpireOpinion() { - delete m_species_name; - delete m_empire_id; - delete m_opinion; -} - -void SetSpeciesEmpireOpinion::Execute(const ScriptingContext& context) const { - if (!context.effect_target) - return; - if (!m_species_name || !m_opinion || !m_empire_id) - return; - - int empire_id = m_empire_id->Eval(context); - if (empire_id == ALL_EMPIRES) - return; - - std::string species_name = m_species_name->Eval(context); - if (species_name.empty()) - return; - - double initial_opinion = GetSpeciesManager().SpeciesEmpireOpinion(species_name, empire_id); - double opinion = m_opinion->Eval(ScriptingContext(context, initial_opinion)); - - GetSpeciesManager().SetSpeciesEmpireOpinion(species_name, empire_id, opinion); -} - -std::string SetSpeciesEmpireOpinion::Dump() const -{ return DumpIndent() + "SetSpeciesEmpireOpinion empire = " + m_empire_id->Dump() + "\n"; } - -void SetSpeciesEmpireOpinion::SetTopLevelContent(const std::string& content_name) { - if (m_empire_id) - m_empire_id->SetTopLevelContent(content_name); - if (m_species_name) - m_species_name->SetTopLevelContent(content_name); - if (m_opinion) - m_opinion->SetTopLevelContent(content_name); -} - -unsigned int SetSpeciesEmpireOpinion::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetSpeciesEmpireOpinion"); - CheckSums::CheckSumCombine(retval, m_species_name); - CheckSums::CheckSumCombine(retval, m_empire_id); - CheckSums::CheckSumCombine(retval, m_opinion); - - TraceLogger() << "GetCheckSum(SetSpeciesEmpireOpinion): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetSpeciesSpeciesOpinion // -/////////////////////////////////////////////////////////// -SetSpeciesSpeciesOpinion::SetSpeciesSpeciesOpinion(ValueRef::ValueRefBase* opinionated_species_name, - ValueRef::ValueRefBase* rated_species_name, - ValueRef::ValueRefBase* opinion) : - m_opinionated_species_name(opinionated_species_name), - m_rated_species_name(rated_species_name), - m_opinion(opinion) -{} - -SetSpeciesSpeciesOpinion::~SetSpeciesSpeciesOpinion() { - delete m_opinionated_species_name; - delete m_rated_species_name; - delete m_opinion; -} - -void SetSpeciesSpeciesOpinion::Execute(const ScriptingContext& context) const { - if (!context.effect_target) - return; - if (!m_opinionated_species_name || !m_opinion || !m_rated_species_name) - return; - - std::string opinionated_species_name = m_opinionated_species_name->Eval(context); - if (opinionated_species_name.empty()) - return; - - std::string rated_species_name = m_rated_species_name->Eval(context); - if (rated_species_name.empty()) - return; - - float initial_opinion = GetSpeciesManager().SpeciesSpeciesOpinion(opinionated_species_name, rated_species_name); - float opinion = m_opinion->Eval(ScriptingContext(context, initial_opinion)); - - GetSpeciesManager().SetSpeciesSpeciesOpinion(opinionated_species_name, rated_species_name, opinion); -} - -std::string SetSpeciesSpeciesOpinion::Dump() const -{ return DumpIndent() + "SetSpeciesSpeciesOpinion" + "\n"; } - -void SetSpeciesSpeciesOpinion::SetTopLevelContent(const std::string& content_name) { - if (m_opinionated_species_name) - m_opinionated_species_name->SetTopLevelContent(content_name); - if (m_rated_species_name) - m_rated_species_name->SetTopLevelContent(content_name); - if (m_opinion) - m_opinion->SetTopLevelContent(content_name); -} - -unsigned int SetSpeciesSpeciesOpinion::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetSpeciesSpeciesOpinion"); - CheckSums::CheckSumCombine(retval, m_opinionated_species_name); - CheckSums::CheckSumCombine(retval, m_rated_species_name); - CheckSums::CheckSumCombine(retval, m_opinion); - - TraceLogger() << "GetCheckSum(SetSpeciesSpeciesOpinion): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// CreatePlanet // -/////////////////////////////////////////////////////////// -CreatePlanet::CreatePlanet(ValueRef::ValueRefBase* type, - ValueRef::ValueRefBase* size, - ValueRef::ValueRefBase* name, - const std::vector& effects_to_apply_after) : - m_type(type), - m_size(size), - m_name(name), - m_effects_to_apply_after(effects_to_apply_after) -{} - -CreatePlanet::~CreatePlanet() { - delete m_type; - delete m_size; - delete m_name; - for (EffectBase* effect : m_effects_to_apply_after) { - delete effect; - } - m_effects_to_apply_after.clear(); -} - -void CreatePlanet::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "CreatePlanet::Execute passed no target object"; - return; - } - std::shared_ptr system = GetSystem(context.effect_target->SystemID()); - if (!system) { - ErrorLogger() << "CreatePlanet::Execute couldn't get a System object at which to create the planet"; - return; - } - - PlanetSize target_size = INVALID_PLANET_SIZE; - PlanetType target_type = INVALID_PLANET_TYPE; - if (std::shared_ptr location_planet = std::dynamic_pointer_cast(context.effect_target)) { - target_size = location_planet->Size(); - target_type = location_planet->Type(); - } - - PlanetSize size = m_size->Eval(ScriptingContext(context, target_size)); - PlanetType type = m_type->Eval(ScriptingContext(context, target_type)); - if (size == INVALID_PLANET_SIZE || type == INVALID_PLANET_TYPE) { - ErrorLogger() << "CreatePlanet::Execute got invalid size or type of planet to create..."; - return; - } - - // determine if and which orbits are available - std::set free_orbits = system->FreeOrbits(); - if (free_orbits.empty()) { - ErrorLogger() << "CreatePlanet::Execute couldn't find any free orbits in system where planet was to be created"; - return; - } - - auto planet = GetUniverse().InsertNew(type, size); - if (!planet) { - ErrorLogger() << "CreatePlanet::Execute unable to create new Planet object"; - return; - } - - system->Insert(planet); // let system chose an orbit for planet - - std::string name_str; - if (m_name) { - name_str = m_name->Eval(context); - if (m_name->ConstantExpr() && UserStringExists(name_str)) - name_str = UserString(name_str); - } else { - name_str = str(FlexibleFormat(UserString("NEW_PLANET_NAME")) % system->Name() % planet->CardinalSuffix()); - } - planet->Rename(name_str); - - // apply after-creation effects - ScriptingContext local_context = context; - local_context.effect_target = planet; - for (EffectBase* effect : m_effects_to_apply_after) { - if (!effect) - continue; - effect->Execute(local_context); - } -} - -std::string CreatePlanet::Dump() const { - std::string retval = DumpIndent() + "CreatePlanet"; - if (m_size) - retval += " size = " + m_size->Dump(); - if (m_type) - retval += " type = " + m_type->Dump(); - if (m_name) - retval += " name = " + m_name->Dump(); - return retval + "\n"; -} - -void CreatePlanet::SetTopLevelContent(const std::string& content_name) { - if (m_type) - m_type->SetTopLevelContent(content_name); - if (m_size) - m_size->SetTopLevelContent(content_name); - if (m_name) - m_name->SetTopLevelContent(content_name); - for (EffectBase* effect : m_effects_to_apply_after) { - if (!effect) - continue; - effect->SetTopLevelContent(content_name); - } -} - -unsigned int CreatePlanet::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "CreatePlanet"); - CheckSums::CheckSumCombine(retval, m_type); - CheckSums::CheckSumCombine(retval, m_size); - CheckSums::CheckSumCombine(retval, m_name); - CheckSums::CheckSumCombine(retval, m_effects_to_apply_after); - - TraceLogger() << "GetCheckSum(CreatePlanet): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// CreateBuilding // -/////////////////////////////////////////////////////////// -CreateBuilding::CreateBuilding(ValueRef::ValueRefBase* building_type_name, - ValueRef::ValueRefBase* name, - const std::vector& effects_to_apply_after) : - m_building_type_name(building_type_name), - m_name(name), - m_effects_to_apply_after(effects_to_apply_after) -{} - -CreateBuilding::~CreateBuilding() { - delete m_building_type_name; - delete m_name; - for (EffectBase* effect : m_effects_to_apply_after) { - delete effect; - } - m_effects_to_apply_after.clear(); -} - -void CreateBuilding::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "CreateBuilding::Execute passed no target object"; - return; - } - std::shared_ptr location = std::dynamic_pointer_cast(context.effect_target); - if (!location) - if (std::shared_ptr location_building = std::dynamic_pointer_cast(context.effect_target)) - location = GetPlanet(location_building->PlanetID()); - if (!location) { - ErrorLogger() << "CreateBuilding::Execute couldn't get a Planet object at which to create the building"; - return; - } - - if (!m_building_type_name) { - ErrorLogger() << "CreateBuilding::Execute has no building type specified!"; - return; - } - - std::string building_type_name = m_building_type_name->Eval(context); - const BuildingType* building_type = GetBuildingType(building_type_name); - if (!building_type) { - ErrorLogger() << "CreateBuilding::Execute couldn't get building type: " << building_type_name; - return; - } - - auto building = GetUniverse().InsertNew(ALL_EMPIRES, building_type_name, ALL_EMPIRES); - if (!building) { - ErrorLogger() << "CreateBuilding::Execute couldn't create building!"; - return; - } - - location->AddBuilding(building->ID()); - building->SetPlanetID(location->ID()); - - building->SetOwner(location->Owner()); - - std::shared_ptr system = GetSystem(location->SystemID()); - if (system) - system->Insert(building); - - if (m_name) { - std::string name_str = m_name->Eval(context); - if (m_name->ConstantExpr() && UserStringExists(name_str)) - name_str = UserString(name_str); - building->Rename(name_str); - } - - // apply after-creation effects - ScriptingContext local_context = context; - local_context.effect_target = building; - for (EffectBase* effect : m_effects_to_apply_after) { - if (!effect) - continue; - effect->Execute(local_context); - } -} - -std::string CreateBuilding::Dump() const { - std::string retval = DumpIndent() + "CreateBuilding"; - if (m_building_type_name) - retval += " type = " + m_building_type_name->Dump(); - if (m_name) - retval += " name = " + m_name->Dump(); - return retval + "\n"; -} - -void CreateBuilding::SetTopLevelContent(const std::string& content_name) { - if (m_building_type_name) - m_building_type_name->SetTopLevelContent(content_name); - if (m_name) - m_name->SetTopLevelContent(content_name); - for (EffectBase* effect : m_effects_to_apply_after) { - if (!effect) - continue; - effect->SetTopLevelContent(content_name); - } -} - -unsigned int CreateBuilding::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "CreateBuilding"); - CheckSums::CheckSumCombine(retval, m_building_type_name); - CheckSums::CheckSumCombine(retval, m_name); - CheckSums::CheckSumCombine(retval, m_effects_to_apply_after); - - TraceLogger() << "GetCheckSum(CreateBuilding): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// CreateShip // -/////////////////////////////////////////////////////////// -CreateShip::CreateShip(ValueRef::ValueRefBase* predefined_ship_design_name, - ValueRef::ValueRefBase* empire_id, - ValueRef::ValueRefBase* species_name, - ValueRef::ValueRefBase* ship_name, - const std::vector& effects_to_apply_after) : - m_design_name(predefined_ship_design_name), - m_design_id(nullptr), - m_empire_id(empire_id), - m_species_name(species_name), - m_name(ship_name), - m_effects_to_apply_after(effects_to_apply_after) -{} - -CreateShip::CreateShip(ValueRef::ValueRefBase* ship_design_id, - ValueRef::ValueRefBase* empire_id, - ValueRef::ValueRefBase* species_name, - ValueRef::ValueRefBase* ship_name, - const std::vector& effects_to_apply_after) : - m_design_name(nullptr), - m_design_id(ship_design_id), - m_empire_id(empire_id), - m_species_name(species_name), - m_name(ship_name), - m_effects_to_apply_after(effects_to_apply_after) -{} - -CreateShip::~CreateShip() { - delete m_design_name; - delete m_design_id; - delete m_empire_id; - delete m_species_name; - delete m_name; - for (EffectBase* effect : m_effects_to_apply_after) { - delete effect; - } - m_effects_to_apply_after.clear(); -} - -void CreateShip::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "CreateShip::Execute passed null target"; - return; - } - - std::shared_ptr system = GetSystem(context.effect_target->SystemID()); - if (!system) { - ErrorLogger() << "CreateShip::Execute passed a target not in a system"; - return; - } - - int design_id = INVALID_DESIGN_ID; - if (m_design_id) { - design_id = m_design_id->Eval(context); - if (!GetShipDesign(design_id)) { - ErrorLogger() << "CreateShip::Execute couldn't get ship design with id: " << design_id; - return; - } - } else if (m_design_name) { - std::string design_name = m_design_name->Eval(context); - const ShipDesign* ship_design = GetPredefinedShipDesign(design_name); - if (!ship_design) { - ErrorLogger() << "CreateShip::Execute couldn't get predefined ship design with name " << m_design_name->Dump(); - return; - } - design_id = ship_design->ID(); - } - if (design_id == INVALID_DESIGN_ID) { - ErrorLogger() << "CreateShip::Execute got invalid ship design id: -1"; - return; - } - - int empire_id = ALL_EMPIRES; - Empire* empire = nullptr; // not const Empire* so that empire::NewShipName can be called - if (m_empire_id) { - empire_id = m_empire_id->Eval(context); - if (empire_id != ALL_EMPIRES) { - empire = GetEmpire(empire_id); - if (!empire) { - ErrorLogger() << "CreateShip::Execute couldn't get empire with id " << empire_id; - return; - } - } - } - - std::string species_name; - if (m_species_name) { - species_name = m_species_name->Eval(context); - if (!species_name.empty() && !GetSpecies(species_name)) { - ErrorLogger() << "CreateShip::Execute couldn't get species with which to create a ship"; - return; - } - } - - //// possible future modification: try to put new ship into existing fleet if - //// ownership with target object's fleet works out (if target is a ship) - //// attempt to find a - //std::shared_ptr fleet = std::dynamic_pointer_cast(target); - //if (!fleet) - // if (std::shared_ptr ship = std::dynamic_pointer_cast(target)) - // fleet = ship->FleetID(); - //// etc. - - auto ship = GetUniverse().InsertNew(empire_id, design_id, species_name, ALL_EMPIRES); - system->Insert(ship); - - if (m_name) { - std::string name_str = m_name->Eval(context); - if (m_name->ConstantExpr() && UserStringExists(name_str)) - name_str = UserString(name_str); - ship->Rename(name_str); - } else if (ship->IsMonster()) { - ship->Rename(NewMonsterName()); - } else if (empire) { - ship->Rename(empire->NewShipName()); - } else { - ship->Rename(ship->Design()->Name()); - } - - ship->ResetTargetMaxUnpairedMeters(); - ship->ResetPairedActiveMeters(); - ship->SetShipMetersToMax(); - - ship->BackPropagateMeters(); - - GetUniverse().SetEmpireKnowledgeOfShipDesign(design_id, empire_id); - - CreateNewFleet(system, ship); - - // apply after-creation effects - ScriptingContext local_context = context; - local_context.effect_target = ship; - for (EffectBase* effect : m_effects_to_apply_after) { - if (!effect) - continue; - effect->Execute(local_context); - } -} - -std::string CreateShip::Dump() const { - std::string retval = DumpIndent() + "CreateShip"; - if (m_design_id) - retval += " designid = " + m_design_id->Dump(); - if (m_design_name) - retval += " designname = " + m_design_name->Dump(); - if (m_empire_id) - retval += " empire = " + m_empire_id->Dump(); - if (m_species_name) - retval += " species = " + m_species_name->Dump(); - if (m_name) - retval += " name = " + m_name->Dump(); - - retval += "\n"; - return retval; -} - -void CreateShip::SetTopLevelContent(const std::string& content_name) { - if (m_design_name) - m_design_name->SetTopLevelContent(content_name); - if (m_design_id) - m_design_id->SetTopLevelContent(content_name); - if (m_empire_id) - m_empire_id->SetTopLevelContent(content_name); - if (m_species_name) - m_species_name->SetTopLevelContent(content_name); - if (m_name) - m_name->SetTopLevelContent(content_name); - for (EffectBase* effect : m_effects_to_apply_after) { - if (!effect) - continue; - effect->SetTopLevelContent(content_name); - } -} - -unsigned int CreateShip::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "CreateShip"); - CheckSums::CheckSumCombine(retval, m_design_name); - CheckSums::CheckSumCombine(retval, m_design_id); - CheckSums::CheckSumCombine(retval, m_empire_id); - CheckSums::CheckSumCombine(retval, m_species_name); - CheckSums::CheckSumCombine(retval, m_name); - CheckSums::CheckSumCombine(retval, m_effects_to_apply_after); - - TraceLogger() << "GetCheckSum(CreateShip): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// CreateField // -/////////////////////////////////////////////////////////// -CreateField::CreateField(ValueRef::ValueRefBase* field_type_name, - ValueRef::ValueRefBase* size, - ValueRef::ValueRefBase* name, - const std::vector& effects_to_apply_after) : - m_field_type_name(field_type_name), - m_x(nullptr), - m_y(nullptr), - m_size(size), - m_name(name), - m_effects_to_apply_after(effects_to_apply_after) -{} - -CreateField::CreateField(ValueRef::ValueRefBase* field_type_name, - ValueRef::ValueRefBase* x, - ValueRef::ValueRefBase* y, - ValueRef::ValueRefBase* size, - ValueRef::ValueRefBase* name, - const std::vector& effects_to_apply_after) : - m_field_type_name(field_type_name), - m_x(x), - m_y(y), - m_size(size), - m_name(name), - m_effects_to_apply_after(effects_to_apply_after) -{} - -CreateField::~CreateField() { - delete m_field_type_name; - delete m_x; - delete m_y; - delete m_size; - delete m_name; - for (EffectBase* effect : m_effects_to_apply_after) { - delete effect; - } - m_effects_to_apply_after.clear(); -} - -void CreateField::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "CreateField::Execute passed null target"; - return; - } - std::shared_ptr target = context.effect_target; - - if (!m_field_type_name) - return; - - const FieldType* field_type = GetFieldType(m_field_type_name->Eval(context)); - if (!field_type) { - ErrorLogger() << "CreateField::Execute couldn't get field type with name: " << m_field_type_name->Dump(); - return; - } - - double size = 10.0; - if (m_size) - size = m_size->Eval(context); - if (size < 1.0) { - ErrorLogger() << "CreateField::Execute given very small / negative size: " << size << " ... so resetting to 1.0"; - size = 1.0; - } - if (size > 10000) { - ErrorLogger() << "CreateField::Execute given very large size: " << size << " ... so resetting to 10000"; - size = 10000; - } - - double x = 0.0; - double y = 0.0; - if (m_x) - x = m_x->Eval(context); - else - x = target->X(); - if (m_y) - y = m_y->Eval(context); - else - y = target->Y(); - - auto field = GetUniverse().InsertNew(field_type->Name(), x, y, size); - if (!field) { - ErrorLogger() << "CreateField::Execute couldn't create field!"; - return; - } - - // if target is a system, and location matches system location, can put - // field into system - std::shared_ptr system = std::dynamic_pointer_cast(target); - if (system && (!m_y || y == system->Y()) && (!m_x || x == system->X())) - system->Insert(field); - - std::string name_str; - if (m_name) { - name_str = m_name->Eval(context); - if (m_name->ConstantExpr() && UserStringExists(name_str)) - name_str = UserString(name_str); - } else { - name_str = UserString(field_type->Name()); - } - field->Rename(name_str); - - // apply after-creation effects - ScriptingContext local_context = context; - local_context.effect_target = field; - for (EffectBase* effect : m_effects_to_apply_after) { - if (!effect) - continue; - effect->Execute(local_context); - } -} - -std::string CreateField::Dump() const { - std::string retval = DumpIndent() + "CreateField"; - if (m_field_type_name) - retval += " type = " + m_field_type_name->Dump(); - if (m_x) - retval += " x = " + m_x->Dump(); - if (m_y) - retval += " y = " + m_y->Dump(); - if (m_size) - retval += " size = " + m_size->Dump(); - if (m_name) - retval += " name = " + m_name->Dump(); - retval += "\n"; - return retval; -} - -void CreateField::SetTopLevelContent(const std::string& content_name) { - if (m_field_type_name) - m_field_type_name->SetTopLevelContent(content_name); - if (m_x) - m_x->SetTopLevelContent(content_name); - if (m_y) - m_y->SetTopLevelContent(content_name); - if (m_size) - m_size->SetTopLevelContent(content_name); - if (m_name) - m_name->SetTopLevelContent(content_name); - for (EffectBase* effect : m_effects_to_apply_after) { - if (!effect) - continue; - effect->SetTopLevelContent(content_name); - } -} - -unsigned int CreateField::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "CreateField"); - CheckSums::CheckSumCombine(retval, m_field_type_name); - CheckSums::CheckSumCombine(retval, m_x); - CheckSums::CheckSumCombine(retval, m_y); - CheckSums::CheckSumCombine(retval, m_size); - CheckSums::CheckSumCombine(retval, m_name); - CheckSums::CheckSumCombine(retval, m_effects_to_apply_after); - - TraceLogger() << "GetCheckSum(CreateField): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// CreateSystem // -/////////////////////////////////////////////////////////// -CreateSystem::CreateSystem(ValueRef::ValueRefBase< ::StarType>* type, - ValueRef::ValueRefBase* x, - ValueRef::ValueRefBase* y, - ValueRef::ValueRefBase* name, - const std::vector& effects_to_apply_after) : - m_type(type), - m_x(x), - m_y(y), - m_name(name), - m_effects_to_apply_after(effects_to_apply_after) -{} - -CreateSystem::CreateSystem(ValueRef::ValueRefBase* x, - ValueRef::ValueRefBase* y, - ValueRef::ValueRefBase* name, - const std::vector& effects_to_apply_after) : - m_type(nullptr), - m_x(x), - m_y(y), - m_name(name), - m_effects_to_apply_after(effects_to_apply_after) -{} - -CreateSystem::~CreateSystem() { - delete m_type; - delete m_x; - delete m_y; - delete m_name; - for (EffectBase* effect : m_effects_to_apply_after) { - delete effect; - } - m_effects_to_apply_after.clear(); -} - -void CreateSystem::Execute(const ScriptingContext& context) const { - // pick a star type - StarType star_type = STAR_NONE; - if (m_type) { - star_type = m_type->Eval(context); - } else { - int max_type_idx = int(NUM_STAR_TYPES) - 1; - int type_idx = RandSmallInt(0, max_type_idx); - star_type = StarType(type_idx); - } - - // pick location - double x = 0.0; - double y = 0.0; - if (m_x) - x = m_x->Eval(context); - if (m_y) - y = m_y->Eval(context); - - std::string name_str; - if (m_name) { - name_str = m_name->Eval(context); - if (m_name->ConstantExpr() && UserStringExists(name_str)) - name_str = UserString(name_str); - } else { - name_str = GenerateSystemName(); - } - - auto system = GetUniverse().InsertNew(star_type, name_str, x, y); - if (!system) { - ErrorLogger() << "CreateSystem::Execute couldn't create system!"; - return; - } - - // apply after-creation effects - ScriptingContext local_context = context; - local_context.effect_target = system; - for (EffectBase* effect : m_effects_to_apply_after) { - if (!effect) - continue; - effect->Execute(local_context); - } -} - -std::string CreateSystem::Dump() const { - std::string retval = DumpIndent() + "CreateSystem"; - if (m_type) - retval += " type = " + m_type->Dump(); - if (m_x) - retval += " x = " + m_x->Dump(); - if (m_y) - retval += " y = " + m_y->Dump(); - if (m_name) - retval += " name = " + m_name->Dump(); - retval += "\n"; - return retval; -} - -void CreateSystem::SetTopLevelContent(const std::string& content_name) { - if (m_x) - m_x->SetTopLevelContent(content_name); - if (m_y) - m_y->SetTopLevelContent(content_name); - if (m_type) - m_type->SetTopLevelContent(content_name); - if (m_name) - m_name->SetTopLevelContent(content_name); - for (EffectBase* effect : m_effects_to_apply_after) { - if (!effect) - continue; - effect->SetTopLevelContent(content_name); - } -} - -unsigned int CreateSystem::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "CreateSystem"); - CheckSums::CheckSumCombine(retval, m_type); - CheckSums::CheckSumCombine(retval, m_x); - CheckSums::CheckSumCombine(retval, m_y); - CheckSums::CheckSumCombine(retval, m_name); - CheckSums::CheckSumCombine(retval, m_effects_to_apply_after); - - TraceLogger() << "GetCheckSum(CreateSystem): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// Destroy // -/////////////////////////////////////////////////////////// -Destroy::Destroy() -{} - -void Destroy::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "Destroy::Execute passed no target object"; - return; - } - - int source_id = INVALID_OBJECT_ID; - if (context.source) - source_id = context.source->ID(); - - GetUniverse().EffectDestroy(context.effect_target->ID(), source_id); -} - -std::string Destroy::Dump() const -{ return DumpIndent() + "Destroy\n"; } - -unsigned int Destroy::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "Destroy"); - - TraceLogger() << "GetCheckSum(Destroy): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// AddSpecial // -/////////////////////////////////////////////////////////// -AddSpecial::AddSpecial(const std::string& name, float capacity) : - m_name(new ValueRef::Constant(name)), - m_capacity(new ValueRef::Constant(capacity)) -{} - -AddSpecial::AddSpecial(ValueRef::ValueRefBase* name, - ValueRef::ValueRefBase* capacity) : - m_name(name), - m_capacity(capacity) -{} - -AddSpecial::~AddSpecial() -{ delete m_name; } - -void AddSpecial::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "AddSpecial::Execute passed no target object"; - return; - } - - std::string name = (m_name ? m_name->Eval(context) : ""); - - float initial_capacity = context.effect_target->SpecialCapacity(name); // returns 0.0f if no such special yet present - float capacity = (m_capacity ? m_capacity->Eval(ScriptingContext(context, initial_capacity)) : initial_capacity); - - context.effect_target->SetSpecialCapacity(name, capacity); -} - -std::string AddSpecial::Dump() const { - return DumpIndent() + "AddSpecial name = " + (m_name ? m_name->Dump() : "") + - " capacity = " + (m_capacity ? m_capacity->Dump() : "0.0") + "\n"; -} - -void AddSpecial::SetTopLevelContent(const std::string& content_name) { - if (m_name) - m_name->SetTopLevelContent(content_name); - if (m_capacity) - m_capacity->SetTopLevelContent(content_name); -} - -unsigned int AddSpecial::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "AddSpecial"); - CheckSums::CheckSumCombine(retval, m_name); - CheckSums::CheckSumCombine(retval, m_capacity); - - TraceLogger() << "GetCheckSum(AddSpecial): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// RemoveSpecial // -/////////////////////////////////////////////////////////// -RemoveSpecial::RemoveSpecial(const std::string& name) : - m_name(new ValueRef::Constant(name)) -{} - -RemoveSpecial::RemoveSpecial(ValueRef::ValueRefBase* name) : - m_name(name) -{} - -RemoveSpecial::~RemoveSpecial() -{ delete m_name; } - -void RemoveSpecial::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "RemoveSpecial::Execute passed no target object"; - return; - } - - std::string name = (m_name ? m_name->Eval(context) : ""); - context.effect_target->RemoveSpecial(name); -} - -std::string RemoveSpecial::Dump() const { - return DumpIndent() + "RemoveSpecial name = " + (m_name ? m_name->Dump() : "") + "\n"; -} - -void RemoveSpecial::SetTopLevelContent(const std::string& content_name) { - if (m_name) - m_name->SetTopLevelContent(content_name); -} - -unsigned int RemoveSpecial::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "RemoveSpecial"); - CheckSums::CheckSumCombine(retval, m_name); - - TraceLogger() << "GetCheckSum(RemoveSpecial): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// AddStarlanes // -/////////////////////////////////////////////////////////// -AddStarlanes::AddStarlanes(Condition::ConditionBase* other_lane_endpoint_condition) : - m_other_lane_endpoint_condition(other_lane_endpoint_condition) -{} - -AddStarlanes::~AddStarlanes() -{ delete m_other_lane_endpoint_condition; } - -void AddStarlanes::Execute(const ScriptingContext& context) const { - // get target system - if (!context.effect_target) { - ErrorLogger() << "AddStarlanes::Execute passed no target object"; - return; - } - std::shared_ptr target_system = std::dynamic_pointer_cast(context.effect_target); - if (!target_system) - target_system = GetSystem(context.effect_target->SystemID()); - if (!target_system) - return; // nothing to do! - - // get other endpoint systems... - Condition::ObjectSet endpoint_objects; - // apply endpoints condition to determine objects whose systems should be - // connected to the source system - m_other_lane_endpoint_condition->Eval(context, endpoint_objects); - - // early exit if there are no valid locations - if (endpoint_objects.empty()) - return; // nothing to do! - - // get systems containing at least one endpoint object - std::set> endpoint_systems; - for (std::shared_ptr endpoint_object : endpoint_objects) { - std::shared_ptr endpoint_system = std::dynamic_pointer_cast(endpoint_object); - if (!endpoint_system) - endpoint_system = GetSystem(endpoint_object->SystemID()); - if (!endpoint_system) - continue; - endpoint_systems.insert(std::const_pointer_cast(endpoint_system)); - } - - // add starlanes from target to endpoint systems - for (std::shared_ptr endpoint_system : endpoint_systems) { - target_system->AddStarlane(endpoint_system->ID()); - endpoint_system->AddStarlane(target_system->ID()); - } -} - -std::string AddStarlanes::Dump() const -{ return DumpIndent() + "AddStarlanes endpoints = " + m_other_lane_endpoint_condition->Dump() + "\n"; } - -void AddStarlanes::SetTopLevelContent(const std::string& content_name) { - if (m_other_lane_endpoint_condition) - m_other_lane_endpoint_condition->SetTopLevelContent(content_name); -} - -unsigned int AddStarlanes::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "AddStarlanes"); - CheckSums::CheckSumCombine(retval, m_other_lane_endpoint_condition); - - TraceLogger() << "GetCheckSum(AddStarlanes): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// RemoveStarlanes // -/////////////////////////////////////////////////////////// -RemoveStarlanes::RemoveStarlanes(Condition::ConditionBase* other_lane_endpoint_condition) : - m_other_lane_endpoint_condition(other_lane_endpoint_condition) -{} - -RemoveStarlanes::~RemoveStarlanes() -{ delete m_other_lane_endpoint_condition; } - -void RemoveStarlanes::Execute(const ScriptingContext& context) const { - // get target system - if (!context.effect_target) { - ErrorLogger() << "AddStarlanes::Execute passed no target object"; - return; - } - std::shared_ptr target_system = std::dynamic_pointer_cast(context.effect_target); - if (!target_system) - target_system = GetSystem(context.effect_target->SystemID()); - if (!target_system) - return; // nothing to do! - - // get other endpoint systems... - - Condition::ObjectSet endpoint_objects; - // apply endpoints condition to determine objects whose systems should be - // connected to the source system - m_other_lane_endpoint_condition->Eval(context, endpoint_objects); - - // early exit if there are no valid locations - can't move anything if there's nowhere to move to - if (endpoint_objects.empty()) - return; // nothing to do! - - // get systems containing at least one endpoint object - std::set> endpoint_systems; - for (std::shared_ptr endpoint_object : endpoint_objects) { - std::shared_ptr endpoint_system = std::dynamic_pointer_cast(endpoint_object); - if (!endpoint_system) - endpoint_system = GetSystem(endpoint_object->SystemID()); - if (!endpoint_system) - continue; - endpoint_systems.insert(std::const_pointer_cast(endpoint_system)); - } - - // remove starlanes from target to endpoint systems - int target_system_id = target_system->ID(); - for (std::shared_ptr endpoint_system : endpoint_systems) { - target_system->RemoveStarlane(endpoint_system->ID()); - endpoint_system->RemoveStarlane(target_system_id); - } -} - -std::string RemoveStarlanes::Dump() const -{ return DumpIndent() + "RemoveStarlanes endpoints = " + m_other_lane_endpoint_condition->Dump() + "\n"; } - -void RemoveStarlanes::SetTopLevelContent(const std::string& content_name) { - if (m_other_lane_endpoint_condition) - m_other_lane_endpoint_condition->SetTopLevelContent(content_name); -} - -unsigned int RemoveStarlanes::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "RemoveStarlanes"); - CheckSums::CheckSumCombine(retval, m_other_lane_endpoint_condition); - - TraceLogger() << "GetCheckSum(RemoveStarlanes): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetStarType // -/////////////////////////////////////////////////////////// -SetStarType::SetStarType(ValueRef::ValueRefBase* type) : - m_type(type) -{} - -SetStarType::~SetStarType() -{ delete m_type; } - -void SetStarType::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "SetStarType::Execute given no target object"; - return; - } - if (std::shared_ptr s = std::dynamic_pointer_cast(context.effect_target)) - s->SetStarType(m_type->Eval(ScriptingContext(context, s->GetStarType()))); - else - ErrorLogger() << "SetStarType::Execute given a non-system target"; -} - -std::string SetStarType::Dump() const -{ return DumpIndent() + "SetStarType type = " + m_type->Dump() + "\n"; } - -void SetStarType::SetTopLevelContent(const std::string& content_name) { - if (m_type) - m_type->SetTopLevelContent(content_name); -} - -unsigned int SetStarType::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetStarType"); - CheckSums::CheckSumCombine(retval, m_type); - - TraceLogger() << "GetCheckSum(SetStarType): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// MoveTo // -/////////////////////////////////////////////////////////// -MoveTo::MoveTo(Condition::ConditionBase* location_condition) : - m_location_condition(location_condition) -{} - -MoveTo::~MoveTo() -{ delete m_location_condition; } - -void MoveTo::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "MoveTo::Execute given no target object"; - return; - } - - Universe& universe = GetUniverse(); - - Condition::ObjectSet valid_locations; - // apply location condition to determine valid location to move target to - m_location_condition->Eval(context, valid_locations); - - // early exit if there are no valid locations - can't move anything if there's nowhere to move to - if (valid_locations.empty()) - return; - - // "randomly" pick a destination - std::shared_ptr destination = std::const_pointer_cast(*valid_locations.begin()); - - // get previous system from which to remove object if necessary - std::shared_ptr old_sys = GetSystem(context.effect_target->SystemID()); - - // do the moving... - if (std::shared_ptr fleet = std::dynamic_pointer_cast(context.effect_target)) { - // fleets can be inserted into the system that contains the destination - // object (or the destination object itself if it is a system) - if (std::shared_ptr dest_system = GetSystem(destination->SystemID())) { - if (fleet->SystemID() != dest_system->ID()) { - // remove fleet from old system, put into new system - if (old_sys) - old_sys->Remove(fleet->ID()); - dest_system->Insert(fleet); - - // also move ships of fleet - for (std::shared_ptr ship : Objects().FindObjects(fleet->ShipIDs())) { - if (old_sys) - old_sys->Remove(ship->ID()); - dest_system->Insert(ship); - } - - ExploreSystem(dest_system->ID(), fleet); - UpdateFleetRoute(fleet, INVALID_OBJECT_ID, INVALID_OBJECT_ID); // inserted into dest_system, so next and previous systems are invalid objects - } - - // if old and new systems are the same, and destination is that - // system, don't need to do anything - - } else { - // move fleet to new location - if (old_sys) - old_sys->Remove(fleet->ID()); - fleet->SetSystem(INVALID_OBJECT_ID); - fleet->MoveTo(destination); - - // also move ships of fleet - for (std::shared_ptr ship : Objects().FindObjects(fleet->ShipIDs())) { - if (old_sys) - old_sys->Remove(ship->ID()); - ship->SetSystem(INVALID_OBJECT_ID); - ship->MoveTo(destination); - } - - - // fleet has been moved to a location that is not a system. - // Presumably this will be located on a starlane between two other - // systems, which may or may not have been explored. Regardless, - // the fleet needs to be given a new next and previous system so it - // can move into a system, or can be ordered to a new location, and - // so that it won't try to move off of starlanes towards some other - // system from its current location (if it was heading to another - // system) and so it won't be stuck in the middle of a starlane, - // unable to move (if it wasn't previously moving) - - // if destination object is a fleet or is part of a fleet, can use - // that fleet's previous and next systems to get valid next and - // previous systems for the target fleet. - std::shared_ptr dest_fleet = std::dynamic_pointer_cast(destination); - if (!dest_fleet) - if (std::shared_ptr dest_ship = std::dynamic_pointer_cast(destination)) - dest_fleet = GetFleet(dest_ship->FleetID()); - if (dest_fleet) { - UpdateFleetRoute(fleet, dest_fleet->NextSystemID(), dest_fleet->PreviousSystemID()); - - } else { - // TODO: need to do something else to get updated previous/next - // systems if the destination is a field. - ErrorLogger() << "MoveTo::Execute couldn't find a way to set the previous and next systems for the target fleet!"; - } - } - - } else if (std::shared_ptr ship = std::dynamic_pointer_cast(context.effect_target)) { - // TODO: make sure colonization doesn't interfere with this effect, and vice versa - - // is destination a ship/fleet ? - std::shared_ptr dest_fleet = std::dynamic_pointer_cast(destination); - if (!dest_fleet) { - std::shared_ptr dest_ship = std::dynamic_pointer_cast(destination); - if (dest_ship) - dest_fleet = GetFleet(dest_ship->FleetID()); - } - if (dest_fleet && dest_fleet->ID() == ship->FleetID()) - return; // already in destination fleet. nothing to do. - - bool same_owners = ship->Owner() == destination->Owner(); - int dest_sys_id = destination->SystemID(); - int ship_sys_id = ship->SystemID(); - - - if (ship_sys_id != dest_sys_id) { - // ship is moving to a different system. - - // remove ship from old system - if (old_sys) { - old_sys->Remove(ship->ID()); - ship->SetSystem(INVALID_OBJECT_ID); - } - - if (std::shared_ptr new_sys = GetSystem(dest_sys_id)) { - // ship is moving to a new system. insert it. - new_sys->Insert(ship); - } else { - // ship is moving to a non-system location. move it there. - ship->MoveTo(std::dynamic_pointer_cast(dest_fleet)); - } - - // may create a fleet for ship below... - } - - std::shared_ptr old_fleet = GetFleet(ship->FleetID()); - - if (dest_fleet && same_owners) { - // ship is moving to a different fleet owned by the same empire, so - // can be inserted into it. - old_fleet->RemoveShip(ship->ID()); - dest_fleet->AddShip(ship->ID()); - ship->SetFleetID(dest_fleet->ID()); - - } else if (dest_sys_id == ship_sys_id && dest_sys_id != INVALID_OBJECT_ID) { - // ship is moving to the system it is already in, but isn't being or - // can't be moved into a specific fleet, so the ship can be left in - // its current fleet and at its current location - - } else if (destination->X() == ship->X() && destination->Y() == ship->Y()) { - // ship is moving to the same location it's already at, but isn't - // being or can't be moved to a specific fleet, so the ship can be - // left in its current fleet and at its current location - - } else { - // need to create a new fleet for ship - if (std::shared_ptr dest_system = GetSystem(dest_sys_id)) { - CreateNewFleet(dest_system, ship); // creates new fleet, inserts fleet into system and ship into fleet - ExploreSystem(dest_system->ID(), ship); - - } else { - CreateNewFleet(destination->X(), destination->Y(), ship); // creates new fleet and inserts ship into fleet - } - } - - if (old_fleet && old_fleet->Empty()) { - old_sys->Remove(old_fleet->ID()); - universe.EffectDestroy(old_fleet->ID(), INVALID_OBJECT_ID); // no particular object destroyed this fleet - } - - } else if (std::shared_ptr planet = std::dynamic_pointer_cast(context.effect_target)) { - // planets need to be located in systems, so get system that contains destination object - - std::shared_ptr dest_system = GetSystem(destination->SystemID()); - if (!dest_system) - return; // can't move a planet to a non-system - - if (planet->SystemID() == dest_system->ID()) - return; // planet already at destination - - if (dest_system->FreeOrbits().empty()) - return; // no room for planet at destination - - if (old_sys) - old_sys->Remove(planet->ID()); - dest_system->Insert(planet); // let system pick an orbit - - // also insert buildings of planet into system. - for (std::shared_ptr building : Objects().FindObjects(planet->BuildingIDs())) { - if (old_sys) - old_sys->Remove(building->ID()); - dest_system->Insert(building); - } - - // buildings planet should be unchanged by move, as should planet's - // records of its buildings - - ExploreSystem(dest_system->ID(), planet); - - - } else if (std::shared_ptr building = std::dynamic_pointer_cast(context.effect_target)) { - // buildings need to be located on planets, so if destination is a - // planet, insert building into it, or attempt to get the planet on - // which the destination object is located and insert target building - // into that - std::shared_ptr dest_planet = std::dynamic_pointer_cast(destination); - if (!dest_planet) { - std::shared_ptr dest_building = std::dynamic_pointer_cast(destination); - if (dest_building) { - dest_planet = GetPlanet(dest_building->PlanetID()); - } - } - if (!dest_planet) - return; - - if (dest_planet->ID() == building->PlanetID()) - return; // nothing to do - - std::shared_ptr dest_system = GetSystem(destination->SystemID()); - if (!dest_system) - return; - - // remove building from old planet / system, add to new planet / system - if (old_sys) - old_sys->Remove(building->ID()); - building->SetSystem(INVALID_OBJECT_ID); - - if (std::shared_ptr old_planet = GetPlanet(building->PlanetID())) - old_planet->RemoveBuilding(building->ID()); - - dest_planet->AddBuilding(building->ID()); - building->SetPlanetID(dest_planet->ID()); - - dest_system->Insert(building); - ExploreSystem(dest_system->ID(), building); - - - } else if (std::shared_ptr system = std::dynamic_pointer_cast(context.effect_target)) { - if (destination->SystemID() != INVALID_OBJECT_ID) { - // TODO: merge systems - return; - } - - // move target system to new destination, and insert destination object - // and related objects into system - system->MoveTo(destination); - - if (destination->ObjectType() == OBJ_FIELD) - system->Insert(destination); - - // find fleets / ships at destination location and insert into system - for (std::shared_ptr obj : Objects().FindObjects()) { - if (obj->X() == system->X() && obj->Y() == system->Y()) - system->Insert(obj); - } - - for (std::shared_ptr obj : Objects().FindObjects()) { - if (obj->X() == system->X() && obj->Y() == system->Y()) - system->Insert(obj); - } - - - } else if (std::shared_ptr field = std::dynamic_pointer_cast(context.effect_target)) { - if (old_sys) - old_sys->Remove(field->ID()); - field->SetSystem(INVALID_OBJECT_ID); - field->MoveTo(destination); - if (std::shared_ptr dest_system = std::dynamic_pointer_cast(destination)) - dest_system->Insert(field); - } -} - -std::string MoveTo::Dump() const -{ return DumpIndent() + "MoveTo destination = " + m_location_condition->Dump() + "\n"; } - -void MoveTo::SetTopLevelContent(const std::string& content_name) { - if (m_location_condition) - m_location_condition->SetTopLevelContent(content_name); -} - -unsigned int MoveTo::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "MoveTo"); - CheckSums::CheckSumCombine(retval, m_location_condition); - - TraceLogger() << "GetCheckSum(MoveTo): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// MoveInOrbit // -/////////////////////////////////////////////////////////// -MoveInOrbit::MoveInOrbit(ValueRef::ValueRefBase* speed, - Condition::ConditionBase* focal_point_condition) : - m_speed(speed), - m_focal_point_condition(focal_point_condition), - m_focus_x(nullptr), - m_focus_y(nullptr) -{} - -MoveInOrbit::MoveInOrbit(ValueRef::ValueRefBase* speed, - ValueRef::ValueRefBase* focus_x/* = 0*/, - ValueRef::ValueRefBase* focus_y/* = 0*/) : - m_speed(speed), - m_focal_point_condition(nullptr), - m_focus_x(focus_x), - m_focus_y(focus_y) -{} - -MoveInOrbit::~MoveInOrbit() { - delete m_speed; - delete m_focal_point_condition; - delete m_focus_x; - delete m_focus_y; -} - -void MoveInOrbit::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "MoveInOrbit::Execute given no target object"; - return; - } - std::shared_ptr target = context.effect_target; - - double focus_x = 0.0, focus_y = 0.0, speed = 1.0; - if (m_focus_x) - focus_x = m_focus_x->Eval(ScriptingContext(context, target->X())); - if (m_focus_y) - focus_y = m_focus_y->Eval(ScriptingContext(context, target->Y())); - if (m_speed) - speed = m_speed->Eval(context); - if (speed == 0.0) - return; - if (m_focal_point_condition) { - Condition::ObjectSet matches; - m_focal_point_condition->Eval(context, matches); - if (matches.empty()) - return; - std::shared_ptr focus_object = *matches.begin(); - focus_x = focus_object->X(); - focus_y = focus_object->Y(); - } - - double focus_to_target_x = target->X() - focus_x; - double focus_to_target_y = target->Y() - focus_y; - double focus_to_target_radius = std::sqrt(focus_to_target_x * focus_to_target_x + - focus_to_target_y * focus_to_target_y); - if (focus_to_target_radius < 1.0) - return; // don't move objects that are too close to focus - - double angle_radians = atan2(focus_to_target_y, focus_to_target_x); - double angle_increment_radians = speed / focus_to_target_radius; - double new_angle_radians = angle_radians + angle_increment_radians; - - double new_x = focus_x + focus_to_target_radius * cos(new_angle_radians); - double new_y = focus_y + focus_to_target_radius * sin(new_angle_radians); - - if (target->X() == new_x && target->Y() == new_y) - return; - - std::shared_ptr old_sys = GetSystem(target->SystemID()); - - if (std::shared_ptr system = std::dynamic_pointer_cast(target)) { - system->MoveTo(new_x, new_y); - return; - - } else if (std::shared_ptr fleet = std::dynamic_pointer_cast(target)) { - if (old_sys) - old_sys->Remove(fleet->ID()); - fleet->SetSystem(INVALID_OBJECT_ID); - fleet->MoveTo(new_x, new_y); - UpdateFleetRoute(fleet, INVALID_OBJECT_ID, INVALID_OBJECT_ID); - - for (std::shared_ptr ship : Objects().FindObjects(fleet->ShipIDs())) { - if (old_sys) - old_sys->Remove(ship->ID()); - ship->SetSystem(INVALID_OBJECT_ID); - ship->MoveTo(new_x, new_y); - } - return; - - } else if (std::shared_ptr ship = std::dynamic_pointer_cast(target)) { - if (old_sys) - old_sys->Remove(ship->ID()); - ship->SetSystem(INVALID_OBJECT_ID); - - std::shared_ptr old_fleet = GetFleet(ship->FleetID()); - if (old_fleet) { - old_fleet->RemoveShip(ship->ID()); - if (old_fleet->Empty()) { - old_sys->Remove(old_fleet->ID()); - GetUniverse().EffectDestroy(old_fleet->ID(), INVALID_OBJECT_ID); // no object in particular destroyed this fleet - } - } - - ship->SetFleetID(INVALID_OBJECT_ID); - ship->MoveTo(new_x, new_y); - - CreateNewFleet(new_x, new_y, ship); // creates new fleet and inserts ship into fleet - return; - - } else if (std::shared_ptr field = std::dynamic_pointer_cast(target)) { - if (old_sys) - old_sys->Remove(field->ID()); - field->SetSystem(INVALID_OBJECT_ID); - field->MoveTo(new_x, new_y); - } - // don't move planets or buildings, as these can't exist outside of systems -} - -std::string MoveInOrbit::Dump() const { - if (m_focal_point_condition) - return DumpIndent() + "MoveInOrbit around = " + m_focal_point_condition->Dump() + "\n"; - else if (m_focus_x && m_focus_y) - return DumpIndent() + "MoveInOrbit x = " + m_focus_x->Dump() + " y = " + m_focus_y->Dump() + "\n"; - else - return DumpIndent() + "MoveInOrbit"; -} - -void MoveInOrbit::SetTopLevelContent(const std::string& content_name) { - if (m_speed) - m_speed->SetTopLevelContent(content_name); - if (m_focal_point_condition) - m_focal_point_condition->SetTopLevelContent(content_name); - if (m_focus_x) - m_focus_x->SetTopLevelContent(content_name); - if (m_focus_y) - m_focus_y->SetTopLevelContent(content_name); -} - -unsigned int MoveInOrbit::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "MoveInOrbit"); - CheckSums::CheckSumCombine(retval, m_speed); - CheckSums::CheckSumCombine(retval, m_focal_point_condition); - CheckSums::CheckSumCombine(retval, m_focus_x); - CheckSums::CheckSumCombine(retval, m_focus_y); - - TraceLogger() << "GetCheckSum(MoveInOrbit): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// MoveTowards // -/////////////////////////////////////////////////////////// -MoveTowards::MoveTowards(ValueRef::ValueRefBase* speed, - Condition::ConditionBase* dest_condition) : - m_speed(speed), - m_dest_condition(dest_condition), - m_dest_x(nullptr), - m_dest_y(nullptr) -{} - -MoveTowards::MoveTowards(ValueRef::ValueRefBase* speed, - ValueRef::ValueRefBase* dest_x/* = 0*/, - ValueRef::ValueRefBase* dest_y/* = 0*/) : - m_speed(speed), - m_dest_condition(nullptr), - m_dest_x(dest_x), - m_dest_y(dest_y) -{} - -MoveTowards::~MoveTowards() { - delete m_speed; - delete m_dest_condition; - delete m_dest_x; - delete m_dest_y; -} - -void MoveTowards::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "MoveTowards::Execute given no target object"; - return; - } - std::shared_ptr target = context.effect_target; - - double dest_x = 0.0, dest_y = 0.0, speed = 1.0; - if (m_dest_x) - dest_x = m_dest_x->Eval(ScriptingContext(context, target->X())); - if (m_dest_y) - dest_y = m_dest_y->Eval(ScriptingContext(context, target->Y())); - if (m_speed) - speed = m_speed->Eval(context); - if (speed == 0.0) - return; - if (m_dest_condition) { - Condition::ObjectSet matches; - m_dest_condition->Eval(context, matches); - if (matches.empty()) - return; - std::shared_ptr focus_object = *matches.begin(); - dest_x = focus_object->X(); - dest_y = focus_object->Y(); - } - - double dest_to_target_x = dest_x - target->X(); - double dest_to_target_y = dest_y - target->Y(); - double dest_to_target_dist = std::sqrt(dest_to_target_x * dest_to_target_x + - dest_to_target_y * dest_to_target_y); - double new_x, new_y; - - if (dest_to_target_dist < speed) { - new_x = dest_x; - new_y = dest_y; - } else { - // ensure no divide by zero issues - if (dest_to_target_dist < 1.0) - dest_to_target_dist = 1.0; - // avoid stalling when right on top of object and attempting to move away from it - if (dest_to_target_x == 0.0 && dest_to_target_y == 0.0) - dest_to_target_x = 1.0; - // move in direction of target - new_x = target->X() + dest_to_target_x / dest_to_target_dist * speed; - new_y = target->Y() + dest_to_target_y / dest_to_target_dist * speed; - } - if (target->X() == new_x && target->Y() == new_y) - return; // nothing to do - - if (std::shared_ptr system = std::dynamic_pointer_cast(target)) { - system->MoveTo(new_x, new_y); - for (std::shared_ptr obj : Objects().FindObjects(system->ObjectIDs())) { - obj->MoveTo(new_x, new_y); - } - // don't need to remove objects from system or insert into it, as all - // contained objects in system are moved with it, maintaining their - // containment situation - - } else if (std::shared_ptr fleet = std::dynamic_pointer_cast(target)) { - std::shared_ptr old_sys = GetSystem(fleet->SystemID()); - if (old_sys) - old_sys->Remove(fleet->ID()); - fleet->SetSystem(INVALID_OBJECT_ID); - fleet->MoveTo(new_x, new_y); - for (std::shared_ptr ship : Objects().FindObjects(fleet->ShipIDs())) { - if (old_sys) - old_sys->Remove(ship->ID()); - ship->SetSystem(INVALID_OBJECT_ID); - ship->MoveTo(new_x, new_y); - } - - // todo: is fleet now close enough to fall into a system? - UpdateFleetRoute(fleet, INVALID_OBJECT_ID, INVALID_OBJECT_ID); - - } else if (std::shared_ptr ship = std::dynamic_pointer_cast(target)) { - std::shared_ptr old_sys = GetSystem(ship->SystemID()); - if (old_sys) - old_sys->Remove(ship->ID()); - ship->SetSystem(INVALID_OBJECT_ID); - - std::shared_ptr old_fleet = GetFleet(ship->FleetID()); - if (old_fleet) - old_fleet->RemoveShip(ship->ID()); - ship->SetFleetID(INVALID_OBJECT_ID); - - CreateNewFleet(new_x, new_y, ship); // creates new fleet and inserts ship into fleet - if (old_fleet && old_fleet->Empty()) { - if (old_sys) - old_sys->Remove(old_fleet->ID()); - GetUniverse().EffectDestroy(old_fleet->ID(), INVALID_OBJECT_ID); // no object in particular destroyed this fleet - } - - } else if (std::shared_ptr field = std::dynamic_pointer_cast(target)) { - std::shared_ptr old_sys = GetSystem(field->SystemID()); - if (old_sys) - old_sys->Remove(field->ID()); - field->SetSystem(INVALID_OBJECT_ID); - field->MoveTo(new_x, new_y); - - } - // don't move planets or buildings, as these can't exist outside of systems -} - -std::string MoveTowards::Dump() const { - if (m_dest_condition) - return DumpIndent() + "MoveTowards destination = " + m_dest_condition->Dump() + "\n"; - else if (m_dest_x && m_dest_y) - return DumpIndent() + "MoveTowards x = " + m_dest_x->Dump() + " y = " + m_dest_y->Dump() + "\n"; - else - return DumpIndent() + "MoveTowards"; -} - -void MoveTowards::SetTopLevelContent(const std::string& content_name) { - if (m_speed) - m_speed->SetTopLevelContent(content_name); - if (m_dest_condition) - m_dest_condition->SetTopLevelContent(content_name); - if (m_dest_x) - m_dest_x->SetTopLevelContent(content_name); - if (m_dest_y) - m_dest_y->SetTopLevelContent(content_name); -} - -unsigned int MoveTowards::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "MoveTowards"); - CheckSums::CheckSumCombine(retval, m_speed); - CheckSums::CheckSumCombine(retval, m_dest_condition); - CheckSums::CheckSumCombine(retval, m_dest_x); - CheckSums::CheckSumCombine(retval, m_dest_y); - - TraceLogger() << "GetCheckSum(MoveTowards): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetDestination // -/////////////////////////////////////////////////////////// -SetDestination::SetDestination(Condition::ConditionBase* location_condition) : - m_location_condition(location_condition) -{} - -SetDestination::~SetDestination() -{ delete m_location_condition; } - -void SetDestination::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "SetDestination::Execute given no target object"; - return; - } - - std::shared_ptr target_fleet = std::dynamic_pointer_cast(context.effect_target); - if (!target_fleet) { - ErrorLogger() << "SetDestination::Execute acting on non-fleet target:"; - context.effect_target->Dump(); - return; - } - - Universe& universe = GetUniverse(); - - Condition::ObjectSet valid_locations; - // apply location condition to determine valid location to move target to - m_location_condition->Eval(context, valid_locations); - - // early exit if there are no valid locations - can't move anything if there's nowhere to move to - if (valid_locations.empty()) - return; - - // "randomly" pick a destination - int destination_idx = RandSmallInt(0, valid_locations.size() - 1); - std::shared_ptr destination = std::const_pointer_cast( - *std::next(valid_locations.begin(), destination_idx)); - int destination_system_id = destination->SystemID(); - - // early exit if destination is not / in a system - if (destination_system_id == INVALID_OBJECT_ID) - return; - - int start_system_id = target_fleet->SystemID(); - if (start_system_id == INVALID_OBJECT_ID) - start_system_id = target_fleet->NextSystemID(); - // abort if no valid starting system - if (start_system_id == INVALID_OBJECT_ID) - return; - - // find shortest path for fleet's owner - std::pair, double> short_path = universe.GetPathfinder()->ShortestPath(start_system_id, destination_system_id, target_fleet->Owner()); - const std::list& route_list = short_path.first; - - // reject empty move paths (no path exists). - if (route_list.empty()) - return; - - // check destination validity: disallow movement that's out of range - std::pair eta = target_fleet->ETA(target_fleet->MovePath(route_list)); - if (eta.first == Fleet::ETA_NEVER || eta.first == Fleet::ETA_OUT_OF_RANGE) - return; - - target_fleet->SetRoute(route_list); -} - -std::string SetDestination::Dump() const -{ return DumpIndent() + "SetDestination destination = " + m_location_condition->Dump() + "\n"; } - -void SetDestination::SetTopLevelContent(const std::string& content_name) { - if (m_location_condition) - m_location_condition->SetTopLevelContent(content_name); -} - -unsigned int SetDestination::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetDestination"); - CheckSums::CheckSumCombine(retval, m_location_condition); - - TraceLogger() << "GetCheckSum(SetDestination): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetAggression // -/////////////////////////////////////////////////////////// -SetAggression::SetAggression(bool aggressive) : - m_aggressive(aggressive) -{} - -void SetAggression::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "SetAggression::Execute given no target object"; - return; - } - - std::shared_ptr target_fleet = std::dynamic_pointer_cast(context.effect_target); - if (!target_fleet) { - ErrorLogger() << "SetAggression::Execute acting on non-fleet target:"; - context.effect_target->Dump(); - return; - } - - target_fleet->SetAggressive(m_aggressive); -} - -std::string SetAggression::Dump() const -{ return DumpIndent() + (m_aggressive ? "SetAggressive" : "SetPassive") + "\n"; } - -unsigned int SetAggression::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetAggression"); - CheckSums::CheckSumCombine(retval, m_aggressive); - - TraceLogger() << "GetCheckSum(SetAggression): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// Victory // -/////////////////////////////////////////////////////////// -Victory::Victory(const std::string& reason_string) : - m_reason_string(reason_string) -{} - -void Victory::Execute(const ScriptingContext& context) const { - if (!context.effect_target) { - ErrorLogger() << "Victory::Execute given no target object"; - return; - } - if (Empire* empire = GetEmpire(context.effect_target->Owner())) - empire->Win(m_reason_string); - else - ErrorLogger() << "Trying to grant victory to a missing empire!"; -} - -std::string Victory::Dump() const -{ return DumpIndent() + "Victory reason = \"" + m_reason_string + "\"\n"; } - -unsigned int Victory::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "Victory"); - CheckSums::CheckSumCombine(retval, m_reason_string); - - TraceLogger() << "GetCheckSum(Victory): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetEmpireTechProgress // -/////////////////////////////////////////////////////////// -SetEmpireTechProgress::SetEmpireTechProgress(ValueRef::ValueRefBase* tech_name, - ValueRef::ValueRefBase* research_progress) : - m_tech_name(tech_name), - m_research_progress(research_progress), - m_empire_id(new ValueRef::Variable(ValueRef::EFFECT_TARGET_REFERENCE, std::vector(1, "Owner"))) -{} - -SetEmpireTechProgress::SetEmpireTechProgress(ValueRef::ValueRefBase* tech_name, - ValueRef::ValueRefBase* research_progress, - ValueRef::ValueRefBase* empire_id) : - m_tech_name(tech_name), - m_research_progress(research_progress), - m_empire_id(empire_id) +Effect::EffectCause::EffectCause() : + cause_type(INVALID_EFFECTS_GROUP_CAUSE_TYPE), + specific_cause(), + custom_label() {} -SetEmpireTechProgress::~SetEmpireTechProgress() { - delete m_tech_name; - delete m_research_progress; - delete m_empire_id; -} - -void SetEmpireTechProgress::Execute(const ScriptingContext& context) const { - if (!m_empire_id) return; - Empire* empire = GetEmpire(m_empire_id->Eval(context)); - if (!empire) return; - - if (!m_tech_name) { - ErrorLogger() << "SetEmpireTechProgress::Execute has not tech name to evaluate"; - return; - } - std::string tech_name = m_tech_name->Eval(context); - if (tech_name.empty()) - return; - - const Tech* tech = GetTech(tech_name); - if (!tech) { - ErrorLogger() << "SetEmpireTechProgress::Execute couldn't get tech with name " << tech_name; - return; - } - - float initial_progress = empire->ResearchProgress(tech_name); - double value = m_research_progress->Eval(ScriptingContext(context, initial_progress)); - empire->SetTechResearchProgress(tech_name, value); -} - -std::string SetEmpireTechProgress::Dump() const { - std::string retval = "SetEmpireTechProgress name = "; - if (m_tech_name) - retval += m_tech_name->Dump(); - if (m_research_progress) - retval += " progress = " + m_research_progress->Dump(); - if (m_empire_id) - retval += " empire = " + m_empire_id->Dump() + "\n"; - return retval; -} - -void SetEmpireTechProgress::SetTopLevelContent(const std::string& content_name) { - if (m_tech_name) - m_tech_name->SetTopLevelContent(content_name); - if (m_research_progress) - m_research_progress->SetTopLevelContent(content_name); - if (m_empire_id) - m_empire_id->SetTopLevelContent(content_name); -} - -unsigned int SetEmpireTechProgress::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetEmpireTechProgress"); - CheckSums::CheckSumCombine(retval, m_tech_name); - CheckSums::CheckSumCombine(retval, m_research_progress); - CheckSums::CheckSumCombine(retval, m_empire_id); - - TraceLogger() << "GetCheckSum(SetEmpireTechProgress): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// GiveEmpireTech // -/////////////////////////////////////////////////////////// -GiveEmpireTech::GiveEmpireTech(ValueRef::ValueRefBase* tech_name, - ValueRef::ValueRefBase* empire_id) : - m_tech_name(tech_name), - m_empire_id(empire_id) +Effect::EffectCause::EffectCause(EffectsCauseType cause_type_, const std::string& specific_cause_, + const std::string& custom_label_) : + cause_type(cause_type_), + specific_cause(specific_cause_), + custom_label(custom_label_) { - if (!m_empire_id) - m_empire_id = new ValueRef::Variable(ValueRef::EFFECT_TARGET_REFERENCE, std::vector(1, "Owner")); -} - -GiveEmpireTech::~GiveEmpireTech() { - delete m_empire_id; - delete m_tech_name; -} - -void GiveEmpireTech::Execute(const ScriptingContext& context) const { - if (!m_empire_id) return; - Empire* empire = GetEmpire(m_empire_id->Eval(context)); - if (!empire) return; - - if (!m_tech_name) - return; - - std::string tech_name = m_tech_name->Eval(context); - - const Tech* tech = GetTech(tech_name); - if (!tech) { - ErrorLogger() << "GiveEmpireTech::Execute couldn't get tech with name: " << tech_name; - return; - } - - empire->AddTech(tech_name); -} - -std::string GiveEmpireTech::Dump() const { - std::string retval = DumpIndent() + "GiveEmpireTech"; - - if (m_tech_name) - retval += " name = " + m_tech_name->Dump(); - - if (m_empire_id) - retval += " empire = " + m_empire_id->Dump(); - - retval += "\n"; - return retval; -} - -void GiveEmpireTech::SetTopLevelContent(const std::string& content_name) { - if (m_empire_id) - m_empire_id->SetTopLevelContent(content_name); - if (m_tech_name) - m_tech_name->SetTopLevelContent(content_name); -} - -unsigned int GiveEmpireTech::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "GiveEmpireTech"); - CheckSums::CheckSumCombine(retval, m_tech_name); - CheckSums::CheckSumCombine(retval, m_empire_id); - - TraceLogger() << "GetCheckSum(GiveEmpireTech): retval: " << retval; - return retval; + //DebugLogger() << "EffectCause(" << cause_type << ", " << specific_cause << ", " << custom_label << ")"; } - -/////////////////////////////////////////////////////////// -// GenerateSitRepMessage // -/////////////////////////////////////////////////////////// -GenerateSitRepMessage::GenerateSitRepMessage(const std::string& message_string, - const std::string& icon, - const MessageParams& message_parameters, - ValueRef::ValueRefBase* recipient_empire_id, - EmpireAffiliationType affiliation, - const std::string label, - bool stringtable_lookup) : - m_message_string(message_string), - m_icon(icon), - m_message_parameters(message_parameters), - m_recipient_empire_id(recipient_empire_id), - m_condition(nullptr), - m_affiliation(affiliation), - m_label(label), - m_stringtable_lookup(stringtable_lookup) -{} - -GenerateSitRepMessage::GenerateSitRepMessage(const std::string& message_string, - const std::string& icon, - const MessageParams& message_parameters, - EmpireAffiliationType affiliation, - Condition::ConditionBase* condition, - const std::string label, - bool stringtable_lookup) : - m_message_string(message_string), - m_icon(icon), - m_message_parameters(message_parameters), - m_recipient_empire_id(nullptr), - m_condition(condition), - m_affiliation(affiliation), - m_label(label), - m_stringtable_lookup(stringtable_lookup) +Effect::AccountingInfo::AccountingInfo() : + EffectCause(), + source_id(INVALID_OBJECT_ID), + meter_change(0.0f), + running_meter_total(0.0f) {} -GenerateSitRepMessage::GenerateSitRepMessage(const std::string& message_string, const std::string& icon, - const MessageParams& message_parameters, - EmpireAffiliationType affiliation, - const std::string& label, - bool stringtable_lookup): - m_message_string(message_string), - m_icon(icon), - m_message_parameters(message_parameters), - m_recipient_empire_id(nullptr), - m_condition(), - m_affiliation(affiliation), - m_label(label), - m_stringtable_lookup(stringtable_lookup) +Effect::AccountingInfo::AccountingInfo( + int source_id_, EffectsCauseType cause_type_, float meter_change_, + float running_meter_total_, const std::string& specific_cause_, + const std::string& custom_label_) : + EffectCause(cause_type_, specific_cause_, custom_label_), + source_id(source_id_), + meter_change(meter_change_), + running_meter_total(running_meter_total_) {} -GenerateSitRepMessage::~GenerateSitRepMessage() { - for (std::pair*>& entry : m_message_parameters) { - delete entry.second; - } - delete m_recipient_empire_id; -} - -void GenerateSitRepMessage::Execute(const ScriptingContext& context) const { - int recipient_id = ALL_EMPIRES; - if (m_recipient_empire_id) - recipient_id = m_recipient_empire_id->Eval(context); - - // track any ship designs used in message, which any recipients must be - // made aware of so sitrep won't have errors - std::set ship_design_ids_to_inform_receipits_of; - - // TODO: should any referenced object IDs being made known at basic visibility? - - - // evaluate all parameter valuerefs so they can be substituted into sitrep template - std::vector> parameter_tag_values; - for (const std::pair*>& entry : m_message_parameters) { - parameter_tag_values.push_back(std::make_pair(entry.first, entry.second->Eval(context))); - - // special case for ship designs: make sure sitrep recipient knows about the design - // so the sitrep won't have errors about unknown designs being referenced - if (entry.first == VarText::PREDEFINED_DESIGN_TAG) { - if (const ShipDesign* design = GetPredefinedShipDesign(entry.second->Eval(context))) { - ship_design_ids_to_inform_receipits_of.insert(design->ID()); - } - } - } - - // whom to send to? - std::set recipient_empire_ids; - switch (m_affiliation) { - case AFFIL_SELF: { - // add just specified empire - if (recipient_id != ALL_EMPIRES) - recipient_empire_ids.insert(recipient_id); - break; - } - - case AFFIL_ALLY: { - // add allies of specified empire - for (const std::map::value_type& empire_id : Empires()) { - if (empire_id.first == recipient_id || recipient_id == ALL_EMPIRES) - continue; - - DiplomaticStatus status = Empires().GetDiplomaticStatus(recipient_id, empire_id.first); - if (status == DIPLO_PEACE) - recipient_empire_ids.insert(empire_id.first); - } - break; - } - - case AFFIL_ENEMY: { - // add enemies of specified empire - for (const std::map::value_type& empire_id : Empires()) { - if (empire_id.first == recipient_id || recipient_id == ALL_EMPIRES) - continue; - - DiplomaticStatus status = Empires().GetDiplomaticStatus(recipient_id, empire_id.first); - if (status == DIPLO_WAR) - recipient_empire_ids.insert(empire_id.first); - } - break; - } - - case AFFIL_CAN_SEE: { - // evaluate condition - Condition::ObjectSet condition_matches; - if (m_condition) - m_condition->Eval(context, condition_matches); - - // add empires that can see any condition-matching object - for (const std::map::value_type& empire_entry : Empires()) { - int empire_id = empire_entry.first; - for (std::shared_ptr object : condition_matches) { - if (object->GetVisibility(empire_id) >= VIS_BASIC_VISIBILITY) { - recipient_empire_ids.insert(empire_id); - break; - } - } - } - break; - } - - case AFFIL_NONE: - // add no empires - break; - - case AFFIL_HUMAN: - // todo: implement this separately, though not high priority since it - // probably doesn't matter if AIs get an extra sitrep message meant for - // human eyes - case AFFIL_ANY: - default: { - // add all empires - for (const std::map::value_type& empire_entry : Empires()) - recipient_empire_ids.insert(empire_entry.first); - break; - } - } - - int sitrep_turn = CurrentTurn() + 1; - - // send to recipient empires - for (int empire_id : recipient_empire_ids) { - Empire* empire = GetEmpire(empire_id); - if (!empire) - continue; - empire->AddSitRepEntry(CreateSitRep(m_message_string, sitrep_turn, m_icon, - parameter_tag_values, m_label, m_stringtable_lookup)); - - // also inform of any ship designs recipients should know about - for (int design_id : ship_design_ids_to_inform_receipits_of) { - GetUniverse().SetEmpireKnowledgeOfShipDesign(design_id, empire_id); - } - } -} - -void GenerateSitRepMessage::Execute(const TargetsCauses& targets_causes, AccountingMap* accounting_map, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) const -{ - if (only_appearance_effects || only_meter_effects) - return; - - // apply this effect for each source causing it - ScriptingContext source_context; - for (const std::pair& targets_entry : targets_causes) { - source_context.source = GetUniverseObject(targets_entry.first.source_object_id); - EffectBase::Execute(source_context, targets_entry.second.target_set); - } -} - -std::string GenerateSitRepMessage::Dump() const { - std::string retval = DumpIndent(); - retval += "GenerateSitRepMessage\n"; - ++g_indent; - retval += DumpIndent() + "message = \"" + m_message_string + "\"" + " icon = " + m_icon + "\n"; - - if (m_message_parameters.size() == 1) { - retval += DumpIndent() + "parameters = tag = " + m_message_parameters[0].first + " data = " + m_message_parameters[0].second->Dump() + "\n"; - } else if (!m_message_parameters.empty()) { - retval += DumpIndent() + "parameters = [ "; - for (const std::pair*>& entry : m_message_parameters) { - retval += " tag = " + entry.first - + " data = " + entry.second->Dump() - + " "; - } - retval += "]\n"; - } - - retval += DumpIndent() + "affiliation = "; - switch (m_affiliation) { - case AFFIL_SELF: retval += "TheEmpire"; break; - case AFFIL_ENEMY: retval += "EnemyOf"; break; - case AFFIL_ALLY: retval += "AllyOf"; break; - case AFFIL_ANY: retval += "AnyEmpire"; break; - case AFFIL_CAN_SEE: retval += "CanSee"; break; - case AFFIL_HUMAN: retval += "Human"; break; - default: retval += "?"; break; - } - - if (m_recipient_empire_id) - retval += "\n" + DumpIndent() + "empire = " + m_recipient_empire_id->Dump() + "\n"; - if (m_condition) - retval += "\n" + DumpIndent() + "condition = " + m_condition->Dump() + "\n"; - --g_indent; - - return retval; -} - -void GenerateSitRepMessage::SetTopLevelContent(const std::string& content_name) { - for (std::pair*>& entry : m_message_parameters) { - entry.second->SetTopLevelContent(content_name); - } - if (m_recipient_empire_id) - m_recipient_empire_id->SetTopLevelContent(content_name); - if (m_condition) - m_condition->SetTopLevelContent(content_name); -} - -unsigned int GenerateSitRepMessage::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "GenerateSitRepMessage"); - CheckSums::CheckSumCombine(retval, m_message_string); - CheckSums::CheckSumCombine(retval, m_icon); - CheckSums::CheckSumCombine(retval, m_message_parameters); - CheckSums::CheckSumCombine(retval, m_recipient_empire_id); - CheckSums::CheckSumCombine(retval, m_condition); - CheckSums::CheckSumCombine(retval, m_affiliation); - CheckSums::CheckSumCombine(retval, m_label); - CheckSums::CheckSumCombine(retval, m_stringtable_lookup); - - TraceLogger() << "GetCheckSum(GenerateSitRepMessage): retval: " << retval; - return retval; +bool Effect::AccountingInfo::operator==(const AccountingInfo& rhs) const { + return + cause_type == rhs.cause_type && + specific_cause == rhs.specific_cause && + custom_label == rhs.custom_label && + source_id == rhs.source_id && + meter_change == rhs.meter_change && + running_meter_total == rhs.running_meter_total; } - -/////////////////////////////////////////////////////////// -// SetOverlayTexture // -/////////////////////////////////////////////////////////// -SetOverlayTexture::SetOverlayTexture(const std::string& texture, ValueRef::ValueRefBase* size) : - m_texture(texture), - m_size(size) +Effect::TargetsAndCause::TargetsAndCause() : + target_set(), + effect_cause() {} -SetOverlayTexture::~SetOverlayTexture() -{ delete m_size; } - -void SetOverlayTexture::Execute(const ScriptingContext& context) const { - if (!context.effect_target) - return; - double size = 1.0; - if (m_size) - size = m_size->Eval(context); - - if (std::shared_ptr system = std::dynamic_pointer_cast(context.effect_target)) - system->SetOverlayTexture(m_texture, size); -} - -void SetOverlayTexture::Execute(const TargetsCauses& targets_causes, AccountingMap* accounting_map, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) const -{ - if (only_generate_sitrep_effects || only_meter_effects) - return; - - // apply this effect for each source causing it - ScriptingContext source_context; - for (const std::pair& targets_entry : targets_causes) { - source_context.source = GetUniverseObject(targets_entry.first.source_object_id); - EffectBase::Execute(source_context, targets_entry.second.target_set); - } -} - -std::string SetOverlayTexture::Dump() const { - std::string retval = DumpIndent() + "SetOverlayTexture texture = " + m_texture; - if (m_size) - retval += " size = " + m_size->Dump(); - retval += "\n"; - return retval; -} - -void SetOverlayTexture::SetTopLevelContent(const std::string& content_name) { - if (m_size) - m_size->SetTopLevelContent(content_name); -} - -unsigned int SetOverlayTexture::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetOverlayTexture"); - CheckSums::CheckSumCombine(retval, m_texture); - CheckSums::CheckSumCombine(retval, m_size); - - TraceLogger() << "GetCheckSum(SetOverlayTexture): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetTexture // -/////////////////////////////////////////////////////////// -SetTexture::SetTexture(const std::string& texture) : - m_texture(texture) +Effect::TargetsAndCause::TargetsAndCause(const TargetSet& target_set_, const EffectCause& effect_cause_) : + target_set(target_set_), + effect_cause(effect_cause_) {} -void SetTexture::Execute(const ScriptingContext& context) const { - if (!context.effect_target) - return; - if (std::shared_ptr planet = std::dynamic_pointer_cast(context.effect_target)) - planet->SetSurfaceTexture(m_texture); -} - -void SetTexture::Execute(const TargetsCauses& targets_causes, AccountingMap* accounting_map, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) const -{ - if (only_generate_sitrep_effects || only_meter_effects) - return; - - // apply this effect for each source causing it - ScriptingContext source_context; - for (const std::pair& targets_entry : targets_causes) { - source_context.source = GetUniverseObject(targets_entry.first.source_object_id); - EffectBase::Execute(source_context, targets_entry.second.target_set); - } -} - -std::string SetTexture::Dump() const -{ return DumpIndent() + "SetTexture texture = " + m_texture + "\n"; } - -unsigned int SetTexture::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetTexture"); - CheckSums::CheckSumCombine(retval, m_texture); - - TraceLogger() << "GetCheckSum(SetTexture): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// SetVisibility // -/////////////////////////////////////////////////////////// -SetVisibility::SetVisibility(Visibility vis, EmpireAffiliationType affiliation, - ValueRef::ValueRefBase* empire_id, - Condition::ConditionBase* of_objects) : - m_vis(vis), - m_empire_id(empire_id), - m_affiliation(affiliation), - m_condition(of_objects) +Effect::SourcedEffectsGroup::SourcedEffectsGroup() : + source_object_id(INVALID_OBJECT_ID) {} -SetVisibility::~SetVisibility() { - delete m_empire_id; - delete m_condition; -} - -void SetVisibility::Execute(const ScriptingContext& context) const { - if (!context.effect_target) - return; - - // Note: currently ignoring upgrade-only flag - - if (m_vis == INVALID_VISIBILITY) - return; - - int empire_id = ALL_EMPIRES; - if (m_empire_id) - empire_id = m_empire_id->Eval(context); - - // whom to set visbility for? - std::set empire_ids; - switch (m_affiliation) { - case AFFIL_SELF: { - // add just specified empire - if (empire_id != ALL_EMPIRES) - empire_ids.insert(empire_id); - break; - } - - case AFFIL_ALLY: { - // add allies of specified empire - for (const std::map::value_type& empire_entry : Empires()) { - if (empire_entry.first == empire_id || empire_id == ALL_EMPIRES) - continue; - - DiplomaticStatus status = Empires().GetDiplomaticStatus(empire_id, empire_entry.first); - if (status == DIPLO_PEACE) - empire_ids.insert(empire_entry.first); - } - break; - } - - case AFFIL_ENEMY: { - // add enemies of specified empire - for (const std::map::value_type& empire_entry : Empires()) { - if (empire_entry.first == empire_id || empire_id == ALL_EMPIRES) - continue; - - DiplomaticStatus status = Empires().GetDiplomaticStatus(empire_id, empire_entry.first); - if (status == DIPLO_WAR) - empire_ids.insert(empire_entry.first); - } - break; - } - - case AFFIL_CAN_SEE: - // unsupported so far... - case AFFIL_HUMAN: - // unsupported so far... - case AFFIL_NONE: - // add no empires - break; - - case AFFIL_ANY: - default: { - // add all empires - for (const std::map::value_type& empire_entry : Empires()) - empire_ids.insert(empire_entry.first); - break; - } - } - - // what to set visibility of? - std::set object_ids; - if (!m_condition) { - object_ids.insert(context.effect_target->ID()); - } else { - Condition::ObjectSet condition_matches; - m_condition->Eval(context, condition_matches); - for (std::shared_ptr object : condition_matches) { - object_ids.insert(object->ID()); - } - } - - for (int emp_id : empire_ids) { - if (!GetEmpire(emp_id)) - continue; - for (int obj_id : object_ids) { - GetUniverse().SetEffectDerivedVisibility(emp_id, obj_id, m_vis); - } - } -} - -std::string SetVisibility::Dump() const { - std::string retval = DumpIndent(); - retval += "SetVisibility visibility = "; - - switch (m_vis) { - case VIS_NO_VISIBILITY: retval += "Invisible"; break; - case VIS_BASIC_VISIBILITY: retval += "Basic"; break; - case VIS_PARTIAL_VISIBILITY:retval += "Partial"; break; - case VIS_FULL_VISIBILITY: retval += "Full"; break; - case INVALID_VISIBILITY: - default: retval += "?"; break; - } - - retval += DumpIndent() + "affiliation = "; - switch (m_affiliation) { - case AFFIL_SELF: retval += "TheEmpire"; break; - case AFFIL_ENEMY: retval += "EnemyOf"; break; - case AFFIL_ALLY: retval += "AllyOf"; break; - case AFFIL_ANY: retval += "AnyEmpire"; break; - case AFFIL_CAN_SEE: retval += "CanSee"; break; - case AFFIL_HUMAN: retval += "Human"; break; - default: retval += "?"; break; - } - - if (m_empire_id) - retval += " empire = " + m_empire_id->Dump(); - - if (m_condition) - retval += " condition = " + m_condition->Dump(); - - retval += "\n"; - return retval; -} - -void SetVisibility::SetTopLevelContent(const std::string& content_name) { - if (m_empire_id) - m_empire_id->SetTopLevelContent(content_name); - if (m_condition) - m_condition->SetTopLevelContent(content_name); -} - -unsigned int SetVisibility::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "SetVisibility"); - CheckSums::CheckSumCombine(retval, m_vis); - CheckSums::CheckSumCombine(retval, m_empire_id); - CheckSums::CheckSumCombine(retval, m_affiliation); - CheckSums::CheckSumCombine(retval, m_condition); - - TraceLogger() << "GetCheckSum(SetVisibility): retval: " << retval; - return retval; -} - - -/////////////////////////////////////////////////////////// -// Conditional // -/////////////////////////////////////////////////////////// -Conditional::Conditional(Condition::ConditionBase* target_condition, - const std::vector& true_effects, - const std::vector& false_effects) : - m_target_condition(target_condition), - m_true_effects(true_effects), - m_false_effects(false_effects) +Effect::SourcedEffectsGroup::SourcedEffectsGroup(int source_object_id_, const EffectsGroup* effects_group_) : + source_object_id(source_object_id_), + effects_group(effects_group_) {} -void Conditional::Execute(const ScriptingContext& context) const { - if (!context.effect_target) - return; - if (!m_target_condition || m_target_condition->Eval(context, context.effect_target)) { - for (EffectBase* effect : m_true_effects) { - if (effect) - effect->Execute(context); - } - } else { - for (EffectBase* effect : m_false_effects) { - if (effect) - effect->Execute(context); - } - } -} - -void Conditional::Execute(const ScriptingContext& context, const TargetSet& targets) const { - if (targets.empty()) - return; - - // apply sub-condition to target set to pick which to act on with which of sub-effects - const Condition::ObjectSet& potential_target_objects = *reinterpret_cast(&targets); - - Condition::ObjectSet matches = potential_target_objects; - Condition::ObjectSet non_matches; - if (m_target_condition) - m_target_condition->Eval(context, matches, non_matches, Condition::MATCHES); - - if (!matches.empty() && !m_true_effects.empty()) { - Effect::TargetSet& match_targets = *reinterpret_cast(&matches); - for (EffectBase* effect : m_true_effects) { - if (effect) - effect->Execute(context, match_targets); - } - } - if (!non_matches.empty() && !m_false_effects.empty()) { - Effect::TargetSet& non_match_targets = *reinterpret_cast(&non_matches); - for (EffectBase* effect : m_false_effects) { - if (effect) - effect->Execute(context, non_match_targets); - } - } -} - -namespace { - void GetFilteredEffects(std::vector& filtered_effects_out, - const std::vector& effects_in, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) - { - filtered_effects_out.clear(); - filtered_effects_out.reserve(effects_in.size()); - for (EffectBase* effect : effects_in) { - if (!effect) - continue; - if (only_meter_effects && !effect->IsMeterEffect()) - continue; - if (only_appearance_effects && !effect->IsAppearanceEffect()) - continue; - if (only_generate_sitrep_effects && !effect->IsSitrepEffect()) - continue; - if (!include_empire_meter_effects && effect->IsEmpireMeterEffect()) - continue; - filtered_effects_out.push_back(effect); - } - } -} - -void Conditional::Execute(const ScriptingContext& context, const TargetSet& targets, - AccountingMap* accounting_map, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) const -{ - // filter true and false effects to get only those that match the bool flags - std::vector filtered_true_effects; - GetFilteredEffects(filtered_true_effects, m_true_effects, only_meter_effects, only_appearance_effects, - include_empire_meter_effects, only_generate_sitrep_effects); - std::vector filtered_false_effects; - GetFilteredEffects(filtered_false_effects, m_false_effects, only_meter_effects, only_appearance_effects, - include_empire_meter_effects, only_generate_sitrep_effects); - - // apply sub-condition to target set to pick which to act on with which of sub-effects - const Condition::ObjectSet& potential_target_objects = *reinterpret_cast(&targets); - Condition::ObjectSet matches = potential_target_objects; - Condition::ObjectSet non_matches; - if (m_target_condition) - m_target_condition->Eval(context, matches, non_matches, Condition::MATCHES); - - // execute filtered true and false effects to target matches and non-matches respectively - if (!matches.empty() && !m_true_effects.empty()) { - Effect::TargetSet& match_targets = *reinterpret_cast(&matches); - for (EffectBase* effect : filtered_true_effects) { - if (!effect) - continue; - if (effect->IsConditionalEffect()) { - if (Conditional* cond_effect = dynamic_cast(effect)) - cond_effect->Execute(context, match_targets, accounting_map, only_meter_effects, - only_appearance_effects, include_empire_meter_effects, only_generate_sitrep_effects); - } else { - effect->Execute(context, match_targets); - } - } - } - if (!non_matches.empty() && !m_false_effects.empty()) { - Effect::TargetSet& non_match_targets = *reinterpret_cast(&non_matches); - for (EffectBase* effect : filtered_false_effects) { - if (!effect) - continue; - if (effect->IsConditionalEffect()) { - if (Conditional* cond_effect = dynamic_cast(effect)) - cond_effect->Execute(context, non_match_targets, accounting_map, only_meter_effects, - only_appearance_effects, include_empire_meter_effects, only_generate_sitrep_effects); - } else { - effect->Execute(context, non_match_targets); - } - } - } -} - -void Conditional::Execute(const TargetsCauses& targets_causes, AccountingMap* accounting_map, - bool only_meter_effects, bool only_appearance_effects, - bool include_empire_meter_effects, bool only_generate_sitrep_effects) const -{ - // filter true and false effects to get only those that match the bool flags - std::vector filtered_true_effects; - GetFilteredEffects(filtered_true_effects, m_true_effects, only_meter_effects, only_appearance_effects, - include_empire_meter_effects, only_generate_sitrep_effects); - std::vector filtered_false_effects; - GetFilteredEffects(filtered_false_effects, m_false_effects, only_meter_effects, only_appearance_effects, - include_empire_meter_effects, only_generate_sitrep_effects); - - - // apply this effect for each source causing it - ScriptingContext source_context; - for (const std::pair& targets_entry : targets_causes) { - source_context.source = GetUniverseObject(targets_entry.first.source_object_id); - - // apply sub-condition to target set to pick which to act on with which of sub-effects - const Condition::ObjectSet& potential_target_objects = *reinterpret_cast(&(targets_entry.second.target_set)); - Condition::ObjectSet matches = potential_target_objects; - Condition::ObjectSet non_matches; - if (m_target_condition) - m_target_condition->Eval(source_context, matches, non_matches, Condition::MATCHES); - - // execute filtered true and false effects to target matches and non-matches respectively - if (!matches.empty() && !m_true_effects.empty()) { - Effect::TargetSet& match_targets = *reinterpret_cast(&matches); - for (EffectBase* effect : filtered_true_effects) { - if (!effect) - continue; - if (effect->IsConditionalEffect()) { - if (Conditional* cond_effect = dynamic_cast(effect)) - cond_effect->Execute(source_context, match_targets, accounting_map, only_meter_effects, - only_appearance_effects, include_empire_meter_effects, only_generate_sitrep_effects); - } else { - effect->Execute(source_context, match_targets); - } - } - } - if (!non_matches.empty() && !m_false_effects.empty()) { - Effect::TargetSet& non_match_targets = *reinterpret_cast(&non_matches); - for (EffectBase* effect : filtered_false_effects) { - if (!effect) - continue; - if (effect->IsConditionalEffect()) { - if (Conditional* cond_effect = dynamic_cast(effect)) - cond_effect->Execute(source_context, non_match_targets, accounting_map, only_meter_effects, - only_appearance_effects, include_empire_meter_effects, only_generate_sitrep_effects); - } else { - effect->Execute(source_context, non_match_targets); - } - } - } - } -} - -std::string Conditional::Dump() const { - std::string retval = DumpIndent() + "If\n"; - ++g_indent; - if (m_target_condition) { - retval += DumpIndent() + "condition =\n"; - ++g_indent; - retval += m_target_condition->Dump(); - --g_indent; - } - - if (m_true_effects.size() == 1) { - retval += DumpIndent() + "effects =\n"; - ++g_indent; - retval += m_true_effects[0]->Dump(); - --g_indent; - } else { - retval += DumpIndent() + "effects = [\n"; - ++g_indent; - for (EffectBase* effect : m_true_effects) { - retval += effect->Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; - } - - if (m_false_effects.empty()) { - } else if (m_false_effects.size() == 1) { - retval += DumpIndent() + "else =\n"; - ++g_indent; - retval += m_false_effects[0]->Dump(); - --g_indent; - } else { - retval += DumpIndent() + "else = [\n"; - ++g_indent; - for (EffectBase* effect : m_false_effects) { - retval += effect->Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; - } - --g_indent; - - return retval; -} - -bool Conditional::IsMeterEffect() const { - for (EffectBase* effect : m_true_effects) { - if (effect->IsMeterEffect()) - return true; - } - for (EffectBase* effect : m_false_effects) { - if (effect->IsMeterEffect()) - return true; - } - return false; -} - -bool Conditional::IsAppearanceEffect() const { - for (EffectBase* effect : m_true_effects) { - if (effect->IsAppearanceEffect()) - return true; - } - for (EffectBase* effect : m_false_effects) { - if (effect->IsAppearanceEffect()) - return true; - } - return false; -} - -bool Conditional::IsSitrepEffect() const { - for (EffectBase* effect : m_true_effects) { - if (effect->IsSitrepEffect()) - return true; - } - for (EffectBase* effect : m_false_effects) { - if (effect->IsSitrepEffect()) - return true; - } - return false; -} - -void Conditional::SetTopLevelContent(const std::string& content_name) { - if (m_target_condition) - m_target_condition->SetTopLevelContent(content_name); - for (EffectBase* effect : m_true_effects) - if (effect) - (effect)->SetTopLevelContent(content_name); - for (EffectBase* effect : m_false_effects) - if (effect) - (effect)->SetTopLevelContent(content_name); -} - -unsigned int Conditional::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "Conditional"); - CheckSums::CheckSumCombine(retval, m_target_condition); - CheckSums::CheckSumCombine(retval, m_true_effects); - CheckSums::CheckSumCombine(retval, m_false_effects); - - TraceLogger() << "GetCheckSum(Conditional): retval: " << retval; - return retval; +bool Effect::SourcedEffectsGroup::operator<(const SourcedEffectsGroup& right) const { + return (this->source_object_id < right.source_object_id || + ((this->source_object_id == right.source_object_id) && this->effects_group < right.effects_group)); } -} // namespace Effect diff --git a/universe/Effect.h b/universe/Effect.h index fdb091b3a21..4912940957c 100644 --- a/universe/Effect.h +++ b/universe/Effect.h @@ -1,1621 +1,194 @@ #ifndef _Effect_h_ #define _Effect_h_ -#include "EffectAccounting.h" -#include "../util/Export.h" +#include "EnumsFwd.h" +#include #include -#include +#include +#include +#include #include +#include + +#include "../util/Export.h" class UniverseObject; struct ScriptingContext; namespace Condition { - struct ConditionBase; - typedef std::vector> ObjectSet; -} - -namespace ValueRef { - template - struct ValueRefBase; + struct Condition; } namespace Effect { -class EffectBase; - - -/** Contains one or more Effects, a Condition which indicates the objects in - * the scope of the Effect(s), and a Condition which indicates whether or not - * the Effect(s) will be executed on the objects in scope during the current - * turn. Since Conditions operate on sets of objects (usually all objects in - * the universe), the activation condition bears some explanation. It exists - * to allow an EffectsGroup to be activated or suppressed based on the source - * object only (the object to which the EffectsGroup is attached). It does - * this by considering the "universe" containing only the source object. If - * the source object meets the activation condition, the EffectsGroup will be - * active in the current turn. */ -class FO_COMMON_API EffectsGroup { -public: - EffectsGroup(Condition::ConditionBase* scope, Condition::ConditionBase* activation, - const std::vector& effects, const std::string& accounting_label = "", - const std::string& stacking_group = "", int priority = 0, - std::string description = "") : - m_scope(scope), - m_activation(activation), - m_stacking_group(stacking_group), - m_effects(effects), - m_accounting_label(accounting_label), - m_priority(priority), - m_description(description) - {} - virtual ~EffectsGroup(); - - //void GetTargetSet(int source_id, TargetSet& targets) const; - //void GetTargetSet(int source_id, TargetSet& targets, const TargetSet& potential_targets) const; - ///** WARNING: this GetTargetSet version will modify potential_targets. - // * in particular, it will move detected targets from potential_targets - // * to targets. Cast the second parameter to \c const \c TargetSet& in - // * order to leave potential_targets unchanged. */ - //void GetTargetSet(int source_id, TargetSet& targets, TargetSet& potential_targets) const; - - /** execute all effects in group */ - void Execute(const TargetsCauses& targets_causes, - AccountingMap* accounting_map = nullptr, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false) const; - - const std::string& StackingGroup() const { return m_stacking_group; } - Condition::ConditionBase* Scope() const { return m_scope; } - Condition::ConditionBase* Activation() const { return m_activation; } - const std::vector& EffectsList() const { return m_effects; } - const std::string& GetDescription() const; - const std::string& AccountingLabel() const { return m_accounting_label; } - int Priority() const { return m_priority; } - std::string Dump() const; - bool HasMeterEffects() const; - bool HasAppearanceEffects() const; - bool HasSitrepEffects() const; - - void SetTopLevelContent(const std::string& content_name); - - virtual unsigned int GetCheckSum() const; - -protected: - Condition::ConditionBase* m_scope; - Condition::ConditionBase* m_activation; - std::string m_stacking_group; - std::vector m_effects; - std::string m_accounting_label; - int m_priority; - std::string m_description; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Returns a single string which `Dump`s a vector of EffectsGroups. */ -FO_COMMON_API std::string Dump(const std::vector>& effects_groups); - -/** The base class for all Effects. When an Effect is executed, the source - * object (the object to which the Effect or its containing EffectGroup is - * attached) and the target object are both required. Note that this means - * that ValueRefs contained within Effects can refer to values in either the - * source or target objects. */ -class FO_COMMON_API EffectBase { -public: - virtual ~EffectBase(); - - virtual void Execute(const ScriptingContext& context) const = 0; - - virtual void Execute(const ScriptingContext& context, const TargetSet& targets) const; - - virtual void Execute(const TargetsCauses& targets_causes, - AccountingMap* accounting_map = nullptr, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false) const; - - virtual std::string Dump() const = 0; - - virtual bool IsMeterEffect() const - { return false; } - - virtual bool IsEmpireMeterEffect() const - { return false; } - - virtual bool IsAppearanceEffect() const - { return false; } - - virtual bool IsSitrepEffect() const - { return false; } - - virtual bool IsConditionalEffect() const - { return false; } - - virtual void SetTopLevelContent(const std::string& content_name) = 0; - - virtual unsigned int GetCheckSum() const; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Does nothing when executed. Useful for triggering side-effects of effect - * execution without modifying the gamestate. */ -class FO_COMMON_API NoOp : public EffectBase { -public: - NoOp(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the meter of the given kind to \a value. The max value of the meter - * is set if \a max == true; otherwise the current value of the meter is set. - * If the target of the Effect does not have the requested meter, nothing is - * done. */ -class FO_COMMON_API SetMeter : public EffectBase { -public: - - SetMeter(MeterType meter, ValueRef::ValueRefBase* value); - SetMeter(MeterType meter, ValueRef::ValueRefBase* value, const std::string& accounting_label); - - virtual ~SetMeter(); - - void Execute(const ScriptingContext& context) const override; - - void Execute(const ScriptingContext& context, const TargetSet& targets) const override; - - void Execute(const TargetsCauses& targets_causes, - AccountingMap* accounting_map = nullptr, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false) const override; - - std::string Dump() const override; - - bool IsMeterEffect() const override - { return true; } - - void SetTopLevelContent(const std::string& content_name) override; - - MeterType GetMeterType() const - { return m_meter; }; - - const std::string& AccountingLabel() const - { return m_accounting_label; } - - unsigned int GetCheckSum() const override; - -private: - MeterType m_meter; - ValueRef::ValueRefBase* m_value; - std::string m_accounting_label; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the indicated meter on all ship parts in the indicated subset. This - * has no effect on non-Ship targets. If slot_type is specified, only parts - * that can mount in the indicated slot type (internal or external) are - * affected (this is not the same at the slot type in which the part is - * actually located, as a part might be mountable in both types, and - * located in a different type than specified, and would be matched). */ -class FO_COMMON_API SetShipPartMeter : public EffectBase { -public: - /** Affects the \a meter_type meter that belongs to part(s) named \a - part_name. */ - SetShipPartMeter(MeterType meter_type, - ValueRef::ValueRefBase* part_name, - ValueRef::ValueRefBase* value); - - virtual ~SetShipPartMeter(); - - void Execute(const ScriptingContext& context) const override; - - void Execute(const ScriptingContext& context, const TargetSet& targets) const override; - - void Execute(const TargetsCauses& targets_causes, - AccountingMap* accounting_map = nullptr, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false) const override; - - std::string Dump() const override; - - bool IsMeterEffect() const override - { return true; } - - void SetTopLevelContent(const std::string& content_name) override; - - const ValueRef::ValueRefBase* GetPartName() const - { return m_part_name; } - - MeterType GetMeterType() const - { return m_meter; } - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_part_name; - MeterType m_meter; - ValueRef::ValueRefBase* m_value; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the indicated meter on the empire with the indicated id to the - * indicated value. If \a meter is not a valid meter for empires, - * does nothing. */ -class FO_COMMON_API SetEmpireMeter : public EffectBase { -public: - SetEmpireMeter(const std::string& meter, ValueRef::ValueRefBase* value); - - SetEmpireMeter(ValueRef::ValueRefBase* empire_id, const std::string& meter, - ValueRef::ValueRefBase* value); - - virtual ~SetEmpireMeter(); - - void Execute(const ScriptingContext& context) const override; - - void Execute(const TargetsCauses& targets_causes, - AccountingMap* accounting_map = nullptr, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false) const override; - - std::string Dump() const override; - - bool IsMeterEffect() const override - { return true; } - - bool IsEmpireMeterEffect() const override - { return true; } - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_empire_id; - std::string m_meter; - ValueRef::ValueRefBase* m_value; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the empire stockpile of the target's owning empire to \a value. If - * the target does not have exactly one owner, nothing is done. */ -class FO_COMMON_API SetEmpireStockpile : public EffectBase { -public: - SetEmpireStockpile(ResourceType stockpile, - ValueRef::ValueRefBase* value); - - SetEmpireStockpile(ValueRef::ValueRefBase* empire_id, - ResourceType stockpile, - ValueRef::ValueRefBase* value); - - virtual ~SetEmpireStockpile(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_empire_id; - ResourceType m_stockpile; - ValueRef::ValueRefBase* m_value; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Makes the target planet the capital of its owner's empire. If the target - * object is not a planet, does not have an owner, or has more than one owner - * the effect does nothing. */ -class FO_COMMON_API SetEmpireCapital : public EffectBase { -public: - explicit SetEmpireCapital(); - - explicit SetEmpireCapital(ValueRef::ValueRefBase* empire_id); - - virtual ~SetEmpireCapital(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_empire_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the planet type of the target to \a type. This has no effect on non-Planet targets. Note that changing the - type of a PT_ASTEROID or PT_GASGIANT planet will also change its size to SZ_TINY or SZ_HUGE, respectively. - Similarly, changing type to PT_ASTEROID or PT_GASGIANT will also cause the size to change to SZ_ASTEROID or - SZ_GASGIANT, respectively. */ -class FO_COMMON_API SetPlanetType : public EffectBase { -public: - explicit SetPlanetType(ValueRef::ValueRefBase* type); - - virtual ~SetPlanetType(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_type; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the planet size of the target to \a size. This has no effect on non- - * Planet targets. Note that changing the size of a PT_ASTEROID or PT_GASGIANT - * planet will also change its type to PT_BARREN. Similarly, changing size to - * SZ_ASTEROID or SZ_GASGIANT will also cause the type to change to PT_ASTEROID - * or PT_GASGIANT, respectively. */ -class FO_COMMON_API SetPlanetSize : public EffectBase { -public: - explicit SetPlanetSize(ValueRef::ValueRefBase* size); - - virtual ~SetPlanetSize(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_size; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the species on the target to \a species_name. This works on planets - * and ships, but has no effect on other objects. */ -class FO_COMMON_API SetSpecies : public EffectBase { -public: - explicit SetSpecies(ValueRef::ValueRefBase* species); - - virtual ~SetSpecies(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_species_name; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets empire \a empire_id as the owner of the target. This has no effect if - * \a empire_id was already the owner of the target object. */ -class FO_COMMON_API SetOwner : public EffectBase { -public: - explicit SetOwner(ValueRef::ValueRefBase* empire_id); - - virtual ~SetOwner(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_empire_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the opinion of Species \a species for empire with id \a empire_id to - * \a opinion */ -class FO_COMMON_API SetSpeciesEmpireOpinion : public EffectBase { -public: - SetSpeciesEmpireOpinion(ValueRef::ValueRefBase* species_name, - ValueRef::ValueRefBase* empire_id, - ValueRef::ValueRefBase* opinion); - - virtual ~SetSpeciesEmpireOpinion(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_species_name; - ValueRef::ValueRefBase* m_empire_id; - ValueRef::ValueRefBase* m_opinion; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the opinion of Species \a opinionated_species for other species - * \a rated_species to \a opinion */ -class FO_COMMON_API SetSpeciesSpeciesOpinion : public EffectBase { -public: - SetSpeciesSpeciesOpinion(ValueRef::ValueRefBase* opinionated_species_name, - ValueRef::ValueRefBase* rated_species_name, - ValueRef::ValueRefBase* opinion); - - virtual ~SetSpeciesSpeciesOpinion(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_opinionated_species_name; - ValueRef::ValueRefBase* m_rated_species_name; - ValueRef::ValueRefBase* m_opinion; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Creates a new Planet with specified \a type and \a size at the system with - * specified \a location_id */ -class FO_COMMON_API CreatePlanet : public EffectBase { -public: - CreatePlanet(ValueRef::ValueRefBase* type, - ValueRef::ValueRefBase* size, - ValueRef::ValueRefBase* name = nullptr, - const std::vector& effects_to_apply_after = std::vector()); - - virtual ~CreatePlanet(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_type; - ValueRef::ValueRefBase* m_size; - ValueRef::ValueRefBase* m_name; - std::vector m_effects_to_apply_after; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Creates a new Building with specified \a type on the \a target Planet. */ -class FO_COMMON_API CreateBuilding : public EffectBase { -public: - explicit CreateBuilding(ValueRef::ValueRefBase* building_type_name, - ValueRef::ValueRefBase* name = nullptr, - const std::vector& effects_to_apply_after = std::vector()); - - virtual ~CreateBuilding(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_building_type_name; - ValueRef::ValueRefBase* m_name; - std::vector m_effects_to_apply_after; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Creates a new Ship with specified \a predefined_ship_design_name design - * from those in the list of PredefinedShipDesignManager, and owned by the - * empire with the specified \a empire_id */ -class FO_COMMON_API CreateShip : public EffectBase { -public: - explicit CreateShip(ValueRef::ValueRefBase* predefined_ship_design_name, - ValueRef::ValueRefBase* empire_id = nullptr, - ValueRef::ValueRefBase* species_name = nullptr, - ValueRef::ValueRefBase* ship_name = nullptr, - const std::vector& effects_to_apply_after = std::vector()); - - explicit CreateShip(ValueRef::ValueRefBase* ship_design_id, - ValueRef::ValueRefBase* empire_id = nullptr, - ValueRef::ValueRefBase* species_name = nullptr, - ValueRef::ValueRefBase* ship_name = nullptr, - const std::vector& effects_to_apply_after = std::vector()); - - virtual ~CreateShip(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_design_name; - ValueRef::ValueRefBase* m_design_id; - ValueRef::ValueRefBase* m_empire_id; - ValueRef::ValueRefBase* m_species_name; - ValueRef::ValueRefBase* m_name; - std::vector m_effects_to_apply_after; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Creates a new Field with specified \a field_type_name FieldType - * of the specified \a size. */ -class FO_COMMON_API CreateField : public EffectBase { -public: - explicit CreateField(ValueRef::ValueRefBase* field_type_name, - ValueRef::ValueRefBase* size = nullptr, - ValueRef::ValueRefBase* name = nullptr, - const std::vector& effects_to_apply_after = std::vector()); - - CreateField(ValueRef::ValueRefBase* field_type_name, - ValueRef::ValueRefBase* x, - ValueRef::ValueRefBase* y, - ValueRef::ValueRefBase* size = nullptr, - ValueRef::ValueRefBase* name = nullptr, - const std::vector& effects_to_apply_after = std::vector()); - - virtual ~CreateField(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_field_type_name; - ValueRef::ValueRefBase* m_x; - ValueRef::ValueRefBase* m_y; - ValueRef::ValueRefBase* m_size; - ValueRef::ValueRefBase* m_name; - std::vector m_effects_to_apply_after; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Creates a new system with the specified \a colour and at the specified - * location. */ -class FO_COMMON_API CreateSystem : public EffectBase { -public: - CreateSystem(ValueRef::ValueRefBase< ::StarType>* type, - ValueRef::ValueRefBase* x, - ValueRef::ValueRefBase* y, - ValueRef::ValueRefBase* name = nullptr, - const std::vector& effects_to_apply_after = std::vector()); - - CreateSystem(ValueRef::ValueRefBase* x, - ValueRef::ValueRefBase* y, - ValueRef::ValueRefBase* name = nullptr, - const std::vector& effects_to_apply_after = std::vector()); - - virtual ~CreateSystem(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase< ::StarType>* m_type; - ValueRef::ValueRefBase* m_x; - ValueRef::ValueRefBase* m_y; - ValueRef::ValueRefBase* m_name; - std::vector m_effects_to_apply_after; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Destroys the target object. When executed on objects that contain other - * objects (such as Fleets and Planets), all contained objects are destroyed - * as well. Destroy effects delay the desctruction of their targets until - * after other all effects have executed, to ensure the source or target of - * other effects are present when they execute. */ -class FO_COMMON_API Destroy : public EffectBase { -public: - Destroy(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Adds the Special with the name \a name to the target object. */ -class FO_COMMON_API AddSpecial : public EffectBase { -public: - explicit AddSpecial(const std::string& name, float capacity = 1.0f); - - explicit AddSpecial(ValueRef::ValueRefBase* name, - ValueRef::ValueRefBase* capacity = nullptr); - - ~AddSpecial(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - const ValueRef::ValueRefBase* GetSpecialName() const - { return m_name; } - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_name; - ValueRef::ValueRefBase* m_capacity; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Removes the Special with the name \a name to the target object. This has - * no effect if no such Special was already attached to the target object. */ -class FO_COMMON_API RemoveSpecial : public EffectBase { -public: - explicit RemoveSpecial(const std::string& name); - - explicit RemoveSpecial(ValueRef::ValueRefBase* name); - - ~RemoveSpecial(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_name; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Creates starlane(s) between the target system and systems that match - * \a other_lane_endpoint_condition */ -class FO_COMMON_API AddStarlanes : public EffectBase { -public: - explicit AddStarlanes(Condition::ConditionBase* other_lane_endpoint_condition); - - virtual ~AddStarlanes(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - Condition::ConditionBase* m_other_lane_endpoint_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Removes starlane(s) between the target system and systems that match - * \a other_lane_endpoint_condition */ -class FO_COMMON_API RemoveStarlanes : public EffectBase { -public: - explicit RemoveStarlanes(Condition::ConditionBase* other_lane_endpoint_condition); - - virtual ~RemoveStarlanes(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - Condition::ConditionBase* m_other_lane_endpoint_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the star type of the target to \a type. This has no effect on - * non-System targets. */ -class FO_COMMON_API SetStarType : public EffectBase { -public: - explicit SetStarType(ValueRef::ValueRefBase* type); - - virtual ~SetStarType(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_type; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Moves an UniverseObject to a location of another UniverseObject that matches - * the condition \a location_condition. If multiple objects match the - * condition, then one is chosen. If no objects match the condition, then - * nothing is done. */ -class FO_COMMON_API MoveTo : public EffectBase { -public: - explicit MoveTo(Condition::ConditionBase* location_condition); - - virtual ~MoveTo(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - Condition::ConditionBase* m_location_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Moves an UniverseObject to a location as though it was moving in orbit of - * some object or position on the map. Sign of \a speed indicates CCW / CW - * rotation.*/ -class FO_COMMON_API MoveInOrbit : public EffectBase { -public: - MoveInOrbit(ValueRef::ValueRefBase* speed, - Condition::ConditionBase* focal_point_condition); - - MoveInOrbit(ValueRef::ValueRefBase* speed, - ValueRef::ValueRefBase* focus_x = nullptr, - ValueRef::ValueRefBase* focus_y = nullptr); - - virtual ~MoveInOrbit(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_speed; - Condition::ConditionBase* m_focal_point_condition; - ValueRef::ValueRefBase* m_focus_x; - ValueRef::ValueRefBase* m_focus_y; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Moves an UniverseObject a specified distance towards some object or - * position on the map. */ -class FO_COMMON_API MoveTowards : public EffectBase { -public: - MoveTowards(ValueRef::ValueRefBase* speed, - Condition::ConditionBase* dest_condition); - - MoveTowards(ValueRef::ValueRefBase* speed, - ValueRef::ValueRefBase* dest_x = nullptr, - ValueRef::ValueRefBase* dest_y = nullptr); - - virtual ~MoveTowards(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_speed; - Condition::ConditionBase* m_dest_condition; - ValueRef::ValueRefBase* m_dest_x; - ValueRef::ValueRefBase* m_dest_y; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets the route of the target fleet to move to an UniverseObject that - * matches the condition \a location_condition. If multiple objects match the - * condition, then one is chosen. If no objects match the condition, then - * nothing is done. */ -class FO_COMMON_API SetDestination : public EffectBase { -public: - explicit SetDestination(Condition::ConditionBase* location_condition); - - virtual ~SetDestination(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - Condition::ConditionBase* m_location_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets aggression level of the target object. */ -class FO_COMMON_API SetAggression : public EffectBase { -public: - explicit SetAggression(bool aggressive); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - bool m_aggressive; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Causes the owner empire of the target object to win the game. If the - * target object has multiple owners, nothing is done. */ -class FO_COMMON_API Victory : public EffectBase { -public: - explicit Victory(const std::string& reason_string); // TODO: Make this a ValueRefBase* - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - std::string m_reason_string; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets whether an empire has researched at tech, and how much research - * progress towards that tech has been completed. */ -class FO_COMMON_API SetEmpireTechProgress : public EffectBase { -public: - SetEmpireTechProgress(ValueRef::ValueRefBase* tech_name, - ValueRef::ValueRefBase* research_progress); - - SetEmpireTechProgress(ValueRef::ValueRefBase* tech_name, - ValueRef::ValueRefBase* research_progress, - ValueRef::ValueRefBase* empire_id); - - virtual ~SetEmpireTechProgress(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_tech_name; - ValueRef::ValueRefBase* m_research_progress; - ValueRef::ValueRefBase* m_empire_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -class FO_COMMON_API GiveEmpireTech : public EffectBase { -public: - explicit GiveEmpireTech(ValueRef::ValueRefBase* tech_name, - ValueRef::ValueRefBase* empire_id = nullptr); - - virtual ~GiveEmpireTech(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - ValueRef::ValueRefBase* m_tech_name; - ValueRef::ValueRefBase* m_empire_id; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Generates a sitrep message for the empire with id \a recipient_empire_id. - * The message text is the user string specified in \a message_string with - * string substitutions into the message text as specified in \a message_parameters - * which are substituted as string parameters %1%, %2%, %3%, etc. in the order - * they are specified. Extra parameters beyond those needed by \a message_string - * are ignored, and missing parameters are left as blank text. */ -class FO_COMMON_API GenerateSitRepMessage : public EffectBase { -public: - typedef std::vector*>> MessageParams; - - GenerateSitRepMessage(const std::string& message_string, const std::string& icon, - const MessageParams& message_parameters, - ValueRef::ValueRefBase* recipient_empire_id, - EmpireAffiliationType affiliation, - const std::string label = "", - bool stringtable_lookup = true); - - GenerateSitRepMessage(const std::string& message_string, const std::string& icon, - const MessageParams& message_parameters, - EmpireAffiliationType affiliation, - Condition::ConditionBase* condition = nullptr, - const std::string label = "", - bool stringtable_lookup = true); - - GenerateSitRepMessage(const std::string& message_string, const std::string& icon, - const MessageParams& message_parameters, - EmpireAffiliationType affiliation, - const std::string& label = "", - bool stringtable_lookup = true); - - virtual ~GenerateSitRepMessage(); - - void Execute(const ScriptingContext& context) const override; - - void Execute(const TargetsCauses& targets_causes, - AccountingMap* accounting_map = nullptr, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false) const override; - - bool IsSitrepEffect() const override - { return true; } - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - const std::string& MessageString() const - { return m_message_string; } - - const std::string& Icon() const - { return m_icon; } - - MessageParams MessageParameters() - { return m_message_parameters; } - - ValueRef::ValueRefBase* RecipientID() const - { return m_recipient_empire_id; } - - Condition::ConditionBase* GetCondition() const - { return m_condition; } - - EmpireAffiliationType Affiliation() const - { return m_affiliation; } - - unsigned int GetCheckSum() const override; - -private: - std::string m_message_string; - std::string m_icon; - MessageParams m_message_parameters; - ValueRef::ValueRefBase* m_recipient_empire_id; - Condition::ConditionBase* m_condition; - EmpireAffiliationType m_affiliation; - std::string m_label; - bool m_stringtable_lookup; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Applies an overlay texture to Systems. */ -class FO_COMMON_API SetOverlayTexture : public EffectBase { -public: - SetOverlayTexture(const std::string& texture, ValueRef::ValueRefBase* size); - - virtual ~SetOverlayTexture(); - - void Execute(const ScriptingContext& context) const override; - - void Execute(const TargetsCauses& targets_causes, - AccountingMap* accounting_map = nullptr, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false) const override; - - std::string Dump() const override; - - bool IsAppearanceEffect() const override - { return true; } - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -private: - std::string m_texture; - ValueRef::ValueRefBase* m_size; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Applies a texture to Planets. */ -class FO_COMMON_API SetTexture : public EffectBase { -public: - explicit SetTexture(const std::string& texture); - - void Execute(const ScriptingContext& context) const override; - - void Execute(const TargetsCauses& targets_causes, - AccountingMap* accounting_map = nullptr, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false) const override; - - std::string Dump() const override; - - bool IsAppearanceEffect() const override - { return true; } - - void SetTopLevelContent(const std::string& content_name) override - {} - - unsigned int GetCheckSum() const override; - -private: - std::string m_texture; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Sets visibility of an object for an empire, independent of standard - * visibility mechanics. */ -class FO_COMMON_API SetVisibility : public EffectBase { -public: - SetVisibility(Visibility vis, EmpireAffiliationType affiliation, - ValueRef::ValueRefBase* empire_id = nullptr, - Condition::ConditionBase* of_objects = nullptr); // if not specified, acts on target. if specified, acts on all matching objects - - virtual ~SetVisibility(); - - void Execute(const ScriptingContext& context) const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - Visibility GetVisibility() const - { return m_vis; } - - ValueRef::ValueRefBase* EmpireID() const - { return m_empire_id; } - - EmpireAffiliationType Affiliation() const - { return m_affiliation; } - - Condition::ConditionBase* OfObjectsCondition() const - { return m_condition; } - - unsigned int GetCheckSum() const override; - -private: - Visibility m_vis; - ValueRef::ValueRefBase* m_empire_id; - EmpireAffiliationType m_affiliation; - Condition::ConditionBase* m_condition; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Executes a set of effects if an execution-time condition is met, or an - * alterative set of effects if the condition is not met. */ -class FO_COMMON_API Conditional : public EffectBase { -public: - Conditional(Condition::ConditionBase* target_condition, - const std::vector& true_effects, - const std::vector& false_effects); - - void Execute(const ScriptingContext& context) const override; - /** Note: executes all of the true or all of the false effects on each - target, without considering any of the only_* type flags. */ - - void Execute(const ScriptingContext& context, const TargetSet& targets) const override; - - void Execute(const TargetsCauses& targets_causes, - AccountingMap* accounting_map = nullptr, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false) const override; - - std::string Dump() const override; - - bool IsMeterEffect() const override; - - bool IsAppearanceEffect() const override; - - bool IsSitrepEffect() const override; - - bool IsConditionalEffect() const override - { return true; } - - void SetTopLevelContent(const std::string& content_name) override; - - unsigned int GetCheckSum() const override; - -protected: - void Execute(const ScriptingContext& context, const TargetSet& targets, - AccountingMap* accounting_map = nullptr, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false) const; - -private: - Condition::ConditionBase* m_target_condition; // condition to apply to each target object to determine which effects to execute - std::vector m_true_effects; // effects to execute if m_target_condition matches target object - std::vector m_false_effects; // effects to execute if m_target_condition does not match target object - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - - -// template implementations -template -void EffectsGroup::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_NVP(m_scope) - & BOOST_SERIALIZATION_NVP(m_activation) - & BOOST_SERIALIZATION_NVP(m_stacking_group) - & BOOST_SERIALIZATION_NVP(m_effects) - & BOOST_SERIALIZATION_NVP(m_description); -} - -template -void EffectBase::serialize(Archive& ar, const unsigned int version) -{} - -template -void NoOp::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase); -} - -template -void SetMeter::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_meter) - & BOOST_SERIALIZATION_NVP(m_value) - & BOOST_SERIALIZATION_NVP(m_accounting_label); -} - -template -void SetShipPartMeter::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_part_name) - & BOOST_SERIALIZATION_NVP(m_meter) - & BOOST_SERIALIZATION_NVP(m_value); -} - -template -void SetEmpireMeter::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_empire_id) - & BOOST_SERIALIZATION_NVP(m_meter) - & BOOST_SERIALIZATION_NVP(m_value); -} - -template -void SetEmpireStockpile::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_empire_id) - & BOOST_SERIALIZATION_NVP(m_stockpile) - & BOOST_SERIALIZATION_NVP(m_value); -} - -template -void SetEmpireCapital::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_empire_id); -} - -template -void SetPlanetType::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_type); -} - -template -void SetPlanetSize::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_size); -} - -template -void SetSpecies::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_species_name); -} - -template -void SetOwner::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_empire_id); -} - -template -void CreatePlanet::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_type) - & BOOST_SERIALIZATION_NVP(m_size) - & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_effects_to_apply_after); -} - -template -void CreateBuilding::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_building_type_name) - & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_effects_to_apply_after); -} - -template -void CreateShip::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_design_name) - & BOOST_SERIALIZATION_NVP(m_design_id) - & BOOST_SERIALIZATION_NVP(m_empire_id) - & BOOST_SERIALIZATION_NVP(m_species_name) - & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_effects_to_apply_after); -} - -template -void CreateField::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_field_type_name) - & BOOST_SERIALIZATION_NVP(m_x) - & BOOST_SERIALIZATION_NVP(m_y) - & BOOST_SERIALIZATION_NVP(m_size) - & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_effects_to_apply_after); -} - -template -void CreateSystem::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_type) - & BOOST_SERIALIZATION_NVP(m_x) - & BOOST_SERIALIZATION_NVP(m_y) - & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_effects_to_apply_after); -} - -template -void Destroy::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase); -} - -template -void AddSpecial::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_capacity); -} - -template -void RemoveSpecial::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_name); -} - -template -void AddStarlanes::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_other_lane_endpoint_condition); -} - -template -void RemoveStarlanes::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_other_lane_endpoint_condition); -} - -template -void SetStarType::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_type); -} - -template -void MoveTo::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_location_condition); -} - -template -void MoveInOrbit::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_speed) - & BOOST_SERIALIZATION_NVP(m_focal_point_condition) - & BOOST_SERIALIZATION_NVP(m_focus_x) - & BOOST_SERIALIZATION_NVP(m_focus_y); -} - -template -void MoveTowards::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_speed) - & BOOST_SERIALIZATION_NVP(m_dest_condition) - & BOOST_SERIALIZATION_NVP(m_dest_x) - & BOOST_SERIALIZATION_NVP(m_dest_y); -} - -template -void SetDestination::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_location_condition); -} - -template -void SetAggression::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_aggressive); -} - -template -void Victory::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_reason_string); -} - -template -void SetEmpireTechProgress::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_tech_name) - & BOOST_SERIALIZATION_NVP(m_research_progress) - & BOOST_SERIALIZATION_NVP(m_empire_id); -} - -template -void GiveEmpireTech::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_tech_name) - & BOOST_SERIALIZATION_NVP(m_empire_id); -} - -template -void GenerateSitRepMessage::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_message_string) - & BOOST_SERIALIZATION_NVP(m_icon) - & BOOST_SERIALIZATION_NVP(m_message_parameters) - & BOOST_SERIALIZATION_NVP(m_recipient_empire_id) - & BOOST_SERIALIZATION_NVP(m_condition) - & BOOST_SERIALIZATION_NVP(m_affiliation) - & BOOST_SERIALIZATION_NVP(m_label) - & BOOST_SERIALIZATION_NVP(m_stringtable_lookup); -} - -template -void SetOverlayTexture::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_texture) - & BOOST_SERIALIZATION_NVP(m_size); -} - -template -void SetTexture::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_texture); -} - -template -void SetVisibility::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_vis) - & BOOST_SERIALIZATION_NVP(m_empire_id) - & BOOST_SERIALIZATION_NVP(m_affiliation) - & BOOST_SERIALIZATION_NVP(m_condition); -} - -template -void Conditional::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(EffectBase) - & BOOST_SERIALIZATION_NVP(m_target_condition) - & BOOST_SERIALIZATION_NVP(m_true_effects) - & BOOST_SERIALIZATION_NVP(m_false_effects); -} -} // namespace Effect - -#endif // _Effect_h_ + struct AccountingInfo; + class EffectsGroup; + + typedef std::vector> TargetSet; + /** Effect accounting information for all meters of all objects that are + * acted on by effects. */ + typedef std::unordered_map>> AccountingMap; + + /** Description of cause of an effect: the general cause type, and the + * specific cause. eg. Building and a particular BuildingType. */ + struct FO_COMMON_API EffectCause { + EffectCause(); + EffectCause(EffectsCauseType cause_type_, const std::string& specific_cause_, + const std::string& custom_label_ = ""); + EffectsCauseType cause_type; ///< general type of effect cause, eg. tech, building, special... + std::string specific_cause; ///< name of specific cause, eg. "Wonder Farm", "Antenna Mk. VI" + std::string custom_label; ///< script-specified accounting label for this effect cause + }; + + /** Combination of targets and cause for an effects group. */ + struct TargetsAndCause { + TargetsAndCause(); + TargetsAndCause(const TargetSet& target_set_, const EffectCause& effect_cause_); + TargetSet target_set; + EffectCause effect_cause; + }; + + /** Combination of an EffectsGroup and the id of a source object. */ + struct SourcedEffectsGroup { + SourcedEffectsGroup(); + SourcedEffectsGroup(int source_object_id_, const EffectsGroup* effects_group_); + bool operator<(const SourcedEffectsGroup& right) const; + int source_object_id; + const EffectsGroup* effects_group; + }; + + /** Map from (effects group and source object) to target set of for + * that effects group with that source object. A multimap is used + * so that a single source object can have multiple instances of the + * same effectsgroup. This is useful when a Ship has multiple copies + * of the same effects group due to having multiple copies of the same + * ship part in its design. */ + typedef std::vector> SourcesEffectsTargetsAndCausesVec; + + /** The base class for all Effects. When an Effect is executed, the source + * object (the object to which the Effect or its containing EffectGroup is + * attached) and the target object are both required. Note that this means + * that ValueRefs contained within Effects can refer to values in either the + * source or target objects. */ + class FO_COMMON_API Effect { + public: + virtual ~Effect(); + + virtual void Execute(ScriptingContext& context) const = 0; + + virtual void Execute(ScriptingContext& context, const TargetSet& targets) const; + + virtual void Execute(ScriptingContext& context, + const TargetSet& targets, + AccountingMap* accounting_map, + const EffectCause& effect_cause, + bool only_meter_effects = false, + bool only_appearance_effects = false, + bool include_empire_meter_effects = false, + bool only_generate_sitrep_effects = false) const; + + virtual std::string Dump(unsigned short ntabs = 0) const = 0; + + virtual bool IsMeterEffect() const { return false; } + virtual bool IsEmpireMeterEffect() const { return false; } + virtual bool IsAppearanceEffect() const { return false; } + virtual bool IsSitrepEffect() const { return false; } + virtual bool IsConditionalEffect() const { return false; } + + // TODO: source-invariant? + + virtual void SetTopLevelContent(const std::string& content_name) = 0; + virtual unsigned int GetCheckSum() const; + + private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); + }; + + /** Accounting information about what the causes are and changes produced + * by effects groups acting on meters of objects. */ + struct FO_COMMON_API AccountingInfo : public EffectCause { + AccountingInfo(); + AccountingInfo(int source_id_, EffectsCauseType cause_type_, float meter_change_, + float running_meter_total_, const std::string& specific_cause_ = "", + const std::string& custom_label_ = ""); + + bool operator==(const AccountingInfo& rhs) const; + + int source_id; ///< source object of effect + float meter_change; ///< net change on meter due to this effect, as best known by client's empire + float running_meter_total;///< meter total as of this effect. + }; + + /** Contains one or more Effects, a Condition which indicates the objects in + * the scope of the Effect(s), and a Condition which indicates whether or not + * the Effect(s) will be executed on the objects in scope during the current + * turn. Since Conditions operate on sets of objects (usually all objects in + * the universe), the activation condition bears some explanation. It exists + * to allow an EffectsGroup to be activated or suppressed based on the source + * object only (the object to which the EffectsGroup is attached). It does + * this by considering the "universe" containing only the source object. If + * the source object meets the activation condition, the EffectsGroup will be + * active in the current turn. */ + class FO_COMMON_API EffectsGroup { + public: + EffectsGroup(std::unique_ptr&& scope, + std::unique_ptr&& activation, + std::vector>&& effects, + const std::string& accounting_label = "", + const std::string& stacking_group = "", + int priority = 0, + const std::string& description = "", + const std::string& content_name = ""); + virtual ~EffectsGroup(); + + /** execute all effects in group */ + void Execute(ScriptingContext& source_context, + const TargetsAndCause& targets_cause, + AccountingMap* accounting_map = nullptr, + bool only_meter_effects = false, + bool only_appearance_effects = false, + bool include_empire_meter_effects = false, + bool only_generate_sitrep_effects = false) const; + + const std::string& StackingGroup() const { return m_stacking_group; } + Condition::Condition* Scope() const { return m_scope.get(); } + Condition::Condition* Activation() const { return m_activation.get(); } + const std::vector EffectsList() const; + const std::string& GetDescription() const; + const std::string& AccountingLabel() const { return m_accounting_label; } + int Priority() const { return m_priority; } + std::string Dump(unsigned short ntabs = 0) const; + bool HasMeterEffects() const; + bool HasAppearanceEffects() const; + bool HasSitrepEffects() const; + + void SetTopLevelContent(const std::string& content_name); + const std::string& TopLevelContent() const { return m_content_name; } + + virtual unsigned int GetCheckSum() const; + + protected: + std::unique_ptr m_scope; + std::unique_ptr m_activation; + std::string m_stacking_group; + std::vector> m_effects; + std::string m_accounting_label; + int m_priority; // constructor sets this, so don't need a default value here + std::string m_description; + std::string m_content_name; + + private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); + }; + + /** Returns a single string which `Dump`s a vector of EffectsGroups. */ + FO_COMMON_API std::string Dump(const std::vector>& effects_groups); +} + +#endif diff --git a/universe/EffectAccounting.cpp b/universe/EffectAccounting.cpp deleted file mode 100644 index c60139f2470..00000000000 --- a/universe/EffectAccounting.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "EffectAccounting.h" - -#include "UniverseObject.h" -#include "ObjectMap.h" -#include "Enums.h" - -Effect::EffectCause::EffectCause() : - cause_type(INVALID_EFFECTS_GROUP_CAUSE_TYPE), - specific_cause(), - custom_label() -{} - -Effect::EffectCause::EffectCause(EffectsCauseType cause_type_, const std::string& specific_cause_, - const std::string& custom_label_) : - cause_type(cause_type_), - specific_cause(specific_cause_), - custom_label(custom_label_) -{ - //DebugLogger() << "EffectCause(" << cause_type << ", " << specific_cause << ", " << custom_label << ")"; -} - -Effect::AccountingInfo::AccountingInfo() : - EffectCause(), - source_id(INVALID_OBJECT_ID), - meter_change(0.0), - running_meter_total(0.0) -{} - -Effect::TargetsAndCause::TargetsAndCause() : - target_set(), - effect_cause() -{} - -Effect::TargetsAndCause::TargetsAndCause(const TargetSet& target_set_, const EffectCause& effect_cause_) : - target_set(target_set_), - effect_cause(effect_cause_) -{} - -Effect::SourcedEffectsGroup::SourcedEffectsGroup() : - source_object_id(INVALID_OBJECT_ID) -{} - -Effect::SourcedEffectsGroup::SourcedEffectsGroup(int source_object_id_, const std::shared_ptr& effects_group_) : - source_object_id(source_object_id_), - effects_group(effects_group_) -{} - -bool Effect::SourcedEffectsGroup::operator<(const SourcedEffectsGroup& right) const { - return (this->source_object_id < right.source_object_id || - ((this->source_object_id == right.source_object_id) && this->effects_group < right.effects_group)); -} - diff --git a/universe/EffectAccounting.h b/universe/EffectAccounting.h deleted file mode 100644 index 94a9fd2ec08..00000000000 --- a/universe/EffectAccounting.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef _Effect_Accounting_h_ -#define _Effect_Accounting_h_ - - -#include "EnumsFwd.h" - -#include -#include -#include -#include - - -class UniverseObject; - -namespace Effect { - class EffectsGroup; - typedef std::vector> TargetSet; - - /** Description of cause of an effect: the general cause type, and the - * specific cause. eg. Building and a particular BuildingType. */ - struct EffectCause { - EffectCause(); - EffectCause(EffectsCauseType cause_type_, const std::string& specific_cause_, - const std::string& custom_label_ = ""); - EffectsCauseType cause_type; ///< general type of effect cause, eg. tech, building, special... - std::string specific_cause; ///< name of specific cause, eg. "Wonder Farm", "Antenna Mk. VI" - std::string custom_label; ///< script-specified accounting label for this effect cause - }; - - /** Accounting information about what the causes are and changes produced - * by effects groups acting on meters of objects. */ - struct AccountingInfo : public EffectCause { - AccountingInfo(); - - int source_id; ///< source object of effect - float meter_change; ///< net change on meter due to this effect, as best known by client's empire - float running_meter_total;///< meter total as of this effect. - }; - - /** Effect accounting information for all meters of all objects that are - * acted on by effects. */ - typedef std::map>> AccountingMap; - - /** Combination of targets and cause for an effects group. */ - struct TargetsAndCause { - TargetsAndCause(); - TargetsAndCause(const TargetSet& target_set_, const EffectCause& effect_cause_); - TargetSet target_set; - EffectCause effect_cause; - }; - - /** Combination of an EffectsGroup and the id of a source object. */ - struct SourcedEffectsGroup { - SourcedEffectsGroup(); - SourcedEffectsGroup(int source_object_id_, const std::shared_ptr& effects_group_); - bool operator<(const SourcedEffectsGroup& right) const; - void Execute(const TargetSet& targets) const; - void Execute(const TargetsAndCause& targets_and_cause, AccountingMap& accounting_map) const; - int source_object_id; - std::shared_ptr effects_group; - }; - - /** Discrepancy between meter's value at start of turn, and the value that - * this client calculate that the meter should have with the knowledge - * available -> the unknown factor affecting the meter. This is used - * when generating effect accounting, in the case where the expected - * and actual meter values don't match. */ - typedef std::map> DiscrepancyMap; - - /** Map from (effects group and source object) to target set of for - * that effects group with that source object. A multimap is used - * so that a single source object can have multiple instances of the - * same effectsgroup. This is useful when a Ship has multiple copies - * of the same effects group due to having multiple copies of the same - * ship part in its design. */ - typedef std::vector> TargetsCauses; -} - -#endif diff --git a/universe/Effects.cpp b/universe/Effects.cpp new file mode 100644 index 00000000000..3a1acc862c3 --- /dev/null +++ b/universe/Effects.cpp @@ -0,0 +1,3835 @@ +#include "Effects.h" + +#include "../util/Logger.h" +#include "../util/OptionsDB.h" +#include "../util/Random.h" +#include "../util/Directories.h" +#include "../util/i18n.h" +#include "../util/SitRepEntry.h" +#include "../Empire/EmpireManager.h" +#include "../Empire/Empire.h" +#include "ValueRefs.h" +#include "Condition.h" +#include "Pathfinder.h" +#include "Universe.h" +#include "UniverseObject.h" +#include "Building.h" +#include "BuildingType.h" +#include "Planet.h" +#include "System.h" +#include "Field.h" +#include "FieldType.h" +#include "Fleet.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Tech.h" +#include "Species.h" +#include "Enums.h" + +#include + +#include +#include + +namespace { + DeclareThreadSafeLogger(effects); +} + +using boost::io::str; + +FO_COMMON_API extern const int INVALID_DESIGN_ID; + +namespace { + /** creates a new fleet at a specified \a x and \a y location within the + * Universe, and and inserts \a ship into it. Used when a ship has been + * moved by the MoveTo effect separately from the fleet that previously + * held it. All ships need to be within fleets. */ + std::shared_ptr CreateNewFleet(double x, double y, std::shared_ptr ship) { + Universe& universe = GetUniverse(); + if (!ship) + return nullptr; + + auto fleet = universe.InsertNew("", x, y, ship->Owner()); + + fleet->Rename(fleet->GenerateFleetName()); + fleet->GetMeter(METER_STEALTH)->SetCurrent(Meter::LARGE_VALUE); + + fleet->AddShips({ship->ID()}); + ship->SetFleetID(fleet->ID()); + fleet->SetAggressive(fleet->HasArmedShips()); + + return fleet; + } + + /** Creates a new fleet at \a system and inserts \a ship into it. Used + * when a ship has been moved by the MoveTo effect separately from the + * fleet that previously held it. Also used by CreateShip effect to give + * the new ship a fleet. All ships need to be within fleets. */ + std::shared_ptr CreateNewFleet(std::shared_ptr system, std::shared_ptr ship, ObjectMap& objects) { + if (!system || !ship) + return nullptr; + + // remove ship from old fleet / system, put into new system if necessary + if (ship->SystemID() != system->ID()) { + if (auto old_system = objects.get(ship->SystemID())) { + old_system->Remove(ship->ID()); + ship->SetSystem(INVALID_OBJECT_ID); + } + system->Insert(ship); + } + + if (ship->FleetID() != INVALID_OBJECT_ID) { + if (auto old_fleet = objects.get(ship->FleetID())) { + old_fleet->RemoveShips({ship->ID()}); + } + } + + // create new fleet for ship, and put it in new system + auto fleet = CreateNewFleet(system->X(), system->Y(), ship); + system->Insert(fleet); + + return fleet; + } + + /** Explores the system with the specified \a system_id for the owner of + * the specified \a target_object. Used when moving objects into a system + * with the MoveTo effect, as otherwise the system wouldn't get explored, + * and objects being moved into unexplored systems might disappear for + * players or confuse the AI. */ + void ExploreSystem(int system_id, std::shared_ptr target_object) { + if (!target_object) + return; + if (Empire* empire = GetEmpire(target_object->Owner())) + empire->AddExploredSystem(system_id); + } + + /** Resets the previous and next systems of \a fleet and recalcultes / + * resets the fleet's move route. Used after a fleet has been moved with + * the MoveTo effect, as its previous route was assigned based on its + * previous location, and may not be valid for its new location. */ + void UpdateFleetRoute(std::shared_ptr fleet, int new_next_system, int new_previous_system, const ObjectMap& objects) { + if (!fleet) { + ErrorLogger() << "UpdateFleetRoute passed a null fleet pointer"; + return; + } + + auto next_system = objects.get(new_next_system); + if (!next_system) { + ErrorLogger() << "UpdateFleetRoute couldn't get new next system with id: " << new_next_system; + return; + } + + if (new_previous_system != INVALID_OBJECT_ID && !objects.get(new_previous_system)) { + ErrorLogger() << "UpdateFleetRoute couldn't get new previous system with id: " << new_previous_system; + } + + fleet->SetNextAndPreviousSystems(new_next_system, new_previous_system); + + + // recalculate route from the shortest path between first system on path and final destination + int start_system = fleet->SystemID(); + if (start_system == INVALID_OBJECT_ID) + start_system = new_next_system; + + int dest_system = fleet->FinalDestinationID(); + + std::pair, double> route_pair = GetPathfinder()->ShortestPath(start_system, dest_system, fleet->Owner()); + + // if shortest path is empty, the route may be impossible or trivial, so just set route to move fleet + // to the next system that it was just set to move to anyway. + if (route_pair.first.empty()) + route_pair.first.push_back(new_next_system); + + + // set fleet with newly recalculated route + try { + fleet->SetRoute(route_pair.first); + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception updating fleet route in effect code: " << e.what(); + } + } + + std::string GenerateSystemName(const ObjectMap& objects) { + static std::vector star_names = UserStringList("STAR_NAMES"); + + // pick a name for the system + for (const std::string& star_name : star_names) { + // does an existing system have this name? + bool dupe = false; + for (auto& system : objects.all()) { + if (system->Name() == star_name) { + dupe = true; + break; // another systme has this name. skip to next potential name. + } + } + if (!dupe) + return star_name; // no systems have this name yet. use it. + } + return ""; // fallback to empty name. + } +} + +namespace Effect { +/////////////////////////////////////////////////////////// +// EffectsGroup // +/////////////////////////////////////////////////////////// +EffectsGroup::EffectsGroup(std::unique_ptr&& scope, + std::unique_ptr&& activation, + std::vector>&& effects, + const std::string& accounting_label, + const std::string& stacking_group, int priority, + const std::string& description, + const std::string& content_name): + m_scope(std::move(scope)), + m_activation(std::move(activation)), + m_stacking_group(stacking_group), + m_effects(std::move(effects)), + m_accounting_label(accounting_label), + m_priority(priority), + m_description(description), + m_content_name(content_name) +{} + +EffectsGroup::~EffectsGroup() +{} + +void EffectsGroup::Execute(ScriptingContext& context, + const TargetsAndCause& targets_cause, + AccountingMap* accounting_map, + bool only_meter_effects, bool only_appearance_effects, + bool include_empire_meter_effects, + bool only_generate_sitrep_effects) const +{ + if (!context.source) // todo: move into loop and check only when an effect is source-variant + WarnLogger() << "EffectsGroup being executed without a defined source object"; + + // execute each effect of the group one by one, unless filtered by flags + for (auto& effect : m_effects) { + // skip excluded effect types + if ( (only_appearance_effects && !effect->IsAppearanceEffect()) + || (only_meter_effects && !effect->IsMeterEffect()) + || (!include_empire_meter_effects && effect->IsEmpireMeterEffect()) + || (only_generate_sitrep_effects && !effect->IsSitrepEffect())) + { continue; } + + effect->Execute(context, targets_cause.target_set, accounting_map, + targets_cause.effect_cause, + only_meter_effects, only_appearance_effects, + include_empire_meter_effects, only_generate_sitrep_effects); + } +} + +const std::vector EffectsGroup::EffectsList() const { + std::vector retval(m_effects.size()); + std::transform(m_effects.begin(), m_effects.end(), retval.begin(), + [](const std::unique_ptr& xx) {return xx.get();}); + return retval; +} + +const std::string& EffectsGroup::GetDescription() const +{ return m_description; } + +std::string EffectsGroup::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "EffectsGroup"; + if (!m_content_name.empty()) + retval += " // from " + m_content_name; + retval += "\n"; + retval += DumpIndent(ntabs+1) + "scope =\n"; + retval += m_scope->Dump(ntabs+2); + if (m_activation) { + retval += DumpIndent(ntabs+1) + "activation =\n"; + retval += m_activation->Dump(ntabs+2); + } + if (!m_stacking_group.empty()) + retval += DumpIndent(ntabs+1) + "stackinggroup = \"" + m_stacking_group + "\"\n"; + if (m_effects.size() == 1) { + retval += DumpIndent(ntabs+1) + "effects =\n"; + retval += m_effects[0]->Dump(ntabs+2); + } else { + retval += DumpIndent(ntabs+1) + "effects = [\n"; + for (auto& effect : m_effects) { + retval += effect->Dump(ntabs+2); + } + retval += DumpIndent(ntabs+1) + "]\n"; + } + return retval; +} + +bool EffectsGroup::HasMeterEffects() const { + for (auto& effect : m_effects) { + if (effect->IsMeterEffect()) + return true; + } + return false; +} + +bool EffectsGroup::HasAppearanceEffects() const { + for (auto& effect : m_effects) { + if (effect->IsAppearanceEffect()) + return true; + } + return false; +} + +bool EffectsGroup::HasSitrepEffects() const { + for (auto& effect : m_effects) { + if (effect->IsSitrepEffect()) + return true; + } + return false; +} + +void EffectsGroup::SetTopLevelContent(const std::string& content_name) { + m_content_name = content_name; + if (m_scope) + m_scope->SetTopLevelContent(content_name); + if (m_activation) + m_activation->SetTopLevelContent(content_name); + for (auto& effect : m_effects) { + effect->SetTopLevelContent(content_name); + } +} + +unsigned int EffectsGroup::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "EffectsGroup"); + CheckSums::CheckSumCombine(retval, m_scope); + CheckSums::CheckSumCombine(retval, m_activation); + CheckSums::CheckSumCombine(retval, m_stacking_group); + CheckSums::CheckSumCombine(retval, m_effects); + CheckSums::CheckSumCombine(retval, m_accounting_label); + CheckSums::CheckSumCombine(retval, m_priority); + CheckSums::CheckSumCombine(retval, m_description); + + TraceLogger() << "GetCheckSum(EffectsGroup): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// Dump function // +/////////////////////////////////////////////////////////// +std::string Dump(const std::vector>& effects_groups) { + std::stringstream retval; + + for (auto& effects_group : effects_groups) { + retval << "\n" << effects_group->Dump(); + } + + return retval.str(); +} + + +/////////////////////////////////////////////////////////// +// Effect // +/////////////////////////////////////////////////////////// +Effect::~Effect() +{} + +void Effect::Execute(ScriptingContext& context, + const TargetSet& targets, + AccountingMap* accounting_map, + const EffectCause& effect_cause, + bool only_meter_effects, + bool only_appearance_effects, + bool include_empire_meter_effects, + bool only_generate_sitrep_effects) const +{ + if ( (only_appearance_effects && !this->IsAppearanceEffect()) + || (only_meter_effects && !this->IsMeterEffect()) + || (!include_empire_meter_effects && this->IsEmpireMeterEffect()) + || (only_generate_sitrep_effects && !this->IsSitrepEffect())) + { return; } + // generic / most effects don't do anything special for accounting, so just + // use standard Execute. overrides may implement something else. + Execute(context, targets); +} + +void Effect::Execute(ScriptingContext& context, const TargetSet& targets) const { + if (targets.empty()) + return; + + // execute effects on targets + ScriptingContext local_context = context; + for (const auto& target : targets) { + local_context.effect_target = target; + Execute(local_context); + } +} + +unsigned int Effect::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "Effect"); + + TraceLogger() << "GetCheckSum(EffectsGroup): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// NoOp // +/////////////////////////////////////////////////////////// +NoOp::NoOp() +{} + +void NoOp::Execute(ScriptingContext& context) const +{} + +std::string NoOp::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "NoOp\n"; } + +unsigned int NoOp::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "NoOp"); + + TraceLogger() << "GetCheckSum(NoOp): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetMeter // +/////////////////////////////////////////////////////////// +SetMeter::SetMeter(MeterType meter, + std::unique_ptr>&& value, + const boost::optional& accounting_label) : + m_meter(meter), + m_value(std::move(value)), + m_accounting_label(accounting_label ? *accounting_label : std::string()) +{} + +void SetMeter::Execute(ScriptingContext& context) const { + if (!context.effect_target) return; + Meter* m = context.effect_target->GetMeter(m_meter); + if (!m) return; + + float val = m_value->Eval(ScriptingContext(context, m->Current())); + m->SetCurrent(val); +} + +void SetMeter::Execute(ScriptingContext& context, + const TargetSet& targets, + AccountingMap* accounting_map, + const EffectCause& effect_cause, + bool only_meter_effects, + bool only_appearance_effects, + bool include_empire_meter_effects, + bool only_generate_sitrep_effects) const +{ + if (only_appearance_effects || only_generate_sitrep_effects) + return; + + TraceLogger(effects) << "\n\nExecute SetMeter effect: \n" << Dump(); + TraceLogger(effects) << "SetMeter execute targets before: "; + for (const auto& target : targets) + TraceLogger(effects) << " ... " << target->Dump(1); + + if (!accounting_map) { + // without accounting, can do default batch execute + Execute(context, targets); + + } else { + // accounting info for this effect on this meter, starting with non-target-dependent info + AccountingInfo info; + info.cause_type = effect_cause.cause_type; + info.specific_cause = effect_cause.specific_cause; + info.custom_label = (m_accounting_label.empty() ? effect_cause.custom_label : m_accounting_label); + info.source_id = context.source->ID(); + + // process each target separately in order to do effect accounting for each + for (auto& target : targets) { + // get Meter for this effect and target + Meter* meter = target->GetMeter(m_meter); + if (!meter) + continue; // some objects might match target conditions, but not actually have the relevant meter. In that case, don't need to do accounting. + + // record pre-effect meter values... + + // accounting info for this effect on this meter of this target + info.running_meter_total = meter->Current(); + + // actually execute effect to modify meter + float val = m_value->Eval(ScriptingContext(context, target, meter->Current())); + meter->SetCurrent(val); + + // update for meter change and new total + info.meter_change = meter->Current() - info.running_meter_total; + info.running_meter_total = meter->Current(); + + // add accounting for this effect to end of vector + (*accounting_map)[target->ID()][m_meter].push_back(info); + } + } + + TraceLogger(effects) << "SetMeter execute targets after: "; + for (auto& target : targets) + TraceLogger(effects) << " ... " << target->Dump(); +} + +void SetMeter::Execute(ScriptingContext& context, const TargetSet& targets) const { + if (targets.empty()) + return; + + if (m_value->TargetInvariant()) { + // meter value does not depend on target, so handle with single ValueRef evaluation + float val = m_value->Eval(context); + for (auto& target : targets) { + Meter* m = target->GetMeter(m_meter); + if (!m) continue; + m->SetCurrent(val); + } + return; + + } else if (m_value->SimpleIncrement()) { + // meter value is a consistent constant increment for each target, so handle with + // deep inspection single ValueRef evaluation + auto op = dynamic_cast*>(m_value.get()); + if (!op) { + ErrorLogger() << "SetMeter::Execute couldn't cast simple increment ValueRef to an Operation. Reverting to standard execute."; + Effect::Execute(context, targets); + return; + } + // RHS should be a ConstantExpr + float increment = op->RHS()->Eval(); + if (op->GetOpType() == ValueRef::PLUS) { + // do nothing to modify increment + } else if (op->GetOpType() == ValueRef::MINUS) { + increment = -increment; + } else { + ErrorLogger() << "SetMeter::Execute got invalid increment optype (not PLUS or MINUS). Reverting to standard execute."; + Effect::Execute(context, targets); + return; + } + //DebugLogger() << "simple increment: " << increment; + // increment all target meters... + for (auto& target : targets) { + Meter* m = target->GetMeter(m_meter); + if (!m) continue; + m->AddToCurrent(increment); + } + return; + } + + // meter value depends on target non-trivially, so handle with default case of per-target ValueRef evaluation + Effect::Execute(context, targets); +} + +std::string SetMeter::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Set"; + switch (m_meter) { + case METER_TARGET_POPULATION: retval += "TargetPopulation"; break; + case METER_TARGET_INDUSTRY: retval += "TargetIndustry"; break; + case METER_TARGET_RESEARCH: retval += "TargetResearch"; break; + case METER_TARGET_TRADE: retval += "TargetTrade"; break; + case METER_TARGET_CONSTRUCTION: retval += "TargetConstruction"; break; + case METER_TARGET_HAPPINESS: retval += "TargetHappiness"; break; + + case METER_MAX_CAPACITY: retval += "MaxCapacity"; break; + + case METER_MAX_FUEL: retval += "MaxFuel"; break; + case METER_MAX_SHIELD: retval += "MaxShield"; break; + case METER_MAX_STRUCTURE: retval += "MaxStructure"; break; + case METER_MAX_DEFENSE: retval += "MaxDefense"; break; + case METER_MAX_SUPPLY: retval += "MaxSupply"; break; + case METER_MAX_STOCKPILE: retval += "MaxStockpile"; break; + case METER_MAX_TROOPS: retval += "MaxTroops"; break; + + case METER_POPULATION: retval += "Population"; break; + case METER_INDUSTRY: retval += "Industry"; break; + case METER_RESEARCH: retval += "Research"; break; + case METER_TRADE: retval += "Trade"; break; + case METER_CONSTRUCTION: retval += "Construction"; break; + case METER_HAPPINESS: retval += "Happiness"; break; + + case METER_CAPACITY: retval += "Capacity"; break; + + case METER_FUEL: retval += "Fuel"; break; + case METER_SHIELD: retval += "Shield"; break; + case METER_STRUCTURE: retval += "Structure"; break; + case METER_DEFENSE: retval += "Defense"; break; + case METER_SUPPLY: retval += "Supply"; break; + case METER_STOCKPILE: retval += "Stockpile"; break; + case METER_TROOPS: retval += "Troops"; break; + + case METER_REBEL_TROOPS: retval += "RebelTroops"; break; + case METER_SIZE: retval += "Size"; break; + case METER_STEALTH: retval += "Stealth"; break; + case METER_DETECTION: retval += "Detection"; break; + case METER_SPEED: retval += "Speed"; break; + + default: retval += "?"; break; + } + retval += " value = " + m_value->Dump(ntabs) + "\n"; + return retval; +} + +void SetMeter::SetTopLevelContent(const std::string& content_name) { + if (m_value) + m_value->SetTopLevelContent(content_name); +} + +unsigned int SetMeter::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetMeter"); + CheckSums::CheckSumCombine(retval, m_meter); + CheckSums::CheckSumCombine(retval, m_value); + CheckSums::CheckSumCombine(retval, m_accounting_label); + + TraceLogger() << "GetCheckSum(SetMeter): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetShipPartMeter // +/////////////////////////////////////////////////////////// +SetShipPartMeter::SetShipPartMeter(MeterType meter, + std::unique_ptr>&& part_name, + std::unique_ptr>&& value) : + m_part_name(std::move(part_name)), + m_meter(meter), + m_value(std::move(value)) +{} + +void SetShipPartMeter::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + DebugLogger() << "SetShipPartMeter::Execute passed null target pointer"; + return; + } + + if (!m_part_name || !m_value) { + ErrorLogger() << "SetShipPartMeter::Execute missing part name or value ValueRefs"; + return; + } + + auto ship = std::dynamic_pointer_cast(context.effect_target); + if (!ship) { + ErrorLogger() << "SetShipPartMeter::Execute acting on non-ship target:"; + //context.effect_target->Dump(); + return; + } + + std::string part_name = m_part_name->Eval(context); + + // get meter, evaluate new value, assign + Meter* meter = ship->GetPartMeter(m_meter, part_name); + if (!meter) + return; + + double val = m_value->Eval(ScriptingContext(context, meter->Current())); + meter->SetCurrent(val); +} + +void SetShipPartMeter::Execute(ScriptingContext& context, + const TargetSet& targets, + AccountingMap* accounting_map, + const EffectCause& effect_cause, + bool only_meter_effects, + bool only_appearance_effects, + bool include_empire_meter_effects, + bool only_generate_sitrep_effects) const +{ + if (only_appearance_effects || only_generate_sitrep_effects) + return; + + TraceLogger(effects) << "\n\nExecute SetShipPartMeter effect: \n" << Dump(); + TraceLogger(effects) << "SetShipPartMeter execute targets before: "; + for (auto& target : targets) + TraceLogger(effects) << " ... " << target->Dump(1); + + Execute(context, targets); + + TraceLogger(effects) << "SetShipPartMeter execute targets after: "; + for (auto& target : targets) + TraceLogger(effects) << " ... " << target->Dump(1); +} + +void SetShipPartMeter::Execute(ScriptingContext& context, const TargetSet& targets) const { + if (targets.empty()) + return; + if (!m_part_name || !m_value) { + ErrorLogger() << "SetShipPartMeter::Execute missing part name or value ValueRefs"; + return; + } + + // TODO: Handle efficiently the case where the part name varies from target + // to target, but the value is target-invariant + if (!m_part_name->TargetInvariant()) { + DebugLogger() << "SetShipPartMeter::Execute has target-variant part name, which it is not (yet) coded to handle efficiently!"; + Effect::Execute(context, targets); + return; + } + + // part name doesn't depend on target, so handle with single ValueRef evaluation + std::string part_name = m_part_name->Eval(context); + + if (m_value->TargetInvariant()) { + // meter value does not depend on target, so handle with single ValueRef evaluation + float val = m_value->Eval(context); + for (auto& target : targets) { + if (target->ObjectType() != OBJ_SHIP) + continue; + auto ship = std::dynamic_pointer_cast(target); + if (!ship) + continue; + Meter* m = ship->GetPartMeter(m_meter, part_name); + if (m) + m->SetCurrent(val); + } + return; + + } else if (m_value->SimpleIncrement()) { + // meter value is a consistent constant increment for each target, so handle with + // deep inspection single ValueRef evaluation + auto op = dynamic_cast*>(m_value.get()); + if (!op) { + ErrorLogger() << "SetShipPartMeter::Execute couldn't cast simple increment ValueRef to an Operation. Reverting to standard execute."; + Effect::Execute(context, targets); + return; + } + // RHS should be a ConstantExpr + float increment = op->RHS()->Eval(); + if (op->GetOpType() == ValueRef::PLUS) { + // do nothing to modify increment + } else if (op->GetOpType() == ValueRef::MINUS) { + increment = -increment; + } else { + ErrorLogger() << "SetShipPartMeter::Execute got invalid increment optype (not PLUS or MINUS). Reverting to standard execute."; + Effect::Execute(context, targets); + return; + } + //DebugLogger() << "simple increment: " << increment; + // increment all target meters... + for (auto& target : targets) { + if (target->ObjectType() != OBJ_SHIP) + continue; + auto ship = std::dynamic_pointer_cast(target); + if (!ship) + continue; + Meter* m = ship->GetPartMeter(m_meter, part_name); + if (m) + m->AddToCurrent(increment); + } + return; + + } else { + //DebugLogger() << "complicated meter adjustment..."; + // meter value depends on target non-trivially, so handle with default case of per-target ValueRef evaluation + Effect::Execute(context, targets); + } +} + +std::string SetShipPartMeter::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs); + switch (m_meter) { + case METER_CAPACITY: retval += "SetCapacity"; break; + case METER_MAX_CAPACITY: retval += "SetMaxCapacity"; break; + case METER_SECONDARY_STAT: retval += "SetSecondaryStat"; break; + case METER_MAX_SECONDARY_STAT: retval += "SetMaxSecondaryStat";break; + default: retval += "Set???"; break; + } + + if (m_part_name) + retval += " partname = " + m_part_name->Dump(ntabs); + + retval += " value = " + m_value->Dump(ntabs); + + return retval; +} + +void SetShipPartMeter::SetTopLevelContent(const std::string& content_name) { + if (m_value) + m_value->SetTopLevelContent(content_name); + if (m_part_name) + m_part_name->SetTopLevelContent(content_name); +} + +unsigned int SetShipPartMeter::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetShipPartMeter"); + CheckSums::CheckSumCombine(retval, m_part_name); + CheckSums::CheckSumCombine(retval, m_meter); + CheckSums::CheckSumCombine(retval, m_value); + + TraceLogger() << "GetCheckSum(SetShipPartMeter): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetEmpireMeter // +/////////////////////////////////////////////////////////// +SetEmpireMeter::SetEmpireMeter(const std::string& meter, std::unique_ptr>&& value) : + m_empire_id(std::make_unique>(ValueRef::EFFECT_TARGET_REFERENCE, std::vector(1, "Owner"))), + m_meter(meter), + m_value(std::move(value)) +{} + +SetEmpireMeter::SetEmpireMeter(std::unique_ptr>&& empire_id, const std::string& meter, + std::unique_ptr>&& value) : + m_empire_id(std::move(empire_id)), + m_meter(meter), + m_value(std::move(value)) +{} + +void SetEmpireMeter::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + DebugLogger() << "SetEmpireMeter::Execute passed null target pointer"; + return; + } + if (!m_empire_id || !m_value || m_meter.empty()) { + ErrorLogger() << "SetEmpireMeter::Execute missing empire id or value ValueRefs, or given empty meter name"; + return; + } + + int empire_id = m_empire_id->Eval(context); + Empire* empire = GetEmpire(empire_id); + if (!empire) { + DebugLogger() << "SetEmpireMeter::Execute unable to find empire with id " << empire_id; + return; + } + + Meter* meter = empire->GetMeter(m_meter); + if (!meter) { + DebugLogger() << "SetEmpireMeter::Execute empire " << empire->Name() << " doesn't have a meter named " << m_meter; + return; + } + + double&& value = m_value->Eval(ScriptingContext(context, meter->Current())); + + meter->SetCurrent(value); +} + +void SetEmpireMeter::Execute(ScriptingContext& context, + const TargetSet& targets, + AccountingMap* accounting_map, + const EffectCause& effect_cause, + bool only_meter_effects, + bool only_appearance_effects, + bool include_empire_meter_effects, + bool only_generate_sitrep_effects) const +{ + if (!include_empire_meter_effects || + only_appearance_effects || + only_generate_sitrep_effects) + { return; } + // presently no accounting done for empire meters. + // TODO: maybe implement empire meter effect accounting? + Execute(context, targets); +} + +void SetEmpireMeter::Execute(ScriptingContext& context, const TargetSet& targets) const { + if (targets.empty()) + return; + if (!m_empire_id || m_meter.empty() || !m_value) { + ErrorLogger() << "SetEmpireMeter::Execute missing empire id or value ValueRefs or meter name"; + return; + } + + // TODO: efficiently handle target invariant empire id and value + Effect::Execute(context, targets); + //return; + + //if (m_empire_id->TargetInvariant() && m_value->TargetInvariant()) { + //} + + ////DebugLogger() << "complicated meter adjustment..."; + //// meter value depends on target non-trivially, so handle with default case of per-target ValueRef evaluation + //Effect::Execute(context, targets); +} + +std::string SetEmpireMeter::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetEmpireMeter meter = " + m_meter + " empire = " + m_empire_id->Dump(ntabs) + " value = " + m_value->Dump(ntabs); } + +void SetEmpireMeter::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); + if (m_value) + m_value->SetTopLevelContent(content_name); +} + +unsigned int SetEmpireMeter::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetEmpireMeter"); + CheckSums::CheckSumCombine(retval, m_empire_id); + CheckSums::CheckSumCombine(retval, m_meter); + CheckSums::CheckSumCombine(retval, m_value); + + TraceLogger() << "GetCheckSum(SetEmpireMeter): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetEmpireStockpile // +/////////////////////////////////////////////////////////// +SetEmpireStockpile::SetEmpireStockpile(ResourceType stockpile, + std::unique_ptr>&& value) : + m_empire_id(std::make_unique>(ValueRef::EFFECT_TARGET_REFERENCE, std::vector(1, "Owner"))), + m_stockpile(stockpile), + m_value(std::move(value)) +{} + +SetEmpireStockpile::SetEmpireStockpile(std::unique_ptr>&& empire_id, + ResourceType stockpile, + std::unique_ptr>&& value) : + m_empire_id(std::move(empire_id)), + m_stockpile(stockpile), + m_value(std::move(value)) +{} + +void SetEmpireStockpile::Execute(ScriptingContext& context) const { + int empire_id = m_empire_id->Eval(context); + + Empire* empire = GetEmpire(empire_id); + if (!empire) { + DebugLogger() << "SetEmpireStockpile::Execute couldn't find an empire with id " << empire_id; + return; + } + + double value = m_value->Eval(ScriptingContext(context, empire->ResourceStockpile(m_stockpile))); + empire->SetResourceStockpile(m_stockpile, value); +} + +std::string SetEmpireStockpile::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs); + switch (m_stockpile) { + // TODO: Support for other resource stockpiles? + case RE_INDUSTRY: retval += "SetEmpireStockpile"; break; + default: retval += "?"; break; + } + retval += " empire = " + m_empire_id->Dump(ntabs) + " value = " + m_value->Dump(ntabs) + "\n"; + return retval; +} + +void SetEmpireStockpile::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); + if (m_value) + m_value->SetTopLevelContent(content_name); +} + +unsigned int SetEmpireStockpile::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetEmpireStockpile"); + CheckSums::CheckSumCombine(retval, m_empire_id); + CheckSums::CheckSumCombine(retval, m_stockpile); + CheckSums::CheckSumCombine(retval, m_value); + + TraceLogger() << "GetCheckSum(SetEmpireStockpile): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetEmpireCapital // +/////////////////////////////////////////////////////////// +SetEmpireCapital::SetEmpireCapital() : + m_empire_id(std::make_unique>(ValueRef::EFFECT_TARGET_REFERENCE, std::vector(1, "Owner"))) +{} + +SetEmpireCapital::SetEmpireCapital(std::unique_ptr>&& empire_id) : + m_empire_id(std::move(empire_id)) +{} + +void SetEmpireCapital::Execute(ScriptingContext& context) const { + int empire_id = m_empire_id->Eval(context); + + Empire* empire = GetEmpire(empire_id); + if (!empire) + return; + + auto planet = std::dynamic_pointer_cast(context.effect_target); + if (!planet) + return; + + empire->SetCapitalID(planet->ID()); +} + +std::string SetEmpireCapital::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetEmpireCapital empire = " + m_empire_id->Dump(ntabs) + "\n"; } + +void SetEmpireCapital::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); +} + +unsigned int SetEmpireCapital::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetEmpireCapital"); + CheckSums::CheckSumCombine(retval, m_empire_id); + + TraceLogger() << "GetCheckSum(SetEmpireCapital): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetPlanetType // +/////////////////////////////////////////////////////////// +SetPlanetType::SetPlanetType(std::unique_ptr>&& type) : + m_type(std::move(type)) +{} + +void SetPlanetType::Execute(ScriptingContext& context) const { + if (auto p = std::dynamic_pointer_cast(context.effect_target)) { + PlanetType type = m_type->Eval(ScriptingContext(context, p->Type())); + p->SetType(type); + if (type == PT_ASTEROIDS) + p->SetSize(SZ_ASTEROIDS); + else if (type == PT_GASGIANT) + p->SetSize(SZ_GASGIANT); + else if (p->Size() == SZ_ASTEROIDS) + p->SetSize(SZ_TINY); + else if (p->Size() == SZ_GASGIANT) + p->SetSize(SZ_HUGE); + } +} + +std::string SetPlanetType::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetPlanetType type = " + m_type->Dump(ntabs) + "\n"; } + +void SetPlanetType::SetTopLevelContent(const std::string& content_name) { + if (m_type) + m_type->SetTopLevelContent(content_name); +} + +unsigned int SetPlanetType::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetPlanetType"); + CheckSums::CheckSumCombine(retval, m_type); + + TraceLogger() << "GetCheckSum(SetPlanetType): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetPlanetSize // +/////////////////////////////////////////////////////////// +SetPlanetSize::SetPlanetSize(std::unique_ptr>&& size) : + m_size(std::move(size)) +{} + +void SetPlanetSize::Execute(ScriptingContext& context) const { + if (auto p = std::dynamic_pointer_cast(context.effect_target)) { + PlanetSize size = m_size->Eval(ScriptingContext(context, p->Size())); + p->SetSize(size); + if (size == SZ_ASTEROIDS) + p->SetType(PT_ASTEROIDS); + else if (size == SZ_GASGIANT) + p->SetType(PT_GASGIANT); + else if (p->Type() == PT_ASTEROIDS || p->Type() == PT_GASGIANT) + p->SetType(PT_BARREN); + } +} + +std::string SetPlanetSize::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetPlanetSize size = " + m_size->Dump(ntabs) + "\n"; } + +void SetPlanetSize::SetTopLevelContent(const std::string& content_name) { + if (m_size) + m_size->SetTopLevelContent(content_name); +} + +unsigned int SetPlanetSize::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetPlanetSize"); + CheckSums::CheckSumCombine(retval, m_size); + + TraceLogger() << "GetCheckSum(SetPlanetSize): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetSpecies // +/////////////////////////////////////////////////////////// +SetSpecies::SetSpecies(std::unique_ptr>&& species) : + m_species_name(std::move(species)) +{} + +void SetSpecies::Execute(ScriptingContext& context) const { + if (auto planet = std::dynamic_pointer_cast(context.effect_target)) { + std::string species_name = m_species_name->Eval(ScriptingContext(context, planet->SpeciesName())); + planet->SetSpecies(species_name); + + // ensure non-empty and permissible focus setting for new species + std::string initial_focus = planet->Focus(); + std::vector available_foci = planet->AvailableFoci(); + + // leave current focus unchanged if available. + for (const std::string& available_focus : available_foci) { + if (available_focus == initial_focus) { + return; + } + } + + // need to set new focus + std::string new_focus; + + const Species* species = GetSpecies(species_name); + std::string preferred_focus; + if (species) + preferred_focus = species->PreferredFocus(); + + // chose preferred focus if available. otherwise use any available focus + bool preferred_available = false; + for (const std::string& available_focus : available_foci) { + if (available_focus == preferred_focus) { + preferred_available = true; + break; + } + } + + if (preferred_available) { + new_focus = std::move(preferred_focus); + } else if (!available_foci.empty()) { + new_focus = *available_foci.begin(); + } + + planet->SetFocus(new_focus); + + } else if (auto ship = std::dynamic_pointer_cast(context.effect_target)) { + std::string species_name = m_species_name->Eval(ScriptingContext(context, ship->SpeciesName())); + ship->SetSpecies(species_name); + } +} + +std::string SetSpecies::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetSpecies name = " + m_species_name->Dump(ntabs) + "\n"; } + +void SetSpecies::SetTopLevelContent(const std::string& content_name) { + if (m_species_name) + m_species_name->SetTopLevelContent(content_name); +} + +unsigned int SetSpecies::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetSpecies"); + CheckSums::CheckSumCombine(retval, m_species_name); + + TraceLogger() << "GetCheckSum(SetSpecies): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetOwner // +/////////////////////////////////////////////////////////// +SetOwner::SetOwner(std::unique_ptr>&& empire_id) : + m_empire_id(std::move(empire_id)) +{} + +void SetOwner::Execute(ScriptingContext& context) const { + if (!context.effect_target) + return; + int initial_owner = context.effect_target->Owner(); + + int empire_id = m_empire_id->Eval(ScriptingContext(context, initial_owner)); + if (initial_owner == empire_id) + return; + + context.effect_target->SetOwner(empire_id); + + if (auto ship = std::dynamic_pointer_cast(context.effect_target)) { + // assigning ownership of a ship requires updating the containing + // fleet, or splitting ship off into a new fleet at the same location + auto fleet = context.ContextObjects().get(ship->FleetID()); + if (!fleet) + return; + if (fleet->Owner() == empire_id) + return; + + // move ship into new fleet + std::shared_ptr new_fleet; + if (auto system = context.ContextObjects().get(ship->SystemID())) + new_fleet = CreateNewFleet(system, ship, context.ContextObjects()); + else + new_fleet = CreateNewFleet(ship->X(), ship->Y(), ship); + if (new_fleet) { + new_fleet->SetNextAndPreviousSystems(fleet->NextSystemID(), fleet->PreviousSystemID()); + } + + // if old fleet is empty, destroy it. Don't reassign ownership of fleet + // in case that would reval something to the recipient that shouldn't be... + if (fleet->Empty()) + GetUniverse().EffectDestroy(fleet->ID(), INVALID_OBJECT_ID); // no particular source destroyed the fleet in this case + } +} + +std::string SetOwner::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetOwner empire = " + m_empire_id->Dump(ntabs) + "\n"; } + +void SetOwner::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); +} + +unsigned int SetOwner::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetOwner"); + CheckSums::CheckSumCombine(retval, m_empire_id); + + TraceLogger() << "GetCheckSum(SetOwner): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetSpeciesEmpireOpinion // +/////////////////////////////////////////////////////////// +SetSpeciesEmpireOpinion::SetSpeciesEmpireOpinion( + std::unique_ptr>&& species_name, + std::unique_ptr>&& empire_id, + std::unique_ptr>&& opinion +) : + m_species_name(std::move(species_name)), + m_empire_id(std::move(empire_id)), + m_opinion(std::move(opinion)) +{} + +void SetSpeciesEmpireOpinion::Execute(ScriptingContext& context) const { + if (!context.effect_target) + return; + if (!m_species_name || !m_opinion || !m_empire_id) + return; + + int empire_id = m_empire_id->Eval(context); + if (empire_id == ALL_EMPIRES) + return; + + std::string species_name = m_species_name->Eval(context); + if (species_name.empty()) + return; + + double initial_opinion = GetSpeciesManager().SpeciesEmpireOpinion(species_name, empire_id); + double opinion = m_opinion->Eval(ScriptingContext(context, initial_opinion)); + + GetSpeciesManager().SetSpeciesEmpireOpinion(species_name, empire_id, opinion); +} + +std::string SetSpeciesEmpireOpinion::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetSpeciesEmpireOpinion empire = " + m_empire_id->Dump(ntabs) + "\n"; } + +void SetSpeciesEmpireOpinion::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); + if (m_species_name) + m_species_name->SetTopLevelContent(content_name); + if (m_opinion) + m_opinion->SetTopLevelContent(content_name); +} + +unsigned int SetSpeciesEmpireOpinion::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetSpeciesEmpireOpinion"); + CheckSums::CheckSumCombine(retval, m_species_name); + CheckSums::CheckSumCombine(retval, m_empire_id); + CheckSums::CheckSumCombine(retval, m_opinion); + + TraceLogger() << "GetCheckSum(SetSpeciesEmpireOpinion): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetSpeciesSpeciesOpinion // +/////////////////////////////////////////////////////////// +SetSpeciesSpeciesOpinion::SetSpeciesSpeciesOpinion( + std::unique_ptr>&& opinionated_species_name, + std::unique_ptr>&& rated_species_name, + std::unique_ptr>&& opinion +) : + m_opinionated_species_name(std::move(opinionated_species_name)), + m_rated_species_name(std::move(rated_species_name)), + m_opinion(std::move(opinion)) +{} + +void SetSpeciesSpeciesOpinion::Execute(ScriptingContext& context) const { + if (!context.effect_target) + return; + if (!m_opinionated_species_name || !m_opinion || !m_rated_species_name) + return; + + std::string opinionated_species_name = m_opinionated_species_name->Eval(context); + if (opinionated_species_name.empty()) + return; + + std::string rated_species_name = m_rated_species_name->Eval(context); + if (rated_species_name.empty()) + return; + + float initial_opinion = GetSpeciesManager().SpeciesSpeciesOpinion(opinionated_species_name, rated_species_name); + float opinion = m_opinion->Eval(ScriptingContext(context, initial_opinion)); + + GetSpeciesManager().SetSpeciesSpeciesOpinion(opinionated_species_name, rated_species_name, opinion); +} + +std::string SetSpeciesSpeciesOpinion::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetSpeciesSpeciesOpinion" + "\n"; } + +void SetSpeciesSpeciesOpinion::SetTopLevelContent(const std::string& content_name) { + if (m_opinionated_species_name) + m_opinionated_species_name->SetTopLevelContent(content_name); + if (m_rated_species_name) + m_rated_species_name->SetTopLevelContent(content_name); + if (m_opinion) + m_opinion->SetTopLevelContent(content_name); +} + +unsigned int SetSpeciesSpeciesOpinion::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetSpeciesSpeciesOpinion"); + CheckSums::CheckSumCombine(retval, m_opinionated_species_name); + CheckSums::CheckSumCombine(retval, m_rated_species_name); + CheckSums::CheckSumCombine(retval, m_opinion); + + TraceLogger() << "GetCheckSum(SetSpeciesSpeciesOpinion): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// CreatePlanet // +/////////////////////////////////////////////////////////// +CreatePlanet::CreatePlanet(std::unique_ptr>&& type, + std::unique_ptr>&& size, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after) : + m_type(std::move(type)), + m_size(std::move(size)), + m_name(std::move(name)), + m_effects_to_apply_after(std::move(effects_to_apply_after)) +{} + +void CreatePlanet::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "CreatePlanet::Execute passed no target object"; + return; + } + auto system = context.ContextObjects().get(context.effect_target->SystemID()); + if (!system) { + ErrorLogger() << "CreatePlanet::Execute couldn't get a System object at which to create the planet"; + return; + } + + PlanetSize target_size = INVALID_PLANET_SIZE; + PlanetType target_type = INVALID_PLANET_TYPE; + if (auto location_planet = std::dynamic_pointer_cast(context.effect_target)) { + target_size = location_planet->Size(); + target_type = location_planet->Type(); + } + + PlanetSize size = m_size->Eval(ScriptingContext(context, target_size)); + PlanetType type = m_type->Eval(ScriptingContext(context, target_type)); + if (size == INVALID_PLANET_SIZE || type == INVALID_PLANET_TYPE) { + ErrorLogger() << "CreatePlanet::Execute got invalid size or type of planet to create..."; + return; + } + + // determine if and which orbits are available + std::set free_orbits = system->FreeOrbits(); + if (free_orbits.empty()) { + ErrorLogger() << "CreatePlanet::Execute couldn't find any free orbits in system where planet was to be created"; + return; + } + + auto planet = GetUniverse().InsertNew(type, size); + if (!planet) { + ErrorLogger() << "CreatePlanet::Execute unable to create new Planet object"; + return; + } + + system->Insert(planet); // let system chose an orbit for planet + + std::string name_str; + if (m_name) { + name_str = m_name->Eval(context); + if (m_name->ConstantExpr() && UserStringExists(name_str)) + name_str = UserString(name_str); + } else { + name_str = str(FlexibleFormat(UserString("NEW_PLANET_NAME")) % system->Name() % planet->CardinalSuffix()); + } + planet->Rename(name_str); + + // apply after-creation effects + ScriptingContext local_context = context; + local_context.effect_target = planet; + for (auto& effect : m_effects_to_apply_after) { + if (!effect) + continue; + effect->Execute(local_context); + } +} + +std::string CreatePlanet::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "CreatePlanet"; + if (m_size) + retval += " size = " + m_size->Dump(ntabs); + if (m_type) + retval += " type = " + m_type->Dump(ntabs); + if (m_name) + retval += " name = " + m_name->Dump(ntabs); + return retval + "\n"; +} + +void CreatePlanet::SetTopLevelContent(const std::string& content_name) { + if (m_type) + m_type->SetTopLevelContent(content_name); + if (m_size) + m_size->SetTopLevelContent(content_name); + if (m_name) + m_name->SetTopLevelContent(content_name); + for (auto& effect : m_effects_to_apply_after) { + if (!effect) + continue; + effect->SetTopLevelContent(content_name); + } +} + +unsigned int CreatePlanet::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "CreatePlanet"); + CheckSums::CheckSumCombine(retval, m_type); + CheckSums::CheckSumCombine(retval, m_size); + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_effects_to_apply_after); + + TraceLogger() << "GetCheckSum(CreatePlanet): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// CreateBuilding // +/////////////////////////////////////////////////////////// +CreateBuilding::CreateBuilding(std::unique_ptr>&& building_type_name, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after) : + m_building_type_name(std::move(building_type_name)), + m_name(std::move(name)), + m_effects_to_apply_after(std::move(effects_to_apply_after)) +{} + +void CreateBuilding::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "CreateBuilding::Execute passed no target object"; + return; + } + auto location = std::dynamic_pointer_cast(context.effect_target); + if (!location) + if (auto location_building = std::dynamic_pointer_cast(context.effect_target)) + location = context.ContextObjects().get(location_building->PlanetID()); + if (!location) { + ErrorLogger() << "CreateBuilding::Execute couldn't get a Planet object at which to create the building"; + return; + } + + if (!m_building_type_name) { + ErrorLogger() << "CreateBuilding::Execute has no building type specified!"; + return; + } + + std::string building_type_name = m_building_type_name->Eval(context); + const BuildingType* building_type = GetBuildingType(building_type_name); + if (!building_type) { + ErrorLogger() << "CreateBuilding::Execute couldn't get building type: " << building_type_name; + return; + } + + auto building = GetUniverse().InsertNew(ALL_EMPIRES, building_type_name, ALL_EMPIRES); + if (!building) { + ErrorLogger() << "CreateBuilding::Execute couldn't create building!"; + return; + } + + location->AddBuilding(building->ID()); + building->SetPlanetID(location->ID()); + + building->SetOwner(location->Owner()); + + auto system = context.ContextObjects().get(location->SystemID()); + if (system) + system->Insert(building); + + if (m_name) { + std::string name_str = m_name->Eval(context); + if (m_name->ConstantExpr() && UserStringExists(name_str)) + name_str = UserString(name_str); + building->Rename(name_str); + } + + // apply after-creation effects + ScriptingContext local_context = context; + local_context.effect_target = building; + for (auto& effect : m_effects_to_apply_after) { + if (!effect) + continue; + effect->Execute(local_context); + } +} + +std::string CreateBuilding::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "CreateBuilding"; + if (m_building_type_name) + retval += " type = " + m_building_type_name->Dump(ntabs); + if (m_name) + retval += " name = " + m_name->Dump(ntabs); + return retval + "\n"; +} + +void CreateBuilding::SetTopLevelContent(const std::string& content_name) { + if (m_building_type_name) + m_building_type_name->SetTopLevelContent(content_name); + if (m_name) + m_name->SetTopLevelContent(content_name); + for (auto& effect : m_effects_to_apply_after) { + if (!effect) + continue; + effect->SetTopLevelContent(content_name); + } +} + +unsigned int CreateBuilding::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "CreateBuilding"); + CheckSums::CheckSumCombine(retval, m_building_type_name); + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_effects_to_apply_after); + + TraceLogger() << "GetCheckSum(CreateBuilding): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// CreateShip // +/////////////////////////////////////////////////////////// +CreateShip::CreateShip(std::unique_ptr>&& predefined_ship_design_name, + std::unique_ptr>&& empire_id, + std::unique_ptr>&& species_name, + std::unique_ptr>&& ship_name, + std::vector>&& effects_to_apply_after) : + m_design_name(std::move(predefined_ship_design_name)), + m_empire_id(std::move(empire_id)), + m_species_name(std::move(species_name)), + m_name(std::move(ship_name)), + m_effects_to_apply_after(std::move(effects_to_apply_after)) +{} + +CreateShip::CreateShip(std::unique_ptr>&& ship_design_id, + std::unique_ptr>&& empire_id, + std::unique_ptr>&& species_name, + std::unique_ptr>&& ship_name, + std::vector>&& effects_to_apply_after) : + m_design_id(std::move(ship_design_id)), + m_empire_id(std::move(empire_id)), + m_species_name(std::move(species_name)), + m_name(std::move(ship_name)), + m_effects_to_apply_after(std::move(effects_to_apply_after)) +{} + +void CreateShip::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "CreateShip::Execute passed null target"; + return; + } + + auto system = context.ContextObjects().get(context.effect_target->SystemID()); + if (!system) { + ErrorLogger() << "CreateShip::Execute passed a target not in a system"; + return; + } + + int design_id = INVALID_DESIGN_ID; + if (m_design_id) { + design_id = m_design_id->Eval(context); + if (!GetShipDesign(design_id)) { + ErrorLogger() << "CreateShip::Execute couldn't get ship design with id: " << design_id; + return; + } + } else if (m_design_name) { + std::string design_name = m_design_name->Eval(context); + const ShipDesign* ship_design = GetPredefinedShipDesign(design_name); + if (!ship_design) { + ErrorLogger() << "CreateShip::Execute couldn't get predefined ship design with name " << m_design_name->Dump(); + return; + } + design_id = ship_design->ID(); + } + if (design_id == INVALID_DESIGN_ID) { + ErrorLogger() << "CreateShip::Execute got invalid ship design id: -1"; + return; + } + + int empire_id = ALL_EMPIRES; + Empire* empire = nullptr; // not const Empire* so that empire::NewShipName can be called + if (m_empire_id) { + empire_id = m_empire_id->Eval(context); + if (empire_id != ALL_EMPIRES) { + empire = GetEmpire(empire_id); + if (!empire) { + ErrorLogger() << "CreateShip::Execute couldn't get empire with id " << empire_id; + return; + } + } + } + + std::string species_name; + if (m_species_name) { + species_name = m_species_name->Eval(context); + if (!species_name.empty() && !GetSpecies(species_name)) { + ErrorLogger() << "CreateShip::Execute couldn't get species with which to create a ship"; + return; + } + } + + //// possible future modification: try to put new ship into existing fleet if + //// ownership with target object's fleet works out (if target is a ship) + //// attempt to find a + //auto fleet = std::dynamic_pointer_cast(target); + //if (!fleet) + // if (auto ship = std::dynamic_pointer_cast(target)) + // fleet = ship->FleetID(); + //// etc. + + auto ship = GetUniverse().InsertNew(empire_id, design_id, species_name, ALL_EMPIRES); + system->Insert(ship); + + if (m_name) { + std::string name_str = m_name->Eval(context); + if (m_name->ConstantExpr() && UserStringExists(name_str)) + name_str = UserString(name_str); + ship->Rename(name_str); + } else if (ship->IsMonster()) { + ship->Rename(NewMonsterName()); + } else if (empire) { + ship->Rename(empire->NewShipName()); + } else { + ship->Rename(ship->Design()->Name()); + } + + ship->ResetTargetMaxUnpairedMeters(); + ship->ResetPairedActiveMeters(); + ship->SetShipMetersToMax(); + + ship->BackPropagateMeters(); + + GetUniverse().SetEmpireKnowledgeOfShipDesign(design_id, empire_id); + + CreateNewFleet(system, ship, context.ContextObjects()); + + // apply after-creation effects + ScriptingContext local_context = context; + local_context.effect_target = ship; + for (auto& effect : m_effects_to_apply_after) { + if (!effect) + continue; + effect->Execute(local_context); + } +} + +std::string CreateShip::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "CreateShip"; + if (m_design_id) + retval += " designid = " + m_design_id->Dump(ntabs); + if (m_design_name) + retval += " designname = " + m_design_name->Dump(ntabs); + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs); + if (m_species_name) + retval += " species = " + m_species_name->Dump(ntabs); + if (m_name) + retval += " name = " + m_name->Dump(ntabs); + + retval += "\n"; + return retval; +} + +void CreateShip::SetTopLevelContent(const std::string& content_name) { + if (m_design_name) + m_design_name->SetTopLevelContent(content_name); + if (m_design_id) + m_design_id->SetTopLevelContent(content_name); + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); + if (m_species_name) + m_species_name->SetTopLevelContent(content_name); + if (m_name) + m_name->SetTopLevelContent(content_name); + for (auto& effect : m_effects_to_apply_after) { + if (!effect) + continue; + effect->SetTopLevelContent(content_name); + } +} + +unsigned int CreateShip::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "CreateShip"); + CheckSums::CheckSumCombine(retval, m_design_name); + CheckSums::CheckSumCombine(retval, m_design_id); + CheckSums::CheckSumCombine(retval, m_empire_id); + CheckSums::CheckSumCombine(retval, m_species_name); + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_effects_to_apply_after); + + TraceLogger() << "GetCheckSum(CreateShip): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// CreateField // +/////////////////////////////////////////////////////////// +CreateField::CreateField(std::unique_ptr>&& field_type_name, + std::unique_ptr>&& size, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after) : + m_field_type_name(std::move(field_type_name)), + m_size(std::move(size)), + m_name(std::move(name)), + m_effects_to_apply_after(std::move(effects_to_apply_after)) +{} + +CreateField::CreateField(std::unique_ptr>&& field_type_name, + std::unique_ptr>&& x, + std::unique_ptr>&& y, + std::unique_ptr>&& size, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after) : + m_field_type_name(std::move(field_type_name)), + m_x(std::move(x)), + m_y(std::move(y)), + m_size(std::move(size)), + m_name(std::move(name)), + m_effects_to_apply_after(std::move(effects_to_apply_after)) +{} + +void CreateField::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "CreateField::Execute passed null target"; + return; + } + auto target = context.effect_target; + + if (!m_field_type_name) + return; + + const FieldType* field_type = GetFieldType(m_field_type_name->Eval(context)); + if (!field_type) { + ErrorLogger() << "CreateField::Execute couldn't get field type with name: " << m_field_type_name->Dump(); + return; + } + + double size = 10.0; + if (m_size) + size = m_size->Eval(context); + if (size < 1.0) { + ErrorLogger() << "CreateField::Execute given very small / negative size: " << size << " ... so resetting to 1.0"; + size = 1.0; + } + if (size > 10000) { + ErrorLogger() << "CreateField::Execute given very large size: " << size << " ... so resetting to 10000"; + size = 10000; + } + + double x = 0.0; + double y = 0.0; + if (m_x) + x = m_x->Eval(context); + else + x = target->X(); + if (m_y) + y = m_y->Eval(context); + else + y = target->Y(); + + auto field = GetUniverse().InsertNew(field_type->Name(), x, y, size); + if (!field) { + ErrorLogger() << "CreateField::Execute couldn't create field!"; + return; + } + + // if target is a system, and location matches system location, can put + // field into system + auto system = std::dynamic_pointer_cast(target); + if (system && (!m_y || y == system->Y()) && (!m_x || x == system->X())) + system->Insert(field); + + std::string name_str; + if (m_name) { + name_str = m_name->Eval(context); + if (m_name->ConstantExpr() && UserStringExists(name_str)) + name_str = UserString(name_str); + } else { + name_str = UserString(field_type->Name()); + } + field->Rename(name_str); + + // apply after-creation effects + ScriptingContext local_context = context; + local_context.effect_target = field; + for (auto& effect : m_effects_to_apply_after) { + if (!effect) + continue; + effect->Execute(local_context); + } +} + +std::string CreateField::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "CreateField"; + if (m_field_type_name) + retval += " type = " + m_field_type_name->Dump(ntabs); + if (m_x) + retval += " x = " + m_x->Dump(ntabs); + if (m_y) + retval += " y = " + m_y->Dump(ntabs); + if (m_size) + retval += " size = " + m_size->Dump(ntabs); + if (m_name) + retval += " name = " + m_name->Dump(ntabs); + retval += "\n"; + return retval; +} + +void CreateField::SetTopLevelContent(const std::string& content_name) { + if (m_field_type_name) + m_field_type_name->SetTopLevelContent(content_name); + if (m_x) + m_x->SetTopLevelContent(content_name); + if (m_y) + m_y->SetTopLevelContent(content_name); + if (m_size) + m_size->SetTopLevelContent(content_name); + if (m_name) + m_name->SetTopLevelContent(content_name); + for (auto& effect : m_effects_to_apply_after) { + if (!effect) + continue; + effect->SetTopLevelContent(content_name); + } +} + +unsigned int CreateField::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "CreateField"); + CheckSums::CheckSumCombine(retval, m_field_type_name); + CheckSums::CheckSumCombine(retval, m_x); + CheckSums::CheckSumCombine(retval, m_y); + CheckSums::CheckSumCombine(retval, m_size); + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_effects_to_apply_after); + + TraceLogger() << "GetCheckSum(CreateField): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// CreateSystem // +/////////////////////////////////////////////////////////// +CreateSystem::CreateSystem(std::unique_ptr>&& type, + std::unique_ptr>&& x, + std::unique_ptr>&& y, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after) : + m_type(std::move(type)), + m_x(std::move(x)), + m_y(std::move(y)), + m_name(std::move(name)), + m_effects_to_apply_after(std::move(effects_to_apply_after)) +{ + DebugLogger() << "Effect System created 1"; +} + +CreateSystem::CreateSystem(std::unique_ptr>&& x, + std::unique_ptr>&& y, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after) : + m_x(std::move(x)), + m_y(std::move(y)), + m_name(std::move(name)), + m_effects_to_apply_after(std::move(effects_to_apply_after)) +{ + DebugLogger() << "Effect System created 2"; +} + +void CreateSystem::Execute(ScriptingContext& context) const { + // pick a star type + StarType star_type = STAR_NONE; + if (m_type) { + star_type = m_type->Eval(context); + } else { + int max_type_idx = int(NUM_STAR_TYPES) - 1; + int type_idx = RandSmallInt(0, max_type_idx); + star_type = StarType(type_idx); + } + + // pick location + double x = 0.0; + double y = 0.0; + if (m_x) + x = m_x->Eval(context); + if (m_y) + y = m_y->Eval(context); + + std::string name_str; + if (m_name) { + name_str = m_name->Eval(context); + if (m_name->ConstantExpr() && UserStringExists(name_str)) + name_str = UserString(name_str); + } else { + name_str = GenerateSystemName(context.ContextObjects()); + } + + auto system = GetUniverse().InsertNew(star_type, name_str, x, y); + if (!system) { + ErrorLogger() << "CreateSystem::Execute couldn't create system!"; + return; + } + + // apply after-creation effects + ScriptingContext local_context = context; + local_context.effect_target = system; + for (auto& effect : m_effects_to_apply_after) { + if (!effect) + continue; + effect->Execute(local_context); + } +} + +std::string CreateSystem::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "CreateSystem"; + if (m_type) + retval += " type = " + m_type->Dump(ntabs); + if (m_x) + retval += " x = " + m_x->Dump(ntabs); + if (m_y) + retval += " y = " + m_y->Dump(ntabs); + if (m_name) + retval += " name = " + m_name->Dump(ntabs); + retval += "\n"; + return retval; +} + +void CreateSystem::SetTopLevelContent(const std::string& content_name) { + if (m_x) + m_x->SetTopLevelContent(content_name); + if (m_y) + m_y->SetTopLevelContent(content_name); + if (m_type) + m_type->SetTopLevelContent(content_name); + if (m_name) + m_name->SetTopLevelContent(content_name); + for (auto& effect : m_effects_to_apply_after) { + if (!effect) + continue; + effect->SetTopLevelContent(content_name); + } +} + +unsigned int CreateSystem::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "CreateSystem"); + CheckSums::CheckSumCombine(retval, m_type); + CheckSums::CheckSumCombine(retval, m_x); + CheckSums::CheckSumCombine(retval, m_y); + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_effects_to_apply_after); + + TraceLogger() << "GetCheckSum(CreateSystem): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// Destroy // +/////////////////////////////////////////////////////////// +Destroy::Destroy() +{} + +void Destroy::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "Destroy::Execute passed no target object"; + return; + } + + int source_id = INVALID_OBJECT_ID; + if (context.source) + source_id = context.source->ID(); + + GetUniverse().EffectDestroy(context.effect_target->ID(), source_id); +} + +std::string Destroy::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "Destroy\n"; } + +unsigned int Destroy::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "Destroy"); + + TraceLogger() << "GetCheckSum(Destroy): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// AddSpecial // +/////////////////////////////////////////////////////////// +AddSpecial::AddSpecial(const std::string& name, float capacity) : + m_name(std::make_unique>(name)), + m_capacity(std::make_unique>(capacity)) +{} + +AddSpecial::AddSpecial(std::unique_ptr>&& name, + std::unique_ptr>&& capacity) : + m_name(std::move(name)), + m_capacity(std::move(capacity)) +{} + +void AddSpecial::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "AddSpecial::Execute passed no target object"; + return; + } + + std::string name = (m_name ? m_name->Eval(context) : ""); + + float initial_capacity = context.effect_target->SpecialCapacity(name); // returns 0.0f if no such special yet present + float capacity = (m_capacity ? m_capacity->Eval(ScriptingContext(context, initial_capacity)) : initial_capacity); + + context.effect_target->SetSpecialCapacity(name, capacity); +} + +std::string AddSpecial::Dump(unsigned short ntabs) const { + return DumpIndent(ntabs) + "AddSpecial name = " + (m_name ? m_name->Dump(ntabs) : "") + + " capacity = " + (m_capacity ? m_capacity->Dump(ntabs) : "0.0") + "\n"; +} + +void AddSpecial::SetTopLevelContent(const std::string& content_name) { + if (m_name) + m_name->SetTopLevelContent(content_name); + if (m_capacity) + m_capacity->SetTopLevelContent(content_name); +} + +unsigned int AddSpecial::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "AddSpecial"); + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_capacity); + + TraceLogger() << "GetCheckSum(AddSpecial): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// RemoveSpecial // +/////////////////////////////////////////////////////////// +RemoveSpecial::RemoveSpecial(const std::string& name) : + m_name(std::make_unique>(name)) +{} + +RemoveSpecial::RemoveSpecial(std::unique_ptr>&& name) : + m_name(std::move(name)) +{} + +void RemoveSpecial::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "RemoveSpecial::Execute passed no target object"; + return; + } + + std::string name = (m_name ? m_name->Eval(context) : ""); + context.effect_target->RemoveSpecial(name); +} + +std::string RemoveSpecial::Dump(unsigned short ntabs) const { + return DumpIndent(ntabs) + "RemoveSpecial name = " + (m_name ? m_name->Dump(ntabs) : "") + "\n"; +} + +void RemoveSpecial::SetTopLevelContent(const std::string& content_name) { + if (m_name) + m_name->SetTopLevelContent(content_name); +} + +unsigned int RemoveSpecial::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "RemoveSpecial"); + CheckSums::CheckSumCombine(retval, m_name); + + TraceLogger() << "GetCheckSum(RemoveSpecial): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// AddStarlanes // +/////////////////////////////////////////////////////////// +AddStarlanes::AddStarlanes(std::unique_ptr&& other_lane_endpoint_condition) : + m_other_lane_endpoint_condition(std::move(other_lane_endpoint_condition)) +{} + +void AddStarlanes::Execute(ScriptingContext& context) const { + // get target system + if (!context.effect_target) { + ErrorLogger() << "AddStarlanes::Execute passed no target object"; + return; + } + auto target_system = std::dynamic_pointer_cast(context.effect_target); + if (!target_system) + target_system = context.ContextObjects().get(context.effect_target->SystemID()); + if (!target_system) + return; // nothing to do! + + // get other endpoint systems... + Condition::ObjectSet endpoint_objects; + // apply endpoints condition to determine objects whose systems should be + // connected to the source system + m_other_lane_endpoint_condition->Eval(context, endpoint_objects); + + // early exit if there are no valid locations + if (endpoint_objects.empty()) + return; // nothing to do! + + // get systems containing at least one endpoint object + std::set> endpoint_systems; + for (auto& endpoint_object : endpoint_objects) { + auto endpoint_system = std::dynamic_pointer_cast(endpoint_object); + if (!endpoint_system) + endpoint_system = context.ContextObjects().get(endpoint_object->SystemID()); + if (!endpoint_system) + continue; + endpoint_systems.insert(std::const_pointer_cast(endpoint_system)); + } + + // add starlanes from target to endpoint systems + for (auto& endpoint_system : endpoint_systems) { + target_system->AddStarlane(endpoint_system->ID()); + endpoint_system->AddStarlane(target_system->ID()); + } +} + +std::string AddStarlanes::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "AddStarlanes endpoints = " + m_other_lane_endpoint_condition->Dump(ntabs) + "\n"; } + +void AddStarlanes::SetTopLevelContent(const std::string& content_name) { + if (m_other_lane_endpoint_condition) + m_other_lane_endpoint_condition->SetTopLevelContent(content_name); +} + +unsigned int AddStarlanes::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "AddStarlanes"); + CheckSums::CheckSumCombine(retval, m_other_lane_endpoint_condition); + + TraceLogger() << "GetCheckSum(AddStarlanes): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// RemoveStarlanes // +/////////////////////////////////////////////////////////// +RemoveStarlanes::RemoveStarlanes(std::unique_ptr&& other_lane_endpoint_condition) : + m_other_lane_endpoint_condition(std::move(other_lane_endpoint_condition)) +{} + +void RemoveStarlanes::Execute(ScriptingContext& context) const { + // get target system + if (!context.effect_target) { + ErrorLogger() << "AddStarlanes::Execute passed no target object"; + return; + } + auto target_system = std::dynamic_pointer_cast(context.effect_target); + if (!target_system) + target_system = context.ContextObjects().get(context.effect_target->SystemID()); + if (!target_system) + return; // nothing to do! + + // get other endpoint systems... + + Condition::ObjectSet endpoint_objects; + // apply endpoints condition to determine objects whose systems should be + // connected to the source system + m_other_lane_endpoint_condition->Eval(context, endpoint_objects); + + // early exit if there are no valid locations - can't move anything if there's nowhere to move to + if (endpoint_objects.empty()) + return; // nothing to do! + + // get systems containing at least one endpoint object + std::set> endpoint_systems; + for (auto& endpoint_object : endpoint_objects) { + auto endpoint_system = std::dynamic_pointer_cast(endpoint_object); + if (!endpoint_system) + endpoint_system = context.ContextObjects().get(endpoint_object->SystemID()); + if (!endpoint_system) + continue; + endpoint_systems.insert(std::const_pointer_cast(endpoint_system)); + } + + // remove starlanes from target to endpoint systems + int target_system_id = target_system->ID(); + for (auto& endpoint_system : endpoint_systems) { + target_system->RemoveStarlane(endpoint_system->ID()); + endpoint_system->RemoveStarlane(target_system_id); + } +} + +std::string RemoveStarlanes::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "RemoveStarlanes endpoints = " + m_other_lane_endpoint_condition->Dump(ntabs) + "\n"; } + +void RemoveStarlanes::SetTopLevelContent(const std::string& content_name) { + if (m_other_lane_endpoint_condition) + m_other_lane_endpoint_condition->SetTopLevelContent(content_name); +} + +unsigned int RemoveStarlanes::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "RemoveStarlanes"); + CheckSums::CheckSumCombine(retval, m_other_lane_endpoint_condition); + + TraceLogger() << "GetCheckSum(RemoveStarlanes): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetStarType // +/////////////////////////////////////////////////////////// +SetStarType::SetStarType(std::unique_ptr>&& type) : + m_type(std::move(type)) +{} + +void SetStarType::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "SetStarType::Execute given no target object"; + return; + } + if (auto s = std::dynamic_pointer_cast(context.effect_target)) + s->SetStarType(m_type->Eval(ScriptingContext(context, s->GetStarType()))); + else + ErrorLogger() << "SetStarType::Execute given a non-system target"; +} + +std::string SetStarType::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetStarType type = " + m_type->Dump(ntabs) + "\n"; } + +void SetStarType::SetTopLevelContent(const std::string& content_name) { + if (m_type) + m_type->SetTopLevelContent(content_name); +} + +unsigned int SetStarType::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetStarType"); + CheckSums::CheckSumCombine(retval, m_type); + + TraceLogger() << "GetCheckSum(SetStarType): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// MoveTo // +/////////////////////////////////////////////////////////// +MoveTo::MoveTo(std::unique_ptr&& location_condition) : + m_location_condition(std::move(location_condition)) +{} + +void MoveTo::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "MoveTo::Execute given no target object"; + return; + } + + Universe& universe = GetUniverse(); + + Condition::ObjectSet valid_locations; + // apply location condition to determine valid location to move target to + m_location_condition->Eval(context, valid_locations); + + // early exit if there are no valid locations - can't move anything if there's nowhere to move to + if (valid_locations.empty()) + return; + + // "randomly" pick a destination + auto destination = std::const_pointer_cast(*valid_locations.begin()); + + // get previous system from which to remove object if necessary + auto old_sys = context.ContextObjects().get(context.effect_target->SystemID()); + + // do the moving... + if (auto fleet = std::dynamic_pointer_cast(context.effect_target)) { + // fleets can be inserted into the system that contains the destination + // object (or the destination object itself if it is a system) + if (auto dest_system = context.ContextObjects().get(destination->SystemID())) { + if (fleet->SystemID() != dest_system->ID()) { + // remove fleet from old system, put into new system + if (old_sys) + old_sys->Remove(fleet->ID()); + dest_system->Insert(fleet); + + // also move ships of fleet + for (auto& ship : context.ContextObjects().find(fleet->ShipIDs())) { + if (old_sys) + old_sys->Remove(ship->ID()); + dest_system->Insert(ship); + } + + ExploreSystem(dest_system->ID(), fleet); + UpdateFleetRoute(fleet, INVALID_OBJECT_ID, INVALID_OBJECT_ID, context.ContextObjects()); // inserted into dest_system, so next and previous systems are invalid objects + } + + // if old and new systems are the same, and destination is that + // system, don't need to do anything + + } else { + // move fleet to new location + if (old_sys) + old_sys->Remove(fleet->ID()); + fleet->SetSystem(INVALID_OBJECT_ID); + fleet->MoveTo(destination); + + // also move ships of fleet + for (auto& ship : context.ContextObjects().find(fleet->ShipIDs())) { + if (old_sys) + old_sys->Remove(ship->ID()); + ship->SetSystem(INVALID_OBJECT_ID); + ship->MoveTo(destination); + } + + + // fleet has been moved to a location that is not a system. + // Presumably this will be located on a starlane between two other + // systems, which may or may not have been explored. Regardless, + // the fleet needs to be given a new next and previous system so it + // can move into a system, or can be ordered to a new location, and + // so that it won't try to move off of starlanes towards some other + // system from its current location (if it was heading to another + // system) and so it won't be stuck in the middle of a starlane, + // unable to move (if it wasn't previously moving) + + // if destination object is a fleet or is part of a fleet, can use + // that fleet's previous and next systems to get valid next and + // previous systems for the target fleet. + auto dest_fleet = std::dynamic_pointer_cast(destination); + if (!dest_fleet) + if (auto dest_ship = std::dynamic_pointer_cast(destination)) + dest_fleet = context.ContextObjects().get(dest_ship->FleetID()); + if (dest_fleet) { + UpdateFleetRoute(fleet, dest_fleet->NextSystemID(), dest_fleet->PreviousSystemID(), context.ContextObjects()); + + } else { + // TODO: need to do something else to get updated previous/next + // systems if the destination is a field. + ErrorLogger() << "MoveTo::Execute couldn't find a way to set the previous and next systems for the target fleet!"; + } + } + + } else if (auto ship = std::dynamic_pointer_cast(context.effect_target)) { + // TODO: make sure colonization doesn't interfere with this effect, and vice versa + + // is destination a ship/fleet ? + auto dest_fleet = std::dynamic_pointer_cast(destination); + if (!dest_fleet) { + auto dest_ship = std::dynamic_pointer_cast(destination); + if (dest_ship) + dest_fleet = context.ContextObjects().get(dest_ship->FleetID()); + } + if (dest_fleet && dest_fleet->ID() == ship->FleetID()) + return; // already in destination fleet. nothing to do. + + bool same_owners = ship->Owner() == destination->Owner(); + int dest_sys_id = destination->SystemID(); + int ship_sys_id = ship->SystemID(); + + + if (ship_sys_id != dest_sys_id) { + // ship is moving to a different system. + + // remove ship from old system + if (old_sys) { + old_sys->Remove(ship->ID()); + ship->SetSystem(INVALID_OBJECT_ID); + } + + if (auto new_sys = context.ContextObjects().get(dest_sys_id)) { + // ship is moving to a new system. insert it. + new_sys->Insert(ship); + } else { + // ship is moving to a non-system location. move it there. + ship->MoveTo(std::dynamic_pointer_cast(dest_fleet)); + } + + // may create a fleet for ship below... + } + + auto old_fleet = context.ContextObjects().get(ship->FleetID()); + + if (dest_fleet && same_owners) { + // ship is moving to a different fleet owned by the same empire, so + // can be inserted into it. + old_fleet->RemoveShips({ship->ID()}); + dest_fleet->AddShips({ship->ID()}); + ship->SetFleetID(dest_fleet->ID()); + + } else if (dest_sys_id == ship_sys_id && dest_sys_id != INVALID_OBJECT_ID) { + // ship is moving to the system it is already in, but isn't being or + // can't be moved into a specific fleet, so the ship can be left in + // its current fleet and at its current location + + } else if (destination->X() == ship->X() && destination->Y() == ship->Y()) { + // ship is moving to the same location it's already at, but isn't + // being or can't be moved to a specific fleet, so the ship can be + // left in its current fleet and at its current location + + } else { + // need to create a new fleet for ship + if (auto dest_system = context.ContextObjects().get(dest_sys_id)) { + CreateNewFleet(dest_system, ship, context.ContextObjects()); // creates new fleet, inserts fleet into system and ship into fleet + ExploreSystem(dest_system->ID(), ship); + + } else { + CreateNewFleet(destination->X(), destination->Y(), ship); // creates new fleet and inserts ship into fleet + } + } + + if (old_fleet && old_fleet->Empty()) { + old_sys->Remove(old_fleet->ID()); + universe.EffectDestroy(old_fleet->ID(), INVALID_OBJECT_ID); // no particular object destroyed this fleet + } + + } else if (auto planet = std::dynamic_pointer_cast(context.effect_target)) { + // planets need to be located in systems, so get system that contains destination object + + auto dest_system = context.ContextObjects().get(destination->SystemID()); + if (!dest_system) + return; // can't move a planet to a non-system + + if (planet->SystemID() == dest_system->ID()) + return; // planet already at destination + + if (dest_system->FreeOrbits().empty()) + return; // no room for planet at destination + + if (old_sys) + old_sys->Remove(planet->ID()); + dest_system->Insert(planet); // let system pick an orbit + + // also insert buildings of planet into system. + for (auto& building : context.ContextObjects().find(planet->BuildingIDs())) { + if (old_sys) + old_sys->Remove(building->ID()); + dest_system->Insert(building); + } + + // buildings planet should be unchanged by move, as should planet's + // records of its buildings + + ExploreSystem(dest_system->ID(), planet); + + + } else if (auto building = std::dynamic_pointer_cast(context.effect_target)) { + // buildings need to be located on planets, so if destination is a + // planet, insert building into it, or attempt to get the planet on + // which the destination object is located and insert target building + // into that + auto dest_planet = std::dynamic_pointer_cast(destination); + if (!dest_planet) { + auto dest_building = std::dynamic_pointer_cast(destination); + if (dest_building) { + dest_planet = context.ContextObjects().get(dest_building->PlanetID()); + } + } + if (!dest_planet) + return; + + if (dest_planet->ID() == building->PlanetID()) + return; // nothing to do + + auto dest_system = context.ContextObjects().get(destination->SystemID()); + if (!dest_system) + return; + + // remove building from old planet / system, add to new planet / system + if (old_sys) + old_sys->Remove(building->ID()); + building->SetSystem(INVALID_OBJECT_ID); + + if (auto old_planet = context.ContextObjects().get(building->PlanetID())) + old_planet->RemoveBuilding(building->ID()); + + dest_planet->AddBuilding(building->ID()); + building->SetPlanetID(dest_planet->ID()); + + dest_system->Insert(building); + ExploreSystem(dest_system->ID(), building); + + + } else if (auto system = std::dynamic_pointer_cast(context.effect_target)) { + if (destination->SystemID() != INVALID_OBJECT_ID) { + // TODO: merge systems + return; + } + + // move target system to new destination, and insert destination object + // and related objects into system + system->MoveTo(destination); + + if (destination->ObjectType() == OBJ_FIELD) + system->Insert(destination); + + // find fleets / ships at destination location and insert into system + for (auto& obj : context.ContextObjects().all()) { + if (obj->X() == system->X() && obj->Y() == system->Y()) + system->Insert(obj); + } + + for (auto& obj : context.ContextObjects().all()) { + if (obj->X() == system->X() && obj->Y() == system->Y()) + system->Insert(obj); + } + + + } else if (auto field = std::dynamic_pointer_cast(context.effect_target)) { + if (old_sys) + old_sys->Remove(field->ID()); + field->SetSystem(INVALID_OBJECT_ID); + field->MoveTo(destination); + if (auto dest_system = std::dynamic_pointer_cast(destination)) + dest_system->Insert(field); + } +} + +std::string MoveTo::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "MoveTo destination = " + m_location_condition->Dump(ntabs) + "\n"; } + +void MoveTo::SetTopLevelContent(const std::string& content_name) { + if (m_location_condition) + m_location_condition->SetTopLevelContent(content_name); +} + +unsigned int MoveTo::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "MoveTo"); + CheckSums::CheckSumCombine(retval, m_location_condition); + + TraceLogger() << "GetCheckSum(MoveTo): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// MoveInOrbit // +/////////////////////////////////////////////////////////// +MoveInOrbit::MoveInOrbit(std::unique_ptr>&& speed, + std::unique_ptr&& focal_point_condition) : + m_speed(std::move(speed)), + m_focal_point_condition(std::move(focal_point_condition)) +{} + +MoveInOrbit::MoveInOrbit(std::unique_ptr>&& speed, + std::unique_ptr>&& focus_x/* = 0*/, + std::unique_ptr>&& focus_y/* = 0*/) : + m_speed(std::move(speed)), + m_focus_x(std::move(focus_x)), + m_focus_y(std::move(focus_y)) +{} + +void MoveInOrbit::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "MoveInOrbit::Execute given no target object"; + return; + } + auto target = context.effect_target; + + double focus_x = 0.0, focus_y = 0.0, speed = 1.0; + if (m_focus_x) + focus_x = m_focus_x->Eval(ScriptingContext(context, target->X())); + if (m_focus_y) + focus_y = m_focus_y->Eval(ScriptingContext(context, target->Y())); + if (m_speed) + speed = m_speed->Eval(context); + if (speed == 0.0) + return; + if (m_focal_point_condition) { + Condition::ObjectSet matches; + m_focal_point_condition->Eval(context, matches); + if (matches.empty()) + return; + std::shared_ptr focus_object = *matches.begin(); + focus_x = focus_object->X(); + focus_y = focus_object->Y(); + } + + double focus_to_target_x = target->X() - focus_x; + double focus_to_target_y = target->Y() - focus_y; + double focus_to_target_radius = std::sqrt(focus_to_target_x * focus_to_target_x + + focus_to_target_y * focus_to_target_y); + if (focus_to_target_radius < 1.0) + return; // don't move objects that are too close to focus + + double angle_radians = atan2(focus_to_target_y, focus_to_target_x); + double angle_increment_radians = speed / focus_to_target_radius; + double new_angle_radians = angle_radians + angle_increment_radians; + + double new_x = focus_x + focus_to_target_radius * cos(new_angle_radians); + double new_y = focus_y + focus_to_target_radius * sin(new_angle_radians); + + if (target->X() == new_x && target->Y() == new_y) + return; + + auto old_sys = context.ContextObjects().get(target->SystemID()); + + if (auto system = std::dynamic_pointer_cast(target)) { + system->MoveTo(new_x, new_y); + return; + + } else if (auto fleet = std::dynamic_pointer_cast(target)) { + if (old_sys) + old_sys->Remove(fleet->ID()); + fleet->SetSystem(INVALID_OBJECT_ID); + fleet->MoveTo(new_x, new_y); + UpdateFleetRoute(fleet, INVALID_OBJECT_ID, INVALID_OBJECT_ID, context.ContextObjects()); + + for (auto& ship : context.ContextObjects().find(fleet->ShipIDs())) { + if (old_sys) + old_sys->Remove(ship->ID()); + ship->SetSystem(INVALID_OBJECT_ID); + ship->MoveTo(new_x, new_y); + } + return; + + } else if (auto ship = std::dynamic_pointer_cast(target)) { + if (old_sys) + old_sys->Remove(ship->ID()); + ship->SetSystem(INVALID_OBJECT_ID); + + auto old_fleet = context.ContextObjects().get(ship->FleetID()); + if (old_fleet) { + old_fleet->RemoveShips({ship->ID()}); + if (old_fleet->Empty()) { + old_sys->Remove(old_fleet->ID()); + GetUniverse().EffectDestroy(old_fleet->ID(), INVALID_OBJECT_ID); // no object in particular destroyed this fleet + } + } + + ship->SetFleetID(INVALID_OBJECT_ID); + ship->MoveTo(new_x, new_y); + + CreateNewFleet(new_x, new_y, ship); // creates new fleet and inserts ship into fleet + return; + + } else if (auto field = std::dynamic_pointer_cast(target)) { + if (old_sys) + old_sys->Remove(field->ID()); + field->SetSystem(INVALID_OBJECT_ID); + field->MoveTo(new_x, new_y); + } + // don't move planets or buildings, as these can't exist outside of systems +} + +std::string MoveInOrbit::Dump(unsigned short ntabs) const { + if (m_focal_point_condition) + return DumpIndent(ntabs) + "MoveInOrbit around = " + m_focal_point_condition->Dump(ntabs) + "\n"; + else if (m_focus_x && m_focus_y) + return DumpIndent(ntabs) + "MoveInOrbit x = " + m_focus_x->Dump(ntabs) + " y = " + m_focus_y->Dump(ntabs) + "\n"; + else + return DumpIndent(ntabs) + "MoveInOrbit"; +} + +void MoveInOrbit::SetTopLevelContent(const std::string& content_name) { + if (m_speed) + m_speed->SetTopLevelContent(content_name); + if (m_focal_point_condition) + m_focal_point_condition->SetTopLevelContent(content_name); + if (m_focus_x) + m_focus_x->SetTopLevelContent(content_name); + if (m_focus_y) + m_focus_y->SetTopLevelContent(content_name); +} + +unsigned int MoveInOrbit::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "MoveInOrbit"); + CheckSums::CheckSumCombine(retval, m_speed); + CheckSums::CheckSumCombine(retval, m_focal_point_condition); + CheckSums::CheckSumCombine(retval, m_focus_x); + CheckSums::CheckSumCombine(retval, m_focus_y); + + TraceLogger() << "GetCheckSum(MoveInOrbit): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// MoveTowards // +/////////////////////////////////////////////////////////// +MoveTowards::MoveTowards(std::unique_ptr>&& speed, + std::unique_ptr&& dest_condition) : + m_speed(std::move(speed)), + m_dest_condition(std::move(dest_condition)) +{} + +MoveTowards::MoveTowards(std::unique_ptr>&& speed, + std::unique_ptr>&& dest_x/* = 0*/, + std::unique_ptr>&& dest_y/* = 0*/) : + m_speed(std::move(speed)), + m_dest_x(std::move(dest_x)), + m_dest_y(std::move(dest_y)) +{} + +void MoveTowards::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "MoveTowards::Execute given no target object"; + return; + } + auto target = context.effect_target; + + double dest_x = 0.0, dest_y = 0.0, speed = 1.0; + if (m_dest_x) + dest_x = m_dest_x->Eval(ScriptingContext(context, target->X())); + if (m_dest_y) + dest_y = m_dest_y->Eval(ScriptingContext(context, target->Y())); + if (m_speed) + speed = m_speed->Eval(context); + if (speed == 0.0) + return; + if (m_dest_condition) { + Condition::ObjectSet matches; + m_dest_condition->Eval(context, matches); + if (matches.empty()) + return; + std::shared_ptr focus_object = *matches.begin(); + dest_x = focus_object->X(); + dest_y = focus_object->Y(); + } + + double dest_to_target_x = dest_x - target->X(); + double dest_to_target_y = dest_y - target->Y(); + double dest_to_target_dist = std::sqrt(dest_to_target_x * dest_to_target_x + + dest_to_target_y * dest_to_target_y); + double new_x, new_y; + + if (dest_to_target_dist < speed) { + new_x = dest_x; + new_y = dest_y; + } else { + // ensure no divide by zero issues + if (dest_to_target_dist < 1.0) + dest_to_target_dist = 1.0; + // avoid stalling when right on top of object and attempting to move away from it + if (dest_to_target_x == 0.0 && dest_to_target_y == 0.0) + dest_to_target_x = 1.0; + // move in direction of target + new_x = target->X() + dest_to_target_x / dest_to_target_dist * speed; + new_y = target->Y() + dest_to_target_y / dest_to_target_dist * speed; + } + if (target->X() == new_x && target->Y() == new_y) + return; // nothing to do + + if (auto system = std::dynamic_pointer_cast(target)) { + system->MoveTo(new_x, new_y); + for (auto& obj : context.ContextObjects().find(system->ObjectIDs())) { + obj->MoveTo(new_x, new_y); + } + // don't need to remove objects from system or insert into it, as all + // contained objects in system are moved with it, maintaining their + // containment situation + + } else if (auto fleet = std::dynamic_pointer_cast(target)) { + auto old_sys = context.ContextObjects().get(fleet->SystemID()); + if (old_sys) + old_sys->Remove(fleet->ID()); + fleet->SetSystem(INVALID_OBJECT_ID); + fleet->MoveTo(new_x, new_y); + for (auto& ship : context.ContextObjects().find(fleet->ShipIDs())) { + if (old_sys) + old_sys->Remove(ship->ID()); + ship->SetSystem(INVALID_OBJECT_ID); + ship->MoveTo(new_x, new_y); + } + + // todo: is fleet now close enough to fall into a system? + UpdateFleetRoute(fleet, INVALID_OBJECT_ID, INVALID_OBJECT_ID, context.ContextObjects()); + + } else if (auto ship = std::dynamic_pointer_cast(target)) { + auto old_sys = context.ContextObjects().get(ship->SystemID()); + if (old_sys) + old_sys->Remove(ship->ID()); + ship->SetSystem(INVALID_OBJECT_ID); + + auto old_fleet = context.ContextObjects().get(ship->FleetID()); + if (old_fleet) + old_fleet->RemoveShips({ship->ID()}); + ship->SetFleetID(INVALID_OBJECT_ID); + + CreateNewFleet(new_x, new_y, ship); // creates new fleet and inserts ship into fleet + if (old_fleet && old_fleet->Empty()) { + if (old_sys) + old_sys->Remove(old_fleet->ID()); + GetUniverse().EffectDestroy(old_fleet->ID(), INVALID_OBJECT_ID); // no object in particular destroyed this fleet + } + + } else if (auto field = std::dynamic_pointer_cast(target)) { + auto old_sys = context.ContextObjects().get(field->SystemID()); + if (old_sys) + old_sys->Remove(field->ID()); + field->SetSystem(INVALID_OBJECT_ID); + field->MoveTo(new_x, new_y); + + } + // don't move planets or buildings, as these can't exist outside of systems +} + +std::string MoveTowards::Dump(unsigned short ntabs) const { + if (m_dest_condition) + return DumpIndent(ntabs) + "MoveTowards destination = " + m_dest_condition->Dump(ntabs) + "\n"; + else if (m_dest_x && m_dest_y) + return DumpIndent(ntabs) + "MoveTowards x = " + m_dest_x->Dump(ntabs) + " y = " + m_dest_y->Dump(ntabs) + "\n"; + else + return DumpIndent(ntabs) + "MoveTowards"; +} + +void MoveTowards::SetTopLevelContent(const std::string& content_name) { + if (m_speed) + m_speed->SetTopLevelContent(content_name); + if (m_dest_condition) + m_dest_condition->SetTopLevelContent(content_name); + if (m_dest_x) + m_dest_x->SetTopLevelContent(content_name); + if (m_dest_y) + m_dest_y->SetTopLevelContent(content_name); +} + +unsigned int MoveTowards::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "MoveTowards"); + CheckSums::CheckSumCombine(retval, m_speed); + CheckSums::CheckSumCombine(retval, m_dest_condition); + CheckSums::CheckSumCombine(retval, m_dest_x); + CheckSums::CheckSumCombine(retval, m_dest_y); + + TraceLogger() << "GetCheckSum(MoveTowards): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetDestination // +/////////////////////////////////////////////////////////// +SetDestination::SetDestination(std::unique_ptr&& location_condition) : + m_location_condition(std::move(location_condition)) +{} + +void SetDestination::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "SetDestination::Execute given no target object"; + return; + } + + auto target_fleet = std::dynamic_pointer_cast(context.effect_target); + if (!target_fleet) { + ErrorLogger() << "SetDestination::Execute acting on non-fleet target:"; + context.effect_target->Dump(); + return; + } + + Universe& universe = GetUniverse(); + + Condition::ObjectSet valid_locations; + // apply location condition to determine valid location to move target to + m_location_condition->Eval(context, valid_locations); + + // early exit if there are no valid locations - can't move anything if there's nowhere to move to + if (valid_locations.empty()) + return; + + // "randomly" pick a destination + int destination_idx = RandSmallInt(0, valid_locations.size() - 1); + auto destination = std::const_pointer_cast( + *std::next(valid_locations.begin(), destination_idx)); + int destination_system_id = destination->SystemID(); + + // early exit if destination is not / in a system + if (destination_system_id == INVALID_OBJECT_ID) + return; + + int start_system_id = target_fleet->SystemID(); + if (start_system_id == INVALID_OBJECT_ID) + start_system_id = target_fleet->NextSystemID(); + // abort if no valid starting system + if (start_system_id == INVALID_OBJECT_ID) + return; + + // find shortest path for fleet's owner + std::pair, double> short_path = universe.GetPathfinder()->ShortestPath(start_system_id, destination_system_id, target_fleet->Owner()); + const std::list& route_list = short_path.first; + + // reject empty move paths (no path exists). + if (route_list.empty()) + return; + + // check destination validity: disallow movement that's out of range + std::pair eta = target_fleet->ETA(target_fleet->MovePath(route_list)); + if (eta.first == Fleet::ETA_NEVER || eta.first == Fleet::ETA_OUT_OF_RANGE) + return; + + try { + target_fleet->SetRoute(route_list); + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception in Effect::SetDestination setting fleet route: " << e.what(); + } +} + +std::string SetDestination::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetDestination destination = " + m_location_condition->Dump(ntabs) + "\n"; } + +void SetDestination::SetTopLevelContent(const std::string& content_name) { + if (m_location_condition) + m_location_condition->SetTopLevelContent(content_name); +} + +unsigned int SetDestination::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetDestination"); + CheckSums::CheckSumCombine(retval, m_location_condition); + + TraceLogger() << "GetCheckSum(SetDestination): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetAggression // +/////////////////////////////////////////////////////////// +SetAggression::SetAggression(bool aggressive) : + m_aggressive(aggressive) +{} + +void SetAggression::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "SetAggression::Execute given no target object"; + return; + } + + auto target_fleet = std::dynamic_pointer_cast(context.effect_target); + if (!target_fleet) { + ErrorLogger() << "SetAggression::Execute acting on non-fleet target:"; + context.effect_target->Dump(); + return; + } + + target_fleet->SetAggressive(m_aggressive); +} + +std::string SetAggression::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + (m_aggressive ? "SetAggressive" : "SetPassive") + "\n"; } + +unsigned int SetAggression::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetAggression"); + CheckSums::CheckSumCombine(retval, m_aggressive); + + TraceLogger() << "GetCheckSum(SetAggression): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// Victory // +/////////////////////////////////////////////////////////// +Victory::Victory(const std::string& reason_string) : + m_reason_string(reason_string) +{} + +void Victory::Execute(ScriptingContext& context) const { + if (!context.effect_target) { + ErrorLogger() << "Victory::Execute given no target object"; + return; + } + if (Empire* empire = GetEmpire(context.effect_target->Owner())) + empire->Win(m_reason_string); + else + ErrorLogger() << "Trying to grant victory to a missing empire!"; +} + +std::string Victory::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "Victory reason = \"" + m_reason_string + "\"\n"; } + +unsigned int Victory::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "Victory"); + CheckSums::CheckSumCombine(retval, m_reason_string); + + TraceLogger() << "GetCheckSum(Victory): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetEmpireTechProgress // +/////////////////////////////////////////////////////////// +SetEmpireTechProgress::SetEmpireTechProgress(std::unique_ptr>&& tech_name, + std::unique_ptr>&& research_progress, + std::unique_ptr>&& empire_id /*= nullptr*/) : + m_tech_name(std::move(tech_name)), + m_research_progress(std::move(research_progress)), + m_empire_id( + empire_id + ? std::move(empire_id) + : std::make_unique>(ValueRef::EFFECT_TARGET_REFERENCE, std::vector(1, "Owner"))) +{} + +void SetEmpireTechProgress::Execute(ScriptingContext& context) const { + if (!m_empire_id) return; + Empire* empire = GetEmpire(m_empire_id->Eval(context)); + if (!empire) return; + + if (!m_tech_name) { + ErrorLogger() << "SetEmpireTechProgress::Execute has not tech name to evaluate"; + return; + } + std::string tech_name = m_tech_name->Eval(context); + if (tech_name.empty()) + return; + + const Tech* tech = GetTech(tech_name); + if (!tech) { + ErrorLogger() << "SetEmpireTechProgress::Execute couldn't get tech with name " << tech_name; + return; + } + + float initial_progress = empire->ResearchProgress(tech_name); + double value = m_research_progress->Eval(ScriptingContext(context, initial_progress)); + empire->SetTechResearchProgress(tech_name, value); +} + +std::string SetEmpireTechProgress::Dump(unsigned short ntabs) const { + std::string retval = "SetEmpireTechProgress name = "; + if (m_tech_name) + retval += m_tech_name->Dump(ntabs); + if (m_research_progress) + retval += " progress = " + m_research_progress->Dump(ntabs); + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs) + "\n"; + return retval; +} + +void SetEmpireTechProgress::SetTopLevelContent(const std::string& content_name) { + if (m_tech_name) + m_tech_name->SetTopLevelContent(content_name); + if (m_research_progress) + m_research_progress->SetTopLevelContent(content_name); + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); +} + +unsigned int SetEmpireTechProgress::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetEmpireTechProgress"); + CheckSums::CheckSumCombine(retval, m_tech_name); + CheckSums::CheckSumCombine(retval, m_research_progress); + CheckSums::CheckSumCombine(retval, m_empire_id); + + TraceLogger() << "GetCheckSum(SetEmpireTechProgress): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// GiveEmpireTech // +/////////////////////////////////////////////////////////// +GiveEmpireTech::GiveEmpireTech(std::unique_ptr>&& tech_name, + std::unique_ptr>&& empire_id) : + m_tech_name(std::move(tech_name)), + m_empire_id(std::move(empire_id)) +{ + if (!m_empire_id) + m_empire_id.reset(new ValueRef::Variable(ValueRef::EFFECT_TARGET_REFERENCE, std::vector(1, "Owner"))); +} + +void GiveEmpireTech::Execute(ScriptingContext& context) const { + if (!m_empire_id) return; + Empire* empire = GetEmpire(m_empire_id->Eval(context)); + if (!empire) return; + + if (!m_tech_name) + return; + + std::string tech_name = m_tech_name->Eval(context); + + const Tech* tech = GetTech(tech_name); + if (!tech) { + ErrorLogger() << "GiveEmpireTech::Execute couldn't get tech with name: " << tech_name; + return; + } + + empire->AddNewlyResearchedTechToGrantAtStartOfNextTurn(tech_name); +} + +std::string GiveEmpireTech::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "GiveEmpireTech"; + + if (m_tech_name) + retval += " name = " + m_tech_name->Dump(ntabs); + + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs); + + retval += "\n"; + return retval; +} + +void GiveEmpireTech::SetTopLevelContent(const std::string& content_name) { + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); + if (m_tech_name) + m_tech_name->SetTopLevelContent(content_name); +} + +unsigned int GiveEmpireTech::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "GiveEmpireTech"); + CheckSums::CheckSumCombine(retval, m_tech_name); + CheckSums::CheckSumCombine(retval, m_empire_id); + + TraceLogger() << "GetCheckSum(GiveEmpireTech): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// GenerateSitRepMessage // +/////////////////////////////////////////////////////////// +GenerateSitRepMessage::GenerateSitRepMessage(const std::string& message_string, + const std::string& icon, + MessageParams&& message_parameters, + std::unique_ptr>&& recipient_empire_id, + EmpireAffiliationType affiliation, + const std::string label, + bool stringtable_lookup) : + m_message_string(message_string), + m_icon(icon), + m_message_parameters(std::move(message_parameters)), + m_recipient_empire_id(std::move(recipient_empire_id)), + m_affiliation(affiliation), + m_label(label), + m_stringtable_lookup(stringtable_lookup) +{} + +GenerateSitRepMessage::GenerateSitRepMessage(const std::string& message_string, + const std::string& icon, + MessageParams&& message_parameters, + EmpireAffiliationType affiliation, + std::unique_ptr&& condition, + const std::string label, + bool stringtable_lookup) : + m_message_string(message_string), + m_icon(icon), + m_message_parameters(std::move(message_parameters)), + m_condition(std::move(condition)), + m_affiliation(affiliation), + m_label(label), + m_stringtable_lookup(stringtable_lookup) +{} + +GenerateSitRepMessage::GenerateSitRepMessage(const std::string& message_string, const std::string& icon, + MessageParams&& message_parameters, + EmpireAffiliationType affiliation, + const std::string& label, + bool stringtable_lookup): + m_message_string(message_string), + m_icon(icon), + m_message_parameters(std::move(message_parameters)), + m_affiliation(affiliation), + m_label(label), + m_stringtable_lookup(stringtable_lookup) +{} + +void GenerateSitRepMessage::Execute(ScriptingContext& context) const { + int recipient_id = ALL_EMPIRES; + if (m_recipient_empire_id) + recipient_id = m_recipient_empire_id->Eval(context); + + // track any ship designs used in message, which any recipients must be + // made aware of so sitrep won't have errors + std::set ship_design_ids_to_inform_receipits_of; + + // TODO: should any referenced object IDs being made known at basic visibility? + + + // evaluate all parameter valuerefs so they can be substituted into sitrep template + std::vector> parameter_tag_values; + for (const auto& entry : m_message_parameters) { + parameter_tag_values.push_back({entry.first, entry.second->Eval(context)}); + + // special case for ship designs: make sure sitrep recipient knows about the design + // so the sitrep won't have errors about unknown designs being referenced + if (entry.first == VarText::PREDEFINED_DESIGN_TAG) { + if (const ShipDesign* design = GetPredefinedShipDesign(entry.second->Eval(context))) { + ship_design_ids_to_inform_receipits_of.insert(design->ID()); + } + } + } + + // whom to send to? + std::set recipient_empire_ids; + switch (m_affiliation) { + case AFFIL_SELF: { + // add just specified empire + if (recipient_id != ALL_EMPIRES) + recipient_empire_ids.insert(recipient_id); + break; + } + + case AFFIL_ALLY: { + // add allies of specified empire + for (auto& empire_id : Empires()) { + if (empire_id.first == recipient_id || recipient_id == ALL_EMPIRES) + continue; + + DiplomaticStatus status = Empires().GetDiplomaticStatus(recipient_id, empire_id.first); + if (status >= DIPLO_ALLIED) + recipient_empire_ids.insert(empire_id.first); + } + break; + } + + case AFFIL_PEACE: { + // add empires at peace with the specified empire + for (auto& empire_id : Empires()) { + if (empire_id.first == recipient_id || recipient_id == ALL_EMPIRES) + continue; + + DiplomaticStatus status = Empires().GetDiplomaticStatus(recipient_id, empire_id.first); + if (status == DIPLO_PEACE) + recipient_empire_ids.insert(empire_id.first); + } + break; + } + + case AFFIL_ENEMY: { + // add enemies of specified empire + for (auto& empire_id : Empires()) { + if (empire_id.first == recipient_id || recipient_id == ALL_EMPIRES) + continue; + + DiplomaticStatus status = Empires().GetDiplomaticStatus(recipient_id, empire_id.first); + if (status == DIPLO_WAR) + recipient_empire_ids.insert(empire_id.first); + } + break; + } + + case AFFIL_CAN_SEE: { + // evaluate condition + Condition::ObjectSet condition_matches; + if (m_condition) + m_condition->Eval(context, condition_matches); + + // add empires that can see any condition-matching object + for (auto& empire_entry : Empires()) { + int empire_id = empire_entry.first; + for (auto& object : condition_matches) { + if (object->GetVisibility(empire_id) >= VIS_BASIC_VISIBILITY) { + recipient_empire_ids.insert(empire_id); + break; + } + } + } + break; + } + + case AFFIL_NONE: + // add no empires + break; + + case AFFIL_HUMAN: + // todo: implement this separately, though not high priority since it + // probably doesn't matter if AIs get an extra sitrep message meant for + // human eyes + case AFFIL_ANY: + default: { + // add all empires + for (auto& empire_entry : Empires()) + recipient_empire_ids.insert(empire_entry.first); + break; + } + } + + int sitrep_turn = CurrentTurn() + 1; + + // send to recipient empires + for (int empire_id : recipient_empire_ids) { + Empire* empire = GetEmpire(empire_id); + if (!empire) + continue; + empire->AddSitRepEntry(CreateSitRep(m_message_string, sitrep_turn, m_icon, + parameter_tag_values, m_label, m_stringtable_lookup)); + + // also inform of any ship designs recipients should know about + for (int design_id : ship_design_ids_to_inform_receipits_of) { + GetUniverse().SetEmpireKnowledgeOfShipDesign(design_id, empire_id); + } + } +} + +std::string GenerateSitRepMessage::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs); + retval += "GenerateSitRepMessage\n"; + retval += DumpIndent(ntabs+1) + "message = \"" + m_message_string + "\"" + " icon = " + m_icon + "\n"; + + if (m_message_parameters.size() == 1) { + retval += DumpIndent(ntabs+1) + "parameters = tag = " + m_message_parameters[0].first + " data = " + m_message_parameters[0].second->Dump(ntabs+1) + "\n"; + } else if (!m_message_parameters.empty()) { + retval += DumpIndent(ntabs+1) + "parameters = [ "; + for (const auto& entry : m_message_parameters) { + retval += " tag = " + entry.first + + " data = " + entry.second->Dump(ntabs+1) + + " "; + } + retval += "]\n"; + } + + retval += DumpIndent(ntabs+1) + "affiliation = "; + switch (m_affiliation) { + case AFFIL_SELF: retval += "TheEmpire"; break; + case AFFIL_ENEMY: retval += "EnemyOf"; break; + case AFFIL_PEACE: retval += "PeaceWith"; break; + case AFFIL_ALLY: retval += "AllyOf"; break; + case AFFIL_ANY: retval += "AnyEmpire"; break; + case AFFIL_CAN_SEE: retval += "CanSee"; break; + case AFFIL_HUMAN: retval += "Human"; break; + default: retval += "?"; break; + } + + if (m_recipient_empire_id) + retval += "\n" + DumpIndent(ntabs+1) + "empire = " + m_recipient_empire_id->Dump(ntabs+1) + "\n"; + if (m_condition) + retval += "\n" + DumpIndent(ntabs+1) + "condition = " + m_condition->Dump(ntabs+1) + "\n"; + + return retval; +} + +void GenerateSitRepMessage::SetTopLevelContent(const std::string& content_name) { + for (auto& entry : m_message_parameters) { + entry.second->SetTopLevelContent(content_name); + } + if (m_recipient_empire_id) + m_recipient_empire_id->SetTopLevelContent(content_name); + if (m_condition) + m_condition->SetTopLevelContent(content_name); +} + +unsigned int GenerateSitRepMessage::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "GenerateSitRepMessage"); + CheckSums::CheckSumCombine(retval, m_message_string); + CheckSums::CheckSumCombine(retval, m_icon); + CheckSums::CheckSumCombine(retval, m_message_parameters); + CheckSums::CheckSumCombine(retval, m_recipient_empire_id); + CheckSums::CheckSumCombine(retval, m_condition); + CheckSums::CheckSumCombine(retval, m_affiliation); + CheckSums::CheckSumCombine(retval, m_label); + CheckSums::CheckSumCombine(retval, m_stringtable_lookup); + + TraceLogger() << "GetCheckSum(GenerateSitRepMessage): retval: " << retval; + return retval; +} + +std::vector*>> +GenerateSitRepMessage::MessageParameters() const { + std::vector*>> retval(m_message_parameters.size()); + std::transform(m_message_parameters.begin(), m_message_parameters.end(), retval.begin(), + [](const std::pair>>& xx) { + return std::make_pair(xx.first, xx.second.get()); + }); + return retval; +} + +/////////////////////////////////////////////////////////// +// SetOverlayTexture // +/////////////////////////////////////////////////////////// +SetOverlayTexture::SetOverlayTexture(const std::string& texture, + std::unique_ptr>&& size) : + m_texture(texture), + m_size(std::move(size)) +{} + +SetOverlayTexture::SetOverlayTexture(const std::string& texture, ValueRef::ValueRef* size) : + m_texture(texture), + m_size(size) +{} + +void SetOverlayTexture::Execute(ScriptingContext& context) const { + if (!context.effect_target) + return; + double size = 1.0; + if (m_size) + size = m_size->Eval(context); + + if (auto system = std::dynamic_pointer_cast(context.effect_target)) + system->SetOverlayTexture(m_texture, size); +} + +std::string SetOverlayTexture::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "SetOverlayTexture texture = " + m_texture; + if (m_size) + retval += " size = " + m_size->Dump(ntabs); + retval += "\n"; + return retval; +} + +void SetOverlayTexture::SetTopLevelContent(const std::string& content_name) { + if (m_size) + m_size->SetTopLevelContent(content_name); +} + +unsigned int SetOverlayTexture::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetOverlayTexture"); + CheckSums::CheckSumCombine(retval, m_texture); + CheckSums::CheckSumCombine(retval, m_size); + + TraceLogger() << "GetCheckSum(SetOverlayTexture): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetTexture // +/////////////////////////////////////////////////////////// +SetTexture::SetTexture(const std::string& texture) : + m_texture(texture) +{} + +void SetTexture::Execute(ScriptingContext& context) const { + if (!context.effect_target) + return; + if (auto planet = std::dynamic_pointer_cast(context.effect_target)) + planet->SetSurfaceTexture(m_texture); +} + +std::string SetTexture::Dump(unsigned short ntabs) const +{ return DumpIndent(ntabs) + "SetTexture texture = " + m_texture + "\n"; } + +unsigned int SetTexture::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetTexture"); + CheckSums::CheckSumCombine(retval, m_texture); + + TraceLogger() << "GetCheckSum(SetTexture): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// SetVisibility // +/////////////////////////////////////////////////////////// +SetVisibility::SetVisibility(std::unique_ptr> vis, + EmpireAffiliationType affiliation, + std::unique_ptr>&& empire_id, + std::unique_ptr&& of_objects) : + m_vis(std::move(vis)), + m_empire_id(std::move(empire_id)), + m_affiliation(affiliation), + m_condition(std::move(of_objects)) +{} + +void SetVisibility::Execute(ScriptingContext& context) const { + if (!context.effect_target) + return; + + // Note: currently ignoring upgrade-only flag + + if (!m_vis) + return; // nothing to evaluate! + + int empire_id = ALL_EMPIRES; + if (m_empire_id) + empire_id = m_empire_id->Eval(context); + + // whom to set visbility for? + std::set empire_ids; + switch (m_affiliation) { + case AFFIL_SELF: { + // add just specified empire + if (empire_id != ALL_EMPIRES) + empire_ids.insert(empire_id); + break; + } + + case AFFIL_ALLY: { + // add allies of specified empire + for (const auto& empire_entry : Empires()) { + if (empire_entry.first == empire_id || empire_id == ALL_EMPIRES) + continue; + + DiplomaticStatus status = Empires().GetDiplomaticStatus(empire_id, empire_entry.first); + if (status >= DIPLO_ALLIED) + empire_ids.insert(empire_entry.first); + } + break; + } + + case AFFIL_PEACE: { + // add empires at peace with the specified empire + for (const auto& empire_entry : Empires()) { + if (empire_entry.first == empire_id || empire_id == ALL_EMPIRES) + continue; + + DiplomaticStatus status = Empires().GetDiplomaticStatus(empire_id, empire_entry.first); + if (status == DIPLO_PEACE) + empire_ids.insert(empire_entry.first); + } + break; + } + + case AFFIL_ENEMY: { + // add enemies of specified empire + for (const auto& empire_entry : Empires()) { + if (empire_entry.first == empire_id || empire_id == ALL_EMPIRES) + continue; + + DiplomaticStatus status = Empires().GetDiplomaticStatus(empire_id, empire_entry.first); + if (status == DIPLO_WAR) + empire_ids.insert(empire_entry.first); + } + break; + } + + case AFFIL_CAN_SEE: + // unsupported so far... + case AFFIL_HUMAN: + // unsupported so far... + case AFFIL_NONE: + // add no empires + break; + + case AFFIL_ANY: + default: { + // add all empires + for (const auto& empire_entry : Empires()) + empire_ids.insert(empire_entry.first); + break; + } + } + + // what to set visibility of? + std::set object_ids; + if (!m_condition) { + object_ids.insert(context.effect_target->ID()); + } else { + Condition::ObjectSet condition_matches; + m_condition->Eval(context, condition_matches); + for (auto& object : condition_matches) { + object_ids.insert(object->ID()); + } + } + + int source_id = INVALID_OBJECT_ID; + if (context.source) + source_id = context.source->ID(); + + for (int emp_id : empire_ids) { + if (!GetEmpire(emp_id)) + continue; + for (int obj_id : object_ids) { + // store source object id and ValueRef to evaluate to determine + // what visibility level to set at time of application + GetUniverse().SetEffectDerivedVisibility(emp_id, obj_id, source_id, m_vis.get()); + } + } +} + +std::string SetVisibility::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs); + + retval += DumpIndent(ntabs) + "SetVisibility affiliation = "; + switch (m_affiliation) { + case AFFIL_SELF: retval += "TheEmpire"; break; + case AFFIL_ENEMY: retval += "EnemyOf"; break; + case AFFIL_PEACE: retval += "PeaceWith"; break; + case AFFIL_ALLY: retval += "AllyOf"; break; + case AFFIL_ANY: retval += "AnyEmpire"; break; + case AFFIL_CAN_SEE: retval += "CanSee"; break; + case AFFIL_HUMAN: retval += "Human"; break; + default: retval += "?"; break; + } + + if (m_empire_id) + retval += " empire = " + m_empire_id->Dump(ntabs); + + if (m_vis) + retval += " visibility = " + m_vis->Dump(ntabs); + + if (m_condition) + retval += " condition = " + m_condition->Dump(ntabs); + + retval += "\n"; + return retval; +} + +void SetVisibility::SetTopLevelContent(const std::string& content_name) { + if (m_vis) + m_vis->SetTopLevelContent(content_name); + if (m_empire_id) + m_empire_id->SetTopLevelContent(content_name); + if (m_condition) + m_condition->SetTopLevelContent(content_name); +} + +unsigned int SetVisibility::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "SetVisibility"); + CheckSums::CheckSumCombine(retval, m_vis.get()); + CheckSums::CheckSumCombine(retval, m_empire_id); + CheckSums::CheckSumCombine(retval, m_affiliation); + CheckSums::CheckSumCombine(retval, m_condition); + + TraceLogger() << "GetCheckSum(SetVisibility): retval: " << retval; + return retval; +} + + +/////////////////////////////////////////////////////////// +// Conditional // +/////////////////////////////////////////////////////////// +Conditional::Conditional(std::unique_ptr&& target_condition, + std::vector>&& true_effects, + std::vector>&& false_effects) : + m_target_condition(std::move(target_condition)), + m_true_effects(std::move(true_effects)), + m_false_effects(std::move(false_effects)) +{} + +void Conditional::Execute(ScriptingContext& context) const { + if (!context.effect_target) + return; + + if (!m_target_condition || m_target_condition->Eval(context, context.effect_target)) { + for (auto& effect : m_true_effects) { + if (effect) + effect->Execute(context); + } + } else { + for (auto& effect : m_false_effects) { + if (effect) + effect->Execute(context); + } + } +} + +void Conditional::Execute(ScriptingContext& context, const TargetSet& targets) const { + if (targets.empty()) + return; + + // apply sub-condition to target set to pick which to act on with which of sub-effects + const Condition::ObjectSet& potential_target_objects = + *reinterpret_cast(&targets); + + Condition::ObjectSet matches = potential_target_objects; + Condition::ObjectSet non_matches; + if (m_target_condition) + m_target_condition->Eval(context, matches, non_matches, Condition::MATCHES); + + if (!matches.empty() && !m_true_effects.empty()) { + TargetSet& match_targets = *reinterpret_cast(&matches); + for (auto& effect : m_true_effects) { + if (effect) + effect->Execute(context, match_targets); + } + } + if (!non_matches.empty() && !m_false_effects.empty()) { + TargetSet& non_match_targets = *reinterpret_cast(&non_matches); + for (auto& effect : m_false_effects) { + if (effect) + effect->Execute(context, non_match_targets); + } + } +} + +void Conditional::Execute(ScriptingContext& context, + const TargetSet& targets, + AccountingMap* accounting_map, + const EffectCause& effect_cause, + bool only_meter_effects, + bool only_appearance_effects, + bool include_empire_meter_effects, + bool only_generate_sitrep_effects) const +{ + TraceLogger(effects) << "\n\nExecute Conditional effect: \n" << Dump(); + + // apply sub-condition to target set to pick which to act on with which of sub-effects + const Condition::ObjectSet& potential_target_objects = + *reinterpret_cast(&targets); + Condition::ObjectSet matches = potential_target_objects; + Condition::ObjectSet non_matches; + + if (m_target_condition) { + if (!m_target_condition->TargetInvariant()) + ErrorLogger() << "Conditional::Execute has a subcondition that depends on the target object. The subcondition is currently evaluated once to pick the targets, so when evaluating it, there is no defined target object. Instead use RootCandidate."; + + m_target_condition->Eval(context, matches, non_matches, Condition::MATCHES); + } + + + // execute true and false effects to target matches and non-matches respectively + if (!matches.empty() && !m_true_effects.empty()) { + TargetSet& match_targets = *reinterpret_cast(&matches); + for (const auto& effect : m_true_effects) { + effect->Execute(context, match_targets, accounting_map, + effect_cause, + only_meter_effects, only_appearance_effects, + include_empire_meter_effects, + only_generate_sitrep_effects); + } + } + if (!non_matches.empty() && !m_false_effects.empty()) { + TargetSet& non_match_targets = *reinterpret_cast(&non_matches); + for (const auto& effect : m_false_effects) { + effect->Execute(context, non_match_targets, accounting_map, + effect_cause, + only_meter_effects, only_appearance_effects, + include_empire_meter_effects, + only_generate_sitrep_effects); + } + } +} + +std::string Conditional::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "If\n"; + if (m_target_condition) { + retval += DumpIndent(ntabs+1) + "condition =\n"; + retval += m_target_condition->Dump(ntabs+2); + } + + if (m_true_effects.size() == 1) { + retval += DumpIndent(ntabs+1) + "effects =\n"; + retval += m_true_effects[0]->Dump(ntabs+2); + } else { + retval += DumpIndent(ntabs+1) + "effects = [\n"; + for (auto& effect : m_true_effects) { + retval += effect->Dump(ntabs+2); + } + retval += DumpIndent(ntabs+1) + "]\n"; + } + + if (m_false_effects.empty()) { + } else if (m_false_effects.size() == 1) { + retval += DumpIndent(ntabs+1) + "else =\n"; + retval += m_false_effects[0]->Dump(ntabs+2); + } else { + retval += DumpIndent(ntabs+1) + "else = [\n"; + for (auto& effect : m_false_effects) { + retval += effect->Dump(ntabs+2); + } + retval += DumpIndent(ntabs+1) + "]\n"; + } + + return retval; +} + +bool Conditional::IsMeterEffect() const { + for (auto& effect : m_true_effects) { + if (effect->IsMeterEffect()) + return true; + } + for (auto& effect : m_false_effects) { + if (effect->IsMeterEffect()) + return true; + } + return false; +} + +bool Conditional::IsAppearanceEffect() const { + for (auto& effect : m_true_effects) { + if (effect->IsAppearanceEffect()) + return true; + } + for (auto& effect : m_false_effects) { + if (effect->IsAppearanceEffect()) + return true; + } + return false; +} + +bool Conditional::IsSitrepEffect() const { + for (auto& effect : m_true_effects) { + if (effect->IsSitrepEffect()) + return true; + } + for (auto& effect : m_false_effects) { + if (effect->IsSitrepEffect()) + return true; + } + return false; +} + +void Conditional::SetTopLevelContent(const std::string& content_name) { + if (m_target_condition) + m_target_condition->SetTopLevelContent(content_name); + for (auto& effect : m_true_effects) + if (effect) + (effect)->SetTopLevelContent(content_name); + for (auto& effect : m_false_effects) + if (effect) + (effect)->SetTopLevelContent(content_name); +} + +unsigned int Conditional::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "Conditional"); + CheckSums::CheckSumCombine(retval, m_target_condition); + CheckSums::CheckSumCombine(retval, m_true_effects); + CheckSums::CheckSumCombine(retval, m_false_effects); + + TraceLogger() << "GetCheckSum(Conditional): retval: " << retval; + return retval; +} + +} // namespace Effect diff --git a/universe/Effects.h b/universe/Effects.h new file mode 100644 index 00000000000..0f0e863edcb --- /dev/null +++ b/universe/Effects.h @@ -0,0 +1,1261 @@ +#ifndef _Effects_h_ +#define _Effects_h_ + +#include "Effect.h" + +#include "../util/Export.h" + +#include +#include + +namespace Condition { + typedef std::vector> ObjectSet; +} + +namespace ValueRef { + template + struct ValueRef; +} + +namespace Effect { +/** Does nothing when executed. Useful for triggering side-effects of effect + * execution without modifying the gamestate. */ +class FO_COMMON_API NoOp final : public Effect { +public: + NoOp(); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override {} + unsigned int GetCheckSum() const override; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the meter of the given kind to \a value. The max value of the meter + * is set if \a max == true; otherwise the current value of the meter is set. + * If the target of the Effect does not have the requested meter, nothing is + * done. */ +class FO_COMMON_API SetMeter final : public Effect { +public: + + SetMeter(MeterType meter, + std::unique_ptr>&& value, + const boost::optional& accounting_label = boost::none); + + void Execute(ScriptingContext& context) const override; + + void Execute(ScriptingContext& context, const TargetSet& targets) const override; + + void Execute(ScriptingContext& context, + const TargetSet& targets, + AccountingMap* accounting_map, + const EffectCause& effect_cause, + bool only_meter_effects = false, + bool only_appearance_effects = false, + bool include_empire_meter_effects = false, + bool only_generate_sitrep_effects = false) const override; + + std::string Dump(unsigned short ntabs = 0) const override; + bool IsMeterEffect() const override { return true; } + void SetTopLevelContent(const std::string& content_name) override; + MeterType GetMeterType() const { return m_meter; }; + const std::string& AccountingLabel() const { return m_accounting_label; } + unsigned int GetCheckSum() const override; + +private: + MeterType m_meter; + std::unique_ptr> m_value; + std::string m_accounting_label; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the indicated meter on all ship parts in the indicated subset. This + * has no effect on non-Ship targets. If slot_type is specified, only parts + * that can mount in the indicated slot type (internal or external) are + * affected (this is not the same at the slot type in which the part is + * actually located, as a part might be mountable in both types, and + * located in a different type than specified, and would be matched). */ +class FO_COMMON_API SetShipPartMeter final : public Effect { +public: + /** Affects the \a meter_type meter that belongs to part(s) named \a + part_name. */ + SetShipPartMeter(MeterType meter_type, + std::unique_ptr>&& part_name, + std::unique_ptr>&& value); + + void Execute(ScriptingContext& context) const override; + void Execute(ScriptingContext& context, const TargetSet& targets) const override; + void Execute(ScriptingContext& context, + const TargetSet& targets, + AccountingMap* accounting_map, + const EffectCause& effect_cause, + bool only_meter_effects = false, + bool only_appearance_effects = false, + bool include_empire_meter_effects = false, + bool only_generate_sitrep_effects = false) const override; + + std::string Dump(unsigned short ntabs = 0) const override; + bool IsMeterEffect() const override { return true; } + void SetTopLevelContent(const std::string& content_name) override; + const ValueRef::ValueRef* GetPartName() const { return m_part_name.get(); } + MeterType GetMeterType() const { return m_meter; } + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_part_name; + MeterType m_meter; + std::unique_ptr> m_value; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the indicated meter on the empire with the indicated id to the + * indicated value. If \a meter is not a valid meter for empires, + * does nothing. */ +class FO_COMMON_API SetEmpireMeter final : public Effect { +public: + SetEmpireMeter(const std::string& meter, std::unique_ptr>&& value); + + SetEmpireMeter(std::unique_ptr>&& empire_id, const std::string& meter, + std::unique_ptr>&& value); + + void Execute(ScriptingContext& context) const override; + void Execute(ScriptingContext& context, const TargetSet& targets) const override; + void Execute(ScriptingContext& context, + const TargetSet& targets, + AccountingMap* accounting_map, + const EffectCause& effect_cause, + bool only_meter_effects = false, + bool only_appearance_effects = false, + bool include_empire_meter_effects = false, + bool only_generate_sitrep_effects = false) const override; + + std::string Dump(unsigned short ntabs = 0) const override; + bool IsMeterEffect() const override { return true; } + bool IsEmpireMeterEffect() const override { return true; } + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_empire_id; + std::string m_meter; + std::unique_ptr> m_value; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the empire stockpile of the target's owning empire to \a value. If + * the target does not have exactly one owner, nothing is done. */ +class FO_COMMON_API SetEmpireStockpile final : public Effect { +public: + SetEmpireStockpile(ResourceType stockpile, + std::unique_ptr>&& value); + SetEmpireStockpile(std::unique_ptr>&& empire_id, + ResourceType stockpile, + std::unique_ptr>&& value); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_empire_id; + ResourceType m_stockpile; + std::unique_ptr> m_value; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Makes the target planet the capital of its owner's empire. If the target + * object is not a planet, does not have an owner, or has more than one owner + * the effect does nothing. */ +class FO_COMMON_API SetEmpireCapital final : public Effect { +public: + explicit SetEmpireCapital(); + explicit SetEmpireCapital(std::unique_ptr>&& empire_id); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the planet type of the target to \a type. This has no effect on non-Planet targets. Note that changing the + type of a PT_ASTEROID or PT_GASGIANT planet will also change its size to SZ_TINY or SZ_HUGE, respectively. + Similarly, changing type to PT_ASTEROID or PT_GASGIANT will also cause the size to change to SZ_ASTEROID or + SZ_GASGIANT, respectively. */ +class FO_COMMON_API SetPlanetType final : public Effect { +public: + explicit SetPlanetType(std::unique_ptr>&& type); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_type; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the planet size of the target to \a size. This has no effect on non- + * Planet targets. Note that changing the size of a PT_ASTEROID or PT_GASGIANT + * planet will also change its type to PT_BARREN. Similarly, changing size to + * SZ_ASTEROID or SZ_GASGIANT will also cause the type to change to PT_ASTEROID + * or PT_GASGIANT, respectively. */ +class FO_COMMON_API SetPlanetSize final : public Effect { +public: + explicit SetPlanetSize(std::unique_ptr>&& size); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_size; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the species on the target to \a species_name. This works on planets + * and ships, but has no effect on other objects. */ +class FO_COMMON_API SetSpecies final : public Effect { +public: + explicit SetSpecies(std::unique_ptr>&& species); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_species_name; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets empire \a empire_id as the owner of the target. This has no effect if + * \a empire_id was already the owner of the target object. */ +class FO_COMMON_API SetOwner final : public Effect { +public: + explicit SetOwner(std::unique_ptr>&& empire_id); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the opinion of Species \a species for empire with id \a empire_id to + * \a opinion */ +class FO_COMMON_API SetSpeciesEmpireOpinion final : public Effect { +public: + SetSpeciesEmpireOpinion(std::unique_ptr>&& species_name, + std::unique_ptr>&& empire_id, + std::unique_ptr>&& opinion); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_species_name; + std::unique_ptr> m_empire_id; + std::unique_ptr> m_opinion; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the opinion of Species \a opinionated_species for other species + * \a rated_species to \a opinion */ +class FO_COMMON_API SetSpeciesSpeciesOpinion final : public Effect { +public: + SetSpeciesSpeciesOpinion(std::unique_ptr>&& opinionated_species_name, + std::unique_ptr>&& rated_species_name, + std::unique_ptr>&& opinion); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_opinionated_species_name; + std::unique_ptr> m_rated_species_name; + std::unique_ptr> m_opinion; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Creates a new Planet with specified \a type and \a size at the system with + * specified \a location_id */ +class FO_COMMON_API CreatePlanet final : public Effect { +public: + CreatePlanet(std::unique_ptr>&& type, + std::unique_ptr>&& size, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_type; + std::unique_ptr> m_size; + std::unique_ptr> m_name; + std::vector> m_effects_to_apply_after; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Creates a new Building with specified \a type on the \a target Planet. */ +class FO_COMMON_API CreateBuilding final : public Effect { +public: + CreateBuilding(std::unique_ptr>&& building_type_name, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_building_type_name; + std::unique_ptr> m_name; + std::vector> m_effects_to_apply_after; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Creates a new Ship with specified \a predefined_ship_design_name design + * from those in the list of PredefinedShipDesignManager, and owned by the + * empire with the specified \a empire_id */ +class FO_COMMON_API CreateShip final : public Effect { +public: + CreateShip(std::unique_ptr>&& predefined_ship_design_name, + std::unique_ptr>&& empire_id, + std::unique_ptr>&& species_name, + std::unique_ptr>&& ship_name, + std::vector>&& effects_to_apply_after); + + CreateShip(std::unique_ptr>&& ship_design_id, + std::unique_ptr>&& empire_id, + std::unique_ptr>&& species_name, + std::unique_ptr>&& ship_name, + std::vector>&& effects_to_apply_after); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_design_name; + std::unique_ptr> m_design_id; + std::unique_ptr> m_empire_id; + std::unique_ptr> m_species_name; + std::unique_ptr> m_name; + std::vector> m_effects_to_apply_after; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Creates a new Field with specified \a field_type_name FieldType + * of the specified \a size. */ +class FO_COMMON_API CreateField final : public Effect { +public: + CreateField(std::unique_ptr>&& field_type_name, + std::unique_ptr>&& size, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after); + + CreateField(std::unique_ptr>&& field_type_name, + std::unique_ptr>&& x, + std::unique_ptr>&& y, + std::unique_ptr>&& size, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_field_type_name; + std::unique_ptr> m_x; + std::unique_ptr> m_y; + std::unique_ptr> m_size; + std::unique_ptr> m_name; + std::vector> m_effects_to_apply_after; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Creates a new system with the specified \a colour and at the specified + * location. */ +class FO_COMMON_API CreateSystem final : public Effect { +public: + CreateSystem(std::unique_ptr>&& type, + std::unique_ptr>&& x, + std::unique_ptr>&& y, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after); + + CreateSystem(std::unique_ptr>&& x, + std::unique_ptr>&& y, + std::unique_ptr>&& name, + std::vector>&& effects_to_apply_after); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_type; + std::unique_ptr> m_x; + std::unique_ptr> m_y; + std::unique_ptr> m_name; + std::vector> m_effects_to_apply_after; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Destroys the target object. When executed on objects that contain other + * objects (such as Fleets and Planets), all contained objects are destroyed + * as well. Destroy effects delay the desctruction of their targets until + * after other all effects have executed, to ensure the source or target of + * other effects are present when they execute. */ +class FO_COMMON_API Destroy final : public Effect { +public: + Destroy(); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override {} + unsigned int GetCheckSum() const override; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Adds the Special with the name \a name to the target object. */ +class FO_COMMON_API AddSpecial final : public Effect { +public: + explicit AddSpecial(const std::string& name, float capacity = 1.0f); + explicit AddSpecial(std::unique_ptr>&& name, + std::unique_ptr>&& capacity = nullptr); + + void Execute(ScriptingContext& context) const override; + + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + const ValueRef::ValueRef* GetSpecialName() const { return m_name.get(); } + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_name; + std::unique_ptr> m_capacity; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Removes the Special with the name \a name to the target object. This has + * no effect if no such Special was already attached to the target object. */ +class FO_COMMON_API RemoveSpecial final : public Effect { +public: + explicit RemoveSpecial(const std::string& name); + explicit RemoveSpecial(std::unique_ptr>&& name); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_name; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Creates starlane(s) between the target system and systems that match + * \a other_lane_endpoint_condition */ +class FO_COMMON_API AddStarlanes final : public Effect { +public: + explicit AddStarlanes(std::unique_ptr&& other_lane_endpoint_condition); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr m_other_lane_endpoint_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Removes starlane(s) between the target system and systems that match + * \a other_lane_endpoint_condition */ +class FO_COMMON_API RemoveStarlanes final : public Effect { +public: + explicit RemoveStarlanes(std::unique_ptr&& other_lane_endpoint_condition); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr m_other_lane_endpoint_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the star type of the target to \a type. This has no effect on + * non-System targets. */ +class FO_COMMON_API SetStarType final : public Effect { +public: + explicit SetStarType(std::unique_ptr>&& type); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_type; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Moves an UniverseObject to a location of another UniverseObject that matches + * the condition \a location_condition. If multiple objects match the + * condition, then one is chosen. If no objects match the condition, then + * nothing is done. */ +class FO_COMMON_API MoveTo final : public Effect { +public: + explicit MoveTo(std::unique_ptr&& location_condition); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr m_location_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Moves an UniverseObject to a location as though it was moving in orbit of + * some object or position on the map. Sign of \a speed indicates CCW / CW + * rotation.*/ +class FO_COMMON_API MoveInOrbit final : public Effect { +public: + MoveInOrbit(std::unique_ptr>&& speed, + std::unique_ptr&& focal_point_condition); + MoveInOrbit(std::unique_ptr>&& speed, + std::unique_ptr>&& focus_x = nullptr, + std::unique_ptr>&& focus_y = nullptr); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_speed; + std::unique_ptr m_focal_point_condition; + std::unique_ptr> m_focus_x; + std::unique_ptr> m_focus_y; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Moves an UniverseObject a specified distance towards some object or + * position on the map. */ +class FO_COMMON_API MoveTowards final : public Effect { +public: + MoveTowards(std::unique_ptr>&& speed, + std::unique_ptr&& dest_condition); + MoveTowards(std::unique_ptr>&& speed, + std::unique_ptr>&& dest_x = nullptr, + std::unique_ptr>&& dest_y = nullptr); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_speed; + std::unique_ptr m_dest_condition; + std::unique_ptr> m_dest_x; + std::unique_ptr> m_dest_y; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets the route of the target fleet to move to an UniverseObject that + * matches the condition \a location_condition. If multiple objects match the + * condition, then one is chosen. If no objects match the condition, then + * nothing is done. */ +class FO_COMMON_API SetDestination final : public Effect { +public: + explicit SetDestination(std::unique_ptr&& location_condition); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr m_location_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets aggression level of the target object. */ +class FO_COMMON_API SetAggression final : public Effect { +public: + explicit SetAggression(bool aggressive); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override {} + unsigned int GetCheckSum() const override; + +private: + bool m_aggressive; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Causes the owner empire of the target object to win the game. If the + * target object has multiple owners, nothing is done. */ +class FO_COMMON_API Victory final : public Effect { +public: + explicit Victory(const std::string& reason_string); // TODO: Make this a ValueRef* + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override {} + unsigned int GetCheckSum() const override; + +private: + std::string m_reason_string; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets whether an empire has researched at tech, and how much research + * progress towards that tech has been completed. */ +class FO_COMMON_API SetEmpireTechProgress final : public Effect { +public: + SetEmpireTechProgress(std::unique_ptr>&& tech_name, + std::unique_ptr>&& research_progress, + std::unique_ptr>&& empire_id = nullptr); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_tech_name; + std::unique_ptr> m_research_progress; + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +class FO_COMMON_API GiveEmpireTech final : public Effect { +public: + explicit GiveEmpireTech(std::unique_ptr>&& tech_name, + std::unique_ptr>&& empire_id = nullptr); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_tech_name; + std::unique_ptr> m_empire_id; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Generates a sitrep message for the empire with id \a recipient_empire_id. + * The message text is the user string specified in \a message_string with + * string substitutions into the message text as specified in \a message_parameters + * which are substituted as string parameters %1%, %2%, %3%, etc. in the order + * they are specified. Extra parameters beyond those needed by \a message_string + * are ignored, and missing parameters are left as blank text. */ +class FO_COMMON_API GenerateSitRepMessage final : public Effect { +public: + using MessageParams = std::vector>>>; + + GenerateSitRepMessage(const std::string& message_string, const std::string& icon, + MessageParams&& message_parameters, + std::unique_ptr>&& recipient_empire_id, + EmpireAffiliationType affiliation, + const std::string label = "", + bool stringtable_lookup = true); + GenerateSitRepMessage(const std::string& message_string, const std::string& icon, + MessageParams&& message_parameters, + EmpireAffiliationType affiliation, + std::unique_ptr&& condition, + const std::string label = "", + bool stringtable_lookup = true); + GenerateSitRepMessage(const std::string& message_string, const std::string& icon, + MessageParams&& message_parameters, + EmpireAffiliationType affiliation, + const std::string& label = "", + bool stringtable_lookup = true); + + void Execute(ScriptingContext& context) const override; + bool IsSitrepEffect() const override { return true; } + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + const std::string& MessageString() const { return m_message_string; } + const std::string& Icon() const { return m_icon; } + + std::vector* >> MessageParameters() const; + + ValueRef::ValueRef* RecipientID() const { return m_recipient_empire_id.get(); } + Condition::Condition* GetCondition() const { return m_condition.get(); } + EmpireAffiliationType Affiliation() const { return m_affiliation; } + unsigned int GetCheckSum() const override; + +private: + std::string m_message_string; + std::string m_icon; + std::vector>>> + m_message_parameters; + std::unique_ptr> + m_recipient_empire_id; + std::unique_ptr + m_condition; + EmpireAffiliationType m_affiliation; + std::string m_label; + bool m_stringtable_lookup; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Applies an overlay texture to Systems. */ +class FO_COMMON_API SetOverlayTexture final : public Effect { +public: + SetOverlayTexture(const std::string& texture, std::unique_ptr>&& size); + SetOverlayTexture(const std::string& texture, ValueRef::ValueRef* size); + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + bool IsAppearanceEffect() const override { return true; } + void SetTopLevelContent(const std::string& content_name) override; + unsigned int GetCheckSum() const override; + +private: + std::string m_texture; + std::unique_ptr> m_size; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Applies a texture to Planets. */ +class FO_COMMON_API SetTexture final : public Effect { +public: + explicit SetTexture(const std::string& texture); + + void Execute(ScriptingContext& context) const override; + + std::string Dump(unsigned short ntabs = 0) const override; + bool IsAppearanceEffect() const override { return true; } + void SetTopLevelContent(const std::string& content_name) override {} + unsigned int GetCheckSum() const override; + +private: + std::string m_texture; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Sets visibility of an object for an empire, independent of standard + * visibility mechanics. */ +class FO_COMMON_API SetVisibility final : public Effect { +public: + SetVisibility(std::unique_ptr> vis, + EmpireAffiliationType affiliation, + std::unique_ptr>&& empire_id = nullptr, + std::unique_ptr&& of_objects = nullptr); // if not specified, acts on target. if specified, acts on all matching objects + + void Execute(ScriptingContext& context) const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + + ValueRef::ValueRef* GetVisibility() const + { return m_vis.get(); } + + ValueRef::ValueRef* EmpireID() const + { return m_empire_id.get(); } + + EmpireAffiliationType Affiliation() const + { return m_affiliation; } + + Condition::Condition* OfObjectsCondition() const + { return m_condition.get(); } + + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_vis; + std::unique_ptr> m_empire_id; + EmpireAffiliationType m_affiliation; + std::unique_ptr m_condition; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Executes a set of effects if an execution-time condition is met, or an + * alterative set of effects if the condition is not met. */ +class FO_COMMON_API Conditional final : public Effect { +public: + Conditional(std::unique_ptr&& target_condition, + std::vector>&& true_effects, + std::vector>&& false_effects); + + void Execute(ScriptingContext& context) const override; + /** Note: executes all of the true or all of the false effects on each + target, without considering any of the only_* type flags. */ + + void Execute(ScriptingContext& context, const TargetSet& targets) const override; + + void Execute(ScriptingContext& context, + const TargetSet& targets, + AccountingMap* accounting_map, + const EffectCause& effect_cause, + bool only_meter_effects = false, + bool only_appearance_effects = false, + bool include_empire_meter_effects = false, + bool only_generate_sitrep_effects = false) const override; + + std::string Dump(unsigned short ntabs = 0) const override; + + bool IsMeterEffect() const override; + bool IsAppearanceEffect() const override; + bool IsSitrepEffect() const override; + bool IsConditionalEffect() const override { return true; } + + void SetTopLevelContent(const std::string& content_name) override; + + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr m_target_condition; // condition to apply to each target object to determine which effects to execute + std::vector> m_true_effects; // effects to execute if m_target_condition matches target object + std::vector> m_false_effects; // effects to execute if m_target_condition does not match target object + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + + +// template implementations +template +void EffectsGroup::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_NVP(m_scope) + & BOOST_SERIALIZATION_NVP(m_activation) + & BOOST_SERIALIZATION_NVP(m_stacking_group) + & BOOST_SERIALIZATION_NVP(m_effects) + & BOOST_SERIALIZATION_NVP(m_description) + & BOOST_SERIALIZATION_NVP(m_content_name); +} + +template +void Effect::serialize(Archive& ar, const unsigned int version) +{} + +template +void NoOp::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect); +} + +template +void SetMeter::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_meter) + & BOOST_SERIALIZATION_NVP(m_value) + & BOOST_SERIALIZATION_NVP(m_accounting_label); +} + +template +void SetShipPartMeter::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_part_name) + & BOOST_SERIALIZATION_NVP(m_meter) + & BOOST_SERIALIZATION_NVP(m_value); +} + +template +void SetEmpireMeter::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_empire_id) + & BOOST_SERIALIZATION_NVP(m_meter) + & BOOST_SERIALIZATION_NVP(m_value); +} + +template +void SetEmpireStockpile::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_empire_id) + & BOOST_SERIALIZATION_NVP(m_stockpile) + & BOOST_SERIALIZATION_NVP(m_value); +} + +template +void SetEmpireCapital::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void SetPlanetType::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_type); +} + +template +void SetPlanetSize::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_size); +} + +template +void SetSpecies::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_species_name); +} + +template +void SetOwner::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void CreatePlanet::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_type) + & BOOST_SERIALIZATION_NVP(m_size) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_effects_to_apply_after); +} + +template +void CreateBuilding::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_building_type_name) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_effects_to_apply_after); +} + +template +void CreateShip::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_design_name) + & BOOST_SERIALIZATION_NVP(m_design_id) + & BOOST_SERIALIZATION_NVP(m_empire_id) + & BOOST_SERIALIZATION_NVP(m_species_name) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_effects_to_apply_after); +} + +template +void CreateField::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_field_type_name) + & BOOST_SERIALIZATION_NVP(m_x) + & BOOST_SERIALIZATION_NVP(m_y) + & BOOST_SERIALIZATION_NVP(m_size) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_effects_to_apply_after); +} + +template +void CreateSystem::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_type) + & BOOST_SERIALIZATION_NVP(m_x) + & BOOST_SERIALIZATION_NVP(m_y) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_effects_to_apply_after); +} + +template +void Destroy::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect); +} + +template +void AddSpecial::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_capacity); +} + +template +void RemoveSpecial::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_name); +} + +template +void AddStarlanes::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_other_lane_endpoint_condition); +} + +template +void RemoveStarlanes::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_other_lane_endpoint_condition); +} + +template +void SetStarType::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_type); +} + +template +void MoveTo::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_location_condition); +} + +template +void MoveInOrbit::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_speed) + & BOOST_SERIALIZATION_NVP(m_focal_point_condition) + & BOOST_SERIALIZATION_NVP(m_focus_x) + & BOOST_SERIALIZATION_NVP(m_focus_y); +} + +template +void MoveTowards::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_speed) + & BOOST_SERIALIZATION_NVP(m_dest_condition) + & BOOST_SERIALIZATION_NVP(m_dest_x) + & BOOST_SERIALIZATION_NVP(m_dest_y); +} + +template +void SetDestination::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_location_condition); +} + +template +void SetAggression::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_aggressive); +} + +template +void Victory::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_reason_string); +} + +template +void SetEmpireTechProgress::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_tech_name) + & BOOST_SERIALIZATION_NVP(m_research_progress) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void GiveEmpireTech::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_tech_name) + & BOOST_SERIALIZATION_NVP(m_empire_id); +} + +template +void GenerateSitRepMessage::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_message_string) + & BOOST_SERIALIZATION_NVP(m_icon) + & BOOST_SERIALIZATION_NVP(m_message_parameters) + & BOOST_SERIALIZATION_NVP(m_recipient_empire_id) + & BOOST_SERIALIZATION_NVP(m_condition) + & BOOST_SERIALIZATION_NVP(m_affiliation) + & BOOST_SERIALIZATION_NVP(m_label) + & BOOST_SERIALIZATION_NVP(m_stringtable_lookup); +} + +template +void SetOverlayTexture::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_texture) + & BOOST_SERIALIZATION_NVP(m_size); +} + +template +void SetTexture::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_texture); +} + +template +void SetVisibility::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_vis) + & BOOST_SERIALIZATION_NVP(m_empire_id) + & BOOST_SERIALIZATION_NVP(m_affiliation) + & BOOST_SERIALIZATION_NVP(m_condition); +} + +template +void Conditional::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Effect) + & BOOST_SERIALIZATION_NVP(m_target_condition) + & BOOST_SERIALIZATION_NVP(m_true_effects) + & BOOST_SERIALIZATION_NVP(m_false_effects); +} +} // namespace Effect + +#endif // _Effects_h_ diff --git a/universe/Encyclopedia.cpp b/universe/Encyclopedia.cpp index a5fd8cdd0d2..cea77c019bf 100644 --- a/universe/Encyclopedia.cpp +++ b/universe/Encyclopedia.cpp @@ -4,39 +4,17 @@ #include "../util/Logger.h" #include "../util/OptionsDB.h" #include "../util/CheckSums.h" -#include "../parse/Parse.h" -const Encyclopedia& GetEncyclopedia() { +Encyclopedia& GetEncyclopedia() { static Encyclopedia encyclopedia; return encyclopedia; } -Encyclopedia::Encyclopedia() : - articles(), - empty_article() -{ - try { - parse::encyclopedia_articles(*this); - DebugLogger() << "Encyclopedia Articles checksum: " << GetCheckSum(); - - } catch (const std::exception& e) { - ErrorLogger() << "Failed parsing encyclopedia articles: error: " << e.what(); - throw e; - } - - TraceLogger() << "(Category) Encyclopedia Articles:"; - for (const auto& entry : articles) { - const std::string& category = entry.first; - for (const EncyclopediaArticle& article : entry.second) - { TraceLogger() << "(" << category << ") : " << article.name; } - } -} - unsigned int Encyclopedia::GetCheckSum() const { unsigned int retval{0}; - for (const auto& n : articles) { + for (const auto& n : Articles()) { CheckSums::CheckSumCombine(retval, n.first); for (const auto& a : n.second) { CheckSums::CheckSumCombine(retval, a.name); @@ -47,7 +25,62 @@ unsigned int Encyclopedia::GetCheckSum() const { } CheckSums::CheckSumCombine(retval, n.second.size()); } - CheckSums::CheckSumCombine(retval, articles.size()); + CheckSums::CheckSumCombine(retval, Articles().size()); return retval; } + +void Encyclopedia::SetArticles(Pending::Pending&& future) +{ m_pending_articles = std::move(future); } + +const Encyclopedia::ArticleMap& Encyclopedia::Articles() const { + if (auto parsed = WaitForPending(m_pending_articles)) { + std::swap(m_articles, *parsed); + + TraceLogger() << "(Category) Encyclopedia Articles:"; + for (const auto& entry : m_articles) { + const std::string& category = entry.first; + for (const EncyclopediaArticle& article : entry.second) + { TraceLogger() << "(" << category << ") : " << article.name; } + } + } + + return m_articles; +} + +const EncyclopediaArticle& Encyclopedia::GetArticleByKey(const std::string& key) const { + const auto& articles = Articles(); + for (auto& category_articles : articles) { + for (auto& article : category_articles.second) { + if (article.name == key) + return article; + } + } + return empty_article; +} + +const EncyclopediaArticle& Encyclopedia::GetArticleByCategoryAndKey( + const std::string& category, const std::string& key) const +{ + const auto& articles = Articles(); + auto it = articles.find(category); + if (it == articles.end()) + return empty_article; + const auto& category_articles = it->second; + for (auto& article : category_articles) { + if (article.name == key) + return article; + } + return empty_article; +} + +const EncyclopediaArticle& Encyclopedia::GetArticleByName(const std::string& name) const { + const auto& articles = Articles(); + for (auto& category_articles : articles) { + for (auto& article : category_articles.second) { + if (UserString(article.name) == name) + return article; + } + } + return empty_article; +} diff --git a/universe/Encyclopedia.h b/universe/Encyclopedia.h index a907346eea8..a6c72051ade 100644 --- a/universe/Encyclopedia.h +++ b/universe/Encyclopedia.h @@ -1,6 +1,10 @@ #ifndef _Encyclopedia_h_ #define _Encyclopedia_h_ +#include "../util/Pending.h" + +#include + #include #include #include @@ -8,13 +12,7 @@ #include "../util/Export.h" struct FO_COMMON_API EncyclopediaArticle { - EncyclopediaArticle() : - name(""), - category(""), - short_description(""), - description(""), - icon("") - {} + EncyclopediaArticle() = default; EncyclopediaArticle(const std::string& name_, const std::string& category_, const std::string& short_description_, const std::string& description_, const std::string& icon_) : @@ -31,13 +29,31 @@ struct FO_COMMON_API EncyclopediaArticle { std::string icon; }; -struct FO_COMMON_API Encyclopedia { - Encyclopedia(); +class FO_COMMON_API Encyclopedia { +public: + // map from category name to list of articles in that category + using ArticleMap = std::map>; + + Encyclopedia() {}; unsigned int GetCheckSum() const; - std::map> articles; - const EncyclopediaArticle empty_article; + + /** Sets articles to the value of \p future. */ + FO_COMMON_API void SetArticles(Pending::Pending&& future); + + FO_COMMON_API const ArticleMap& Articles() const; + + FO_COMMON_API const EncyclopediaArticle& GetArticleByKey(const std::string& key) const; + FO_COMMON_API const EncyclopediaArticle& GetArticleByCategoryAndKey(const std::string& category, const std::string& key) const; + FO_COMMON_API const EncyclopediaArticle& GetArticleByName(const std::string& name) const; + + const EncyclopediaArticle empty_article; +private: + mutable ArticleMap m_articles; + + /** Future articles. mutable so that it can be assigned to m_articles when completed.*/ + mutable boost::optional> m_pending_articles = boost::none; }; -FO_COMMON_API const Encyclopedia& GetEncyclopedia(); +FO_COMMON_API Encyclopedia& GetEncyclopedia(); #endif diff --git a/universe/Enums.cpp b/universe/Enums.cpp index 64e1b97e334..242e54a7337 100644 --- a/universe/Enums.cpp +++ b/universe/Enums.cpp @@ -2,12 +2,25 @@ #include +namespace { + const std::string EMPTY_STRING = ""; + const std::string PATH_BINARY_STR = "PATH_BINARY"; + const std::string PATH_RESOURCE_STR = "PATH_RESOURCE"; + const std::string PATH_DATA_ROOT_STR = "PATH_DATA_ROOT"; + const std::string PATH_DATA_USER_STR = "PATH_DATA_USER"; + const std::string PATH_CONFIG_STR = "PATH_CONFIG"; + const std::string PATH_SAVE_STR = "PATH_SAVE"; + const std::string PATH_TEMP_STR = "PATH_TEMP"; + const std::string PATH_PYTHON_STR = "PATH_PYTHON"; + const std::string PATH_INVALID_STR = "PATH_INVALID"; +} MeterType ResourceToMeter(ResourceType type) { switch (type) { case RE_INDUSTRY: return METER_INDUSTRY; case RE_RESEARCH: return METER_RESEARCH; case RE_TRADE: return METER_TRADE; + case RE_STOCKPILE: return METER_STOCKPILE; default: assert(0); return INVALID_METER_TYPE; @@ -20,6 +33,7 @@ MeterType ResourceToTargetMeter(ResourceType type) { case RE_INDUSTRY: return METER_TARGET_INDUSTRY; case RE_RESEARCH: return METER_TARGET_RESEARCH; case RE_TRADE: return METER_TARGET_TRADE; + case RE_STOCKPILE: return METER_MAX_STOCKPILE; default: assert(0); return INVALID_METER_TYPE; @@ -32,6 +46,7 @@ ResourceType MeterToResource(MeterType type) { case METER_INDUSTRY: return RE_INDUSTRY; case METER_RESEARCH: return RE_RESEARCH; case METER_TRADE: return RE_TRADE; + case METER_STOCKPILE: return RE_STOCKPILE; default: assert(0); return INVALID_RESOURCE_TYPE; @@ -39,20 +54,57 @@ ResourceType MeterToResource(MeterType type) { } } +const std::map& AssociatedMeterTypes() { + static const std::map meters = { + {METER_POPULATION, METER_TARGET_POPULATION}, + {METER_INDUSTRY, METER_TARGET_INDUSTRY}, + {METER_RESEARCH, METER_TARGET_RESEARCH}, + {METER_TRADE, METER_TARGET_TRADE}, + {METER_CONSTRUCTION, METER_TARGET_CONSTRUCTION}, + {METER_HAPPINESS, METER_TARGET_HAPPINESS}, + {METER_FUEL, METER_MAX_FUEL}, + {METER_SHIELD, METER_MAX_SHIELD}, + {METER_STRUCTURE, METER_MAX_STRUCTURE}, + {METER_DEFENSE, METER_MAX_DEFENSE}, + {METER_TROOPS, METER_MAX_TROOPS}, + {METER_SUPPLY, METER_MAX_SUPPLY}, + {METER_STOCKPILE, METER_MAX_STOCKPILE}}; + return meters; +} + MeterType AssociatedMeterType(MeterType meter_type) { - switch (meter_type) { - case METER_POPULATION: return METER_TARGET_POPULATION; break; - case METER_INDUSTRY: return METER_TARGET_INDUSTRY; break; - case METER_RESEARCH: return METER_TARGET_RESEARCH; break; - case METER_TRADE: return METER_TARGET_TRADE; break; - case METER_CONSTRUCTION:return METER_TARGET_CONSTRUCTION; break; - case METER_HAPPINESS: return METER_TARGET_HAPPINESS; break; - case METER_FUEL: return METER_MAX_FUEL; break; - case METER_SHIELD: return METER_MAX_SHIELD; break; - case METER_STRUCTURE: return METER_MAX_STRUCTURE; break; - case METER_DEFENSE: return METER_MAX_DEFENSE; break; - case METER_TROOPS: return METER_MAX_TROOPS; break; - case METER_SUPPLY: return METER_MAX_SUPPLY; break; - default: return INVALID_METER_TYPE; break; + auto mt_pair_it = AssociatedMeterTypes().find(meter_type); + if (mt_pair_it == AssociatedMeterTypes().end()) + return INVALID_METER_TYPE; + return mt_pair_it->second; +} + +const std::string& PathTypeToString(PathType path_type) { + switch (path_type) { + case PATH_BINARY: return PATH_BINARY_STR; + case PATH_RESOURCE: return PATH_RESOURCE_STR; + case PATH_PYTHON: return PATH_PYTHON_STR; + case PATH_DATA_ROOT: return PATH_DATA_ROOT_STR; + case PATH_DATA_USER: return PATH_DATA_USER_STR; + case PATH_CONFIG: return PATH_CONFIG_STR; + case PATH_SAVE: return PATH_SAVE_STR; + case PATH_TEMP: return PATH_TEMP_STR; + case PATH_INVALID: return PATH_INVALID_STR; + default: return EMPTY_STRING; + } +} + +const std::vector& PathTypeStrings() { + static std::vector path_type_list; + if (path_type_list.empty()) { + for (auto path_type = PathType(0); path_type < PATH_INVALID; path_type = PathType(path_type + 1)) { + // PATH_PYTHON is only valid for FREEORION_WIN32 or FREEORION_MACOSX +#if defined(FREEORION_LINUX) + if (path_type == PATH_PYTHON) + continue; +#endif + path_type_list.push_back(PathTypeToString(path_type)); + } } + return path_type_list; } diff --git a/universe/Enums.h b/universe/Enums.h index dddb5d745e9..09dadcd79a5 100644 --- a/universe/Enums.h +++ b/universe/Enums.h @@ -4,6 +4,8 @@ #include #include +#include +#include #include "../util/Export.h" @@ -99,6 +101,7 @@ GG_ENUM(MeterType, METER_MAX_STRUCTURE, METER_MAX_DEFENSE, METER_MAX_SUPPLY, + METER_MAX_STOCKPILE, METER_MAX_TROOPS, METER_POPULATION, @@ -116,6 +119,7 @@ GG_ENUM(MeterType, METER_STRUCTURE, METER_DEFENSE, METER_SUPPLY, + METER_STOCKPILE, METER_TROOPS, METER_REBEL_TROOPS, @@ -171,6 +175,7 @@ GG_ENUM(EmpireAffiliationType, INVALID_EMPIRE_AFFIL_TYPE = -1, AFFIL_SELF, ///< the given empire iteslf AFFIL_ENEMY, ///< enemies of the given empire + AFFIL_PEACE, ///< empires at peace with the given empire AFFIL_ALLY, ///< allies of the given empire AFFIL_ANY, ///< any empire AFFIL_NONE, ///< no empire @@ -188,17 +193,6 @@ GG_ENUM(DiplomaticStatus, NUM_DIPLO_STATUSES ) -/** types of items that can be unlocked for empires */ -GG_ENUM(UnlockableItemType, - INVALID_UNLOCKABLE_ITEM_TYPE = -1, - UIT_BUILDING, ///< a kind of Building - UIT_SHIP_PART, ///< a kind of ship part (which are placed into hulls to make designs) - UIT_SHIP_HULL, ///< a ship hull (into which parts are placed) - UIT_SHIP_DESIGN, ///< a complete ship design - UIT_TECH, ///< a technology - NUM_UNLOCKABLE_ITEM_TYPES ///< keep last, the number of types of unlockable item -) - /** Research status of techs, relating to whether they have been or can be researched */ GG_ENUM(TechStatus, INVALID_TECH_STATUS = -1, @@ -217,6 +211,7 @@ GG_ENUM(BuildType, BT_BUILDING, ///< a Building object is being built BT_SHIP, ///< a Ship object is being built BT_PROJECT, ///< a project may produce effects while on the queue, may or may not ever complete, and does not result in a ship or building being produced + BT_STOCKPILE, NUM_BUILD_TYPES ) @@ -226,32 +221,10 @@ GG_ENUM(ResourceType, RE_INDUSTRY, RE_TRADE, RE_RESEARCH, + RE_STOCKPILE, NUM_RESOURCE_TYPES ) -/** Types "classes" of ship parts */ -GG_ENUM(ShipPartClass, - INVALID_SHIP_PART_CLASS = -1, - PC_DIRECT_WEAPON, ///< direct-fire weapons - PC_FIGHTER_BAY, ///< launch aparatus for fighters, which are self-propelled platforms that function independently of ships in combat, but don't exist on the main game map - PC_FIGHTER_HANGAR, ///< storage for fighters, also determines their weapon strength stat - PC_SHIELD, ///< energy-based defense - PC_ARMOUR, ///< defensive material on hull of ship - PC_TROOPS, ///< ground troops, used to conquer planets - PC_DETECTION, ///< range of vision and seeing through stealth - PC_STEALTH, ///< hiding from enemies - PC_FUEL, ///< distance that can be traveled away from resupply - PC_COLONY, ///< transports colonists and allows ships to make new colonies - PC_SPEED, ///< affects ship speed on starlanes - PC_GENERAL, ///< special purpose parts that don't fall into another class - PC_BOMBARD, ///< permit orbital bombardment by ships against planets - PC_INDUSTRY, ///< generates production points for owner at its location - PC_RESEARCH, ///< generates research points for owner - PC_TRADE, ///< generates trade points for owner - PC_PRODUCTION_LOCATION, ///< allows production items to be produced at its location - NUM_SHIP_PART_CLASSES -) - /* Types of slots in hulls. Parts may be restricted to only certain slot types */ GG_ENUM(ShipSlotType, INVALID_SHIP_SLOT_TYPE = -1, @@ -271,6 +244,10 @@ FO_COMMON_API MeterType ResourceToTargetMeter(ResourceType type); * resource type exists, returns INVALID_RESOURCE_TYPE. */ FO_COMMON_API ResourceType MeterToResource(MeterType type); +/** Returns mapping from active to target or max meter types that correspond. + * eg. METER_RESEARCH -> METER_TARGET_RESEARCH */ +FO_COMMON_API const std::map& AssociatedMeterTypes(); + /** Returns the target or max meter type that is associated with the given * active meter type. If no associated meter type exists, INVALID_METER_TYPE * is returned. */ @@ -326,5 +303,24 @@ GG_ENUM(ModeratorActionSetting, MAS_CreatePlanet ) +/** Types of root directories */ +GG_ENUM(PathType, + PATH_BINARY, + PATH_RESOURCE, + PATH_PYTHON, + PATH_DATA_ROOT, + PATH_DATA_USER, + PATH_CONFIG, + PATH_SAVE, + PATH_TEMP, + PATH_INVALID +) + +/** Returns a string representation of PathType */ +FO_COMMON_API const std::string& PathTypeToString(PathType path_type); + +/** Returns a vector of strings for all PathTypes */ +FO_COMMON_API const std::vector& PathTypeStrings(); + #endif // _Enums_h_ diff --git a/universe/EnumsFwd.h b/universe/EnumsFwd.h index 3ba408c609d..104362df05c 100644 --- a/universe/EnumsFwd.h +++ b/universe/EnumsFwd.h @@ -22,5 +22,6 @@ enum Visibility : int; enum CaptureResult : int; enum EffectsCauseType : int; enum ModeratorActionSetting : int; +enum PathType : int; #endif // _EnumsFwd_h_ diff --git a/universe/Field.cpp b/universe/Field.cpp index 3114b184105..30a24c59598 100644 --- a/universe/Field.cpp +++ b/universe/Field.cpp @@ -1,39 +1,13 @@ #include "Field.h" -#include "Condition.h" -#include "Effect.h" #include "Enums.h" +#include "FieldType.h" #include "Meter.h" #include "Predicates.h" #include "Universe.h" -#include "ValueRef.h" -#include "../parse/Parse.h" #include "../util/AppInterface.h" -#include "../util/OptionsDB.h" -#include "../util/Logger.h" -#include "../util/CheckSums.h" +#include "../util/i18n.h" -#include -#include - -namespace { - std::shared_ptr - IncreaseMeter(MeterType meter_type, double increase) { - typedef std::shared_ptr EffectsGroupPtr; - typedef std::vector Effects; - Condition::Source* scope = new Condition::Source; - Condition::Source* activation = nullptr; - ValueRef::ValueRefBase* vr = - new ValueRef::Operation( - ValueRef::PLUS, - new ValueRef::Variable(ValueRef::EFFECT_TARGET_VALUE_REFERENCE, std::vector()), - new ValueRef::Constant(increase) - ); - return EffectsGroupPtr( - new Effect::EffectsGroup( - scope, activation, Effects(1, new Effect::SetMeter(meter_type, vr)))); - } -} ///////////////////////////////////////////////// // Field // @@ -110,9 +84,9 @@ bool Field::HasTag(const std::string& name) const { UniverseObjectType Field::ObjectType() const { return OBJ_FIELD; } -std::string Field::Dump() const { +std::string Field::Dump(unsigned short ntabs) const { std::stringstream os; - os << UniverseObject::Dump(); + os << UniverseObject::Dump(ntabs); os << " field type: " << m_type_name; return os.str(); } @@ -159,134 +133,3 @@ void Field::ClampMeters() { // intentionally not clamping METER_SPEED, to allow negative speeds UniverseObject::GetMeter(METER_SIZE)->ClampCurrentToRange(); } - -///////////////////////////////////////////////// -// FieldType // -///////////////////////////////////////////////// -FieldType::FieldType(const std::string& name, const std::string& description, - float stealth, const std::set& tags, - const std::vector>& effects, - const std::string& graphic) : - m_name(name), - m_description(description), - m_stealth(stealth), - m_tags(), - m_effects(effects), - m_graphic(graphic) -{ - for (const std::string& tag : tags) - m_tags.insert(boost::to_upper_copy(tag)); - - if (m_stealth != 0.0f) - m_effects.push_back(IncreaseMeter(METER_STEALTH, m_stealth)); - - for (std::shared_ptr effect : m_effects) { - effect->SetTopLevelContent(m_name); - } -} - -FieldType::~FieldType() -{} - -std::string FieldType::Dump() const { - std::string retval = DumpIndent() + "FieldType\n"; - ++g_indent; - retval += DumpIndent() + "name = \"" + m_name + "\"\n"; - retval += DumpIndent() + "description = \"" + m_description + "\"\n"; - retval += DumpIndent() + "location = \n"; - //++g_indent; - //retval += m_location->Dump(); - //--g_indent; - if (m_effects.size() == 1) { - retval += DumpIndent() + "effectsgroups =\n"; - ++g_indent; - retval += m_effects[0]->Dump(); - --g_indent; - } else { - retval += DumpIndent() + "effectsgroups = [\n"; - ++g_indent; - for (std::shared_ptr effect : m_effects) { - retval += effect->Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; - } - retval += DumpIndent() + "graphic = \"" + m_graphic + "\"\n"; - --g_indent; - return retval; -} - -unsigned int FieldType::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, m_name); - CheckSums::CheckSumCombine(retval, m_description); - CheckSums::CheckSumCombine(retval, m_stealth); - CheckSums::CheckSumCombine(retval, m_tags); - CheckSums::CheckSumCombine(retval, m_effects); - CheckSums::CheckSumCombine(retval, m_graphic); - - return retval; -} - -///////////////////////////////////////////////// -// FieldTypeManager // -///////////////////////////////////////////////// -// static(s) -FieldTypeManager* FieldTypeManager::s_instance = nullptr; - -FieldTypeManager::FieldTypeManager() { - if (s_instance) - throw std::runtime_error("Attempted to create more than one FieldTypeManager."); - - try { - parse::fields(m_field_types); - } catch (const std::exception& e) { - ErrorLogger() << "Failed parsing fields: error: " << e.what(); - throw e; - } - - TraceLogger() << "Field Types:"; - for (const auto& entry : *this) - TraceLogger() << " ... " << entry.first; - - // Only update the global pointer on sucessful construction. - s_instance = this; - - DebugLogger() << "FieldTypeManager checksum: " << GetCheckSum(); -} - -FieldTypeManager::~FieldTypeManager() { - for (const std::map::value_type& entry : m_field_types) { - delete entry.second; - } -} - -const FieldType* FieldTypeManager::GetFieldType(const std::string& name) const { - std::map::const_iterator it = m_field_types.find(name); - return it != m_field_types.end() ? it->second : nullptr; -} - -FieldTypeManager& FieldTypeManager::GetFieldTypeManager() { - static FieldTypeManager manager; - return manager; -} - -unsigned int FieldTypeManager::GetCheckSum() const { - unsigned int retval{0}; - for (auto const& name_type_pair : m_field_types) - CheckSums::CheckSumCombine(retval, name_type_pair); - CheckSums::CheckSumCombine(retval, m_field_types.size()); - - return retval; -} - - -/////////////////////////////////////////////////////////// -// Free Functions // -/////////////////////////////////////////////////////////// -FieldTypeManager& GetFieldTypeManager() -{ return FieldTypeManager::GetFieldTypeManager(); } - -const FieldType* GetFieldType(const std::string& name) -{ return FieldTypeManager::GetFieldTypeManager().GetFieldType(name); } diff --git a/universe/Field.h b/universe/Field.h index 6a501f648d8..396755dc856 100644 --- a/universe/Field.h +++ b/universe/Field.h @@ -4,6 +4,7 @@ #include "UniverseObject.h" #include "../util/Export.h" +#include "../util/Pending.h" namespace Effect { class EffectsGroup; @@ -13,35 +14,33 @@ namespace Effect { class FO_COMMON_API Field : public UniverseObject { public: /** \name Accessors */ //@{ - std::set Tags() const override; + std::set Tags() const override; + bool HasTag(const std::string& name) const override; - bool HasTag(const std::string& name) const override; + UniverseObjectType ObjectType() const override; - UniverseObjectType ObjectType() const override; + std::string Dump(unsigned short ntabs = 0) const override; - std::string Dump() const override; + int ContainerObjectID() const override; + bool ContainedBy(int object_id) const override; - int ContainerObjectID() const override; + const std::string& PublicName(int empire_id) const override; + const std::string& FieldTypeName() const { return m_type_name; } - bool ContainedBy(int object_id) const override; + /* Field is (presently) the only distributed UniverseObject that isn't just + * location at a single point in space. These functions check if locations + * or objecs are within this field's area. */ + bool InField(std::shared_ptr obj) const; + bool InField(double x, double y) const; std::shared_ptr Accept(const UniverseObjectVisitor& visitor) const override; - - const std::string& PublicName(int empire_id) const override; - - void ClampMeters() override; - - const std::string& FieldTypeName() const { return m_type_name; } - - bool InField(std::shared_ptr obj) const; - - bool InField(double x, double y) const; //@} /** \name Mutators */ //@{ void Copy(std::shared_ptr copied_object, int empire_id = ALL_EMPIRES) override; void ResetTargetMaxUnpairedMeters() override; + void ClampMeters() override; //@} protected: @@ -49,111 +48,24 @@ class FO_COMMON_API Field : public UniverseObject { /** \name Structors */ //@{ Field(); - Field(const std::string& field_type, double x, double y, double radius); - - template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); - public: + Field(const std::string& field_type, double x, double y, double radius); ~Field(); protected: + template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); + /** Returns new copy of this Field. */ Field* Clone(int empire_id = ALL_EMPIRES) const override; //@} private: - std::string m_type_name; + std::string m_type_name; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -/** A specification for a type of field. */ -class FO_COMMON_API FieldType { -public: - /** \name Structors */ //@{ - FieldType(const std::string& name, const std::string& description, - float stealth, const std::set& tags, - const std::vector>& effects, - const std::string& graphic); - ~FieldType(); - //@} - - /** \name Accessors */ //@{ - const std::string& Name() const { return m_name; } ///< returns the unique name for this type of field - const std::string& Description() const { return m_description; } ///< returns a text description of this type of building - std::string Dump() const; ///< returns a data file format representation of this object - float Stealth() const { return m_stealth; } ///< returns stealth of field type - const std::set& Tags() const { return m_tags; } - - /** Returns the EffectsGroups that encapsulate the effects of thisi - FieldType. */ - const std::vector>& Effects() const - { return m_effects; } - - const std::string& Graphic() const { return m_graphic; } ///< returns the name of the grapic file for this field type - - /** Returns a number, calculated from the contained data, which should be - * different for different contained data, and must be the same for - * the same contained data, and must be the same on different platforms - * and executions of the program and the function. Useful to verify that - * the parsed content is consistent without sending it all between - * clients and server. */ - unsigned int GetCheckSum() const; - //@} - -private: - std::string m_name; - std::string m_description; - float m_stealth; - std::set m_tags; - std::vector> m_effects; - std::string m_graphic; -}; - - -class FieldTypeManager { -public: - typedef std::map::const_iterator iterator; - - /** \name Accessors */ //@{ - /** returns the field type with the name \a name; you should use the - * free function GetFieldType(...) instead, mainly to save some typing. */ - const FieldType* GetFieldType(const std::string& name) const; - - /** iterator to the first field type */ - iterator begin() const { return m_field_types.begin(); } - - /** iterator to the last + 1th field type */ - iterator end() const { return m_field_types.end(); } - - /** returns the instance of this singleton class; you should use the free - * function GetFieldTypeManager() instead */ - static FieldTypeManager& GetFieldTypeManager(); - - /** Returns a number, calculated from the contained data, which should be - * different for different contained data, and must be the same for - * the same contained data, and must be the same on different platforms - * and executions of the program and the function. Useful to verify that - * the parsed content is consistent without sending it all between - * clients and server. */ - unsigned int GetCheckSum() const; - //@} -private: - FieldTypeManager(); - ~FieldTypeManager(); - std::map m_field_types; - static FieldTypeManager* s_instance; -}; - -/** returns the singleton field type manager */ -FO_COMMON_API FieldTypeManager& GetFieldTypeManager(); - -/** Returns the BuildingType specification object for a field of - * type \a name. If no such FieldType exists, 0 is returned instead. */ -FO_COMMON_API const FieldType* GetFieldType(const std::string& name); - - -#endif // _Ship_h_ +#endif // _Field_h_ diff --git a/universe/FieldType.cpp b/universe/FieldType.cpp new file mode 100644 index 00000000000..ba7e9ef7bf7 --- /dev/null +++ b/universe/FieldType.cpp @@ -0,0 +1,143 @@ +#include "FieldType.h" + +#include +#include "ConditionSource.h" +#include "Effects.h" +#include "Enums.h" +#include "ValueRefs.h" +#include "UniverseObject.h" +#include "../util/CheckSums.h" + + +namespace { + std::shared_ptr + IncreaseMeter(MeterType meter_type, double increase) { + typedef std::vector> Effects; + auto scope = std::make_unique(); + std::unique_ptr activation = nullptr; + auto vr = + std::make_unique>( + ValueRef::PLUS, + std::make_unique>( + ValueRef::EFFECT_TARGET_VALUE_REFERENCE, std::vector()), + std::make_unique>(increase) + ); + auto effects = Effects(); + effects.push_back(std::make_unique(meter_type, std::move(vr))); + return std::make_shared(std::move(scope), std::move(activation), std::move(effects)); + } +} + + +FieldType::FieldType(const std::string& name, const std::string& description, + float stealth, const std::set& tags, + std::vector>&& effects, + const std::string& graphic) : + m_name(name), + m_description(description), + m_stealth(stealth), + m_tags(), + m_effects(), + m_graphic(graphic) +{ + for (const std::string& tag : tags) + m_tags.insert(boost::to_upper_copy(tag)); + + for (auto&& effect : effects) + m_effects.emplace_back(std::move(effect)); + + if (m_stealth != 0.0f) + m_effects.push_back(IncreaseMeter(METER_STEALTH, m_stealth)); + + for (auto& effect : m_effects) { + effect->SetTopLevelContent(m_name); + } +} + +std::string FieldType::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "FieldType\n"; + retval += DumpIndent(ntabs+1) + "name = \"" + m_name + "\"\n"; + retval += DumpIndent(ntabs+1) + "description = \"" + m_description + "\"\n"; + retval += DumpIndent(ntabs+1) + "location = \n"; + if (m_effects.size() == 1) { + retval += DumpIndent(ntabs+1) + "effectsgroups =\n"; + retval += m_effects[0]->Dump(ntabs+2); + } else { + retval += DumpIndent(ntabs+1) + "effectsgroups = [\n"; + for (auto& effect : m_effects) { + retval += effect->Dump(ntabs+2); + } + retval += DumpIndent(ntabs+1) + "]\n"; + } + retval += DumpIndent(ntabs+1) + "graphic = \"" + m_graphic + "\"\n"; + return retval; +} + +unsigned int FieldType::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_description); + CheckSums::CheckSumCombine(retval, m_stealth); + CheckSums::CheckSumCombine(retval, m_tags); + CheckSums::CheckSumCombine(retval, m_effects); + CheckSums::CheckSumCombine(retval, m_graphic); + + DebugLogger() << "FieldTypeManager checksum: " << retval; + return retval; +} + + +FieldTypeManager* FieldTypeManager::s_instance = nullptr; + +FieldTypeManager::FieldTypeManager() { + if (s_instance) + throw std::runtime_error("Attempted to create more than one FieldTypeManager."); + + // Only update the global pointer on sucessful construction. + s_instance = this; +} + +const FieldType* FieldTypeManager::GetFieldType(const std::string& name) const { + CheckPendingFieldTypes(); + auto it = m_field_types.find(name); + return it != m_field_types.end() ? it->second.get() : nullptr; +} + +FieldTypeManager::iterator FieldTypeManager::begin() const { + CheckPendingFieldTypes(); + return m_field_types.begin(); +} + +FieldTypeManager::iterator FieldTypeManager::end() const { + CheckPendingFieldTypes(); + return m_field_types.end(); +} + +FieldTypeManager& FieldTypeManager::GetFieldTypeManager() { + static FieldTypeManager manager; + return manager; +} + +unsigned int FieldTypeManager::GetCheckSum() const { + CheckPendingFieldTypes(); + unsigned int retval{0}; + for (auto const& name_type_pair : m_field_types) + CheckSums::CheckSumCombine(retval, name_type_pair); + CheckSums::CheckSumCombine(retval, m_field_types.size()); + + return retval; +} + +void FieldTypeManager::SetFieldTypes(Pending::Pending&& future) +{ m_pending_types = std::move(future); } + +void FieldTypeManager::CheckPendingFieldTypes() const +{ Pending::SwapPending(m_pending_types, m_field_types); } + + +FieldTypeManager& GetFieldTypeManager() +{ return FieldTypeManager::GetFieldTypeManager(); } + +const FieldType* GetFieldType(const std::string& name) +{ return FieldTypeManager::GetFieldTypeManager().GetFieldType(name); } diff --git a/universe/FieldType.h b/universe/FieldType.h new file mode 100644 index 00000000000..34a857c0980 --- /dev/null +++ b/universe/FieldType.h @@ -0,0 +1,124 @@ +#ifndef _FieldType_h_ +#define _FieldType_h_ + +#include +#include +#include +#include +#include +#include "../util/Export.h" +#include "../util/Pending.h" + + +namespace Effect { + class EffectsGroup; +} + + +//! A specification for a type of Field. +class FO_COMMON_API FieldType { +public: + FieldType(const std::string& name, const std::string& description, + float stealth, const std::set& tags, + std::vector>&& effects, + const std::string& graphic); + + //! Returns the unique name for this type of field + auto Name() const -> const std::string& + { return m_name; } + + //! Returns a text description of this type of building + auto Description() const -> const std::string& + { return m_description; } + + //! Returns a data file format representation of this object + auto Dump(unsigned short ntabs = 0) const -> std::string; + + //! Returns stealth of field type + auto Stealth() const -> float + { return m_stealth; } + + auto Tags() const -> const std::set& + { return m_tags; } + + //! Returns the EffectsGroups that encapsulate the effects of this + //! FieldType. + auto Effects() const -> const std::vector>& + { return m_effects; } + + //! Returns the name of the grapic file for this field type + auto Graphic() const -> const std::string& + { return m_graphic; } + + //! Returns a number, calculated from the contained data, which should be + //! different for different contained data, and must be the same for + //! the same contained data, and must be the same on different platforms + //! and executions of the program and the function. Useful to verify that + //! the parsed content is consistent without sending it all between + //! clients and server. + auto GetCheckSum() const -> unsigned int; + +private: + std::string m_name; + std::string m_description; + float m_stealth; + std::set m_tags; + std::vector> m_effects; + std::string m_graphic; +}; + + +class FieldTypeManager { +public: + using container_type = std::map>; + using iterator = container_type::const_iterator; + + //! Returns the field type with the name \a name; you should use the free + //! free function GetFieldType(...) instead, mainly to save some typing. + auto GetFieldType(const std::string& name) const -> const FieldType*; + + //! iterator to the first field type + FO_COMMON_API auto begin() const -> iterator; + + //! iterator to the last + 1th field type + FO_COMMON_API auto end() const -> iterator; + + //! Returns the instance of this singleton class; you should use the free + //! function GetFieldTypeManager() instead + static auto GetFieldTypeManager() -> FieldTypeManager&; + + //! Returns a number, calculated from the contained data, which should be + //! different for different contained data, and must be the same for + //! the same contained data, and must be the same on different platforms + //! and executions of the program and the function. Useful to verify that + //! the parsed content is consistent without sending it all between + //! clients and server. + auto GetCheckSum() const -> unsigned int; + + //! Sets types to the value of @p future. + FO_COMMON_API void SetFieldTypes(Pending::Pending&& future); + +private: + FieldTypeManager(); + + //! Assigns any m_pending_types to m_field_types. + void CheckPendingFieldTypes() const; + + //! Future types being parsed by parser. + //! mutable so that it can be assigned to m_field_types when completed. + mutable boost::optional> m_pending_types = boost::none; + + mutable container_type m_field_types; + + static FieldTypeManager* s_instance; +}; + + +//! Returns the singleton field type manager +FO_COMMON_API auto GetFieldTypeManager() -> FieldTypeManager&; + +//! Returns the BuildingType specification object for a field of +//! type @p name. If no such FieldType exists, nullptr is returned instead. +FO_COMMON_API auto GetFieldType(const std::string& name) -> const FieldType*; + +#endif // _FieldType_h_ diff --git a/universe/Fighter.cpp b/universe/Fighter.cpp index 30f27004e15..6d4a1579a07 100644 --- a/universe/Fighter.cpp +++ b/universe/Fighter.cpp @@ -2,17 +2,20 @@ #include "Predicates.h" #include "Enums.h" +#include "../Empire/EmpireManager.h" +#include "../util/AppInterface.h" #include "../util/Logger.h" Fighter::Fighter() {} -Fighter::Fighter(int empire_id, int launched_from_id, const std::string& species_name, float damage) : +Fighter::Fighter(int empire_id, int launched_from_id, const std::string& species_name, float damage, const ::Condition::Condition* combat_targets) : UniverseObject(), m_damage(damage), m_launched_from_id(launched_from_id), - m_species_name(species_name) + m_species_name(species_name), + m_combat_targets(combat_targets) { this->SetOwner(empire_id); UniverseObject::Init(); @@ -21,9 +24,19 @@ Fighter::Fighter(int empire_id, int launched_from_id, const std::string& species Fighter::~Fighter() {} +bool Fighter::HostileToEmpire(int empire_id) const { + if (OwnedBy(empire_id)) + return false; + return empire_id == ALL_EMPIRES || Unowned() || + Empires().GetDiplomaticStatus(Owner(), empire_id) == DIPLO_WAR; +} + UniverseObjectType Fighter::ObjectType() const { return OBJ_FIGHTER; } +const ::Condition::Condition* Fighter::CombatTargets() const +{ return m_combat_targets; } + float Fighter::Damage() const { return m_damage; } @@ -39,9 +52,9 @@ const std::string& Fighter::SpeciesName() const void Fighter::SetDestroyed(bool destroyed) { m_destroyed = destroyed; } -std::string Fighter::Dump() const { +std::string Fighter::Dump(unsigned short ntabs) const { std::stringstream os; - os << UniverseObject::Dump(); + os << UniverseObject::Dump(ntabs); os << " (Combat Fighter) damage: " << m_damage; if (m_destroyed) os << " (DESTROYED)"; @@ -69,5 +82,6 @@ void Fighter::Copy(std::shared_ptr copied_object, int empi UniverseObject::Copy(copied_object, VIS_FULL_VISIBILITY, std::set()); this->m_damage = copied_fighter->m_damage; - this->m_destroyed= copied_fighter->m_destroyed; + this->m_destroyed = copied_fighter->m_destroyed; + this->m_combat_targets = copied_fighter->m_combat_targets; } diff --git a/universe/Fighter.h b/universe/Fighter.h index 8191d4fc810..b2ea4c0e790 100644 --- a/universe/Fighter.h +++ b/universe/Fighter.h @@ -2,6 +2,7 @@ #define _Fighter_h_ #include "UniverseObject.h" +#include "../universe/Condition.h" #include "../util/Export.h" //////////////////////////////////////////////// @@ -11,18 +12,16 @@ * can be stored in the same container as other combat objects. */ class FO_COMMON_API Fighter : public UniverseObject { public: - Fighter(int empire_id, int launched_from_id, const std::string& species_name, float damage); + Fighter(int empire_id, int launched_from_id, const std::string& species_name, float damage, const ::Condition::Condition* combat_targets); Fighter(); ~Fighter(); + bool HostileToEmpire(int empire_id) const override; UniverseObjectType ObjectType() const override; - - std::string Dump() const override; - + std::string Dump(unsigned short ntabs = 0) const override; std::shared_ptr Accept(const UniverseObjectVisitor& visitor) const override; - void Copy(std::shared_ptr copied_object, int empire_id = ALL_EMPIRES) override; - + const ::Condition::Condition* CombatTargets() const; float Damage() const; bool Destroyed() const; int LaunchedFrom() const; @@ -37,6 +36,7 @@ class FO_COMMON_API Fighter : public UniverseObject { bool m_destroyed = false; // was attacked by anything -> destroyed int m_launched_from_id = INVALID_OBJECT_ID; // from what object (ship?) was this fighter launched std::string m_species_name; + const ::Condition::Condition* m_combat_targets; }; -#endif // _Ship_h_ +#endif // _Fighter_h_ diff --git a/universe/Fleet.cpp b/universe/Fleet.cpp index 0809aaf0b09..4eabc6115e8 100644 --- a/universe/Fleet.cpp +++ b/universe/Fleet.cpp @@ -10,14 +10,13 @@ #include "../util/i18n.h" #include "../util/Logger.h" #include "../util/ScopedTimer.h" +#include "../util/AppInterface.h" #include "../Empire/Empire.h" #include "../Empire/EmpireManager.h" #include "../Empire/Supply.h" +#include #include -#include - -#include namespace { const bool ALLOW_ALLIED_SUPPLY = true; @@ -29,54 +28,53 @@ namespace { bool SystemHasNoVisibleStarlanes(int system_id, int empire_id) { return !GetPathfinder()->SystemHasVisibleStarlanes(system_id, empire_id); } - template - double PathLength(const IT& begin, const IT& end) { - if (begin == end) { - return 0; - } else { - double distance = 0.0; - for (std::list::const_iterator it = begin; it != end; ++it) { - std::list::const_iterator next_it = it; ++next_it; - //DebugLogger() << "Fleet::SetRoute() new route has system id " << *it; - - if (next_it == end) - break; // current system is the last on the route, so don't need to add any additional distance. - - std::shared_ptr cur_sys = GetSystem(*it); - - if (!cur_sys) { - ErrorLogger() << "Fleet::SetRoute() couldn't get system with id " << *it; - return distance; - } - - std::shared_ptr next_sys = GetSystem(*next_it); - if (!next_sys) { - ErrorLogger() << "Fleet::SetRoute() couldn't get system with id " << *next_it; - return distance; - } + void MoveFleetWithShips(Fleet& fleet, double x, double y){ + fleet.MoveTo(x, y); - double dist_x = next_sys->X() - cur_sys->X(); - double dist_y = next_sys->Y() - cur_sys->Y(); - distance += std::sqrt(dist_x*dist_x + dist_y*dist_y); - } - return distance; + for (auto& ship : Objects().find(fleet.ShipIDs())) { + ship->MoveTo(x, y); } } - void MoveFleetWithShips(std::shared_ptr& fleet, double x, double y){ - fleet->MoveTo(x, y); + void InsertFleetWithShips(Fleet& fleet, std::shared_ptr& system){ + system->Insert(fleet.shared_from_this()); - for (std::shared_ptr ship : Objects().FindObjects(fleet->ShipIDs())) { - ship->MoveTo(x, y); + for (auto& ship : Objects().find(fleet.ShipIDs())) { + system->Insert(ship); } } - void InsertFleetWithShips(std::shared_ptr& fleet, std::shared_ptr& system){ - system->Insert(fleet); + /** Return \p full_route terminates at \p last_system or before the first + system not known to the \p empire_id. */ + std::list TruncateRouteToEndAtSystem(const std::list& full_route, int empire_id, int last_system) { - for (std::shared_ptr ship : Objects().FindObjects(fleet->ShipIDs())) { - system->Insert(ship); + if (full_route.empty() || (last_system == INVALID_OBJECT_ID)) + return std::list(); + + auto visible_end_it = full_route.cend(); + if (last_system != full_route.back()) { + visible_end_it = std::find(full_route.begin(), full_route.end(), last_system); + + // if requested last system not in route, do nothing + if (visible_end_it == full_route.end()) + return std::list(); + + ++visible_end_it; } + + // Remove any extra systems from the route after the apparent destination. + // SystemHasNoVisibleStarlanes determines if empire_id knows about the + // system and/or its starlanes. It is enforced on the server in the + // visibility calculations that an owning empire knows about a) the + // system containing a fleet, b) the starlane on which a fleet is travelling + // and c) both systems terminating a starlane on which a fleet is travelling. + auto end_it = std::find_if(full_route.begin(), visible_end_it, + boost::bind(&SystemHasNoVisibleStarlanes, _1, empire_id)); + + std::list truncated_route; + std::copy(full_route.begin(), end_it, std::back_inserter(truncated_route)); + + return truncated_route; } } @@ -106,7 +104,7 @@ Fleet* Fleet::Clone(int empire_id) const { void Fleet::Copy(std::shared_ptr copied_object, int empire_id) { if (copied_object.get() == this) return; - std::shared_ptr copied_fleet = std::dynamic_pointer_cast(copied_object); + auto copied_fleet = std::dynamic_pointer_cast(copied_object); if (!copied_fleet) { ErrorLogger() << "Fleet::Copy passed an object that wasn't a Fleet"; return; @@ -114,66 +112,55 @@ void Fleet::Copy(std::shared_ptr copied_object, int empire int copied_object_id = copied_object->ID(); Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(copied_object_id, empire_id); - std::set visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id); + auto visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id); UniverseObject::Copy(copied_object, vis, visible_specials); if (vis >= VIS_BASIC_VISIBILITY) { - this->m_ships = copied_fleet->VisibleContainedObjectIDs(empire_id); + m_ships = copied_fleet->VisibleContainedObjectIDs(empire_id); - this->m_next_system = copied_fleet->m_next_system; - this->m_prev_system = copied_fleet->m_prev_system; - this->m_arrived_this_turn = copied_fleet->m_arrived_this_turn; - this->m_arrival_starlane = copied_fleet->m_arrival_starlane; + m_next_system = ((EmpireKnownObjects(empire_id).get(copied_fleet->m_next_system)) + ? copied_fleet->m_next_system : INVALID_OBJECT_ID); + m_prev_system = ((EmpireKnownObjects(empire_id).get(copied_fleet->m_prev_system)) + ? copied_fleet->m_prev_system : INVALID_OBJECT_ID); + m_arrived_this_turn = copied_fleet->m_arrived_this_turn; + m_arrival_starlane = copied_fleet->m_arrival_starlane; if (vis >= VIS_PARTIAL_VISIBILITY) { - this->m_aggressive = copied_fleet->m_aggressive; - if (this->Unowned()) - this->m_name = copied_fleet->m_name; + m_aggressive = copied_fleet->m_aggressive; + if (Unowned()) + m_name = copied_fleet->m_name; - if (vis >= VIS_FULL_VISIBILITY) { - this->m_travel_route = copied_fleet->m_travel_route; - this->m_travel_distance = copied_fleet->m_travel_distance; - this->m_ordered_given_to_empire_id =copied_fleet->m_ordered_given_to_empire_id; + // Truncate the travel route to only systems known to empire_id + int moving_to = (vis >= VIS_FULL_VISIBILITY + ? (!copied_fleet->m_travel_route.empty() + ? copied_fleet->m_travel_route.back() + : INVALID_OBJECT_ID) + : m_next_system); - } else { - int moving_to = copied_fleet->m_next_system; - std::list travel_route; - double travel_distance = copied_fleet->m_travel_distance; - - const std::list& copied_fleet_route = copied_fleet->m_travel_route; - - m_travel_route.clear(); - if (!copied_fleet->m_travel_route.empty()) - m_travel_route.push_back(moving_to); - ShortenRouteToEndAtSystem(travel_route, moving_to); - if (!travel_route.empty() - && travel_route.front() != INVALID_OBJECT_ID - && travel_route.size() != copied_fleet_route.size()) - { - try { - travel_distance -= GetPathfinder()->ShortestPath(travel_route.back(), - copied_fleet_route.back()).second; - } catch (...) { - DebugLogger() << "Fleet::Copy couldn't find route to system(s):" - << " travel route back: " << travel_route.back() - << " or copied fleet route back: " << copied_fleet_route.back(); - } - } + m_travel_route = TruncateRouteToEndAtSystem(copied_fleet->m_travel_route, empire_id, moving_to); - this->m_travel_route = travel_route; - this->m_travel_distance = travel_distance; - } + + if (vis >= VIS_FULL_VISIBILITY) + m_ordered_given_to_empire_id =copied_fleet->m_ordered_given_to_empire_id; } } } +bool Fleet::HostileToEmpire(int empire_id) const +{ + if (OwnedBy(empire_id)) + return false; + return empire_id == ALL_EMPIRES || Unowned() || + Empires().GetDiplomaticStatus(Owner(), empire_id) == DIPLO_WAR; +} + UniverseObjectType Fleet::ObjectType() const { return OBJ_FLEET; } -std::string Fleet::Dump() const { +std::string Fleet::Dump(unsigned short ntabs) const { std::stringstream os; - os << UniverseObject::Dump(); + os << UniverseObject::Dump(ntabs); os << ( m_aggressive ? " agressive" : " passive") << " cur system: " << SystemID() << " moving to: " << FinalDestinationID() @@ -181,7 +168,7 @@ std::string Fleet::Dump() const { << " next system: " << m_next_system << " arrival lane: " << m_arrival_starlane << " ships: "; - for (std::set::const_iterator it = m_ships.begin(); it != m_ships.end();) { + for (auto it = m_ships.begin(); it != m_ships.end();) { int ship_id = *it; ++it; os << ship_id << (it == m_ships.end() ? "" : ", "); @@ -196,7 +183,7 @@ const std::set& Fleet::ContainedObjectIDs() const { return m_ships; } bool Fleet::Contains(int object_id) const -{ return object_id != INVALID_OBJECT_ID && m_ships.find(object_id) != m_ships.end(); } +{ return object_id != INVALID_OBJECT_ID && m_ships.count(object_id); } bool Fleet::ContainedBy(int object_id) const { return object_id != INVALID_OBJECT_ID && this->SystemID() == object_id; } @@ -215,6 +202,26 @@ const std::string& Fleet::PublicName(int empire_id) const { return UserString("OBJ_FLEET"); } +int Fleet::MaxShipAgeInTurns() const { + if (m_ships.empty()) + return INVALID_OBJECT_AGE; + + bool fleet_is_scrapped = true; + int retval = 0; + for (const auto& ship : Objects().find(m_ships)) { + if (!ship || ship->OrderedScrapped()) + continue; + if (ship->AgeInTurns() > retval) + retval = ship->AgeInTurns(); + fleet_is_scrapped = false; + } + + if (fleet_is_scrapped) + retval = 0; + + return retval; +} + const std::list& Fleet::TravelRoute() const { return m_travel_route; } @@ -231,43 +238,49 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b if (route.size() == 2 && route.front() == route.back()) return retval; // nowhere to go => empty path if (this->Speed() < FLEET_MOVEMENT_EPSILON) { - retval.push_back(MovePathNode(this->X(), this->Y(), true, ETA_NEVER, this->SystemID(), INVALID_OBJECT_ID, INVALID_OBJECT_ID)); + retval.emplace_back(this->X(), this->Y(), true, ETA_NEVER, + this->SystemID(), + INVALID_OBJECT_ID, + INVALID_OBJECT_ID, + false); return retval; // can't move => path is just this system with explanatory ETA } float fuel = Fuel(); float max_fuel = MaxFuel(); - //DebugLogger() << "Fleet " << this->Name() << " movePath fuel: " << fuel << " sys id: " << this->SystemID(); + auto RouteNums = [route]() { + std::stringstream ss; + for (int waypoint : route) + ss << waypoint << " "; + return ss.str(); + }; + TraceLogger() << "Fleet::MovePath for Fleet " << this->Name() << " (" << this->ID() + << ") fuel: " << fuel << " at sys id: " << this->SystemID() << " route: " + << RouteNums(); // determine all systems where fleet(s) can be resupplied if fuel runs out const Empire* empire = GetEmpire(this->Owner()); - const std::set fleet_supplied_systems = GetSupplyManager().FleetSupplyableSystemIDs(this->Owner(), ALLOW_ALLIED_SUPPLY); - const std::set& unobstructed_systems = empire ? empire->SupplyUnobstructedSystems() : EMPTY_SET; + auto fleet_supplied_systems = GetSupplyManager().FleetSupplyableSystemIDs(this->Owner(), ALLOW_ALLIED_SUPPLY); + auto& unobstructed_systems = empire ? empire->SupplyUnobstructedSystems() : EMPTY_SET; // determine if, given fuel available and supplyable systems, fleet will ever be able to move if (fuel < 1.0f && this->SystemID() != INVALID_OBJECT_ID && - fleet_supplied_systems.find(this->SystemID()) == fleet_supplied_systems.end()) + !fleet_supplied_systems.count(this->SystemID())) { - MovePathNode node(this->X(), this->Y(), true, ETA_OUT_OF_RANGE, - this->SystemID(), - INVALID_OBJECT_ID, - INVALID_OBJECT_ID); - retval.push_back(node); + retval.emplace_back(this->X(), this->Y(), true, ETA_OUT_OF_RANGE, + this->SystemID(), + INVALID_OBJECT_ID, + INVALID_OBJECT_ID); return retval; // can't move => path is just this system with explanatory ETA } - // blockade debug logging - //DebugLogger() << "Fleet::MovePath for fleet " << this->Name() << " ID(" << this->ID() <<") and route:"; - //for (int waypoint : route) - // DebugLogger() << "Fleet::MovePath ... " << waypoint; - //DebugLogger() << "Fleet::MovePath END of Route "; // get iterator pointing to std::shared_ptr on route that is the first after where this fleet is currently. // if this fleet is in a system, the iterator will point to the system after the current in the route // if this fleet is not in a system, the iterator will point to the first system in the route - std::list::const_iterator route_it = route.begin(); + auto route_it = route.begin(); if (*route_it == SystemID()) ++route_it; // first system in route is current system of this fleet. skip to the next system if (route_it == route.end()) @@ -275,44 +288,45 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b // get current, previous and next systems of fleet - std::shared_ptr cur_system = GetSystem(this->SystemID()); // may be 0 - std::shared_ptr prev_system = GetSystem(this->PreviousSystemID());// may be 0 if this fleet is not moving or ordered to move - std::shared_ptr next_system = GetSystem(*route_it); // can't use this->NextSystemID() because this fleet may not be moving and may not have a next system. this might occur when a fleet is in a system, not ordered to move or ordered to move to a system, but a projected fleet move line is being calculated to a different system + auto cur_system = Objects().get(this->SystemID()); // may be 0 + auto prev_system = Objects().get(this->PreviousSystemID()); // may be 0 if this fleet is not moving or ordered to move + auto next_system = Objects().get(*route_it); // can't use this->NextSystemID() because this fleet may not be moving and may not have a next system. this might occur when a fleet is in a system, not ordered to move or ordered to move to a system, but a projected fleet move line is being calculated to a different system if (!next_system) { - ErrorLogger() << "Fleet::MovePath couldn't get next system with id " << *route_it << " for this fleet " << this->Name(); + ErrorLogger() << "Fleet::MovePath couldn't get next system with id " << *route_it << " for fleet " << this->Name() << "(" << this->ID() << ")"; return retval; } - //DebugLogger() << "initial cur system: " << (cur_system ? cur_system->Name() : "(none)") << - // " prev system: " << (prev_system ? prev_system->Name() : "(none)") << - // " next system: " << (next_system ? next_system->Name() : "(none)"); + TraceLogger() << "Initial cur system: " << (cur_system ? cur_system->Name() : "(none)") << "(" << (cur_system ? cur_system->ID() : -1) << ")" + << " prev system: " << (prev_system ? prev_system->Name() : "(none)") << "(" << (prev_system ? prev_system->ID() : -1) << ")" + << " next system: " << (next_system ? next_system->Name() : "(none)") << "(" << (next_system ? next_system->ID() : -1) << ")"; bool is_post_blockade = false; if (cur_system) { //DebugLogger() << "Fleet::MovePath starting in system "<< SystemID(); - if (flag_blockades && next_system->ID() != m_arrival_starlane && - (unobstructed_systems.find(cur_system->ID()) == unobstructed_systems.end())) - { - //DebugLogger() << "Fleet::MovePath checking blockade from "<< cur_system->ID() << " to "<< next_system->ID(); - if (BlockadedAtSystem(cur_system->ID(), next_system->ID())){ + if (flag_blockades) { + if (BlockadedAtSystem(cur_system->ID(), next_system->ID())) { // blockade debug logging - //DebugLogger() << "Fleet::MovePath finds system " <Name() << " (" <ID() << - // ") blockaded for fleet " << this->Name(); + TraceLogger() << "Fleet::MovePath checking blockade from "<< cur_system->ID() << " to "<< next_system->ID(); + TraceLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" <ID() + << ") blockaded for fleet " << this->Name(); is_post_blockade = true; } else { - // blockade debug logging - //DebugLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" << cur_system->ID() << - // ") NOT blockaded for fleet " << this->Name(); + // blockade debug logging, but only for the more complex situations + if (next_system->ID() != m_arrival_starlane && !unobstructed_systems.count(cur_system->ID())) { + TraceLogger() << "Fleet::MovePath checking blockade from "<< cur_system->ID() << " to " << next_system->ID(); + TraceLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" << cur_system->ID() + << ") NOT blockaded for fleet " << this->Name(); + } } } } // place initial position MovePathNode - MovePathNode initial_pos(this->X(), this->Y(), false /* not an end of turn node */, 0 /* turns taken to reach position of node */, - (cur_system ? cur_system->ID() : INVALID_OBJECT_ID), - (prev_system ? prev_system->ID() : INVALID_OBJECT_ID), - (next_system ? next_system->ID() : INVALID_OBJECT_ID)); - retval.push_back(initial_pos); + retval.emplace_back(this->X(), this->Y(), false, 0, + (cur_system ? cur_system->ID() : INVALID_OBJECT_ID), + (prev_system ? prev_system->ID() : INVALID_OBJECT_ID), + (next_system ? next_system->ID() : INVALID_OBJECT_ID), + false); const int TOO_LONG = 100; // limit on turns to simulate. 99 turns max keeps ETA to two digits, making UI work better @@ -322,7 +336,6 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b double cur_y = this->Y(); double next_x = next_system->X(); double next_y = next_system->Y(); - bool stopped_at_system = bool(cur_system); // simulate fleet movement given known speed, starting position, fuel limit and systems on route // need to populate retval with MovePathNodes that indicate the correct position, whether this @@ -337,11 +350,10 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b // each loop iteration moves the current position to the next location of interest along the move // path, and then adds a node at that position. - //DebugLogger() << " starting iteration"; - //if (cur_system) - // DebugLogger() << " at system " << cur_system->Name() << " with id " << cur_system->ID(); - //else - // DebugLogger() << " at (" << cur_x << ", " << cur_y << ")"; + if (cur_system) + TraceLogger() << "Starting iteration at system " << cur_system->Name() << " (" << cur_system->ID() << ")"; + else + TraceLogger() << "Starting iteration at (" << cur_x << ", " << cur_y << ")"; // Make sure that there actually still is a starlane between the two systems // we are between @@ -353,23 +365,21 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b } else { ErrorLogger() << "Fleet::MovePath: No previous or current system!?"; } - if (prev_or_cur) { - if (!prev_or_cur->HasStarlaneTo(next_system->ID())) { - DebugLogger() << "Fleet::MovePath: No starlane connection between systems " << prev_or_cur->ID() << " and " << next_system->ID() - << ". Abandoning the rest of the route."; - return retval; - } + if (prev_or_cur && !prev_or_cur->HasStarlaneTo(next_system->ID())) { + DebugLogger() << "Fleet::MovePath for Fleet " << this->Name() << " (" << this->ID() + << ") No starlane connection between systems " << prev_or_cur->Name() << "(" << prev_or_cur->ID() + << ") and " << next_system->Name() << "(" << next_system->ID() + << "). Abandoning the rest of the route. Route was: " << RouteNums(); + return retval; } // check if fuel limits movement or current system refuels passing fleet if (cur_system) { // check if current system has fuel supply available - if (fleet_supplied_systems.find(cur_system->ID()) != fleet_supplied_systems.end()) { - // current system has fuel supply. don't restrict movement - // if had just stopped here, then replenish fleet's supply - if (stopped_at_system) - fuel = max_fuel; + if (fleet_supplied_systems.count(cur_system->ID())) { + // current system has fuel supply. replenish fleet's supply and don't restrict movement + fuel = max_fuel; //DebugLogger() << " ... at system with fuel supply. replenishing and continuing movement"; } else { @@ -386,7 +396,6 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b } } - stopped_at_system = false; // find distance to next system along path from current position double dist_to_next_system = std::sqrt((next_x - cur_x)*(next_x - cur_x) + (next_y - cur_y)*(next_y - cur_y)); //DebugLogger() << " ... dist to next system: " << dist_to_next_system; @@ -439,10 +448,7 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b cur_x = cur_system->X(); // update positions to ensure no round-off-errors cur_y = cur_system->Y(); - //DebugLogger() << " ... arrived at system: " << cur_system->Name(); - - if (end_turn_at_cur_position) - stopped_at_system = true; + TraceLogger() << " ... arrived at system: " << cur_system->Name(); bool clear_exit = cur_system->ID() == m_arrival_starlane; //just part of the test for the moment // attempt to get next system on route, to update next system. if new current @@ -450,24 +456,26 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b ++route_it; if (route_it != route.end()) { // update next system on route and distance to it from current position - next_system = GetEmpireKnownSystem(*route_it, this->Owner()); + next_system = EmpireKnownObjects(this->Owner()).get(*route_it); if (next_system) { - //DebugLogger() << "Fleet::MovePath checking unrestriced lane travel"; - clear_exit = clear_exit || (next_system && next_system->ID() == m_arrival_starlane) || - (empire && empire->UnrestrictedLaneTravel(cur_system->ID(), next_system->ID())); + TraceLogger() << "Fleet::MovePath checking unrestriced lane travel from Sys(" + << cur_system->ID() << ") to Sys(" << (next_system && next_system->ID()) << ")"; + clear_exit = clear_exit || next_system->ID() == m_arrival_starlane || + (empire && empire->PreservedLaneTravel(cur_system->ID(), next_system->ID())); } } if (flag_blockades && !clear_exit) { - //DebugLogger() << "Fleet::MovePath checking blockades at system " << cur_system->Name() << " (" << cur_system->ID() << - // ") for fleet " << this->Name() << " travelling to system " << (*route_it); + TraceLogger() << "Fleet::MovePath checking blockades at system " << cur_system->Name() + << " (" << cur_system->ID() << ") for fleet " << this->Name() + << " travelling to system " << (*route_it); if (cur_system && next_system && BlockadedAtSystem(cur_system->ID(), next_system->ID())) { // blockade debug logging - //DebugLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" << cur_system->ID() << - // ") blockaded for fleet " << this->Name(); + TraceLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" << cur_system->ID() + << ") blockaded for fleet " << this->Name(); is_post_blockade = true; } else { - //DebugLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" << cur_system->ID() << - // ") NOT blockaded for fleet " << this->Name(); + TraceLogger() << "Fleet::MovePath finds system " << cur_system->Name() << " (" << cur_system->ID() + << ") NOT blockaded for fleet " << this->Name(); } } @@ -485,7 +493,7 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b // if new position is an obstructed system, must end turn here // on client side, if have stale info on cur_system it may appear blockaded even if not actually obstructed, // and so will force a stop in that situation - if (cur_system && (unobstructed_systems.find(cur_system->ID()) == unobstructed_systems.end())) { + if (cur_system && !unobstructed_systems.count(cur_system->ID())) { turn_dist_remaining = 0.0; end_turn_at_cur_position = true; } @@ -498,16 +506,16 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b } // blockade debug logging - //DebugLogger() << "Fleet::MovePath for fleet " << this->Name() << " id " << this->ID() << " adding node at sysID " << - // (cur_system ? cur_system->ID() : INVALID_OBJECT_ID) << " with post blockade status " << is_post_blockade << - // " and ETA " << turns_taken; + TraceLogger() << "Fleet::MovePath for fleet " << this->Name() << " id " << this->ID() << " adding node at sysID " + << (cur_system ? cur_system->ID() : INVALID_OBJECT_ID) << " with post blockade status " + << is_post_blockade << " and ETA " << turns_taken; // add MovePathNode for current position (end of turn position and/or system location) - MovePathNode cur_pos(cur_x, cur_y, end_turn_at_cur_position, turns_taken, - (cur_system ? cur_system->ID() : INVALID_OBJECT_ID), - (prev_system ? prev_system->ID() : INVALID_OBJECT_ID), - (next_system ? next_system->ID() : INVALID_OBJECT_ID), is_post_blockade); - retval.push_back(cur_pos); + retval.emplace_back(cur_x, cur_y, end_turn_at_cur_position, turns_taken, + (cur_system ? cur_system->ID() : INVALID_OBJECT_ID), + (prev_system ? prev_system->ID() : INVALID_OBJECT_ID), + (next_system ? next_system->ID() : INVALID_OBJECT_ID), + is_post_blockade); // if the turn ended at this position, increment the turns taken and @@ -525,16 +533,16 @@ std::list Fleet::MovePath(const std::list& route, bool flag_b if (turns_taken == TOO_LONG) turns_taken = ETA_NEVER; // blockade debug logging - //DebugLogger() << "Fleet::MovePath for fleet " << this->Name()<<" id "<ID()<<" adding node at sysID "<< - // (cur_system ? cur_system->ID() : INVALID_OBJECT_ID) << " with post blockade status " << is_post_blockade << - // " and ETA " << turns_taken; + TraceLogger() << "Fleet::MovePath for fleet " << this->Name()<<" id "<ID()<<" adding node at sysID " + << (cur_system ? cur_system->ID() : INVALID_OBJECT_ID) << " with post blockade status " + << is_post_blockade << " and ETA " << turns_taken; - MovePathNode final_pos(cur_x, cur_y, true, turns_taken, - (cur_system ? cur_system->ID() : INVALID_OBJECT_ID), - (prev_system ? prev_system->ID() : INVALID_OBJECT_ID), - (next_system ? next_system->ID() : INVALID_OBJECT_ID), is_post_blockade); - retval.push_back(final_pos); - //DebugLogger() << "Fleet::MovePath for fleet " << this->Name()<<" id "<ID()<<" is complete"; + retval.emplace_back(cur_x, cur_y, true, turns_taken, + (cur_system ? cur_system->ID() : INVALID_OBJECT_ID), + (prev_system ? prev_system->ID() : INVALID_OBJECT_ID), + (next_system ? next_system->ID() : INVALID_OBJECT_ID), + is_post_blockade); + TraceLogger() << "Fleet::MovePath for fleet " << this->Name() << "(" << this->ID() << ") is complete"; return retval; } @@ -556,7 +564,7 @@ std::pair Fleet::ETA(const std::list& move_path) const { // general case: there is a multi-node path. return the ETA of the first object node, and the ETA of the last node int last_stop_eta = move_path.rbegin()->eta; int first_stop_eta = last_stop_eta; - for (std::list::const_iterator it = ++(move_path.begin()); it != move_path.end(); ++it) { + for (auto it = ++(move_path.begin()); it != move_path.end(); ++it) { const MovePathNode& node = *it; if (node.object_id != INVALID_OBJECT_ID) { first_stop_eta = node.eta; @@ -575,7 +583,7 @@ float Fleet::Fuel() const { float fuel = Meter::LARGE_VALUE; bool is_fleet_scrapped = true; - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { + for (auto& ship : Objects().find(m_ships)) { const Meter* meter = ship->UniverseObject::GetMeter(METER_FUEL); if (!meter) { ErrorLogger() << "Fleet::Fuel skipping ship with no fuel meter"; @@ -601,7 +609,7 @@ float Fleet::MaxFuel() const { float max_fuel = Meter::LARGE_VALUE; bool is_fleet_scrapped = true; - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { + for (auto& ship : Objects().find(m_ships)) { const Meter* meter = ship->UniverseObject::GetMeter(METER_MAX_FUEL); if (!meter) { ErrorLogger() << "Fleet::MaxFuel skipping ship with no max fuel meter"; @@ -626,72 +634,76 @@ int Fleet::FinalDestinationID() const { } } -bool Fleet::HasMonsters() const { - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { - if (ship->IsMonster()) - return true; +namespace { + bool HasXShips(const std::function&)>& pred, + const std::set& ship_ids) + { + // Searching for each Ship one at a time is faster than + // find(ship_ids), because an early exit avoids searching the + // remaining ids. + return std::any_of( + ship_ids.begin(), ship_ids.end(), + [&pred](const int ship_id) { + const auto& ship = Objects().get(ship_id); + if (!ship) { + WarnLogger() << "Object map is missing ship with expected id " << ship_id; + return false; + } + return pred(ship); + }); } - return false; +} + +bool Fleet::HasMonsters() const { + auto isX = [](const std::shared_ptr& ship){ return ship->IsMonster(); }; + return HasXShips(isX, m_ships); } bool Fleet::HasArmedShips() const { - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { - if (ship->IsArmed()) - return true; - } - return false; + auto isX = [](const std::shared_ptr& ship){ return ship->IsArmed(); }; + return HasXShips(isX, m_ships); } bool Fleet::HasFighterShips() const { - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { - if (ship->HasFighters()) - return true; - } - return false; + auto isX = [](const std::shared_ptr& ship){ return ship->HasFighters(); }; + return HasXShips(isX, m_ships); } bool Fleet::HasColonyShips() const { - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { + auto isX = [](const std::shared_ptr& ship) { if (ship->CanColonize()) - if (const ShipDesign* design = ship->Design()) - if (design->ColonyCapacity() > 0.0) + if (const auto design = ship->Design()) + if (design->ColonyCapacity() > 0.0f) return true; - } - return false; + return false; + }; + return HasXShips(isX, m_ships); } bool Fleet::HasOutpostShips() const { - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { + auto isX = [](const std::shared_ptr& ship) { if (ship->CanColonize()) - if (const ShipDesign* design = ship->Design()) - if (design->ColonyCapacity() == 0.0) + if (const auto design = ship->Design()) + if (design->ColonyCapacity() == 0.0f) return true; - } - return false; + return false; + }; + return HasXShips(isX, m_ships); } bool Fleet::HasTroopShips() const { - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { - if (ship->HasTroops()) - return true; - } - return false; + auto isX = [](const std::shared_ptr& ship){ return ship->HasTroops(); }; + return HasXShips(isX, m_ships); } bool Fleet::HasShipsOrderedScrapped() const { - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { - if (ship->OrderedScrapped()) - return true; - } - return false; + auto isX = [](const std::shared_ptr& ship){ return ship->OrderedScrapped(); }; + return HasXShips(isX, m_ships); } bool Fleet::HasShipsWithoutScrapOrders() const { - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { - if (!ship->OrderedScrapped()) - return true; - } - return false; + auto isX = [](const std::shared_ptr& ship){ return !ship->OrderedScrapped(); }; + return HasXShips(isX, m_ships); } float Fleet::ResourceOutput(ResourceType type) const { @@ -703,7 +715,7 @@ float Fleet::ResourceOutput(ResourceType type) const { return output; // determine resource output of each ship in this fleet - for (std::shared_ptr ship : Objects().FindObjects(m_ships)) { + for (auto& ship : Objects().find(m_ships)) { output += ship->CurrentMeterValue(meter_type); } return output; @@ -716,8 +728,6 @@ std::shared_ptr Fleet::Accept(const UniverseObjectVisitor& visit { return visitor.Visit(std::const_pointer_cast(std::static_pointer_cast(shared_from_this()))); } void Fleet::SetRoute(const std::list& route) { - //DebugLogger() << "Fleet::SetRoute() "; - if (UnknownRoute()) throw std::invalid_argument("Fleet::SetRoute() : Attempted to set an unknown route."); @@ -726,40 +736,59 @@ void Fleet::SetRoute(const std::list& route) { m_travel_route = route; - // Moving to where we are is not moving at all + TraceLogger() << "Fleet::SetRoute: " << this->Name() << " (" << this->ID() << ") input: " << [&]() { + std::stringstream ss; + for (int id : m_travel_route) + if (const auto obj = Objects().get(id)) + ss << obj->Name() << " (" << id << ") "; + return ss.str(); + }(); + if (m_travel_route.size() == 1 && this->SystemID() == m_travel_route.front()) { + // Fleet was ordered to move to the system where it is currently, + // without an intermediate stop m_travel_route.clear(); - m_next_system = INVALID_OBJECT_ID; - } - - - // calculate length of line segments between systems on route, and sum up to determine length of route between - // systems on route. (Might later add distance from fleet to first system on route to this to get the total - // route length, or this may itself be the total route length if the fleet is at the first system on the route). - m_travel_distance = PathLength(m_travel_route.begin(), m_travel_route.end()); - + m_prev_system = m_next_system = INVALID_OBJECT_ID; - if (!m_travel_route.empty()) { - // if we're already moving, add in the distance from where we are to the first system in the route - if (SystemID() != route.front()) { - std::shared_ptr starting_system = GetSystem(route.front()); - if (!starting_system) { - ErrorLogger() << "Fleet::SetRoute couldn't get system with id " << route.front(); - return; - } - double dist_x = starting_system->X() - this->X(); - double dist_y = starting_system->Y() - this->Y(); - m_travel_distance += std::sqrt(dist_x*dist_x + dist_y*dist_y); - } + } else if (!m_travel_route.empty()) { + // Fleet was given a route to follow... if (m_prev_system != SystemID() && m_prev_system == m_travel_route.front()) { - m_prev_system = m_next_system; // if already in transit and turning around, swap prev and next + // Fleet was ordered to return to its previous system directly + m_prev_system = m_next_system; } else if (SystemID() == route.front()) { + // Fleet was ordered to follow a route that starts at its current system m_prev_system = SystemID(); } - std::list::const_iterator it = m_travel_route.begin(); - m_next_system = m_prev_system == SystemID() ? (*++it) : (*it); + + auto it = m_travel_route.begin(); + if (m_prev_system == SystemID() && m_travel_route.size() > 1) { + m_next_system = *++it; + } else { + m_next_system = *it; + } + + } else if (m_travel_route.empty() && this->SystemID() != INVALID_OBJECT_ID) { + // route is empty and fleet is in a system. no movement needed. + // ensure next and previous systems are reset + m_prev_system = m_next_system = INVALID_OBJECT_ID; + + } else { // m_travel_route.empty() && this->SystemID() == INVALID_OBJECT_ID + if (m_next_system != INVALID_OBJECT_ID) { + ErrorLogger() << "Fleet::SetRoute fleet " << this->Name() << " has empty route but fleet is not in a system. Resetting route to end at next system: " << m_next_system; + m_travel_route.push_back(m_next_system); + } else { + ErrorLogger() << "Fleet::SetRoute fleet " << this->Name() << " has empty route but fleet is not in a system, and has no next system set."; + } } + TraceLogger() << "Fleet::SetRoute: " << this->Name() << " (" << this->ID() << ") final: " << [&]() { + std::stringstream ss; + for (int id : m_travel_route) + if (const auto obj = Objects().get(id)) + ss << obj->Name() << " (" << id << ") "; + return ss.str(); + }(); + StateChangedSignal(); } @@ -770,12 +799,6 @@ void Fleet::SetAggressive(bool aggressive/* = true*/) { StateChangedSignal(); } -void Fleet::AddShip(int ship_id) { - std::vector ship_ids; - ship_ids.push_back(ship_id); - AddShips(ship_ids); -} - void Fleet::AddShips(const std::vector& ship_ids) { size_t old_ships_size = m_ships.size(); std::copy(ship_ids.begin(), ship_ids.end(), std::inserter(m_ships, m_ships.end())); @@ -783,12 +806,6 @@ void Fleet::AddShips(const std::vector& ship_ids) { StateChangedSignal(); } -void Fleet::RemoveShip(int ship_id) { - std::vector ship_ids; - ship_ids.push_back(ship_id); - RemoveShips(ship_ids); -} - void Fleet::RemoveShips(const std::vector& ship_ids) { size_t old_ships_size = m_ships.size(); for (int ship_id : ship_ids) @@ -804,112 +821,130 @@ void Fleet::SetNextAndPreviousSystems(int next, int prev) { } void Fleet::MovementPhase() { - //DebugLogger() << "Fleet::MovementPhase this: " << this->Name() << " id: " << this->ID(); - - std::shared_ptr fleet = std::dynamic_pointer_cast(shared_from_this()); - if (fleet.get() != this) { - ErrorLogger() << "Fleet::MovementPhase was passed a std::shared_ptr different from itself."; - return; - } - - Empire* empire = GetEmpire(fleet->Owner()); + Empire* empire = GetEmpire(Owner()); std::set supply_unobstructed_systems; if (empire) supply_unobstructed_systems.insert(empire->SupplyUnobstructedSystems().begin(), empire->SupplyUnobstructedSystems().end()); - std::vector> ships = Objects().FindObjects(m_ships); + auto ships = Objects().find(m_ships); // if owner of fleet can resupply ships at the location of this fleet, then // resupply all ships in this fleet - if (GetSupplyManager().SystemHasFleetSupply(fleet->SystemID(), fleet->Owner(), ALLOW_ALLIED_SUPPLY)) { - for (std::shared_ptr ship : ships) { + if (GetSupplyManager().SystemHasFleetSupply(SystemID(), Owner(), ALLOW_ALLIED_SUPPLY)) { + for (auto& ship : ships) ship->Resupply(); - } } - std::shared_ptr current_system = GetSystem(fleet->SystemID()); - std::shared_ptr initial_system = current_system; - std::list move_path = fleet->MovePath(); + auto current_system = Objects().get(SystemID()); + auto initial_system = current_system; + auto move_path = MovePath(); + + if (!move_path.empty()) { + DebugLogger() << "Fleet::MovementPhase " << this->Name() << " (" << this->ID() + << ") route:" << [&]() { + std::stringstream ss; + for (auto sys_id : this->TravelRoute()) { + auto sys = Objects().get(sys_id); + if (sys) + ss << " " << sys->Name() << " (" << sys_id << ")"; + else + ss << " (??\?) (" << sys_id << ")"; + } + return ss.str(); + }() + << " move path:" << [&]() { + std::stringstream ss; + for (auto node : move_path) { + auto sys = Objects().get(node.object_id); + if (sys) + ss << " " << sys->Name() << " (" << node.object_id << ")"; + else + ss << " (-)"; + } + return ss.str(); + }(); + } else { + // enforce m_next_system and m_prev_system being INVALID_OBJECT_ID when + // move path is empty. bug was reported where m_next_system was somehow + // left with a system ID in it, which was never reset, and lead to + // supply propagation issues + m_next_system = m_prev_system = INVALID_OBJECT_ID; + } - // If the move path cannot lead to our destination, - // make our route take us as far as it can + // If the move path cannot lead to the destination, + // make the route go as far as it can if (!move_path.empty() && !m_travel_route.empty() && move_path.back().object_id != m_travel_route.back()) { - std::list shortened_route = fleet->TravelRoute(); - fleet->ShortenRouteToEndAtSystem(shortened_route, move_path.back().object_id); - fleet->SetRoute(shortened_route); - move_path = fleet->MovePath(); + auto shortened_route = TruncateRouteToEndAtSystem(m_travel_route, Owner(), move_path.back().object_id); + try { + SetRoute(shortened_route); + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception in Fleet MovementPhase shorentning route: " << e.what(); + } + move_path = MovePath(); } - std::list::const_iterator it = move_path.begin(); - std::list::const_iterator next_it = it; + auto it = move_path.begin(); + auto next_it = it; if (next_it != move_path.end()) ++next_it; // is the fleet stuck in a system for a whole turn? if (current_system) { - ///update m_arrival_starlane if no blockade, if needed - if (supply_unobstructed_systems.find(fleet->SystemID()) != supply_unobstructed_systems.end()) { - fleet->m_arrival_starlane = fleet->SystemID();//allows departure via any starlane - } - - // in a system. if there is no system after the current one in the - // path, or the current and next nodes have the same system id, that - // is an actual system, or if blockaded from intended path, then won't - // be moving this turn. + // update m_arrival_starlane if no blockade, if needed + if (supply_unobstructed_systems.count(SystemID())) + m_arrival_starlane = SystemID();// allows departure via any starlane + + // in a system. if either: + // - there is no system after the current one in the path + // - the current and next nodes are the same and are system IDs or actual systems (not empty space) + // - this fleet is blockaded from its intended path + // then this fleet won't be moving further this turn bool stopped = false; + if (next_it == move_path.end()) { + // at end of path stopped = true; } else if (it->object_id != INVALID_OBJECT_ID && it->object_id == next_it->object_id) { + // arriving at system stopped = true; - } else if (fleet->m_arrival_starlane != fleet->SystemID()) { + } else if (m_arrival_starlane != SystemID()) { + // blockaded int next_sys_id; if (next_it->object_id != INVALID_OBJECT_ID) { next_sys_id = next_it->object_id; } else { next_sys_id = next_it->lane_end_id; } - stopped = fleet->BlockadedAtSystem(fleet->SystemID(), next_sys_id); + stopped = BlockadedAtSystem(SystemID(), next_sys_id); } - if (stopped) { - //// fuel regeneration for ships in stationary fleet - //if (fleet->FinalDestinationID() == INVALID_OBJECT_ID || - // fleet->FinalDestinationID() == fleet->SystemID()) - //{ - // for (std::shared_ptr ship : ships) { - // if (Meter* fuel_meter = ship->UniverseObject::GetMeter(METER_FUEL)) { - // fuel_meter->AddToCurrent(0.1001f); // .0001 to prevent rounding down - // fuel_meter->BackPropagate(); - // } - // } - //} + if (stopped) return; - } else { - // record previous system on fleet's path, and the starlane along - // which it will arrive at the next system (for blockading purposes) - fleet->m_arrival_starlane = fleet->SystemID(); - fleet->m_prev_system = fleet->SystemID(); - - // remove fleet and ships from system they are departing - current_system->Remove(fleet->ID()); - fleet->SetSystem(INVALID_OBJECT_ID); - for (std::shared_ptr ship : ships) { - current_system->Remove(ship->ID()); - ship->SetSystem(INVALID_OBJECT_ID); - } + // record previous system on fleet's path, and the starlane along + // which it will arrive at the next system (for blockading purposes) + m_arrival_starlane = SystemID(); + m_prev_system = SystemID(); + + // remove fleet and ships from system they are departing + current_system->Remove(ID()); + SetSystem(INVALID_OBJECT_ID); + for (auto& ship : ships) { + current_system->Remove(ship->ID()); + ship->SetSystem(INVALID_OBJECT_ID); } } // if fleet not moving, nothing more to do. - if (move_path.empty() || move_path.size() == 1) { + if (move_path.empty()) { + m_next_system = m_prev_system = INVALID_OBJECT_ID; return; } @@ -919,7 +954,7 @@ void Fleet::MovementPhase() { for (it = move_path.begin(); it != move_path.end(); ++it) { next_it = it; ++next_it; - std::shared_ptr system = GetSystem(it->object_id); + auto system = Objects().get(it->object_id); // is this system the last node reached this turn? either it's an end of turn node, // or there are no more nodes after this one on path @@ -930,27 +965,14 @@ void Fleet::MovementPhase() { // node is a system. explore system for all owners of this fleet if (empire) { empire->AddExploredSystem(it->object_id); - empire->RecordPendingLaneUpdate(it->object_id, fleet->m_prev_system); // specifies the lane from it->object_id back to m_prev_system is available + empire->RecordPendingLaneUpdate(it->object_id, m_prev_system); // specifies the lane from it->object_id back to m_prev_system is available } - fleet->m_prev_system = system->ID(); // passing a system, so update previous system of this fleet + m_prev_system = system->ID(); // passing a system, so update previous system of this fleet // reached a system, so remove it from the route - if (fleet->m_travel_route.front() == system->ID()) { + if (m_travel_route.front() == system->ID()) m_travel_route.erase(m_travel_route.begin()); - } else { - std::stringstream ss; - for (const auto& loc : m_travel_route) { - auto route_system = GetSystem(loc); - ss << (route_system ? route_system->Name() : "invalid id") << "("<< loc << ") "; - } - ErrorLogger() << "Fleet::MovementPhase: Encountered an unexpected system on route." - << " Fleet is " << fleet->Name() << "(" << fleet->ID() << ")" - << " Expected system is " << system->Name() << "(" << system->ID() << ")" - << " but front of route is " << fleet->m_travel_route.front() - << " Whole route is [" << ss.str() <<"]"; - // TODO: Notify the suer with a sitrep? - } bool resupply_here = GetSupplyManager().SystemHasFleetSupply(system->ID(), this->Owner(), ALLOW_ALLIED_SUPPLY); @@ -958,7 +980,7 @@ void Fleet::MovementPhase() { if (resupply_here) { //DebugLogger() << " ... node has fuel supply. consumed fuel for movement reset to 0 and fleet resupplied"; fuel_consumed = 0.0f; - for (std::shared_ptr ship : ships) { + for (auto& ship : ships) { ship->Resupply(); } } @@ -967,18 +989,22 @@ void Fleet::MovementPhase() { // is system the last node reached this turn? if (node_is_next_stop) { // fleet ends turn at this node. insert fleet and ships into system - InsertFleetWithShips(fleet, system); + InsertFleetWithShips(*this, system); current_system = system; - if (supply_unobstructed_systems.find(fleet->SystemID()) != supply_unobstructed_systems.end()) { - fleet->m_arrival_starlane = fleet->SystemID();//allows departure via any starlane - } + if (supply_unobstructed_systems.count(SystemID())) + m_arrival_starlane = SystemID();//allows departure via any starlane + + // Add current system to the start of any existing route for next turn + if (!m_travel_route.empty() && m_travel_route.front() != SystemID()) + m_travel_route.push_front(SystemID()); + break; } else { // fleet will continue past this system this turn. - fleet->m_arrival_starlane = fleet->m_prev_system; + m_arrival_starlane = m_prev_system; if (!resupply_here) { fuel_consumed += 1.0f; } @@ -986,9 +1012,9 @@ void Fleet::MovementPhase() { } else { // node is not a system. - fleet->m_arrival_starlane = fleet->m_prev_system; + m_arrival_starlane = m_prev_system; if (node_is_next_stop) { // node is not a system, but is it the last node reached this turn? - MoveFleetWithShips(fleet, it->x, it->y); + MoveFleetWithShips(*this, it->x, it->y); break; } } @@ -996,26 +1022,26 @@ void Fleet::MovementPhase() { // update next system - if (!fleet->m_travel_route.empty() && next_it != move_path.end() && it != move_path.end()) { + if (!m_travel_route.empty() && next_it != move_path.end() && it != move_path.end()) { // there is another system later on the path to aim for. find it for (; next_it != move_path.end(); ++next_it) { - if (GetSystem(next_it->object_id)) { + if (Objects().get(next_it->object_id)) { //DebugLogger() << "___ setting m_next_system to " << next_it->object_id; - fleet->m_next_system = next_it->object_id; + m_next_system = next_it->object_id; break; } } } else { // no more systems on path - fleet->m_arrived_this_turn = current_system != initial_system; - fleet->m_next_system = fleet->m_prev_system = INVALID_OBJECT_ID; + m_arrived_this_turn = current_system != initial_system; + m_next_system = m_prev_system = INVALID_OBJECT_ID; } // consume fuel from ships in fleet if (fuel_consumed > 0.0f) { - for (std::shared_ptr ship : ships) { + for (auto& ship : ships) { if (Meter* meter = ship->UniverseObject::GetMeter(METER_FUEL)) { meter->AddToCurrent(-fuel_consumed); meter->BackPropagate(); @@ -1041,16 +1067,24 @@ void Fleet::CalculateRouteTo(int target_system_id) { //DebugLogger() << "Fleet::CalculateRoute"; if (target_system_id == INVALID_OBJECT_ID) { - SetRoute(route); + try { + SetRoute(route); + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception in Fleet CalculateRouteTo: " << e.what(); + } return; } if (m_prev_system != INVALID_OBJECT_ID && SystemID() == m_prev_system) { // if we haven't actually left yet, we have to move from whichever system we are at now - if (!GetSystem(target_system_id)) { + if (!Objects().get(target_system_id)) { // destination system doesn't exist or doesn't exist in known universe, so can't move to it. leave route empty. - SetRoute(route); + try { + SetRoute(route); + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception in Fleet CalculateRouteTo: " << e.what(); + } return; } @@ -1061,37 +1095,16 @@ void Fleet::CalculateRouteTo(int target_system_id) { DebugLogger() << "Fleet::CalculateRoute couldn't find route to system(s):" << " fleet's previous: " << m_prev_system << " or moving to: " << target_system_id; } - SetRoute(path.first); + try { + SetRoute(path.first); + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception in Fleet CalculateRouteTo: " << e.what(); + } return; } - int dest_system_id = target_system_id; - // Geoff: commenting out the early exit code of the owner of a fleet doesn't - // have visibility of the destination system, since this was preventing the - // human client's attempts to find routes for enemy fleets, for which the - // player's client doesn't know which systems are visible, and since - // visibility of a system on the current turn isn't necessary to plot - // route to it now that empire's can remember systems they've seen on - // previous turns. - //if (universe.GetObjectVisibilityByEmpire(dest_system_id, this->Owner()) <= VIS_NO_VISIBILITY) { - // // destination system isn't visible to this fleet's owner, so the fleet can't move to it - // - // // check if system to which fleet is moving is visible to the fleet's owner. this should always be true, but just in case... - // if (universe.GetObjectVisibilityByEmpire(m_next_system, this->Owner()) <= VIS_NO_VISIBILITY) - // return; // next system also isn't visible; leave route empty. - // - // // safety check: ensure supposedly visible object actually exists in known universe. - // if (!GetSystem(m_next_system)) { - // ErrorLogger() << "Fleet::CalculateRoute found system with id " << m_next_system << " should be visible to this fleet's owner, but the system doesn't exist in the known universe!"; - // return; // abort if object doesn't exist in known universe... can't path to it if it's not there, even if it's considered visible for some reason... - // } - // - // // next system is visible, so move to that instead of ordered destination (m_moving_to) - // dest_system_id = m_next_system; - //} - // if we're between systems, the shortest route may be through either one if (this->CanChangeDirectionEnRoute()) { std::pair, double> path1; @@ -1101,12 +1114,12 @@ void Fleet::CalculateRouteTo(int target_system_id) { DebugLogger() << "Fleet::CalculateRoute couldn't find route to system(s):" << " fleet's next: " << m_next_system << " or destination: " << dest_system_id; } - const std::list& sys_list1 = path1.first; + auto& sys_list1 = path1.first; if (sys_list1.empty()) { ErrorLogger() << "Fleet::CalculateRoute got empty route from ShortestPath"; return; } - std::shared_ptr obj = GetUniverseObject(sys_list1.front()); + auto obj = Objects().get(sys_list1.front()); if (!obj) { ErrorLogger() << "Fleet::CalculateRoute couldn't get path start object with id " << path1.first.front(); return; @@ -1122,12 +1135,12 @@ void Fleet::CalculateRouteTo(int target_system_id) { DebugLogger() << "Fleet::CalculateRoute couldn't find route to system(s):" << " fleet's previous: " << m_prev_system << " or destination: " << dest_system_id; } - const std::list& sys_list2 = path2.first; + auto& sys_list2 = path2.first; if (sys_list2.empty()) { ErrorLogger() << "Fleet::CalculateRoute got empty route from ShortestPath"; return; } - obj = GetUniverseObject(sys_list2.front()); + obj = Objects().get(sys_list2.front()); if (!obj) { ErrorLogger() << "Fleet::CalculateRoute couldn't get path start object with id " << path2.first.front(); return; @@ -1136,11 +1149,15 @@ void Fleet::CalculateRouteTo(int target_system_id) { dist_y = obj->Y() - this->Y(); double dist2 = std::sqrt(dist_x*dist_x + dist_y*dist_y); - // pick whichever path is quicker - if (dist1 + path1.second < dist2 + path2.second) { - SetRoute(path1.first); - } else { - SetRoute(path2.first); + try { + // pick whichever path is quicker + if (dist1 + path1.second < dist2 + path2.second) { + SetRoute(path1.first); + } else { + SetRoute(path2.first); + } + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception in Fleet CalculateRouteTo: " << e.what(); } } else { @@ -1152,18 +1169,23 @@ void Fleet::CalculateRouteTo(int target_system_id) { DebugLogger() << "Fleet::CalculateRoute couldn't find route to system(s):" << " fleet's next: " << m_next_system << " or destination: " << dest_system_id; } - SetRoute(path.first); + try { + SetRoute(path.first); + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception in Fleet CalculateRouteTo: " << e.what(); + } } } bool Fleet::Blockaded() const { - /** returns true if fleet is in a system and is prevented from using at least - one starlane entry to leave its current system */ - std::shared_ptr system = GetSystem(this->SystemID()); + auto system = Objects().get(this->SystemID()); if (!system) return false; + if (m_next_system != INVALID_OBJECT_ID) + return BlockadedAtSystem(SystemID(), m_next_system); + for (const auto& target_system : system->StarlanesWormholes()) { if (BlockadedAtSystem(this->SystemID(), target_system.first)) return true; @@ -1182,7 +1204,7 @@ bool Fleet::BlockadedAtSystem(int start_system_id, int dest_system_id) const { * system post-combat which can detect this fleet. Fleets arriving at the * same time do not blockade each other. Unrestricted lane access (i.e, * (fleet->ArrivalStarlane() == system->ID()) ) is used as a proxy for - * order of arrival -- if an enemy has unrestricted l*ane access and you + * order of arrival -- if an enemy has unrestricted lane access and you * don't, they must have arrived before you, or be in cahoots with someone * who did. */ @@ -1192,41 +1214,43 @@ bool Fleet::BlockadedAtSystem(int start_system_id, int dest_system_id) const { } bool not_yet_in_system = SystemID() != start_system_id; + if (!not_yet_in_system && m_arrival_starlane == dest_system_id) + return false; + // find which empires have blockading aggressive armed ships in system; // fleets that just arrived do not blockade by themselves, but may // reinforce a preexisting blockade, and may possibly contribute to detection - std::shared_ptr current_system = GetSystem(start_system_id); + auto current_system = Objects().get(start_system_id); if (!current_system) { DebugLogger() << "Fleet::BlockadedAtSystem fleet " << ID() << " considering system (" << start_system_id << ") but can't retrieve system copy"; return false; } - const Empire* empire = GetEmpire(this->Owner()); + auto empire = GetEmpire(this->Owner()); if (empire) { - std::set unobstructed_systems = empire->SupplyUnobstructedSystems(); - if (unobstructed_systems.find(start_system_id) != unobstructed_systems.end()) + auto unobstructed_systems = empire->SupplyUnobstructedSystems(); + if (unobstructed_systems.count(start_system_id)) return false; - if (empire->UnrestrictedLaneTravel(start_system_id, dest_system_id)) { + if (empire->PreservedLaneTravel(start_system_id, dest_system_id)) { return false; } else { - //DebugLogger() << "Fleet::BlockadedAtSystem fleet " << ID() << " considering travel from system (" << start_system_id << ") to system (" << dest_system_id << ")"; + TraceLogger() << "Fleet::BlockadedAtSystem fleet " << ID() << " considering travel from system (" << start_system_id << ") to system (" << dest_system_id << ")"; } } float lowest_ship_stealth = 99999.9f; // arbitrary large number. actual stealth of ships should be less than this... - for (std::shared_ptr ship : Objects().FindObjects(this->ShipIDs())) { + for (auto& ship : Objects().find(this->ShipIDs())) { if (lowest_ship_stealth > ship->CurrentMeterValue(METER_STEALTH)) lowest_ship_stealth = ship->CurrentMeterValue(METER_STEALTH); } float monster_detection = 0.0f; - std::vector> fleets = - Objects().FindObjects(current_system->FleetIDs()); - for (std::shared_ptr fleet : fleets) { + auto fleets = Objects().find(current_system->FleetIDs()); + for (auto& fleet : fleets) { if (!fleet->Unowned()) continue; - for (std::shared_ptr ship : Objects().FindObjects(fleet->ShipIDs())) { + for (auto& ship : Objects().find(fleet->ShipIDs())) { float cur_detection = ship->CurrentMeterValue(METER_DETECTION); if (cur_detection >= monster_detection) monster_detection = cur_detection; @@ -1234,7 +1258,7 @@ bool Fleet::BlockadedAtSystem(int start_system_id, int dest_system_id) const { } bool can_be_blockaded = false; - for (std::shared_ptr fleet : fleets) { + for (auto& fleet : fleets) { if (fleet->NextSystemID() != INVALID_OBJECT_ID) //fleets trying to leave this turn can't blockade pre-combat. continue; bool unrestricted = (fleet->m_arrival_starlane == start_system_id); @@ -1243,23 +1267,42 @@ bool Fleet::BlockadedAtSystem(int start_system_id, int dest_system_id) const { return false; continue; } + if (!unrestricted && !not_yet_in_system) + continue; + bool can_see; - if (!fleet->Unowned()) { + if (!fleet->Unowned()) can_see = (GetEmpire(fleet->Owner())->GetMeter("METER_DETECTION_STRENGTH")->Current() >= lowest_ship_stealth); - } else { + else can_see = (monster_detection >= lowest_ship_stealth); - } + if (!can_see) + continue; + bool at_war = Unowned() || fleet->Unowned() || Empires().GetDiplomaticStatus(this->Owner(), fleet->Owner()) == DIPLO_WAR; + if (!at_war) + continue; bool aggressive = (fleet->Aggressive() || fleet->Unowned()); + if (!aggressive) + continue; + // Newly created ships/monsters are not allowed to block other fleet movement since they have not even + // potentially gone through a combat round at the present location. Potential sources for such new ships are + // monsters created via Effect and Ships/fleets newly constructed by empires. We check ship ages not fleet + // ageas since fleets can be created/destroyed as purely organizational matters. Since these checks are + // pertinent just during those stages of turn processing immediately following turn number advancement, + // whereas the new ships were created just prior to turn advamcenemt, we require age greater than 1. + if (fleet->MaxShipAgeInTurns() <= 1) + continue; + // These are the most costly checks. Do them last + if (!fleet->HasArmedShips()) + continue; + + // don't exit early here, because blockade may yet be thwarted by ownership & presence check above + can_be_blockaded = true; - if (aggressive && (fleet->HasArmedShips() || fleet->HasFighterShips()) && at_war && can_see && (unrestricted || not_yet_in_system)) - can_be_blockaded = true; // don't exit early here, because blockade may yet be thwarted by ownership & presence check above - } - if (can_be_blockaded) { - return true; } - return false; + + return can_be_blockaded; } float Fleet::Speed() const { @@ -1268,8 +1311,7 @@ float Fleet::Speed() const { bool fleet_is_scrapped = true; float retval = MAX_SHIP_SPEED; // max speed no ship can go faster than - for (int ship_id : m_ships) { - std::shared_ptr ship = GetShip(ship_id); + for (const auto& ship : Objects().find(m_ships)) { if (!ship || ship->OrderedScrapped()) continue; if (ship->Speed() < retval) @@ -1289,15 +1331,12 @@ float Fleet::Damage() const { bool fleet_is_scrapped = true; float retval = 0.0f; - for (int ship_id : m_ships) { - if (std::shared_ptr ship = GetShip(ship_id)) { - if (!ship->OrderedScrapped()) { - if (const ShipDesign* design = ship->Design()){ - retval += design->Attack(); - } - fleet_is_scrapped = false; - } - } + for (const auto& ship : Objects().find(m_ships)) { + if (!ship || ship->OrderedScrapped()) + continue; + if (const auto design = ship->Design()) + retval += design->Attack(); + fleet_is_scrapped = false; } if (fleet_is_scrapped) @@ -1312,13 +1351,11 @@ float Fleet::Structure() const { bool fleet_is_scrapped = true; float retval = 0.0f; - for (int ship_id : m_ships) { - if (std::shared_ptr ship = GetShip(ship_id)) { - if (!ship->OrderedScrapped()) { - retval += ship->CurrentMeterValue(METER_STRUCTURE); - fleet_is_scrapped = false; - } - } + for (const auto& ship : Objects().find(m_ships)) { + if (!ship || ship->OrderedScrapped()) + continue; + retval += ship->CurrentMeterValue(METER_STRUCTURE); + fleet_is_scrapped = false; } if (fleet_is_scrapped) @@ -1333,13 +1370,11 @@ float Fleet::Shields() const { bool fleet_is_scrapped = true; float retval = 0.0f; - for (int ship_id : m_ships) { - if (std::shared_ptr ship = GetShip(ship_id)) { - if (!ship->OrderedScrapped()) { - retval += ship->CurrentMeterValue(METER_SHIELD); - fleet_is_scrapped = false; - } - } + for (const auto& ship : Objects().find(m_ships)) { + if (!ship || ship->OrderedScrapped()) + continue; + retval += ship->CurrentMeterValue(METER_SHIELD); + fleet_is_scrapped = false; } if (fleet_is_scrapped) @@ -1348,29 +1383,9 @@ float Fleet::Shields() const { return retval; } -void Fleet::ShortenRouteToEndAtSystem(std::list& travel_route, int last_system) { - std::list::iterator visible_end_it; - if (last_system != FinalDestinationID()) { - visible_end_it = std::find(m_travel_route.begin(), m_travel_route.end(), last_system); - - // if requested last system not in route, do nothing - if (visible_end_it == m_travel_route.end()) - return; - - ++visible_end_it; - - } else { - visible_end_it = m_travel_route.end(); - } - - // remove any extra systems from the route after the apparent destination - std::list::iterator end_it = std::find_if(m_travel_route.begin(), visible_end_it, boost::bind(&SystemHasNoVisibleStarlanes, _1, this->Owner())); - std::copy(m_travel_route.begin(), end_it, std::back_inserter(travel_route)); - - // If no Systems in a nonempty route are known reachable, default to just - // showing the next system on the route. - if (travel_route.empty() && !m_travel_route.empty()) - travel_route.push_back(*m_travel_route.begin()); +namespace { + bool IsCombatShip(const Ship& ship) + { return ship.IsArmed() || ship.HasFighters() || ship.CanHaveTroops() || ship.CanBombard(); } } std::string Fleet::GenerateFleetName() { @@ -1380,94 +1395,28 @@ std::string Fleet::GenerateFleetName() { return UserString("NEW_FLEET_NAME_NO_NUMBER"); std::vector> ships; - for (int ship_id : m_ships) { - if (std::shared_ptr ship = GetShip(ship_id)) { - ships.push_back(ship); - } - } - - std::vector>::iterator it = ships.begin(); - - // TODO C++11 replace all of these loops with std::all_of - bool all_monster(true); - while (it != ships.end()) { - if (!(*it)->IsMonster()) { - all_monster = false; - break; - } - ++it; - } - - if (all_monster) - return boost::io::str(FlexibleFormat(UserString("NEW_MONSTER_FLEET_NAME")) % ID()); - - bool all_colony(true); - it = ships.begin(); - while (it != ships.end()) { - if (!(*it)->CanColonize()) { - all_colony = false; - break; - } - ++it; - } - - if (all_colony) - return boost::io::str(FlexibleFormat(UserString("NEW_COLONY_FLEET_NAME")) % ID()); - - bool all_non_coms(true); - it = ships.begin(); - while (it != ships.end()) { - if ((*it)->IsArmed() || (*it)->HasFighters() || (*it)->CanHaveTroops() || (*it)->CanBombard()) { - all_non_coms = false; - break; - } - ++it; - } - - if (all_non_coms) - return boost::io::str(FlexibleFormat(UserString("NEW_RECON_FLEET_NAME")) % ID()); - - bool all_troop(true); - it = ships.begin(); - while (it != ships.end()) { - if (!(*it)->CanHaveTroops()) { - all_troop = false; - break; - } - ++it; - } - - if (all_troop) - return boost::io::str(FlexibleFormat(UserString("NEW_TROOP_FLEET_NAME")) % ID()); - - bool all_bombard(true); - it = ships.begin(); - while (it != ships.end()) { - if (!(*it)->CanBombard()) { - all_bombard = false; - break; - } - ++it; - } - - if (all_bombard) - return boost::io::str(FlexibleFormat(UserString("NEW_BOMBARD_FLEET_NAME")) % ID()); - - bool mixed_combat(true); - it = ships.begin(); - while (it != ships.end()) { - if (!((*it)->IsArmed() || (*it)->HasFighters() || (*it)->CanHaveTroops() || (*it)->CanBombard())) { - mixed_combat = false; - break; - } - ++it; + for (const auto& ship : Objects().find(m_ships)) { + if (!ship) + continue; + ships.push_back(ship); } - if (mixed_combat) - return boost::io::str(FlexibleFormat(UserString("NEW_BATTLE_FLEET_NAME")) % ID()); - - - return boost::io::str(FlexibleFormat(UserString("NEW_FLEET_NAME")) % ID()); + std::string fleet_name_key = UserStringNop("NEW_FLEET_NAME"); + + if (boost::algorithm::all_of(ships, [](const auto& ship){ return ship->IsMonster(); })) + fleet_name_key = UserStringNop("NEW_MONSTER_FLEET_NAME"); + else if (boost::algorithm::all_of(ships, [](const auto& ship){ return ship->CanColonize(); })) + fleet_name_key = UserStringNop("NEW_COLONY_FLEET_NAME"); + else if (boost::algorithm::all_of(ships, [](const auto& ship){ return !IsCombatShip(*ship); })) + fleet_name_key = UserStringNop("NEW_RECON_FLEET_NAME"); + else if (boost::algorithm::all_of(ships, [](const auto& ship){ return ship->CanHaveTroops(); })) + fleet_name_key = UserStringNop("NEW_TROOP_FLEET_NAME"); + else if (boost::algorithm::all_of(ships, [](const auto& ship){ return ship->CanBombard(); })) + fleet_name_key = UserStringNop("NEW_BOMBARD_FLEET_NAME"); + else if (boost::algorithm::all_of(ships, [](const auto& ship){ return IsCombatShip(*ship); })) + fleet_name_key = UserStringNop("NEW_BATTLE_FLEET_NAME"); + + return boost::io::str(FlexibleFormat(UserString(fleet_name_key)) % ID()); } void Fleet::SetGiveToEmpire(int empire_id) { diff --git a/universe/Fleet.h b/universe/Fleet.h index 25e89521c4e..32e9689454d 100644 --- a/universe/Fleet.h +++ b/universe/Fleet.h @@ -30,9 +30,11 @@ struct MovePathNode { class FO_COMMON_API Fleet : public UniverseObject { public: /** \name Accessors */ //@{ + bool HostileToEmpire(int empire_id) const override; + UniverseObjectType ObjectType() const override; - std::string Dump() const override; + std::string Dump(unsigned short ntabs = 0) const override; int ContainerObjectID() const override; @@ -47,6 +49,7 @@ class FO_COMMON_API Fleet : public UniverseObject { std::shared_ptrAccept(const UniverseObjectVisitor& visitor) const override; const std::set& ShipIDs() const { return m_ships; } ///< returns set of IDs of ships in fleet. + int MaxShipAgeInTurns() const; ///< Returns the age of the oldest ship in the fleet /** Returns the list of systems that this fleet will move through en route * to its destination (may be empty). If this fleet is currently at a @@ -64,44 +67,45 @@ class FO_COMMON_API Fleet : public UniverseObject { std::list MovePath(bool flag_blockades = false) const; ///< Returns MovePath for fleet's current TravelRoute std::pair ETA() const; ///< Returns the number of turns which must elapse before the fleet arrives at its current final destination and the turns to the next system, respectively. std::pair ETA(const std::list& move_path) const; ///< Returns the number of turns which must elapse before the fleet arrives at the final destination and next system in the spepcified \a move_path - float Damage() const; ///< Returns total amount of damage this fleet has, which is the sum of the ships' damage - float Structure() const; ///< Returns total amount of structure this fleet has, which is the sum of the ships' structure - float Shields() const; ///< Returns total amount of shields this fleet has, which is the sum of the ships' shields - float Fuel() const; ///< Returns effective amount of fuel this fleet has, which is the least of the amounts of fuel that the ships have - float MaxFuel() const; ///< Returns effective maximum amount of fuel this fleet has, which is the least of the max amounts of fuel that the ships can have - int FinalDestinationID() const; ///< Returns ID of system that this fleet is moving to or INVALID_OBJECT_ID if staying still. - int PreviousSystemID() const { return m_prev_system; } ///< Returns ID of system that this fleet is moving away from as it moves to its destination. - int NextSystemID() const { return m_next_system; } ///< Returns ID of system that this fleet is moving to next as it moves to its destination. - bool Blockaded() const; ///< returns true iff at least one system exit is blocked for this fleet - bool BlockadedAtSystem(int start_system_id, int dest_system_id) const; ///< returns true iff this fleet's movement would be blockaded at system. - float Speed() const; ///< Returns speed of fleet. (Should be equal to speed of slowest ship in fleet, unless in future the calculation of fleet speed changes.) - bool CanChangeDirectionEnRoute() const { return false; } ///< Returns true iff this fleet can change its direction while in interstellar space. - bool HasMonsters() const; ///< returns true iff this fleet contains monster ships. - bool HasArmedShips() const; ///< Returns true if there is at least one armed ship in the fleet. - bool HasFighterShips() const; ///< Returns true if there is at least one ship with fighters in the fleet. - bool HasColonyShips() const; ///< Returns true if there is at least one colony ship with nonzero capacity in the fleet. - bool HasOutpostShips() const; ///< Returns true if there is at least one colony ship with zero capacity in the fleet - bool HasTroopShips() const; ///< Returns true if there is at least one troop ship in the fleet. - bool HasShipsOrderedScrapped() const; ///< Returns true if there is at least one ship ordered scrapped in the fleet. - bool HasShipsWithoutScrapOrders() const; ///< Returns true if there is at least one ship without any scrap orders in the fleet. - int NumShips() const { return m_ships.size(); } ///< Returns number of ships in fleet. - bool Empty() const { return m_ships.empty(); } ///< Returns true if fleet contains no ships, false otherwise. - float ResourceOutput(ResourceType type) const; + + float Damage() const; ///< Returns total amount of damage this fleet has, which is the sum of the ships' damage + float Structure() const; ///< Returns total amount of structure this fleet has, which is the sum of the ships' structure + float Shields() const; ///< Returns total amount of shields this fleet has, which is the sum of the ships' shields + float Fuel() const; ///< Returns effective amount of fuel this fleet has, which is the least of the amounts of fuel that the ships have + float MaxFuel() const; ///< Returns effective maximum amount of fuel this fleet has, which is the least of the max amounts of fuel that the ships can have + int FinalDestinationID() const; ///< Returns ID of system that this fleet is moving to or INVALID_OBJECT_ID if staying still. + int PreviousSystemID() const { return m_prev_system; } ///< Returns ID of system that this fleet is moving away from as it moves to its destination. + int NextSystemID() const { return m_next_system; } ///< Returns ID of system that this fleet is moving to next as it moves to its destination. + bool Blockaded() const; ///< returns true iff either (i) fleet is stationary and at least one system exit is blocked for this fleet or (ii) fleet is attempting to depart a system along a blocked system exit + bool BlockadedAtSystem(int start_system_id, int dest_system_id) const; ///< returns true iff this fleet's movement would be blockaded at system. + float Speed() const; ///< Returns speed of fleet. (Should be equal to speed of slowest ship in fleet, unless in future the calculation of fleet speed changes.) + bool CanChangeDirectionEnRoute() const { return false; } ///< Returns true iff this fleet can change its direction while in interstellar space. + bool HasMonsters() const; ///< returns true iff this fleet contains monster ships. + bool HasArmedShips() const; ///< Returns true if there is at least one armed ship in the fleet, meaning it has direct fire weapons or fighters that can be launched and that do damage + bool HasFighterShips() const; ///< Returns true if there is at least one ship with fighters in the fleet. + bool HasColonyShips() const; ///< Returns true if there is at least one colony ship with nonzero capacity in the fleet. + bool HasOutpostShips() const; ///< Returns true if there is at least one colony ship with zero capacity in the fleet + bool HasTroopShips() const; ///< Returns true if there is at least one troop ship in the fleet. + bool HasShipsOrderedScrapped() const; ///< Returns true if there is at least one ship ordered scrapped in the fleet. + bool HasShipsWithoutScrapOrders() const; ///< Returns true if there is at least one ship without any scrap orders in the fleet. + int NumShips() const { return m_ships.size(); } ///< Returns number of ships in fleet. + bool Empty() const { return m_ships.empty(); } ///< Returns true if fleet contains no ships, false otherwise. + float ResourceOutput(ResourceType type) const; /** Returns true iff this fleet is moving, but the route is unknown. This * is usually the case when a foreign player A's fleet is represented on * another player B's client, and player B has never seen one or more of * the systems in the fleet's route. */ - bool UnknownRoute() const; + bool UnknownRoute() const; /** Returns true iff this fleet arrived at its current System this turn. */ - bool ArrivedThisTurn() const { return m_arrived_this_turn; } + bool ArrivedThisTurn() const { return m_arrived_this_turn; } /** Has two primary uses: orientation in tactical combat, and determination of starlane blockade restrictions. * Returns the ID of the starlane that this fleet arrived on, if it arrived into a blockade which is not yet broken. * If in a system and not blockaded, the value is the current system ID. The blockade intent is that you can't * break a blockade unless you beat the blockaders (via combat or they retreat).**/ - int ArrivalStarlane() const { return m_arrival_starlane; } + int ArrivalStarlane() const { return m_arrival_starlane; } //@} /** \name Mutators */ //@{ @@ -111,30 +115,28 @@ class FO_COMMON_API Fleet : public UniverseObject { void ResetTargetMaxUnpairedMeters() override; - void SetRoute(const std::list& route); ///< sets this fleet to move through the series of systems in the list, in order - void CalculateRouteTo(int target_system_id); ///< sets this fleet to move through the series of systems that makes the shortest path from its current location to target_system_id + void SetRoute(const std::list& route); ///< sets this fleet to move through the series of systems in the list, in order + void CalculateRouteTo(int target_system_id); ///< sets this fleet to move through the series of systems that makes the shortest path from its current location to target_system_id - void SetAggressive(bool aggressive = true); ///< sets this fleet to be agressive (true) or passive (false) + void SetAggressive(bool aggressive = true); ///< sets this fleet to be agressive (true) or passive (false) - void AddShip(int ship_id); ///< adds the ship to the fleet - void AddShips(const std::vector& ship_ids); ///< adds the ships to the fleet - void RemoveShip(int ship_id); ///< removes the ship from the fleet. - void RemoveShips(const std::vector& ship_ids); ///< removes the ships from the fleet. + void AddShips(const std::vector& ship_ids); ///< adds the ships to the fleet + void RemoveShips(const std::vector& ship_ids); ///< removes the ships from the fleet. - void SetNextAndPreviousSystems(int next, int prev); ///< sets the previous and next systems for this fleet. Useful after moving a moving fleet to a different location, so that it moves along its new local starlanes - void SetArrivalStarlane(int starlane) { m_arrival_starlane = starlane; } ///< sets the arrival starlane, used to clear blockaded status after combat - void ClearArrivalFlag() { m_arrived_this_turn = false; } ///< used to clear the m_arrived_this_turn flag, prior to any fleets moving, for accurate blockade tests + void SetNextAndPreviousSystems(int next, int prev); ///< sets the previous and next systems for this fleet. Useful after moving a moving fleet to a different location, so that it moves along its new local starlanes + void SetArrivalStarlane(int starlane) { m_arrival_starlane = starlane; }///< sets the arrival starlane, used to clear blockaded status after combat + void ClearArrivalFlag() { m_arrived_this_turn = false; }///< used to clear the m_arrived_this_turn flag, prior to any fleets moving, for accurate blockade tests - void SetGiveToEmpire(int empire_id); ///< marks fleet to be given to empire - void ClearGiveToEmpire(); ///< marks fleet not to be given to any empire + void SetGiveToEmpire(int empire_id); ///< marks fleet to be given to empire + void ClearGiveToEmpire(); ///< marks fleet not to be given to any empire //@} /* returns a name for a fleet based on its ships*/ - std::string GenerateFleetName(); + std::string GenerateFleetName(); - static const int ETA_NEVER; ///< returned by ETA when fleet can't reach destination due to lack of route or inability to move - static const int ETA_UNKNOWN; ///< returned when ETA can't be determined - static const int ETA_OUT_OF_RANGE; ///< returned by ETA when fleet can't reach destination due to insufficient fuel capacity and lack of fleet resupply on route + static const int ETA_NEVER; ///< returned by ETA when fleet can't reach destination due to lack of route or inability to move + static const int ETA_UNKNOWN; ///< returned when ETA can't be determined + static const int ETA_OUT_OF_RANGE; ///< returned by ETA when fleet can't reach destination due to insufficient fuel capacity and lack of fleet resupply on route protected: friend class Universe; @@ -142,9 +144,12 @@ class FO_COMMON_API Fleet : public UniverseObject { /** \name Structors */ //@{ Fleet() {} + +public: Fleet(const std::string& name, double x, double y, int owner); ///< general ctor taking name, position and owner id - template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); +protected: + template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); public: ~Fleet() {} @@ -155,9 +160,6 @@ class FO_COMMON_API Fleet : public UniverseObject { //@} private: - ///< removes any systems on the route after the specified system - void ShortenRouteToEndAtSystem(std::list& travel_route, int last_system); - std::set m_ships; // these two uniquely describe the starlane graph edge the fleet is on, if it it's on one @@ -176,16 +178,13 @@ class FO_COMMON_API Fleet : public UniverseObject { * unknown. The list may also be empty, which indicates that the fleet * is not planning to move. */ std::list m_travel_route; - mutable double m_travel_distance = 0.0; bool m_arrived_this_turn = false; int m_arrival_starlane = INVALID_OBJECT_ID; // see comment for ArrivalStarlane() friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -BOOST_CLASS_VERSION(Fleet, 2) - #endif // _Fleet_h_ diff --git a/universe/FleetPlan.cpp b/universe/FleetPlan.cpp new file mode 100644 index 00000000000..08126fcf98a --- /dev/null +++ b/universe/FleetPlan.cpp @@ -0,0 +1,12 @@ +#include "FleetPlan.h" + + +#include "../util/i18n.h" + + +const std::string& FleetPlan::Name() const { + if (m_name_in_stringtable) + return UserString(m_name); + else + return m_name; +} diff --git a/universe/FleetPlan.h b/universe/FleetPlan.h new file mode 100644 index 00000000000..6061d770d78 --- /dev/null +++ b/universe/FleetPlan.h @@ -0,0 +1,80 @@ +#ifndef _FleetPlan_h_ +#define _FleetPlan_h_ + + +#include +#include +#include +#include "../universe/Condition.h" +#include "../util/Export.h" + + +//! Prepopulated Fleet, consisting of a name and list of ShipDesign names. +//! +//! FleetPlan%s are used for providing Fleets during universe generation or +//! during game events. +//! +//! Fleet plans are loaded from the `default/scripting/starting_unlocks/fleets.inf` +//! file, the ShipDesign referenced are liste in `default/scripting/ship_designs`. +class FO_COMMON_API FleetPlan { +public: + FleetPlan(const std::string& fleet_name, const std::vector& ship_design_names, + bool lookup_name_userstring = false) : + m_name(fleet_name), + m_ship_designs(ship_design_names), + m_name_in_stringtable(lookup_name_userstring) + {} + + FleetPlan() : + m_name(""), + m_ship_designs(), + m_name_in_stringtable(false) + {} + + auto Name() const -> const std::string&; + + auto ShipDesigns() const -> const std::vector& + { return m_ship_designs; } + +protected: + std::string m_name; + std::vector m_ship_designs; + bool m_name_in_stringtable; +}; + + +//! Spawning instruction for Monster Fleets during universe generation. +class FO_COMMON_API MonsterFleetPlan : public FleetPlan { +public: + MonsterFleetPlan(const std::string& fleet_name, const std::vector& ship_design_names, + double spawn_rate = 1.0, int spawn_limit = 9999, + std::unique_ptr&& location = nullptr, + bool lookup_name_userstring = false) : + FleetPlan(fleet_name, ship_design_names, lookup_name_userstring), + m_spawn_rate(spawn_rate), + m_spawn_limit(spawn_limit), + m_location(std::move(location)) + {} + + MonsterFleetPlan() : + FleetPlan() + {} + + auto SpawnRate() const -> auto + { return m_spawn_rate; } + + auto SpawnLimit() const -> int + { return m_spawn_limit; } + + auto Location() const -> const Condition::Condition* + { return m_location.get(); } + +protected: + double m_spawn_rate = 1.0; + int m_spawn_limit = 9999; + // Use shared_ptr insead of unique_ptr because boost::python requires a deleter + const std::shared_ptr m_location; +}; + + +#endif // __FleetPlan_h_ diff --git a/universe/IDAllocator.cpp b/universe/IDAllocator.cpp index b78dbe29f0d..71455cf1943 100644 --- a/universe/IDAllocator.cpp +++ b/universe/IDAllocator.cpp @@ -5,6 +5,7 @@ #include "../util/Random.h" #include "../util/Serialize.h" #include "../util/Serialize.ipp" +#include "../util/AppInterface.h" #include @@ -38,21 +39,23 @@ IDAllocator::IDAllocator(const int server_id, // Assign the server to the first offset m_offset_to_empire_id[(ii - m_zero) % m_stride] = m_server_id; - m_empire_id_to_next_assigned_object_id.insert( - std::make_pair(m_server_id, ii)); + m_empire_id_to_next_assigned_object_id.insert({m_server_id, ii}); ++ii; for (const auto empire_id : client_ids) { if (empire_id == m_server_id) continue; AssigningEmpireForID(ii) = empire_id; - m_empire_id_to_next_assigned_object_id.insert( - std::make_pair(empire_id, ii)); + m_empire_id_to_next_assigned_object_id.insert({empire_id, ii}); ++ii; } } int IDAllocator::NewID() { + // increment next id for this client until next id is not an already-used id + IncrementNextAssignedId(m_empire_id, Objects().HighestObjectID()); + IncrementNextAssignedId(m_empire_id, GetUniverse().HighestDestroyedObjectID()); + // Find the next id for this client in the table. auto&& it = m_empire_id_to_next_assigned_object_id.find(m_empire_id); if (it == m_empire_id_to_next_assigned_object_id.end()) { @@ -65,8 +68,9 @@ int IDAllocator::NewID() { auto apparent_assigning_empire = AssigningEmpireForID(retval); if (apparent_assigning_empire != m_empire_id) - ErrorLogger() << "m_empire_id " << m_empire_id << " does not match apparent assiging id " - << apparent_assigning_empire << " for id = " << retval << " m_zero = " << m_zero; + ErrorLogger() << "m_empire_id " << m_empire_id << " does not match apparent assigning id " + << apparent_assigning_empire << " for id = " << retval << " m_zero = " << m_zero + << " stride = " << m_stride; // Increment the next id if not exhausted if (it->second >= m_exhausted_threshold) { @@ -79,7 +83,7 @@ int IDAllocator::NewID() { ErrorLogger() << "Object IDs are exhausted. No objects can be added to the Universe."; if (retval >= m_warn_threshold) - WarnLogger() << "Object IDs are almost exhausted. Currently assiging id, " << retval; + WarnLogger() << "Object IDs are almost exhausted. Currently assigning id, " << retval; TraceLogger(IDallocator) << "Allocating id = " << retval << " for empire = " << it->first; return retval; @@ -157,14 +161,6 @@ bool IDAllocator::UpdateIDAndCheckIfOwned(const ID_t checked_id) { return true;; } -void IDAllocator::FixLegacyOrderIDs(const ID_t id) { - // For legacy saved games orders will contain ids not in the appropriate partition. - // Advance all paritions past id so there will be no conflicts. - // This should stop happening after all of a saved games unprocessed orders are processed. - for (const auto assigning_empire : m_offset_to_empire_id) - IncrementNextAssignedId(assigning_empire, id); -} - IDAllocator::ID_t& IDAllocator::AssigningEmpireForID(ID_t id) { return m_offset_to_empire_id[(id - m_zero) % m_stride]; } @@ -219,7 +215,7 @@ void IDAllocator::ObfuscateBeforeSerialization() { // Check that this does not exhaust the ids auto new_max_next_id = max_next_assigned + max_random_offset + m_stride; if (new_max_next_id > m_warn_threshold) - WarnLogger() << "Object IDs are almost exhausted. Currently assiging id, " << new_max_next_id; + WarnLogger() << "Object IDs are almost exhausted. Currently assigning id, " << new_max_next_id; if (new_max_next_id > m_exhausted_threshold) { ErrorLogger() << "Object IDs are exhausted. No objects can be added to the Universe."; @@ -235,8 +231,29 @@ void IDAllocator::ObfuscateBeforeSerialization() { auto new_next_id = empire_random_offset + m_zero; // Increment until it is at the correct offset - while ((new_next_id - m_zero) % m_stride != assigning_empire_offset_modulus) + ID_t ii_dont_check_more_than_m_stride_ids = 0; + while (AssigningEmpireForID(new_next_id) != assigning_empire && ii_dont_check_more_than_m_stride_ids <= m_stride) { ++new_next_id; + ++ii_dont_check_more_than_m_stride_ids; + } + + // If m_stride consecutive ids have been checked, then + // assigning_empire is not in m_offset_to_empire_id, which is an error. + if (ii_dont_check_more_than_m_stride_ids > m_stride) { + ErrorLogger() + << "While obfuscating id allocation empire " << assigning_empire + << "is missing from the table m_offset_to_empire_id: " + << "[(offset, empire id), " << [this]() { + std::stringstream ss; + std::size_t offset = 0; + for (auto& empire_id : m_offset_to_empire_id) { + ss << " (" << offset++ << ", " << empire_id << "), "; + } + return ss.str(); + }() << "]" + << " Empire " << assigning_empire + << " may not be able to create new designs or objects."; + } m_empire_id_to_next_assigned_object_id[assigning_empire] = new_next_id; @@ -266,16 +283,18 @@ std::string IDAllocator::StateString() const { return ss.str(); } -template -void IDAllocator::SerializeForEmpire(Archive& ar, const unsigned int version, const int empire_id) { +template +void IDAllocator::SerializeForEmpire(Archive& ar, const unsigned int version, int empire_id) { DebugLogger(IDallocator) << (Archive::is_loading::value ? "Deserialize " : "Serialize ") << "IDAllocator() server id = " << m_server_id << " empire id = " << empire_id; ar & BOOST_SERIALIZATION_NVP(m_invalid_id) & BOOST_SERIALIZATION_NVP(m_temp_id) - & BOOST_SERIALIZATION_NVP(m_stride) - & BOOST_SERIALIZATION_NVP(m_server_id) + & BOOST_SERIALIZATION_NVP(m_stride); + if (version > 0) + ar & BOOST_SERIALIZATION_NVP(m_zero); + ar & BOOST_SERIALIZATION_NVP(m_server_id) & BOOST_SERIALIZATION_NVP(m_warn_threshold) & BOOST_SERIALIZATION_NVP(m_exhausted_threshold); @@ -295,21 +314,21 @@ void IDAllocator::SerializeForEmpire(Archive& ar, const unsigned int version, co } else { + if (m_empire_id != empire_id && m_empire_id != m_server_id) + ErrorLogger() << "An empire with id = " << m_empire_id << " which is not the server " + << "is attempting to serialize the IDAllocator for a different empire " << empire_id; + // If the target empire is the server, provide the full map. if (empire_id == m_server_id) { - if (m_empire_id != m_server_id) - ErrorLogger() << "An empire with id = " << m_empire_id << " which is not the server " - << "is attempting to serialize the IDAllocator for the server."; ar & BOOST_SERIALIZATION_NVP(m_empire_id) & BOOST_SERIALIZATION_NVP(m_empire_id_to_next_assigned_object_id) & BOOST_SERIALIZATION_NVP(m_offset_to_empire_id); } else { - auto temp_empire_id = empire_id; - ar & BOOST_SERIALIZATION_NVP(temp_empire_id); + ar & boost::serialization::make_nvp(BOOST_PP_STRINGIZE(m_empire_id), empire_id); // Filter the map for empires so they only have their own actual next id and no // information about other clients. - std::unordered_map temp_empire_id_to_object_id{}; + std::unordered_map temp_empire_id_to_object_id{}; auto temp_offset_to_empire_id = std::vector(m_offset_to_empire_id.size(), m_server_id); auto&& it = m_empire_id_to_next_assigned_object_id.find(empire_id); @@ -318,11 +337,11 @@ void IDAllocator::SerializeForEmpire(Archive& ar, const unsigned int version, co << empire_id << " not in id manager table."; } else { temp_empire_id_to_object_id.insert(*it); - temp_offset_to_empire_id[it->second % m_stride] = empire_id; + temp_offset_to_empire_id[(it->second - m_zero) % m_stride] = empire_id; } - ar & BOOST_SERIALIZATION_NVP(temp_empire_id_to_object_id); - ar & BOOST_SERIALIZATION_NVP(temp_offset_to_empire_id); + ar & boost::serialization::make_nvp(BOOST_PP_STRINGIZE(m_empire_id_to_next_assigned_object_id), temp_empire_id_to_object_id); + ar & boost::serialization::make_nvp(BOOST_PP_STRINGIZE(m_offset_to_empire_id), temp_offset_to_empire_id); DebugLogger(IDallocator) << "Serialized [" << [this, &temp_empire_id_to_object_id](){ std::stringstream ss; diff --git a/universe/IDAllocator.h b/universe/IDAllocator.h index 7e60b6e1c12..67f44cd2a38 100644 --- a/universe/IDAllocator.h +++ b/universe/IDAllocator.h @@ -32,8 +32,6 @@ class IDAllocator { /** \p client_ids are all the player ids in the game. \p highest_pre_allocated_id is the used for legacy loads to offset the newly allocated id to after the ones from the save game. - TODO: Remove the extra code legacy loading before v0.4.8, which will be a save game - compatibility break. */ IDAllocator(const int server_id, const std::vector& client_ids, @@ -62,17 +60,13 @@ class IDAllocator { nothing else. That means that id modulo m_stride == client's offset.*/ bool UpdateIDAndCheckIfOwned(const ID_t id); - /** On the server advance all client's next_ids so they won't conflict with - \p id. On the client do nothing. */ - void FixLegacyOrderIDs(const ID_t id); - /** ObfuscateBeforeSerialization randomizes which client is using which modulus each turn before IDAllocator is serialized and sent to the clients. */ void ObfuscateBeforeSerialization(); /** Serialize while stripping out information not known to \p empire_id. */ - template - void SerializeForEmpire(Archive& ar, const unsigned int version, const int empire_id); + template + void SerializeForEmpire(Archive& ar, const unsigned int version, int empire_id); private: /** Return the empire that should have assigned \p id. */ diff --git a/universe/Meter.cpp b/universe/Meter.cpp index 383e42d2c1d..6e2e164f0b6 100644 --- a/universe/Meter.cpp +++ b/universe/Meter.cpp @@ -6,13 +6,6 @@ const float Meter::LARGE_VALUE = static_cast(2 << 15); const float Meter::INVALID_VALUE = -LARGE_VALUE; -Meter::Meter() -{} - -Meter::Meter(float current_value) : - m_current_value(current_value) -{} - Meter::Meter(float current_value, float initial_value) : m_current_value(current_value), m_initial_value(initial_value) @@ -24,7 +17,7 @@ float Meter::Current() const float Meter::Initial() const { return m_initial_value; } -std::string Meter::Dump() const { +std::string Meter::Dump(unsigned short ntabs) const { std::ostringstream strstm; strstm.precision(5); strstm << "Cur: " << m_current_value << " Init: " << m_initial_value; diff --git a/universe/Meter.h b/universe/Meter.h index 2f0887317c3..49ca3646019 100644 --- a/universe/Meter.h +++ b/universe/Meter.h @@ -19,11 +19,7 @@ class FO_COMMON_API Meter { /** \name Structors */ //@{ /** Creates a new meter with both initial and current value set to DEFAULT_VALUE. */ - Meter(); - - /** Creates a new meter with the current value set to @p current_value and - the initial value set to DEFAULT_VALUE. */ - explicit Meter(float current_value); + Meter() = default; /** Creates a new meter with the current value set to @p current_value and the initial value set to @p initial_value. */ @@ -31,41 +27,41 @@ class FO_COMMON_API Meter { //@} /** \name Accessors */ //@{ - float Current() const; ///< returns the current value of the meter - float Initial() const; ///< returns the value of the meter as it was at the beginning of the turn + float Current() const; ///< returns the current value of the meter + float Initial() const; ///< returns the value of the meter as it was at the beginning of the turn - std::string Dump() const; ///< returns text of meter values + std::string Dump(unsigned short ntabs = 0) const; ///< returns text of meter values //@} /** \name Mutators */ //@{ - void SetCurrent(float current_value); ///< sets current value, leaving initial value unchanged - void Set(float current_value, float initial_value); ///< sets current and initial values - void ResetCurrent(); ///< sets current value to DEFAULT_VALUE - void Reset(); ///< sets current and initial values to DEFAULT_VALUE + void SetCurrent(float current_value); ///< sets current value, leaving initial value unchanged + void Set(float current_value, float initial_value); ///< sets current and initial values + void ResetCurrent(); ///< sets current value to DEFAULT_VALUE + void Reset(); ///< sets current and initial values to DEFAULT_VALUE - void AddToCurrent(float adjustment); ///< adds \a current to the current value of the Meter - void ClampCurrentToRange(float min = DEFAULT_VALUE, float max = LARGE_VALUE); ///< ensures the current value falls in the range [\a min, \a max] + void AddToCurrent(float adjustment); ///< adds \a current to the current value of the Meter + void ClampCurrentToRange(float min = DEFAULT_VALUE, float max = LARGE_VALUE); ///< ensures the current value falls in the range [\a min, \a max] - void BackPropagate(); ///< sets previous equal to initial, then sets initial equal to current + void BackPropagate(); ///< sets previous equal to initial, then sets initial equal to current //@} - static constexpr float DEFAULT_VALUE = 0.0f; ///< value assigned to current or initial when resetting or when no value is specified in a constructor - static const float LARGE_VALUE; ///< a very large number, which is useful to set current to when it will be later clamped, to ensure that the result is the max value in the clamp range - static const float INVALID_VALUE; ///< sentinel value to indicate no valid value for this meter + static constexpr float DEFAULT_VALUE = 0.0f;///< value assigned to current or initial when resetting or when no value is specified in a constructor + static const float LARGE_VALUE; ///< a very large number, which is useful to set current to when it will be later clamped, to ensure that the result is the max value in the clamp range + static const float INVALID_VALUE; ///< sentinel value to indicate no valid value for this meter private: - float m_current_value = DEFAULT_VALUE; - float m_initial_value = DEFAULT_VALUE; + float m_current_value = DEFAULT_VALUE; + float m_initial_value = DEFAULT_VALUE; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; BOOST_CLASS_VERSION(Meter, 1) // template implementations -template +template void Meter::serialize(Archive& ar, const unsigned int version) { if (Archive::is_loading::value && version < 1) { diff --git a/universe/ObjectMap.cpp b/universe/ObjectMap.cpp index fab2d5fdccd..707a32db6f2 100644 --- a/universe/ObjectMap.cpp +++ b/universe/ObjectMap.cpp @@ -10,6 +10,7 @@ #include "Field.h" #include "Enums.h" #include "../util/Logger.h" +#include "../util/AppInterface.h" #define FOR_EACH_SPECIALIZED_MAP(f, ...) { f(m_resource_centers, ##__VA_ARGS__); \ @@ -24,17 +25,43 @@ #define FOR_EACH_MAP(f, ...) { f(m_objects, ##__VA_ARGS__); \ FOR_EACH_SPECIALIZED_MAP(f, ##__VA_ARGS__); } +#define FOR_EACH_EXISTING_MAP(f, ...) { f(m_existing_objects, ##__VA_ARGS__); \ + f(m_existing_resource_centers, ##__VA_ARGS__); \ + f(m_existing_pop_centers, ##__VA_ARGS__); \ + f(m_existing_ships, ##__VA_ARGS__); \ + f(m_existing_fleets, ##__VA_ARGS__); \ + f(m_existing_planets, ##__VA_ARGS__); \ + f(m_existing_systems, ##__VA_ARGS__); \ + f(m_existing_buildings, ##__VA_ARGS__); \ + f(m_existing_fields, ##__VA_ARGS__); } + + +namespace { + template + static void ClearMap(ObjectMap::container_type& map) + { map.clear(); } + + template + static void TryInsertIntoMap(ObjectMap::container_type& map, std::shared_ptr item) + { + if (dynamic_cast(item.get())) + map[item->ID()] = std::dynamic_pointer_cast(item); + } + + template + void EraseFromMap(ObjectMap::container_type& map, int id) + { map.erase(id); } +} + + ///////////////////////////////////////////// // class ObjectMap ///////////////////////////////////////////// ObjectMap::ObjectMap() {} -ObjectMap::~ObjectMap() { - // Make sure to call ObjectMap::Clear() before destruction somewhere if - // this ObjectMap contains any unique pointers to UniverseObject objects. - // Otherwise, the pointed-to UniverseObjects will be leaked memory... -} +ObjectMap::~ObjectMap() +{} void ObjectMap::Copy(const ObjectMap& copied_map, int empire_id/* = ALL_EMPIRES*/) { if (&copied_map == this) @@ -42,8 +69,8 @@ void ObjectMap::Copy(const ObjectMap& copied_map, int empire_id/* = ALL_EMPIRES* // loop through objects in copied map, copying or cloning each depending // on whether there already is a corresponding object in this map - for (const_iterator<> it = copied_map.const_begin(); it != copied_map.const_end(); ++it) - this->CopyObject(*it, empire_id); + for (const auto& obj : copied_map.all()) + this->CopyObject(obj, empire_id); } void ObjectMap::CopyForSerialize(const ObjectMap& copied_map) { @@ -64,10 +91,10 @@ void ObjectMap::CopyObject(std::shared_ptr source, int emp if (GetUniverse().GetObjectVisibilityByEmpire(source_id, empire_id) <= VIS_NO_VISIBILITY) return; - if (std::shared_ptr destination = this->Object(source_id)) { + if (auto destination = this->get(source_id)) { destination->Copy(source, empire_id); // there already is a version of this object present in this ObjectMap, so just update it } else { - InsertCore(std::shared_ptr(source->Clone()), empire_id); // this object is not yet present in this ObjectMap, so add a new UniverseObject object for it + insertCore(std::shared_ptr(source->Clone()), empire_id); // this object is not yet present in this ObjectMap, so add a new UniverseObject object for it } } @@ -77,108 +104,21 @@ ObjectMap* ObjectMap::Clone(int empire_id) const { return result; } -int ObjectMap::NumObjects() const -{ return static_cast(m_objects.size()); } - -bool ObjectMap::Empty() const +bool ObjectMap::empty() const { return m_objects.empty(); } -std::shared_ptr ObjectMap::Object(int id) const -{ return Object(id); } - -std::shared_ptr ObjectMap::Object(int id) -{ return Object(id); } - -std::vector> ObjectMap::FindObjects(const std::vector& object_ids) const { - std::vector> result; - for (int object_id : object_ids) - if (std::shared_ptr obj = Object(object_id)) - result.push_back(obj); - else - ErrorLogger() << "ObjectMap::FindObjects couldn't find object with id " << object_id; - return result; -} - -std::vector> ObjectMap::FindObjects(const std::set& object_ids) const { - std::vector> result; - for (int object_id : object_ids) - if (std::shared_ptr obj = Object(object_id)) - result.push_back(obj); - else - ErrorLogger() << "ObjectMap::FindObjects couldn't find object with id " << object_id; - return result; -} - -std::vector> ObjectMap::FindObjects(const std::vector& object_ids) { - std::vector> result; - for (int object_id : object_ids) - if (std::shared_ptr obj = Object(object_id)) - result.push_back(obj); - else - ErrorLogger() << "ObjectMap::FindObjects couldn't find object with id " << object_id; - return result; -} - -std::vector> ObjectMap::FindObjects(const std::set& object_ids) { - std::vector> result; - for (int object_id : object_ids) - if (std::shared_ptr obj = Object(object_id)) - result.push_back(obj); - else - ErrorLogger() << "ObjectMap::FindObjects couldn't find object with id " << object_id; - return result; +int ObjectMap::HighestObjectID() const { + if (m_objects.empty()) + return INVALID_OBJECT_ID; + return m_objects.rbegin()->first; } -std::vector> ObjectMap::FindObjects(const UniverseObjectVisitor& visitor) const { - std::vector> result; - for (const_iterator<> it = const_begin(); it != const_end(); ++it) { - if (std::shared_ptr obj = it->Accept(visitor)) - result.push_back(Object(obj->ID())); - } - return result; -} - -std::vector> ObjectMap::FindObjects(const UniverseObjectVisitor& visitor) { - std::vector> result; - for (std::shared_ptr obj : *this) { - if (std::shared_ptr match = obj->Accept(visitor)) - result.push_back(Object(match->ID())); - } - return result; -} - -std::vector ObjectMap::FindObjectIDs(const UniverseObjectVisitor& visitor) const { - std::vector result; - for (const_iterator<> it = const_begin(); it != const_end(); ++it) { - if (it->Accept(visitor)) - result.push_back(it->ID()); - } - return result; -} - -std::vector ObjectMap::FindObjectIDs() const { - return FindObjectIDs(); -} - -ObjectMap::iterator<> ObjectMap::begin() -{ return begin(); } - -ObjectMap::iterator<> ObjectMap::end() -{ return end(); } - -ObjectMap::const_iterator<> ObjectMap::const_begin() const -{ return const_begin(); } - -ObjectMap::const_iterator<> ObjectMap::const_end() const -{ return const_end(); } - -void ObjectMap::InsertCore(std::shared_ptr item, int empire_id/* = ALL_EMPIRES*/) { +void ObjectMap::insertCore(std::shared_ptr item, int empire_id/* = ALL_EMPIRES*/) { FOR_EACH_MAP(TryInsertIntoMap, item); if (item && - GetUniverse().EmpireKnownDestroyedObjectIDs(empire_id).find(item->ID()) == - GetUniverse().EmpireKnownDestroyedObjectIDs(empire_id).end()) + !GetUniverse().EmpireKnownDestroyedObjectIDs(empire_id).count(item->ID())) { - std::shared_ptr this_item = this->Object(item->ID()); + auto this_item = this->get(item->ID()); m_existing_objects[item->ID()] = this_item; switch (item->ObjectType()) { case OBJ_BUILDING: @@ -207,66 +147,64 @@ void ObjectMap::InsertCore(std::shared_ptr item, int empire_id/* case OBJ_SYSTEM: m_existing_systems[item->ID()] = this_item; break; + case OBJ_FIGHTER: default: break; } } } -std::shared_ptr ObjectMap::Remove(int id) { +std::shared_ptr ObjectMap::erase(int id) { // search for object in objects map - std::map>::iterator it = m_objects.find(id); + auto it = m_objects.find(id); if (it == m_objects.end()) return nullptr; //DebugLogger() << "Object was removed: " << it->second->Dump(); // object found, so store pointer for later... - std::shared_ptr result = it->second; + auto result = it->second; // and erase from pointer maps m_objects.erase(it); FOR_EACH_SPECIALIZED_MAP(EraseFromMap, id); - m_existing_objects.erase(id); - m_existing_buildings.erase(id); - m_existing_fields.erase(id); - m_existing_fleets.erase(id); - m_existing_ships.erase(id); - m_existing_planets.erase(id); - m_existing_pop_centers.erase(id); - m_existing_resource_centers.erase(id); - m_existing_systems.erase(id); + FOR_EACH_EXISTING_MAP(EraseFromMap, id); return result; } -void ObjectMap::Clear() { +void ObjectMap::clear() { FOR_EACH_MAP(ClearMap); + FOR_EACH_EXISTING_MAP(ClearMap); } void ObjectMap::swap(ObjectMap& rhs) { + // SwapMap uses ObjectMap::Map but this function isn't available for the existing maps, + // so the FOR_EACH_EXISTING_MAP macro doesn't work with with SwapMap + // and it is instead necessary to write them out explicitly. + m_existing_objects.swap(rhs.m_existing_objects); + m_existing_buildings.swap(rhs.m_existing_buildings); + m_existing_fields.swap(rhs.m_existing_fields); + m_existing_fleets.swap(rhs.m_existing_fleets); + m_existing_ships.swap(rhs.m_existing_ships); + m_existing_planets.swap(rhs.m_existing_planets); + m_existing_pop_centers.swap(rhs.m_existing_pop_centers); + m_existing_resource_centers.swap(rhs.m_existing_resource_centers); + m_existing_systems.swap(rhs.m_existing_systems); FOR_EACH_MAP(SwapMap, rhs); } std::vector ObjectMap::FindExistingObjectIDs() const { std::vector result; - for (const std::map>::value_type& entry : m_existing_objects) + for (const auto& entry : m_existing_objects) { result.push_back(entry.first); } return result; } void ObjectMap::UpdateCurrentDestroyedObjects(const std::set& destroyed_object_ids) { - m_existing_objects.clear(); - m_existing_buildings.clear(); - m_existing_fields.clear(); - m_existing_fleets.clear(); - m_existing_ships.clear(); - m_existing_planets.clear(); - m_existing_pop_centers.clear(); - m_existing_resource_centers.clear(); - m_existing_systems.clear(); - for (const std::map>::value_type& entry : m_objects) { + FOR_EACH_EXISTING_MAP(ClearMap); + for (const auto& entry : m_objects) { if (!entry.second) continue; - if (destroyed_object_ids.find(entry.first) != destroyed_object_ids.end()) + if (destroyed_object_ids.count(entry.first)) continue; - std::shared_ptr this_item = this->Object(entry.first); + auto this_item = this->get(entry.first); m_existing_objects[entry.first] = this_item; switch (entry.second->ObjectType()) { case OBJ_BUILDING: @@ -295,6 +233,7 @@ void ObjectMap::UpdateCurrentDestroyedObjects(const std::set& destroyed_obj case OBJ_SYSTEM: m_existing_systems[entry.first] = this_item; break; + case OBJ_FIGHTER: default: break; } @@ -303,15 +242,15 @@ void ObjectMap::UpdateCurrentDestroyedObjects(const std::set& destroyed_obj void ObjectMap::AuditContainment(const std::set& destroyed_object_ids) { // determine all objects that some other object thinks contains them - std::map> contained_objs; - std::map> contained_planets; - std::map> contained_buildings; - std::map> contained_fleets; - std::map> contained_ships; - std::map> contained_fields; - - for (std::shared_ptr contained : *this) { - if (destroyed_object_ids.find(contained->ID()) != destroyed_object_ids.end()) + std::map> contained_objs; + std::map> contained_planets; + std::map> contained_buildings; + std::map> contained_fleets; + std::map> contained_ships; + std::map> contained_fields; + + for (const auto& contained : all()) { + if (destroyed_object_ids.count(contained->ID())) continue; int contained_id = contained->ID(); @@ -322,7 +261,7 @@ void ObjectMap::AuditContainment(const std::set& destroyed_object_ids) { continue; // store systems' contained objects - if (this->Object(sys_id)) { // although this is expected to be a system, can't use Object here due to CopyForSerialize not copying the type-specific objects info + if (this->get(sys_id)) { // although this is expected to be a system, can't use Object here due to CopyForSerialize not copying the type-specific objects info contained_objs[sys_id].insert(contained_id); if (type == OBJ_PLANET) @@ -338,18 +277,18 @@ void ObjectMap::AuditContainment(const std::set& destroyed_object_ids) { } // store planets' contained buildings - if (type == OBJ_BUILDING && this->Object(alt_id)) + if (type == OBJ_BUILDING && this->get(alt_id)) contained_buildings[alt_id].insert(contained_id); // store fleets' contained ships - if (type == OBJ_SHIP && this->Object(alt_id)) + if (type == OBJ_SHIP && this->get(alt_id)) contained_ships[alt_id].insert(contained_id); } // set contained objects of all possible containers - for (std::shared_ptr obj : *this) { + for (const auto& obj : all()) { if (obj->ObjectType() == OBJ_SYSTEM) { - std::shared_ptr sys = std::dynamic_pointer_cast(obj); + auto sys = std::dynamic_pointer_cast(obj); if (!sys) continue; sys->m_objects = contained_objs[sys->ID()]; @@ -358,13 +297,15 @@ void ObjectMap::AuditContainment(const std::set& destroyed_object_ids) { sys->m_fleets = contained_fleets[sys->ID()]; sys->m_ships = contained_ships[sys->ID()]; sys->m_fields = contained_fields[sys->ID()]; + } else if (obj->ObjectType() == OBJ_PLANET) { - std::shared_ptr plt = std::dynamic_pointer_cast(obj); + auto plt = std::dynamic_pointer_cast(obj); if (!plt) continue; plt->m_buildings = contained_buildings[plt->ID()]; + } else if (obj->ObjectType() == OBJ_FLEET) { - std::shared_ptr flt = std::dynamic_pointer_cast(obj); + auto flt = std::dynamic_pointer_cast(obj); if (!flt) continue; flt->m_ships = contained_ships[flt->ID()]; @@ -374,22 +315,21 @@ void ObjectMap::AuditContainment(const std::set& destroyed_object_ids) { void ObjectMap::CopyObjectsToSpecializedMaps() { FOR_EACH_SPECIALIZED_MAP(ClearMap); - for (std::map>::iterator it = Map().begin(); - it != Map().end(); ++it) - { FOR_EACH_SPECIALIZED_MAP(TryInsertIntoMap, it->second); } + for (const auto& entry : Map()) + { FOR_EACH_SPECIALIZED_MAP(TryInsertIntoMap, entry.second); } } -std::string ObjectMap::Dump() const { +std::string ObjectMap::Dump(unsigned short ntabs) const { std::ostringstream dump_stream; dump_stream << "ObjectMap contains UniverseObjects: " << std::endl; - for (const_iterator<> it = const_begin(); it != const_end(); ++it) - dump_stream << it->Dump() << std::endl; + for (const auto& obj : all()) + dump_stream << obj->Dump(ntabs) << std::endl; dump_stream << std::endl; return dump_stream.str(); } -std::shared_ptr ObjectMap::ExistingObject(int id) { - std::map>::iterator it = m_existing_objects.find(id); +std::shared_ptr ObjectMap::ExistingObject(int id) const { + auto it = m_existing_objects.find(id); if (it != m_existing_objects.end()) return it->second; return nullptr; @@ -397,94 +337,80 @@ std::shared_ptr ObjectMap::ExistingObject(int id) { // Static helpers -template -void ObjectMap::EraseFromMap(std::map>& map, int id) -{ map.erase(id); } - -template -void ObjectMap::ClearMap(std::map>& map) -{ map.clear(); } - -template -void ObjectMap::SwapMap(std::map>& map, ObjectMap& rhs) +template +void ObjectMap::SwapMap(ObjectMap::container_type& map, ObjectMap& rhs) { map.swap(rhs.Map()); } -template -void ObjectMap::TryInsertIntoMap(std::map>& map, std::shared_ptr item) { - if (dynamic_cast(item.get())) - map[item->ID()] = std::dynamic_pointer_cast(item); -} - // template specializations template <> -const std::map>& ObjectMap::Map() const +const ObjectMap::container_type& ObjectMap::Map() const { return m_objects; } template <> -const std::map>& ObjectMap::Map() const +const ObjectMap::container_type& ObjectMap::Map() const { return m_resource_centers; } template <> -const std::map>& ObjectMap::Map() const +const ObjectMap::container_type& ObjectMap::Map() const { return m_pop_centers; } template <> -const std::map>& ObjectMap::Map() const +const ObjectMap::container_type& ObjectMap::Map() const { return m_ships; } template <> -const std::map>& ObjectMap::Map() const +const ObjectMap::container_type& ObjectMap::Map() const { return m_fleets; } template <> -const std::map>& ObjectMap::Map() const +const ObjectMap::container_type& ObjectMap::Map() const { return m_planets; } template <> -const std::map>& ObjectMap::Map() const +const ObjectMap::container_type& ObjectMap::Map() const { return m_systems; } template <> -const std::map>& ObjectMap::Map() const +const ObjectMap::container_type& ObjectMap::Map() const { return m_buildings; } template <> -const std::map>& ObjectMap::Map() const +const ObjectMap::container_type& ObjectMap::Map() const { return m_fields; } template <> -std::map>& ObjectMap::Map() +ObjectMap::container_type& ObjectMap::Map() { return m_objects; } template <> -std::map>& ObjectMap::Map() +ObjectMap::container_type& ObjectMap::Map() { return m_resource_centers; } template <> -std::map>& ObjectMap::Map() +ObjectMap::container_type& ObjectMap::Map() { return m_pop_centers; } template <> -std::map>& ObjectMap::Map() +ObjectMap::container_type& ObjectMap::Map() { return m_ships; } template <> -std::map>& ObjectMap::Map() +ObjectMap::container_type& ObjectMap::Map() { return m_fleets; } template <> -std::map>& ObjectMap::Map() +ObjectMap::container_type& ObjectMap::Map() { return m_planets; } template <> -std::map>& ObjectMap::Map() +ObjectMap::container_type& ObjectMap::Map() { return m_systems; } template <> -std::map>& ObjectMap::Map() +ObjectMap::container_type& ObjectMap::Map() { return m_buildings; } template <> -std::map>& ObjectMap::Map() +ObjectMap::container_type& ObjectMap::Map() { return m_fields; } diff --git a/universe/ObjectMap.h b/universe/ObjectMap.h index b94f06e02e1..4719d72d055 100644 --- a/universe/ObjectMap.h +++ b/universe/ObjectMap.h @@ -2,8 +2,10 @@ #define _Object_Map_h_ +#include +#include +#include #include -#include #include "../util/Export.h" @@ -12,6 +14,7 @@ #include #include #include +#include struct UniverseObjectVisitor; @@ -32,271 +35,98 @@ FO_COMMON_API extern const int ALL_EMPIRES; /** Contains a set of objects that make up a (known or complete) Universe. */ class FO_COMMON_API ObjectMap { public: - template - struct iterator : private std::map>::iterator { - iterator(const typename std::map>::iterator& base, ObjectMap& owner) : - std::map>::iterator(base), - m_owner(owner) - { Refresh(); } - - std::shared_ptr operator *() const - { return m_current_ptr; } - - // The result of this operator is not intended to be stored, so it's safe to - // return a reference to an instance variable that's going to be soon overwritten. - std::shared_ptr& operator ->() const - { return m_current_ptr; } - - iterator& operator ++() { - std::map>::iterator::operator++(); - Refresh(); - return *this; - } - - iterator operator ++(int) { - iterator result = iterator(std::map>::iterator::operator++(0), m_owner); - Refresh(); - return result; - } - - iterator& operator --() { - std::map>::iterator::operator--(); - Refresh(); - return *this; - } - - iterator operator --(int) { - iterator result = iterator(std::map>::iterator::operator--(0), m_owner); - Refresh(); - return result; - } - - bool operator ==(const iterator& other) const - { return (typename std::map>::iterator(*this) == other); } - - bool operator !=(const iterator& other) const - { return (typename std::map>::iterator(*this) != other);} - - private: - // - mutable std::shared_ptr m_current_ptr; - ObjectMap& m_owner; - - // We always want m_current_ptr to be pointing to our parent iterator's - // current item, if it is a valid object. Otherwise, we just want to - // return a "null" pointer. We assume that we are dealing with valid - // iterators in the range [begin(), end()]. - void Refresh() const { - if (typename std::map>::iterator(*this) == (m_owner.Map().end())) { - m_current_ptr = nullptr; - } else { - m_current_ptr = std::shared_ptr(std::map>::iterator::operator*().second); - } - } - }; - - template - struct const_iterator : private std::map>::const_iterator { - const_iterator(const typename std::map>::const_iterator& base, const ObjectMap& owner) : - std::map>::const_iterator(base), - m_owner(owner) - { Refresh(); } - - std::shared_ptr operator *() const - { return m_current_ptr; } - - // The result of this operator is not intended to be stored, so it's safe to - // return a reference to an instance variable that's going to be soon overwritten. - std::shared_ptr& operator ->() const - { return m_current_ptr; } - - const_iterator& operator ++() { - std::map>::const_iterator::operator++(); - Refresh(); - return *this; - } - - const_iterator operator ++(int) { - const_iterator result = std::map>::const_iterator::operator++(0); - Refresh(); - return result; - } - - const_iterator& operator --() { - std::map>::const_iterator::operator--(); - Refresh(); - return *this; - } - - const_iterator operator --(int) { - const_iterator result = std::map>::const_iterator::operator--(0); - Refresh(); - return result; - } - - bool operator ==(const const_iterator& other) const - { return (typename std::map>::const_iterator(*this) == other); } - - bool operator !=(const const_iterator& other) const - { return (typename std::map>::const_iterator(*this) != other); } - - private: - // See iterator for comments. - mutable std::shared_ptr m_current_ptr; - const ObjectMap& m_owner; - - // We always want m_current_ptr to be pointing to our parent iterator's current item, if it is a valid object. - // Otherwise, we just want to return a "null" pointer. We assume that we are dealing with valid iterators in - // the range [begin(), end()]. - void Refresh() const { - if (typename std::map>::const_iterator(*this) == (m_owner.Map().end())) { - m_current_ptr = nullptr; - } else { - m_current_ptr = std::shared_ptr(std::map>::const_iterator::operator*().second); - } - } - }; + template + using container_type = std::map>; /** \name Structors */ //@{ ObjectMap(); - ~ObjectMap(); /** Copies contents of this ObjectMap to a new ObjectMap, which is * returned. Copies are limited to only duplicate information that the * empire with id \a empire_id would know about the copied objects. */ - ObjectMap* Clone(int empire_id = ALL_EMPIRES) const; + ObjectMap* Clone(int empire_id = ALL_EMPIRES) const; //@} /** \name Accessors */ //@{ - /** Returns number of objects in this ObjectMap */ - int NumObjects() const; - /** Returns the number of objects of the specified class in this ObjectMap. */ - template - int NumObjects() const; + template + std::size_t size() const; /** Returns true if this ObjectMap contains no objects */ - bool Empty() const; - - /** Returns a pointer to the universe object with ID number \a id, - * or a null std::shared_ptr if none exists */ - std::shared_ptr Object(int id) const; - - /** Returns a pointer to the universe object with ID number \a id, - * or a null std::shared_ptr if none exists */ - std::shared_ptr Object(int id); + bool empty() const; /** Returns a pointer to the object of type T with ID number \a id. * Returns a null std::shared_ptr if none exists or the object with * ID \a id is not of type T. */ - template - std::shared_ptr Object(int id) const; + template + std::shared_ptr get(int id) const; /** Returns a pointer to the object of type T with ID number \a id. * Returns a null std::shared_ptr if none exists or the object with * ID \a id is not of type T. */ - template - std::shared_ptr Object(int id); - - /** Returns a vector containing the objects with ids in \a object_ids */ - std::vector> FindObjects(const std::vector& object_ids) const; - std::vector> FindObjects(const std::set& object_ids) const; + template + std::shared_ptr get(int id); - /** Returns a vector containing the objects with ids in \a object_ids */ - std::vector> FindObjects(const std::vector& object_ids); - std::vector> FindObjects(const std::set& object_ids); + using id_range = boost::any_range; /** Returns a vector containing the objects with ids in \a object_ids that * are of type T */ - template - std::vector> FindObjects(const std::vector& object_ids) const; - - template - std::vector> FindObjects(const std::set& object_ids) const; + template + std::vector> find(const id_range& object_ids) const; /** Returns a vector containing the objects with ids in \a object_ids that * are of type T */ - template - std::vector> FindObjects(const std::vector& object_ids); - - template - std::vector> FindObjects(const std::set& object_ids); + template + std::vector> find(const id_range& object_ids); /** Returns all the objects that match \a visitor */ - std::vector> FindObjects(const UniverseObjectVisitor& visitor) const; + template + std::vector> find(const UniverseObjectVisitor& visitor) const; /** Returns all the objects that match \a visitor */ - std::vector> FindObjects(const UniverseObjectVisitor& visitor); + template + std::vector> find(const UniverseObjectVisitor& visitor); /** Returns all the objects of type T */ - template - std::vector> FindObjects() const; + template + boost::select_second_const_range> all() const + { return Map() | boost::adaptors::map_values; } /** Returns all the objects of type T */ - template - std::vector> FindObjects(); - - /** Returns the IDs of all the objects that match \a visitor */ - std::vector FindObjectIDs(const UniverseObjectVisitor& visitor) const; - - /** Returns the IDs of all the objects of type T */ - template - std::vector FindObjectIDs() const; + template + boost::select_second_mutable_range> all() + { return Map() | boost::adaptors::map_values; } /** Returns the IDs of all objects not known to have been destroyed. */ std::vector FindExistingObjectIDs() const; - /** Returns the IDs of all objects in this ObjectMap */ - std::vector FindObjectIDs() const; - - /** iterators */ - // these first 4 are primarily for convenience - iterator<> begin(); - iterator<> end(); - const_iterator<> const_begin() const; - const_iterator<> const_end() const; + /** Returns highest used object ID in this ObjectMap */ + int HighestObjectID() const; - template - iterator begin(); - template - iterator end(); - template - const_iterator const_begin() const; - template - const_iterator const_end() const; - - std::string Dump() const; + std::string Dump(unsigned short ntabs = 0) const; /** */ - std::shared_ptr ExistingObject(int id); - const std::map>& ExistingObjects() - { return m_existing_objects; } + std::shared_ptr ExistingObject(int id) const; - const std::map>& ExistingResourceCenters() + const container_type& ExistingObjects() const + { return m_existing_objects; } + const container_type& ExistingResourceCenters() const { return m_existing_resource_centers; } - - const std::map>& ExistingPopCenters() + const container_type& ExistingPopCenters() const { return m_existing_pop_centers; } - - const std::map>& ExistingShips() + const container_type& ExistingShips() const { return m_existing_ships; } - - const std::map>& ExistingFleets() + const container_type& ExistingFleets() const { return m_existing_fleets; } - - const std::map>& ExistingPlanets() + const container_type& ExistingPlanets() const { return m_existing_planets; } - - const std::map>& ExistingSystems() + const container_type& ExistingSystems() const { return m_existing_systems; } - - const std::map>& ExistingBuildings() + const container_type& ExistingBuildings() const { return m_existing_buildings; } - - const std::map>& ExistingFields() + const container_type& ExistingFields() const { return m_existing_fields; } - //@} /** \name Mutators */ //@{ @@ -313,12 +143,12 @@ class FO_COMMON_API ObjectMap { * Copy or Clone functions of the copied UniverseObjects. Any objects * in this ObjectMap that have no corresponding object in \a copied_map * are left unchanged. */ - void Copy(const ObjectMap& copied_map, int empire_id = ALL_EMPIRES); + void Copy(const ObjectMap& copied_map, int empire_id = ALL_EMPIRES); /** Copies the contents of the ObjectMap \a copied_map into this ObjectMap, in * preparation for serializing this ObjectMap. The normal object-by-object * CopyObject process is bypassed and only m_objects is copied, in a direct fashion. */ - void CopyForSerialize(const ObjectMap& copied_map); + void CopyForSerialize(const ObjectMap& copied_map); /** Copies the passed \a object into this ObjectMap, overwriting any * existing information about that object or creating a new object in this @@ -333,273 +163,206 @@ class FO_COMMON_API ObjectMap { /** Adds object \a obj to the map under its ID, if it is a valid object. * If there already was an object in the map with the id \a id then * that object will be removed. */ - template - void Insert(std::shared_ptr obj, int empire_id = ALL_EMPIRES); + template + void insert(std::shared_ptr obj, int empire_id = ALL_EMPIRES); /** Removes object with id \a id from map, and returns that object, if * there was an object under that ID in the map. If no such object * existed in the map, a null shared_ptr is returned and nothing is * removed. The ObjectMap will no longer share ownership of the * returned object. */ - std::shared_ptr Remove(int id); + std::shared_ptr erase(int id); /** Empties map, removing shared ownership by this map of all * previously contained objects. */ - void Clear(); + void clear(); /** Swaps the contents of *this with \a rhs. */ - void swap(ObjectMap& rhs); + void swap(ObjectMap& rhs); /** */ - void UpdateCurrentDestroyedObjects(const std::set& destroyed_object_ids); + void UpdateCurrentDestroyedObjects(const std::set& destroyed_object_ids); /** Recalculates contained objects for all objects in this ObjectMap based * on what other objects exist in this ObjectMap. Useful to eliminate * cases where there are inconsistencies between whan an object thinks it * contains, and what other objects think they are contained by the first * object. */ - void AuditContainment(const std::set& destroyed_object_ids); + void AuditContainment(const std::set& destroyed_object_ids); //@} private: - void InsertCore(std::shared_ptr item, int empire_id = ALL_EMPIRES); - - void CopyObjectsToSpecializedMaps(); - - template - const std::map>& Map() const; - - template - std::map>& Map(); - - template - static void ClearMap(std::map>& map); - - template - static void TryInsertIntoMap(std::map>& map, std::shared_ptr item); - - template - static void EraseFromMap(std::map>& map, int id); - - template - static void SwapMap(std::map>& map, ObjectMap& rhs); - - std::map> m_objects; - - std::map> m_resource_centers; - - std::map> m_pop_centers; - - std::map> m_ships; - - std::map> m_fleets; - - std::map> m_planets; - - std::map> m_systems; - - std::map> m_buildings; - - std::map> m_fields; - - std::map> m_existing_objects; - - std::map> m_existing_resource_centers; - - std::map> m_existing_pop_centers; - - std::map> m_existing_ships; - - std::map> m_existing_fleets; - - std::map> m_existing_planets; - - std::map> m_existing_systems; - - std::map> m_existing_buildings; - - std::map> m_existing_fields; + void insertCore(std::shared_ptr item, int empire_id = ALL_EMPIRES); + + void CopyObjectsToSpecializedMaps(); + + template + const container_type& Map() const; + + template + container_type& Map(); + + template + static void SwapMap(container_type& map, ObjectMap& rhs); + + container_type m_objects; + container_type m_resource_centers; + container_type m_pop_centers; + container_type m_ships; + container_type m_fleets; + container_type m_planets; + container_type m_systems; + container_type m_buildings; + container_type m_fields; + + container_type m_existing_objects; + container_type m_existing_resource_centers; + container_type m_existing_pop_centers; + container_type m_existing_ships; + container_type m_existing_fleets; + container_type m_existing_planets; + container_type m_existing_systems; + container_type m_existing_buildings; + container_type m_existing_fields; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -template -ObjectMap::iterator ObjectMap::begin() -{ return iterator(Map::type>().begin(), *this); } - -template -ObjectMap::iterator ObjectMap::end() -{ return iterator(Map::type>().end(), *this); } - -template -ObjectMap::const_iterator ObjectMap::const_begin() const -{ return const_iterator(Map::type>().begin(), *this); } - -template -ObjectMap::const_iterator ObjectMap::const_end() const -{ return const_iterator(Map::type>().end(), *this); } - -template -std::shared_ptr ObjectMap::Object(int id) const { - typename std::map::type>>::const_iterator it = - Map::type>().find(id); +template +std::shared_ptr ObjectMap::get(int id) const { + auto it = Map::type>().find(id); return std::shared_ptr( - it != Map::type>().end() + it != Map::type>().end() ? it->second : nullptr); } -template -std::shared_ptr ObjectMap::Object(int id) { - typename std::map::type>>::iterator it = - Map::type>().find(id); +template +std::shared_ptr ObjectMap::get(int id) { + auto it = Map::type>().find(id); return std::shared_ptr( - it != Map::type>().end() + it != Map::type>().end() ? it->second : nullptr); } -template -std::vector> ObjectMap::FindObjects() const { - std::vector> result; - for (const_iterator it = const_begin(); it != const_end(); ++it) - result.push_back(*it); - return result; -} - -template -std::vector> ObjectMap::FindObjects() { - std::vector> result; - for (iterator it = begin(); it != end(); ++it) - result.push_back(*it); - return result; -} - -template -std::vector ObjectMap::FindObjectIDs() const { - std::vector result; - for (typename std::map>::const_iterator - it = Map::type>().begin(); - it != Map::type>().end(); ++it) - { result.push_back(it->first); } - return result; -} - -template -std::vector> ObjectMap::FindObjects(const std::vector& object_ids) const { +template +std::vector> ObjectMap::find(const id_range& object_ids) const { std::vector> retval; - typedef typename boost::remove_const::type mutableT; + retval.reserve(boost::size(object_ids)); + typedef typename std::remove_const::type mutableT; for (int object_id : object_ids) { - typename std::map>::const_iterator map_it = Map().find(object_id); + auto map_it = Map().find(object_id); if (map_it != Map().end()) retval.push_back(std::shared_ptr(map_it->second)); } return retval; } -template -std::vector> ObjectMap::FindObjects(const std::set& object_ids) const { - std::vector> retval; - typedef typename boost::remove_const::type mutableT; +template +std::vector> ObjectMap::find(const id_range& object_ids) { + std::vector> retval; + retval.reserve(boost::size(object_ids)); + typedef typename std::remove_const::type mutableT; for (int object_id : object_ids) { - typename std::map>::const_iterator map_it = Map().find(object_id); + auto map_it = Map().find(object_id); if (map_it != Map().end()) - retval.push_back(std::shared_ptr(map_it->second)); + retval.push_back(std::shared_ptr(map_it->second)); } return retval; } -template -std::vector> ObjectMap::FindObjects(const std::vector& object_ids) { - std::vector> retval; - typedef typename boost::remove_const::type mutableT; - for (int object_id : object_ids) { - typename std::map>::const_iterator map_it = Map().find(object_id); - if (map_it != Map().end()) - retval.push_back(std::shared_ptr(map_it->second)); +template +std::vector> ObjectMap::find(const UniverseObjectVisitor& visitor) const { + std::vector> result; + typedef typename std::remove_const::type mutableT; + result.reserve(size()); + for (auto entry : Map()) { + if (entry.second->Accept(visitor)) + result.push_back(entry.second); } - return retval; + return result; } -template -std::vector> ObjectMap::FindObjects(const std::set& object_ids) { - std::vector> retval; - typedef typename boost::remove_const::type mutableT; - for (int object_id : object_ids) { - typename std::map>::const_iterator map_it = Map().find(object_id); - if (map_it != Map().end()) - retval.push_back(std::shared_ptr(map_it->second)); +template +std::vector> ObjectMap::find(const UniverseObjectVisitor& visitor) { + std::vector> result; + typedef typename std::remove_const::type mutableT; + result.reserve(size()); + for (const auto& entry : Map()) { + if (entry.second->Accept(visitor)) + result.push_back(entry.second); } - return retval; + return result; } -template -int ObjectMap::NumObjects() const -{ return Map::type>().size(); } +template +std::size_t ObjectMap::size() const +{ return Map::type>().size(); } -template -void ObjectMap::Insert(std::shared_ptr item, int empire_id /* = ALL_EMPIRES */) { +template +void ObjectMap::insert(std::shared_ptr item, int empire_id /* = ALL_EMPIRES */) { if (!item) return; - InsertCore(std::dynamic_pointer_cast(item), empire_id); + insertCore(std::dynamic_pointer_cast(item), empire_id); } // template specializations template <> -FO_COMMON_API const std::map>& ObjectMap::Map() const; +FO_COMMON_API const ObjectMap::container_type& ObjectMap::Map() const; template <> -FO_COMMON_API const std::map>& ObjectMap::Map() const; +FO_COMMON_API const ObjectMap::container_type& ObjectMap::Map() const; template <> -FO_COMMON_API const std::map>& ObjectMap::Map() const; +FO_COMMON_API const ObjectMap::container_type& ObjectMap::Map() const; template <> -FO_COMMON_API const std::map>& ObjectMap::Map() const; +FO_COMMON_API const ObjectMap::container_type& ObjectMap::Map() const; template <> -FO_COMMON_API const std::map>& ObjectMap::Map() const; +FO_COMMON_API const ObjectMap::container_type& ObjectMap::Map() const; template <> -FO_COMMON_API const std::map>& ObjectMap::Map() const; +FO_COMMON_API const ObjectMap::container_type& ObjectMap::Map() const; template <> -FO_COMMON_API const std::map>& ObjectMap::Map() const; +FO_COMMON_API const ObjectMap::container_type& ObjectMap::Map() const; template <> -FO_COMMON_API const std::map>& ObjectMap::Map() const; +FO_COMMON_API const ObjectMap::container_type& ObjectMap::Map() const; template <> -FO_COMMON_API const std::map>& ObjectMap::Map() const; +FO_COMMON_API const ObjectMap::container_type& ObjectMap::Map() const; template <> -FO_COMMON_API std::map>& ObjectMap::Map(); +FO_COMMON_API ObjectMap::container_type& ObjectMap::Map(); template <> -FO_COMMON_API std::map>& ObjectMap::Map(); +FO_COMMON_API ObjectMap::container_type& ObjectMap::Map(); template <> -FO_COMMON_API std::map>& ObjectMap::Map(); +FO_COMMON_API ObjectMap::container_type& ObjectMap::Map(); template <> -FO_COMMON_API std::map>& ObjectMap::Map(); +FO_COMMON_API ObjectMap::container_type& ObjectMap::Map(); template <> -FO_COMMON_API std::map>& ObjectMap::Map(); +FO_COMMON_API ObjectMap::container_type& ObjectMap::Map(); template <> -FO_COMMON_API std::map>& ObjectMap::Map(); +FO_COMMON_API ObjectMap::container_type& ObjectMap::Map(); template <> -FO_COMMON_API std::map>& ObjectMap::Map(); +FO_COMMON_API ObjectMap::container_type& ObjectMap::Map(); template <> -FO_COMMON_API std::map>& ObjectMap::Map(); +FO_COMMON_API ObjectMap::container_type& ObjectMap::Map(); template <> -FO_COMMON_API std::map>& ObjectMap::Map(); +FO_COMMON_API ObjectMap::container_type& ObjectMap::Map(); #endif diff --git a/universe/Pathfinder.cpp b/universe/Pathfinder.cpp index c4127431280..004bec985d2 100644 --- a/universe/Pathfinder.cpp +++ b/universe/Pathfinder.cpp @@ -2,13 +2,15 @@ #include "../util/Logger.h" #include "../util/ScopedTimer.h" +#include "../util/AppInterface.h" #include "../Empire/EmpireManager.h" +#include "Field.h" #include "Fleet.h" #include "Ship.h" #include "System.h" #include "UniverseObject.h" -#include "ValueRef.h" #include "Universe.h" +#include "Predicates.h" #include #include @@ -39,7 +41,8 @@ namespace { The table is assumed symmetric. If present row i element j will equal row j element i. */ - template struct distance_matrix_storage { + template + struct distance_matrix_storage { typedef T value_type; ///< An integral type for number of hops. typedef std::vector& row_ref; ///< A reference to row type @@ -83,7 +86,7 @@ namespace { //may be true that because this is a computed value that depends on //the system topology that almost never changes that the //synchronization costs way out-weigh the saved computation costs. - template + template class distance_matrix_cache { public: distance_matrix_cache(Storage& the_storage) : m_storage(the_storage) {} @@ -106,10 +109,10 @@ namespace { // the function so that the function still has the required signature. /// Cache miss handler - typedef boost::function cache_miss_handler; + typedef std::function cache_miss_handler; /// A function to examine an entire row cache hit - typedef boost::function cache_hit_handler; + typedef std::function cache_hit_handler; /** Retrieve a single element at (\p ii, \p jj). * On cache miss call the \p fill_row which must fill the row @@ -222,7 +225,7 @@ namespace SystemPathing { struct FoundDestination {}; // exception type thrown when destination is found PathFindingShortCircuitingVisitor(int dest_system) : destination_system(dest_system) {} - template + template void operator()(Vertex u, Graph& g) { if (static_cast(u) == destination_system) @@ -236,7 +239,8 @@ namespace SystemPathing { * - short-circuit exit on found match * - maximum search depth */ - template class BFSVisitorImpl + template + class BFSVisitorImpl { public: class FoundDestination {}; @@ -306,7 +310,7 @@ namespace SystemPathing { * just that system in it, and the path lenth is 0. If there is no path * between the two vertices, then the list is empty and the path length * is -1.0 */ - template + template std::pair, double> ShortestPathImpl(const Graph& graph, int system1_id, int system2_id, double linear_distance, const boost::unordered_map& id_to_graph_index) { @@ -386,7 +390,7 @@ namespace SystemPathing { * as system2_id, the path has just that system in it, and the path lenth * is 0. If there is no path between the two vertices, then the list is * empty and the path length is -1 */ - template + template std::pair, int> LeastJumpsPathImpl(const Graph& graph, int system1_id, int system2_id, const boost::unordered_map& id_to_graph_index, int max_jumps = INT_MAX) @@ -450,7 +454,7 @@ namespace SystemPathing { return retval; } - template + template std::multimap ImmediateNeighborsImpl(const Graph& graph, int system_id, const boost::unordered_map& id_to_graph_index) { @@ -461,9 +465,11 @@ namespace SystemPathing { std::multimap retval; ConstEdgeWeightPropertyMap edge_weight_map = boost::get(boost::edge_weight, graph); ConstSystemIDPropertyMap sys_id_property_map = boost::get(vertex_system_id_t(), graph); - std::pair edges = boost::out_edges(id_to_graph_index.at(system_id), graph); - for (OutEdgeIterator it = edges.first; it != edges.second; ++it) - { retval.insert(std::make_pair(edge_weight_map[*it], sys_id_property_map[boost::target(*it, graph)])); } + auto edges = boost::out_edges(id_to_graph_index.at(system_id), graph); + for (OutEdgeIterator it = edges.first; it != edges.second; ++it) { + retval.insert({edge_weight_map[*it], + sys_id_property_map[boost::target(*it, graph)]}); + } return retval; } @@ -487,11 +493,8 @@ namespace { typedef boost::adjacency_list SystemGraph; - struct EdgeVisibilityFilter { - EdgeVisibilityFilter() : - m_graph(nullptr), - m_empire_id(ALL_EMPIRES) - {} + struct EdgeVisibilityFilter { + EdgeVisibilityFilter() {} EdgeVisibilityFilter(const SystemGraph* graph, int empire_id) : m_graph(graph), @@ -515,7 +518,7 @@ namespace { int sys_id_2 = sys_id_property_map[sys_graph_index_2]; // look up lane between systems - std::shared_ptr system1 = GetEmpireKnownSystem(sys_id_1, m_empire_id); + std::shared_ptr system1 = EmpireKnownObjects(m_empire_id).get(sys_id_1); if (!system1) { ErrorLogger() << "EdgeDescriptor::operator() couldn't find system with id " << sys_id_1; return false; @@ -528,12 +531,101 @@ namespace { } private: - const SystemGraph* m_graph; - int m_empire_id; + const SystemGraph* m_graph = nullptr; + int m_empire_id = ALL_EMPIRES; }; typedef boost::filtered_graph EmpireViewSystemGraph; typedef std::map> EmpireViewSystemGraphMap; + + void AddSystemPredicate(const Pathfinder::SystemExclusionPredicateType& pred) { + for (auto empire : Empires()) { + auto empire_id = empire.first; + SystemPredicateFilter sys_pred_filter(&system_graph, empire_id, pred); + auto sys_pred_filtered_graph_ptr = std::make_shared(system_graph, sys_pred_filter); + + auto pred_it = system_pred_graph_views.find(pred); + if (pred_it == system_pred_graph_views.end()) { + EmpireSystemPredicateMap empire_graph_map; + empire_graph_map.emplace(empire_id, std::move(sys_pred_filtered_graph_ptr)); + system_pred_graph_views.emplace(pred, std::move(empire_graph_map)); + } else if (pred_it->second.count(empire_id)) { + pred_it->second.at(empire_id) = std::move(sys_pred_filtered_graph_ptr); + } else { + pred_it->second.emplace(empire_id, std::move(sys_pred_filtered_graph_ptr)); + } + } + } + + struct SystemPredicateFilter { + SystemPredicateFilter() {} + + SystemPredicateFilter(const SystemGraph* graph, int empire_id, + const Pathfinder::SystemExclusionPredicateType& pred) : + m_graph(graph), + m_empire_id(empire_id), + m_pred(pred) + { + if (!graph) + ErrorLogger() << "ExcludeObjectFilter passed null graph pointer"; + } + + template + bool operator()(const EdgeDescriptor& edge) const { + if (!m_graph) + return true; + + // get system ids from graph indices + + // for reverse-lookup System universe ID from graph index + ConstSystemIDPropertyMap sys_id_property_map = boost::get(vertex_system_id_t(), *m_graph); + int sys_graph_index_1 = boost::source(edge, *m_graph); + int sys_id_1 = sys_id_property_map[sys_graph_index_1]; + int sys_graph_index_2 = boost::target(edge, *m_graph); + int sys_id_2 = sys_id_property_map[sys_graph_index_2]; + + // look up objects in system + auto system1 = EmpireKnownObjects(m_empire_id).get(sys_id_1); + if (!system1) { + ErrorLogger() << "Invalid source system " << sys_id_1; + return true; + } + auto system2 = EmpireKnownObjects(m_empire_id).get(sys_id_2); + if (!system2) { + ErrorLogger() << "Invalid target system " << sys_id_2; + return true; + } + + if (!system1->HasStarlaneTo(system2->ID())) { + DebugLogger() << "No starlane from " << system1->ID() << " to " << system2->ID(); + return false; + } + + // Discard edge if it finds a contained object or matches either system for visitor + for (auto object : EmpireKnownObjects(m_empire_id).find(*m_pred.get())) { + if (!object) + continue; + // object is destination system + if (object->ID() == system2->ID()) + return false; + + // object contained by destination system + if (object->ContainedBy(system2->ID())) + return false; + } + + return true; + } + + private: + const SystemGraph* m_graph = nullptr; + int m_empire_id = ALL_EMPIRES; + Pathfinder::SystemExclusionPredicateType m_pred; + }; + typedef boost::filtered_graph SystemPredicateGraph; + typedef std::map> EmpireSystemPredicateMap; + typedef std::map SystemPredicateGraphMap; + // declare property map types for properties declared above typedef boost::property_map::const_type ConstSystemIDPropertyMap; typedef boost::property_map::type SystemIDPropertyMap; @@ -544,7 +636,11 @@ namespace { SystemGraph system_graph; ///< a graph in which the systems are vertices and the starlanes are edges EmpireViewSystemGraphMap empire_system_graph_views; ///< a map of empire IDs to the views of the system graph by those empires + /** Empire system graphs indexed by object predicate */ + SystemPredicateGraphMap system_pred_graph_views; + std::unordered_set system_predicates; }; + } ///////////////////////////////////////////// @@ -560,6 +656,8 @@ class Pathfinder::PathfinderImpl { int JumpDistanceBetweenObjects(int object1_id, int object2_id) const; std::pair, double> ShortestPath(int system1_id, int system2_id, int empire_id = ALL_EMPIRES) const; + std::pair, double> ShortestPath(int system1_id, int system2_id, int empire_id, + const Pathfinder::SystemExclusionPredicateType& sys_pred) const; double ShortestPathDistance(int object1_id, int object2_id) const; std::pair, int> LeastJumpsPath( int system1_id, int system2_id, int empire_id = ALL_EMPIRES, int max_jumps = INT_MAX) const; @@ -627,11 +725,11 @@ Pathfinder::~Pathfinder() {} namespace { - std::shared_ptr FleetFromObject(std::shared_ptr obj) { + std::shared_ptr FleetFromObject(const std::shared_ptr& obj) { std::shared_ptr retval = std::dynamic_pointer_cast(obj); if (!retval) { - if (std::shared_ptr ship = std::dynamic_pointer_cast(obj)) - retval = GetFleet(ship->FleetID()); + if (auto ship = std::dynamic_pointer_cast(obj)) + retval = Objects().get(ship->FleetID()); } return retval; } @@ -641,7 +739,8 @@ namespace { */ void Pathfinder::PathfinderImpl::HandleCacheMiss(size_t ii, distance_matrix_storage::row_ref row) const { - typedef boost::iterator_property_map::iterator, boost::identity_property_map> DistancePropertyMap; + typedef boost::iterator_property_map::iterator, + boost::identity_property_map> DistancePropertyMap; distance_matrix_storage::row_ref distance_buffer = row; distance_buffer.assign(m_system_jumps.size(), SHRT_MAX); @@ -663,12 +762,12 @@ double Pathfinder::LinearDistance(int system1_id, int system2_id) const { } double Pathfinder::PathfinderImpl::LinearDistance(int system1_id, int system2_id) const { - std::shared_ptr system1 = GetSystem(system1_id); + const auto system1 = Objects().get(system1_id); if (!system1) { ErrorLogger() << "Universe::LinearDistance passed invalid system id: " << system1_id; throw std::out_of_range("system1_id invalid"); } - std::shared_ptr system2 = GetSystem(system2_id); + const auto system2 = Objects().get(system2_id); if (!system2) { ErrorLogger() << "Universe::LinearDistance passed invalid system id: " << system2_id; throw std::out_of_range("system2_id invalid"); @@ -722,26 +821,33 @@ namespace { typedef boost::variant> GeneralizedLocationType; /** Return the location of \p obj.*/ - GeneralizedLocationType GeneralizedLocation(std::shared_ptr obj) { + GeneralizedLocationType GeneralizedLocation(const std::shared_ptr& obj) { if (!obj) return nullptr; int system_id = obj->SystemID(); - std::shared_ptr system = GetSystem(system_id); + auto system = Objects().get(system_id); if (system) return system_id; - std::shared_ptr fleet = FleetFromObject(obj); + auto fleet = FleetFromObject(obj); if (fleet) return std::make_pair(fleet->PreviousSystemID(), fleet->NextSystemID()); + if (std::dynamic_pointer_cast(obj)) + return nullptr; + + // Don't generate an error message for temporary objects. + if (obj->ID() == TEMPORARY_OBJECT_ID) + return nullptr; + ErrorLogger() << "GeneralizedLocationType unable to locate " << obj->Name() << "(" << obj->ID() << ")"; return nullptr; } /** Return the location of the object with id \p object_id.*/ GeneralizedLocationType GeneralizedLocation(int object_id) { - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); return GeneralizedLocation(obj); } @@ -851,11 +957,13 @@ int Pathfinder::PathfinderImpl::JumpDistanceBetweenObjects(int object1_id, int o return boost::apply_visitor(visitor, obj1); } -std::pair, double> Pathfinder::ShortestPath(int system1_id, int system2_id, int empire_id/* = ALL_EMPIRES*/) const { - return pimpl->ShortestPath(system1_id, system2_id, empire_id); -} +std::pair, double> Pathfinder::ShortestPath(int system1_id, int system2_id, + int empire_id/* = ALL_EMPIRES*/) const +{ return pimpl->ShortestPath(system1_id, system2_id, empire_id); } -std::pair, double> Pathfinder::PathfinderImpl::ShortestPath(int system1_id, int system2_id, int empire_id/* = ALL_EMPIRES*/) const { +std::pair, double> Pathfinder::PathfinderImpl::ShortestPath(int system1_id, int system2_id, + int empire_id/* = ALL_EMPIRES*/) const +{ if (empire_id == ALL_EMPIRES) { // find path on full / complete system graph try { @@ -870,8 +978,7 @@ std::pair, double> Pathfinder::PathfinderImpl::ShortestPath(int s } // find path on single empire's view of system graph - GraphImpl::EmpireViewSystemGraphMap::const_iterator graph_it = - m_graph_impl->empire_system_graph_views.find(empire_id); + auto graph_it = m_graph_impl->empire_system_graph_views.find(empire_id); if (graph_it == m_graph_impl->empire_system_graph_views.end()) { ErrorLogger() << "PathfinderImpl::ShortestPath passed unknown empire id: " << empire_id; throw std::out_of_range("PathfinderImpl::ShortestPath passed unknown empire id"); @@ -887,6 +994,43 @@ std::pair, double> Pathfinder::PathfinderImpl::ShortestPath(int s } } +std::pair, double> Pathfinder::ShortestPath(int system1_id, int system2_id, int empire_id, + const SystemExclusionPredicateType& system_predicate) const +{ return pimpl->ShortestPath(system1_id, system2_id, empire_id, system_predicate); } + +std::pair, double> Pathfinder::PathfinderImpl::ShortestPath( + int system1_id, int system2_id, int empire_id, const Pathfinder::SystemExclusionPredicateType& sys_pred) const +{ + if (empire_id == ALL_EMPIRES) { + ErrorLogger() << "Invalid empire " << empire_id; + throw std::out_of_range("PathfinderImpl::ShortestPath passed invalid empire id"); + } + + auto func_it = m_graph_impl->system_pred_graph_views.find(sys_pred); + if (func_it == m_graph_impl->system_pred_graph_views.end()) { + m_graph_impl->AddSystemPredicate(sys_pred); + func_it = m_graph_impl->system_pred_graph_views.find(sys_pred); + if (func_it == m_graph_impl->system_pred_graph_views.end()) { + ErrorLogger() << "No graph views found for predicate"; + throw std::out_of_range("PathfinderImpl::ShortestPath No graph views found for predicate"); + } + } + auto graph_it = func_it->second.find(empire_id); + if (graph_it == func_it->second.end()) { + ErrorLogger() << "No graph view found for empire " << empire_id; + throw std::out_of_range("PathfinderImpl::ShortestPath No graph view for empire"); + } + + try { + auto linear_distance = LinearDistance(system1_id, system2_id); + return ShortestPathImpl(*graph_it->second, system1_id, system2_id, + linear_distance, m_system_id_to_graph_index); + } catch (const std::out_of_range&) { + ErrorLogger() << "Invalid system id(s): " << system1_id << ", " << system2_id; + throw; + } +} + double Pathfinder::ShortestPathDistance(int object1_id, int object2_id) const { return pimpl->ShortestPathDistance(object1_id, object2_id); } @@ -895,16 +1039,16 @@ double Pathfinder::PathfinderImpl::ShortestPathDistance(int object1_id, int obje // If one or both objects are (in) a fleet between systems, use the destination system // and add the distance from the fleet to the destination system, essentially calculating // the distance travelled until both could be in the same system. - std::shared_ptr obj1 = GetUniverseObject(object1_id); + const auto obj1 = Objects().get(object1_id); if (!obj1) return -1; - std::shared_ptr obj2 = GetUniverseObject(object2_id); + const auto obj2 = Objects().get(object2_id); if (!obj2) return -1; - std::shared_ptr system_one = GetSystem(obj1->SystemID()); - std::shared_ptr system_two = GetSystem(obj2->SystemID()); + auto system_one = Objects().get(obj1->SystemID()); + auto system_two = Objects().get(obj2->SystemID()); std::pair< std::list< int >, double > path_len_pair; double dist1(0.0), dist2(0.0); std::shared_ptr fleet; @@ -913,7 +1057,7 @@ double Pathfinder::PathfinderImpl::ShortestPathDistance(int object1_id, int obje fleet = FleetFromObject(obj1); if (!fleet) return -1; - if (std::shared_ptr next_sys = GetSystem(fleet->NextSystemID())) { + if (auto next_sys = Objects().get(fleet->NextSystemID())) { system_one = next_sys; dist1 = std::sqrt(pow((next_sys->X() - fleet->X()), 2) + pow((next_sys->Y() - fleet->Y()), 2)); } @@ -923,7 +1067,7 @@ double Pathfinder::PathfinderImpl::ShortestPathDistance(int object1_id, int obje fleet = FleetFromObject(obj2); if (!fleet) return -1; - if (std::shared_ptr next_sys = GetSystem(fleet->NextSystemID())) { + if (auto next_sys = Objects().get(fleet->NextSystemID())) { system_two = next_sys; dist2 = std::sqrt(pow((next_sys->X() - fleet->X()), 2) + pow((next_sys->Y() - fleet->Y()), 2)); } @@ -959,8 +1103,7 @@ std::pair, int> Pathfinder::PathfinderImpl::LeastJumpsPath( } // find path on single empire's view of system graph - GraphImpl::EmpireViewSystemGraphMap::const_iterator graph_it = - m_graph_impl->empire_system_graph_views.find(empire_id); + auto graph_it = m_graph_impl->empire_system_graph_views.find(empire_id); if (graph_it == m_graph_impl->empire_system_graph_views.end()) { ErrorLogger() << "PathfinderImpl::LeastJumpsPath passed unknown empire id: " << empire_id; throw std::out_of_range("PathfinderImpl::LeastJumpsPath passed unknown empire id"); @@ -975,41 +1118,42 @@ std::pair, int> Pathfinder::PathfinderImpl::LeastJumpsPath( } } -bool Pathfinder::SystemsConnected(int system1_id, int system2_id, int empire_id) const { - return pimpl->SystemsConnected(system1_id, system2_id, empire_id); -} +bool Pathfinder::SystemsConnected(int system1_id, int system2_id, int empire_id) const +{ return pimpl->SystemsConnected(system1_id, system2_id, empire_id); } bool Pathfinder::PathfinderImpl::SystemsConnected(int system1_id, int system2_id, int empire_id) const { - //DebugLogger() << "SystemsConnected(" << system1_id << ", " << system2_id << ", " << empire_id << ")"; - std::pair, int> path = LeastJumpsPath(system1_id, system2_id, empire_id); - //DebugLogger() << "SystemsConnected returned path of size: " << path.first.size(); + TraceLogger() << "SystemsConnected(" << system1_id << ", " << system2_id << ", " << empire_id << ")"; + auto path = LeastJumpsPath(system1_id, system2_id, empire_id); + TraceLogger() << "SystemsConnected returned path of size: " << path.first.size(); bool retval = !path.first.empty(); - //DebugLogger() << "SystemsConnected retval: " << retval; + TraceLogger() << "SystemsConnected retval: " << retval; return retval; } -bool Pathfinder::SystemHasVisibleStarlanes(int system_id, int empire_id) const { - return pimpl->SystemHasVisibleStarlanes(system_id, empire_id); -} +bool Pathfinder::SystemHasVisibleStarlanes(int system_id, int empire_id) const +{ return pimpl->SystemHasVisibleStarlanes(system_id, empire_id); } bool Pathfinder::PathfinderImpl::SystemHasVisibleStarlanes(int system_id, int empire_id) const { - if (std::shared_ptr system = GetEmpireKnownSystem(system_id, empire_id)) + if (auto system = EmpireKnownObjects(empire_id).get(system_id)) if (!system->StarlanesWormholes().empty()) return true; return false; } -std::multimap Pathfinder::ImmediateNeighbors(int system_id, int empire_id/* = ALL_EMPIRES*/) const { - return pimpl->ImmediateNeighbors(system_id, empire_id); -} +std::multimap Pathfinder::ImmediateNeighbors(int system_id, int empire_id/* = ALL_EMPIRES*/) const +{ return pimpl->ImmediateNeighbors(system_id, empire_id); } -std::multimap Pathfinder::PathfinderImpl::ImmediateNeighbors(int system_id, int empire_id/* = ALL_EMPIRES*/) const { +std::multimap Pathfinder::PathfinderImpl::ImmediateNeighbors( + int system_id, int empire_id/* = ALL_EMPIRES*/) const +{ if (empire_id == ALL_EMPIRES) { - return ImmediateNeighborsImpl(m_graph_impl->system_graph, system_id, m_system_id_to_graph_index); + return ImmediateNeighborsImpl(m_graph_impl->system_graph, system_id, + m_system_id_to_graph_index); } else { - GraphImpl::EmpireViewSystemGraphMap::const_iterator graph_it = m_graph_impl->empire_system_graph_views.find(empire_id); + auto graph_it = m_graph_impl->empire_system_graph_views.find(empire_id); if (graph_it != m_graph_impl->empire_system_graph_views.end()) - return ImmediateNeighborsImpl(*graph_it->second, system_id, m_system_id_to_graph_index); + return ImmediateNeighborsImpl(*graph_it->second, system_id, + m_system_id_to_graph_index); } return std::multimap(); } @@ -1017,8 +1161,8 @@ std::multimap Pathfinder::PathfinderImpl::ImmediateNeighbors(int sy void Pathfinder::PathfinderImpl::WithinJumpsCacheHit( std::unordered_set* result, size_t jump_limit, - size_t ii, distance_matrix_storage::row_ref row) const { - + size_t ii, distance_matrix_storage::row_ref row) const +{ // Scan the LUT of system ids and add any result from the row within // the neighborhood range to the results. for (auto system_id_and_ii : m_system_id_to_graph_index) { @@ -1028,9 +1172,8 @@ void Pathfinder::PathfinderImpl::WithinJumpsCacheHit( } } -std::unordered_set Pathfinder::WithinJumps(size_t jumps, const std::vector& candidates) const { - return pimpl->WithinJumps(jumps, candidates); -} +std::unordered_set Pathfinder::WithinJumps(size_t jumps, const std::vector& candidates) const +{ return pimpl->WithinJumps(jumps, candidates); } std::unordered_set Pathfinder::PathfinderImpl::WithinJumps( size_t jumps, const std::vector& candidates) const @@ -1136,7 +1279,8 @@ void Pathfinder::PathfinderImpl::WithinJumpsOfOthersCacheHit( } } -std::pair>, std::vector>> +std::pair>, + std::vector>> Pathfinder::WithinJumpsOfOthers( int jumps, const std::vector>& candidates, @@ -1145,7 +1289,8 @@ Pathfinder::WithinJumpsOfOthers( return pimpl->WithinJumpsOfOthers(jumps, candidates, stationary); } -std::pair>, std::vector>> +std::pair>, + std::vector>> Pathfinder::PathfinderImpl::WithinJumpsOfOthers( int jumps, const std::vector>& candidates, @@ -1169,7 +1314,7 @@ Pathfinder::PathfinderImpl::WithinJumpsOfOthers( far.push_back(candidate); } - return {near, far}; + return {near, far}; //, wherever you are... } bool Pathfinder::PathfinderImpl::WithinJumpsOfOthers( @@ -1197,19 +1342,16 @@ bool Pathfinder::PathfinderImpl::WithinJumpsOfOthers( return within_jumps; } - -int Pathfinder::NearestSystemTo(double x, double y) const { - return pimpl->NearestSystemTo(x, y); -} +int Pathfinder::NearestSystemTo(double x, double y) const +{ return pimpl->NearestSystemTo(x, y); } int Pathfinder::PathfinderImpl::NearestSystemTo(double x, double y) const { - double min_dist2 = DBL_MAX; + double min_dist2 = std::numeric_limits::max(); int min_dist2_sys_id = INVALID_OBJECT_ID; - std::vector> systems = Objects().FindObjects(); + auto systems = Objects().all(); - for (auto const& system : systems) - { + for (auto const& system : systems) { double xs = system->X(); double ys = system->Y(); double dist2 = (xs-x)*(xs-x) + (ys-y)*(ys-y); @@ -1224,19 +1366,26 @@ int Pathfinder::PathfinderImpl::NearestSystemTo(double x, double y) const { } -void Pathfinder::InitializeSystemGraph(const std::vector system_ids, int for_empire_id) { - return pimpl->InitializeSystemGraph(system_ids, for_empire_id); -} +void Pathfinder::InitializeSystemGraph(const std::vector system_ids, int for_empire_id) +{ return pimpl->InitializeSystemGraph(system_ids, for_empire_id); } -void Pathfinder::PathfinderImpl::InitializeSystemGraph(const std::vector system_ids, int for_empire_id) { - typedef boost::graph_traits::edge_descriptor EdgeDescriptor; +void Pathfinder::PathfinderImpl::InitializeSystemGraph( + const std::vector system_ids, int for_empire_id) +{ auto new_graph_impl = std::make_shared(); - // std::vector system_ids = ::EmpireKnownObjects(for_empire_id).FindObjectIDs(); + // auto system_ids = ::EmpireKnownObjects(for_empire_id).FindObjectIDs(); // NOTE: this initialization of graph_changed prevents testing for edges between nonexistant vertices bool graph_changed = system_ids.size() != boost::num_vertices(m_graph_impl->system_graph); - //DebugLogger() << "InitializeSystemGraph(" << for_empire_id << ") system_ids: (" << system_ids.size() << ")"; - //for (int id : system_ids) - // DebugLogger() << " ... " << *it; + + auto ints_to_string = [](const std::vector& ints_vec) { + std::stringstream o; + for (auto id : ints_vec) + o << id << " "; + return o.str(); + }; + TraceLogger() << "InitializeSystemGraph(" << for_empire_id + << ") system_ids: (" << system_ids.size() << "): " + << ints_to_string(system_ids); GraphImpl::SystemIDPropertyMap sys_id_property_map = boost::get(vertex_system_id_t(), new_graph_impl->system_graph); @@ -1260,7 +1409,7 @@ void Pathfinder::PathfinderImpl::InitializeSystemGraph(const std::vector sy // add edges for all starlanes for (size_t system1_index = 0; system1_index < system_ids.size(); ++system1_index) { int system1_id = system_ids[system1_index]; - std::shared_ptr system1 = GetEmpireKnownSystem(system1_id, for_empire_id); + std::shared_ptr system1 = EmpireKnownObjects(for_empire_id).get(system1_id); //std::shared_ptr & system1 = systems[system1_index]; // add edges and edge weights @@ -1274,13 +1423,13 @@ void Pathfinder::PathfinderImpl::InitializeSystemGraph(const std::vector sy continue; // get new_graph_impl->system_graph index for this system - boost::unordered_map::iterator reverse_lookup_map_it = m_system_id_to_graph_index.find(lane_dest_id); + auto reverse_lookup_map_it = m_system_id_to_graph_index.find(lane_dest_id); if (reverse_lookup_map_it == m_system_id_to_graph_index.end()) continue; // couldn't find destination system id in vertex lookup map; don't add to graph size_t lane_dest_graph_index = reverse_lookup_map_it->second; - std::pair add_edge_result = - boost::add_edge(system1_index, lane_dest_graph_index, new_graph_impl->system_graph); + auto add_edge_result = boost::add_edge(system1_index, lane_dest_graph_index, + new_graph_impl->system_graph); if (add_edge_result.second) { // if this is a non-duplicate starlane or wormhole if (lane_dest.second) { // if this is a wormhole @@ -1302,7 +1451,9 @@ void Pathfinder::PathfinderImpl::InitializeSystemGraph(const std::vector sy // if all previous edges still exist in the new graph, and the number of vertices and edges hasn't changed, // then no vertices or edges can have been added either, so it is still the same graph - graph_changed = graph_changed || boost::num_edges(new_graph_impl->system_graph) != boost::num_edges(m_graph_impl->system_graph); + graph_changed = graph_changed || + boost::num_edges(new_graph_impl->system_graph) != + boost::num_edges(m_graph_impl->system_graph); if (graph_changed) { new_graph_impl.swap(m_graph_impl); @@ -1313,12 +1464,12 @@ void Pathfinder::PathfinderImpl::InitializeSystemGraph(const std::vector sy UpdateEmpireVisibilityFilteredSystemGraphs(for_empire_id); } -void Pathfinder::UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id) { - return pimpl->UpdateEmpireVisibilityFilteredSystemGraphs(for_empire_id); -} +void Pathfinder::UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id) +{ return pimpl->UpdateEmpireVisibilityFilteredSystemGraphs(for_empire_id); } void Pathfinder::PathfinderImpl::UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id) { m_graph_impl->empire_system_graph_views.clear(); + m_graph_impl->system_pred_graph_views.clear(); // if building system graph views for all empires, then each empire's graph // should accurately filter for that empire's visibility. if building @@ -1348,4 +1499,6 @@ void Pathfinder::PathfinderImpl::UpdateEmpireVisibilityFilteredSystemGraphs(int m_graph_impl->empire_system_graph_views[empire_id] = filtered_graph_ptr; } } + for (const auto& prev_pred : m_graph_impl->system_predicates) + m_graph_impl->AddSystemPredicate(prev_pred); } diff --git a/universe/Pathfinder.h b/universe/Pathfinder.h index e3fe274dc99..4b1b1751aaa 100644 --- a/universe/Pathfinder.h +++ b/universe/Pathfinder.h @@ -3,8 +3,6 @@ #include "UniverseObject.h" -#include - #include #include #include @@ -21,8 +19,8 @@ * around the Universe. */ class FO_COMMON_API Pathfinder { public: - typedef std::shared_ptr ConstPtr; + typedef std::shared_ptr SystemExclusionPredicateType; /** \name Structors */ //@{ Pathfinder(); @@ -34,19 +32,19 @@ class FO_COMMON_API Pathfinder { /** Returns the straight-line distance between the objects with the given * IDs. \throw std::out_of_range This function will throw if either object * ID is out of range. */ - double LinearDistance(int object1_id, int object2_id) const; + double LinearDistance(int object1_id, int object2_id) const; /** Returns the number of starlane jumps between the systems with the given * IDs. If there is no path between the systems, -1 is returned. * \throw std::out_of_range This function will throw if either system * ID is not a valid system id. */ - short JumpDistanceBetweenSystems(int system1_id, int system2_id) const; + short JumpDistanceBetweenSystems(int system1_id, int system2_id) const; /** Returns the number of starlane jumps between any two objects, accounting * for cases where one or the other are fleets / ships on starlanes between * systems. Returns INT_MAX when no path exists, or either object does not * exist. */ - int JumpDistanceBetweenObjects(int object1_id, int object2_id) const; + int JumpDistanceBetweenObjects(int object1_id, int object2_id) const; /** Returns the sequence of systems, including \a system1_id and * \a system2_id, that defines the shortest path from \a system1 to @@ -57,14 +55,25 @@ class FO_COMMON_API Pathfinder { * visibility if \a empire_id == ALL_EMPIRES. * \throw std::out_of_range This function will throw if either system ID * is out of range, or if the empire ID is not known. */ - std::pair, double> - ShortestPath(int system1_id, int system2_id, int empire_id = ALL_EMPIRES) const; + std::pair, double> ShortestPath(int system1_id, int system2_id, int empire_id = ALL_EMPIRES) const; + + /** Shortest path known to an empire between two systems, excluding routes + * for systems containing objects for @p system_predicate. + * @param system1_id source System id + * @param system2_id destination System id + * @param empire_id ID of viewing Empire + * @param system_predicate UniverseObjectVisitor, A System is excluded as a potential node in any route + * if it is or contains a matched object + * + * @returns list of System ids, distance between systems */ + std::pair, double> ShortestPath(int system1_id, int system2_id, int empire_id, + const SystemExclusionPredicateType& system_predicate) const; /** Returns the shortest starlane path distance between any two objects, accounting * for cases where one or the other are fleets / ships on starlanes between * systems. Returns -1 when no path exists, or either object does not * exist. */ - double ShortestPathDistance(int object1_id, int object2_id) const; + double ShortestPathDistance(int object1_id, int object2_id) const; /** Returns the sequence of systems, including \a system1 and \a system2, * that defines the path with the fewest jumps from \a system1 to @@ -74,9 +83,8 @@ class FO_COMMON_API Pathfinder { * \a empire_id == ALL_EMPIRES. \throw std::out_of_range This function * will throw if either system ID is out of range or if the empire ID is * not known. */ - std::pair, int> - LeastJumpsPath(int system1_id, int system2_id, int empire_id = ALL_EMPIRES, - int max_jumps = INT_MAX) const; + std::pair, int> LeastJumpsPath(int system1_id, int system2_id, int empire_id = ALL_EMPIRES, + int max_jumps = INT_MAX) const; /** Returns whether there is a path known to empire \a empire_id between * system \a system1 and system \a system2. The path is calculated using @@ -84,7 +92,7 @@ class FO_COMMON_API Pathfinder { * if \a empire_id == ALL_EMPIRES. \throw std::out_of_range This function * will throw if either system ID is out of range or if the empire ID is * not known. */ - bool SystemsConnected(int system1_id, int system2_id, int empire_id = ALL_EMPIRES) const; + bool SystemsConnected(int system1_id, int system2_id, int empire_id = ALL_EMPIRES) const; /** Returns true iff \a system is reachable from another system (i.e. it * has at least one known starlane to it). This does not guarantee that @@ -93,7 +101,7 @@ class FO_COMMON_API Pathfinder { * The starlanes considered depend on their visibility for empire * \a empire_id, or without regard to visibility if * \a empire_id == ALL_EMPIRES. */ - bool SystemHasVisibleStarlanes(int system_id, int empire_id = ALL_EMPIRES) const; + bool SystemHasVisibleStarlanes(int system_id, int empire_id = ALL_EMPIRES) const; /** Returns the systems that are one starlane hop away from system * \a system. The returned systems are indexed by distance from @@ -104,7 +112,7 @@ class FO_COMMON_API Pathfinder { * ID is out of range. */ //TODO empire_id is never set to anything other than self, which in //the AI's is the same as ALL_EMPIRES - std::multimap ImmediateNeighbors(int system_id, int empire_id = ALL_EMPIRES) const; + std::multimap ImmediateNeighbors(int system_id, int empire_id = ALL_EMPIRES) const; /** Returns the system ids of systems that are within \p jumps of the \p candidates system ids.*/ @@ -112,7 +120,8 @@ class FO_COMMON_API Pathfinder { /** Returns the partition (near, far) of the \p candidate objects into two sets, those that are within \p jumps of the \p stationary objects and that are not.*/ - std::pair>, std::vector>> + std::pair>, + std::vector>> WithinJumpsOfOthers( int jumps, const std::vector>& candidates, @@ -129,14 +138,12 @@ class FO_COMMON_API Pathfinder { /** Fills pathfinding data structure and determines least jumps distances * between systems for the empire with id \a for_empire_id or uses the * main / true / visible objects if \a for_empire_id is ALL_EMPIRES*/ - void InitializeSystemGraph(const std::vector system_ids, int for_empire_id = ALL_EMPIRES); + void InitializeSystemGraph(const std::vector system_ids, int for_empire_id = ALL_EMPIRES); /** Regenerates per-empire system view graphs by filtering the complete * system graph based on empire visibility. Does not regenerate the base * graph to account for actual system-starlane connectivity changes. */ - void UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id = ALL_EMPIRES); - - + void UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id = ALL_EMPIRES); //@} class PathfinderImpl; diff --git a/universe/Planet.cpp b/universe/Planet.cpp index 6976b4ec513..1cd761285d3 100644 --- a/universe/Planet.cpp +++ b/universe/Planet.cpp @@ -1,20 +1,23 @@ #include "Planet.h" #include "Building.h" +#include "BuildingType.h" +#include "Condition.h" #include "Fleet.h" #include "Ship.h" #include "System.h" #include "Predicates.h" #include "Species.h" -#include "Condition.h" #include "Universe.h" -#include "ValueRef.h" #include "Enums.h" +#include "ValueRef.h" #include "../util/Logger.h" +#include "../util/GameRules.h" #include "../util/OptionsDB.h" #include "../util/Random.h" #include "../util/Directories.h" #include "../util/SitRepEntry.h" +#include "../util/i18n.h" #include "../Empire/Empire.h" #include "../Empire/EmpireManager.h" @@ -89,7 +92,7 @@ Planet* Planet::Clone(int empire_id) const { void Planet::Copy(std::shared_ptr copied_object, int empire_id) { if (copied_object.get() == this) return; - std::shared_ptr copied_planet = std::dynamic_pointer_cast(copied_object); + auto copied_planet = std::dynamic_pointer_cast(copied_object); if (!copied_planet) { ErrorLogger() << "Planet::Copy passed an object that wasn't a Planet"; return; @@ -97,7 +100,7 @@ void Planet::Copy(std::shared_ptr copied_object, int empir int copied_object_id = copied_object->ID(); Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(copied_object_id, empire_id); - std::set visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id); + auto visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id); UniverseObject::Copy(copied_object, vis, visible_specials); PopCenter::Copy(copied_planet, vis); @@ -114,7 +117,9 @@ void Planet::Copy(std::shared_ptr copied_object, int empir this->m_initial_orbital_position = copied_planet->m_initial_orbital_position; this->m_rotational_period = copied_planet->m_rotational_period; this->m_axial_tilt = copied_planet->m_axial_tilt; - this->m_just_conquered = copied_planet->m_just_conquered; + this->m_turn_last_conquered = copied_planet->m_turn_last_conquered; + this->m_turn_last_colonized = copied_planet->m_turn_last_colonized; + if (vis >= VIS_PARTIAL_VISIBILITY) { if (vis >= VIS_FULL_VISIBILITY) { @@ -135,6 +140,24 @@ void Planet::Copy(std::shared_ptr copied_object, int empir } } +bool Planet::HostileToEmpire(int empire_id) const +{ + if (OwnedBy(empire_id)) + return false; + + // Empire owned planets are hostile to ALL_EMPIRES + if (empire_id == ALL_EMPIRES) + return !Unowned(); + + // Unowned planets are only considered hostile if populated + auto pop_meter = GetMeter(METER_TARGET_POPULATION); + if (Unowned()) + return pop_meter && (pop_meter->Current() != 0.0f); + + // both empires are normal empires + return Empires().GetDiplomaticStatus(Owner(), empire_id) == DIPLO_WAR; +} + std::set Planet::Tags() const { const Species* species = GetSpecies(SpeciesName()); if (!species) @@ -151,28 +174,29 @@ bool Planet::HasTag(const std::string& name) const { UniverseObjectType Planet::ObjectType() const { return OBJ_PLANET; } -std::string Planet::Dump() const { +std::string Planet::Dump(unsigned short ntabs) const { std::stringstream os; - os << UniverseObject::Dump(); - os << PopCenter::Dump(); - os << ResourceCenter::Dump(); + os << UniverseObject::Dump(ntabs); + os << PopCenter::Dump(ntabs); + os << ResourceCenter::Dump(ntabs); os << " type: " << m_type << " original type: " << m_original_type << " size: " << m_size << " rot period: " << m_rotational_period << " axis tilt: " << m_axial_tilt << " buildings: "; - for (std::set::const_iterator it = m_buildings.begin(); it != m_buildings.end();) { + for (auto it = m_buildings.begin(); it != m_buildings.end();) { int building_id = *it; ++it; os << building_id << (it == m_buildings.end() ? "" : ", "); } if (m_is_about_to_be_colonized) - os << " (About to be Colonize)"; + os << " (About to be Colonized)"; if (m_is_about_to_be_invaded) os << " (About to be Invaded)"; - if (m_just_conquered) - os << " (Just Conquered)"; + + os << " colonized on turn: " << m_turn_last_colonized; + os << " conquered on turn: " << m_turn_last_conquered; if (m_is_about_to_be_bombarded) os << " (About to be Bombarded)"; if (m_ordered_given_to_empire_id != ALL_EMPIRES) @@ -182,15 +206,16 @@ std::string Planet::Dump() const { return os.str(); } -int Planet::SizeAsInt() const { +int Planet::HabitableSize() const { + const auto& gr = GetGameRules(); switch (m_size) { - case SZ_GASGIANT: return 6; break; - case SZ_HUGE: return 5; break; - case SZ_LARGE: return 4; break; - case SZ_MEDIUM: return 3; break; - case SZ_ASTEROIDS: return 3; break; - case SZ_SMALL: return 2; break; - case SZ_TINY: return 1; break; + case SZ_GASGIANT: return gr.Get("RULE_HABITABLE_SIZE_GASGIANT"); break; + case SZ_HUGE: return gr.Get("RULE_HABITABLE_SIZE_HUGE"); break; + case SZ_LARGE: return gr.Get("RULE_HABITABLE_SIZE_LARGE"); break; + case SZ_MEDIUM: return gr.Get("RULE_HABITABLE_SIZE_MEDIUM"); break; + case SZ_ASTEROIDS: return gr.Get("RULE_HABITABLE_SIZE_ASTEROIDS"); break; + case SZ_SMALL: return gr.Get("RULE_HABITABLE_SIZE_SMALL"); break; + case SZ_TINY: return gr.Get("RULE_HABITABLE_SIZE_TINY"); break; default: return 0; break; } } @@ -198,6 +223,8 @@ int Planet::SizeAsInt() const { void Planet::Init() { AddMeter(METER_SUPPLY); AddMeter(METER_MAX_SUPPLY); + AddMeter(METER_STOCKPILE); + AddMeter(METER_MAX_STOCKPILE); AddMeter(METER_SHIELD); AddMeter(METER_MAX_SHIELD); AddMeter(METER_DEFENSE); @@ -405,54 +432,6 @@ float Planet::InitialMeterValue(MeterType type) const float Planet::CurrentMeterValue(MeterType type) const { return UniverseObject::CurrentMeterValue(type); } -float Planet::NextTurnCurrentMeterValue(MeterType type) const { - MeterType max_meter_type = INVALID_METER_TYPE; - switch (type) { - case METER_TARGET_POPULATION: - case METER_POPULATION: - case METER_TARGET_HAPPINESS: - case METER_HAPPINESS: - return PopCenterNextTurnMeterValue(type); - break; - case METER_TARGET_INDUSTRY: - case METER_TARGET_RESEARCH: - case METER_TARGET_TRADE: - case METER_TARGET_CONSTRUCTION: - case METER_INDUSTRY: - case METER_RESEARCH: - case METER_TRADE: - case METER_CONSTRUCTION: - return ResourceCenterNextTurnMeterValue(type); - break; - case METER_SHIELD: max_meter_type = METER_MAX_SHIELD; break; - case METER_TROOPS: max_meter_type = METER_MAX_TROOPS; break; - case METER_DEFENSE: max_meter_type = METER_MAX_DEFENSE; break; - case METER_SUPPLY: max_meter_type = METER_MAX_SUPPLY; break; - break; - default: - return UniverseObject::NextTurnCurrentMeterValue(type); - } - - const Meter* meter = GetMeter(type); - if (!meter) { - throw std::invalid_argument("Planet::NextTurnCurrentMeterValue passed meter type that the Planet does not have, but should: " + boost::lexical_cast(type)); - } - float current_meter_value = meter->Current(); - - const Meter* max_meter = GetMeter(max_meter_type); - if (!max_meter) { - throw std::runtime_error("Planet::NextTurnCurrentMeterValue dealing with invalid meter type: " + boost::lexical_cast(type)); - } - float max_meter_value = max_meter->Current(); - - // being attacked prevents meter growth - if (LastTurnAttackedByShip() >= CurrentTurn()) - return std::min(current_meter_value, max_meter_value); - - // currently meter growth is one per turn. - return std::min(current_meter_value + 1.0f, max_meter_value); -} - std::string Planet::CardinalSuffix() const { std::string retval = ""; // Early return for invalid ID @@ -461,7 +440,7 @@ std::string Planet::CardinalSuffix() const { return retval; } - std::shared_ptr cur_system = GetSystem(SystemID()); + auto cur_system = Objects().get(SystemID()); // Early return for no system if (!cur_system) { ErrorLogger() << "Planet " << Name() << "(" << ID() @@ -493,7 +472,7 @@ std::string Planet::CardinalSuffix() const { continue; } - PlanetType other_planet_type = GetPlanet(sys_orbit)->Type(); + PlanetType other_planet_type = Objects().get(sys_orbit)->Type(); if (other_planet_type == INVALID_PLANET_TYPE) continue; @@ -535,20 +514,20 @@ const std::set& Planet::ContainedObjectIDs() const { return m_buildings; } bool Planet::Contains(int object_id) const -{ return object_id != INVALID_OBJECT_ID && m_buildings.find(object_id) != m_buildings.end(); } +{ return object_id != INVALID_OBJECT_ID && m_buildings.count(object_id); } bool Planet::ContainedBy(int object_id) const { return object_id != INVALID_OBJECT_ID && this->SystemID() == object_id; } std::vector Planet::AvailableFoci() const { std::vector retval; - std::shared_ptr this_planet = std::dynamic_pointer_cast(UniverseObject::shared_from_this()); + auto this_planet = std::dynamic_pointer_cast(UniverseObject::shared_from_this()); if (!this_planet) return retval; ScriptingContext context(this_planet); - if (const Species* species = GetSpecies(this_planet->SpeciesName())) { - for (const FocusType& focus_type : species->Foci()) { - if (const Condition::ConditionBase* location = focus_type.Location()) { + if (const auto* species = GetSpecies(this_planet->SpeciesName())) { + for (const auto& focus_type : species->Foci()) { + if (const auto* location = focus_type.Location()) { if (location->Eval(context, this_planet)) retval.push_back(focus_type.Name()); } @@ -612,7 +591,7 @@ void Planet::AddBuilding(int building_id) { } bool Planet::RemoveBuilding(int building_id) { - if (m_buildings.find(building_id) != m_buildings.end()) { + if (m_buildings.count(building_id)) { m_buildings.erase(building_id); StateChangedSignal(); return true; @@ -626,6 +605,8 @@ void Planet::Reset() { GetMeter(METER_SUPPLY)->Reset(); GetMeter(METER_MAX_SUPPLY)->Reset(); + GetMeter(METER_STOCKPILE)->Reset(); + GetMeter(METER_MAX_STOCKPILE)->Reset(); GetMeter(METER_SHIELD)->Reset(); GetMeter(METER_MAX_SHIELD)->Reset(); GetMeter(METER_DEFENSE)->Reset(); @@ -634,12 +615,15 @@ void Planet::Reset() { GetMeter(METER_REBEL_TROOPS)->Reset(); if (m_is_about_to_be_colonized && !OwnedBy(ALL_EMPIRES)) { - for (int building_id : m_buildings) - if (std::shared_ptr building = GetBuilding(building_id)) - building->Reset(); + for (const auto& building : Objects().find(m_buildings)) { + if (!building) + continue; + building->Reset(); + } } - m_just_conquered = false; + //m_turn_last_colonized left unchanged + //m_turn_last_conquered left unchanged m_is_about_to_be_colonized = false; m_is_about_to_be_invaded = false; m_is_about_to_be_bombarded = false; @@ -658,13 +642,13 @@ void Planet::Depopulate() { } void Planet::Conquer(int conquerer) { - m_just_conquered = true; + m_turn_last_conquered = CurrentTurn(); // deal with things on production queue located at this planet Empire::ConquerProductionQueueItemsAtLocation(ID(), conquerer); // deal with UniverseObjects (eg. buildings) located on this planet - for (std::shared_ptr building : Objects().FindObjects(m_buildings)) { + for (auto& building : Objects().find(m_buildings)) { const BuildingType* type = GetBuildingType(building->BuildingTypeName()); // determine what to do with building of this type... @@ -677,7 +661,7 @@ void Planet::Conquer(int conquerer) { // destroy object //DebugLogger() << "Planet::Conquer destroying object: " << building->Name(); this->RemoveBuilding(building->ID()); - if (std::shared_ptr system = GetSystem(this->SystemID())) + if (auto system = Objects().get(this->SystemID())) system->Remove(building->ID()); GetUniverse().Destroy(building->ID()); } else if (cap_result == CR_RETAIN) { @@ -690,6 +674,8 @@ void Planet::Conquer(int conquerer) { GetMeter(METER_SUPPLY)->SetCurrent(0.0f); GetMeter(METER_SUPPLY)->BackPropagate(); + GetMeter(METER_STOCKPILE)->SetCurrent(0.0f); + GetMeter(METER_STOCKPILE)->BackPropagate(); GetMeter(METER_INDUSTRY)->SetCurrent(0.0f); GetMeter(METER_INDUSTRY)->BackPropagate(); GetMeter(METER_RESEARCH)->SetCurrent(0.0f); @@ -708,6 +694,12 @@ void Planet::Conquer(int conquerer) { GetMeter(METER_DETECTION)->BackPropagate(); } +void Planet::SetSpecies(const std::string& species_name) { + if (SpeciesName().empty() && !species_name.empty()) + m_turn_last_colonized = CurrentTurn(); // if setting species with an effect, not via Colonize, consider it a colonization when there was no previous species set + PopCenter::SetSpecies(species_name); +} + bool Planet::Colonize(int empire_id, const std::string& species_name, double population) { const Species* species = nullptr; @@ -716,12 +708,13 @@ bool Planet::Colonize(int empire_id, const std::string& species_name, double pop // check if specified species exists and get reference species = GetSpecies(species_name); if (!species) { - ErrorLogger() << "Planet::Colonize couldn't get species already on planet with name: " << species_name; + ErrorLogger() << "Planet::Colonize couldn't get species: " << species_name; return false; } // check if specified species can colonize this planet if (EnvironmentForSpecies(species_name) < PE_HOSTILE) { - ErrorLogger() << "Planet::Colonize: can't colonize planet already populated by species " << species_name; + ErrorLogger() << "Planet::Colonize: can't colonize planet with species " << species_name << " because planet is " + << m_type << " which for that species is environment: " << EnvironmentForSpecies(species_name); return false; } } @@ -731,10 +724,11 @@ bool Planet::Colonize(int empire_id, const std::string& species_name, double pop Reset(); } else { PopCenter::Reset(); - for (int building_id : m_buildings) - if (std::shared_ptr building = GetBuilding(building_id)) - building->Reset(); - m_just_conquered = false; + for (const auto& building : Objects().find(m_buildings)) { + if (!building) + continue; + building->Reset(); + } m_is_about_to_be_colonized = false; m_is_about_to_be_invaded = false; m_is_about_to_be_bombarded = false; @@ -744,14 +738,15 @@ bool Planet::Colonize(int empire_id, const std::string& species_name, double pop // if desired pop > 0, we want a colony, not an outpost, so we have to set the colony species if (population > 0.0) SetSpecies(species_name); + m_turn_last_colonized = CurrentTurn(); // may be redundant with same in SetSpecies, but here occurrs always, whereas in SetSpecies is only done if species is initially empty // find a default focus. use first defined available focus. // AvailableFoci function should return a vector of all names of // available foci. - std::vector available_foci = AvailableFoci(); + auto available_foci = AvailableFoci(); if (species && !available_foci.empty()) { bool found_preference = false; - for (const std::string& focus : available_foci) { + for (const auto& focus : available_foci) { if (!focus.empty() && focus == species->PreferredFocus()) { SetFocus(focus); found_preference = true; @@ -775,8 +770,8 @@ bool Planet::Colonize(int empire_id, const std::string& species_name, double pop SetOwner(empire_id); // if there are buildings on the planet, set the specified empire as their owner too - for (std::shared_ptr building : Objects().FindObjects(BuildingIDs())) - { building->SetOwner(empire_id); } + for (auto& building : Objects().find(BuildingIDs())) + building->SetOwner(empire_id); return true; } @@ -830,24 +825,18 @@ void Planet::SetSurfaceTexture(const std::string& texture) { void Planet::PopGrowthProductionResearchPhase() { UniverseObject::PopGrowthProductionResearchPhase(); - - bool just_conquered = m_just_conquered; - // do not do production if planet was just conquered - m_just_conquered = false; - - if (!just_conquered) - ResourceCenterPopGrowthProductionResearchPhase(); - PopCenterPopGrowthProductionResearchPhase(); + // should be run after a meter update, but before a backpropagation, so check current, not initial, meter values + // check for colonies without positive population, and change to outposts - if (!SpeciesName().empty() && GetMeter(METER_POPULATION)->Current() <= 0.0f) { + if (!SpeciesName().empty() && CurrentMeterValue(METER_POPULATION) <= 0.0f) { if (Empire* empire = GetEmpire(this->Owner())) { empire->AddSitRepEntry(CreatePlanetDepopulatedSitRep(this->ID())); if (!HasTag(TAG_STAT_SKIP_DEPOP)) { // record depopulation of planet with species while owned by this empire - std::map::iterator species_it = empire->SpeciesPlanetsDepoped().find(SpeciesName()); + auto species_it = empire->SpeciesPlanetsDepoped().find(SpeciesName()); if (species_it == empire->SpeciesPlanetsDepoped().end()) empire->SpeciesPlanetsDepoped()[SpeciesName()] = 1; else @@ -858,14 +847,6 @@ void Planet::PopGrowthProductionResearchPhase() { PopCenter::Reset(); } - if (!just_conquered) { - GetMeter(METER_SHIELD)->SetCurrent(Planet::NextTurnCurrentMeterValue(METER_SHIELD)); - GetMeter(METER_DEFENSE)->SetCurrent(Planet::NextTurnCurrentMeterValue(METER_DEFENSE)); - GetMeter(METER_TROOPS)->SetCurrent(Planet::NextTurnCurrentMeterValue(METER_TROOPS)); - GetMeter(METER_REBEL_TROOPS)->SetCurrent(Planet::NextTurnCurrentMeterValue(METER_REBEL_TROOPS)); - GetMeter(METER_SUPPLY)->SetCurrent(Planet::NextTurnCurrentMeterValue(METER_SUPPLY)); - } - StateChangedSignal(); } @@ -874,14 +855,8 @@ void Planet::ResetTargetMaxUnpairedMeters() { ResourceCenterResetTargetMaxUnpairedMeters(); PopCenterResetTargetMaxUnpairedMeters(); - // give planets base stealth slightly above zero, so that they can't be - // seen from a distance without high detection ability - if (Meter* stealth = GetMeter(METER_STEALTH)) { - stealth->ResetCurrent(); - //stealth->AddToCurrent(0.01f); - } - GetMeter(METER_MAX_SUPPLY)->ResetCurrent(); + GetMeter(METER_MAX_STOCKPILE)->ResetCurrent(); GetMeter(METER_MAX_SHIELD)->ResetCurrent(); GetMeter(METER_MAX_DEFENSE)->ResetCurrent(); GetMeter(METER_MAX_TROOPS)->ResetCurrent(); @@ -902,6 +877,8 @@ void Planet::ClampMeters() { UniverseObject::GetMeter(METER_TROOPS)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_TROOPS)->Current()); UniverseObject::GetMeter(METER_MAX_SUPPLY)->ClampCurrentToRange(); UniverseObject::GetMeter(METER_SUPPLY)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_SUPPLY)->Current()); + UniverseObject::GetMeter(METER_MAX_STOCKPILE)->ClampCurrentToRange(); + UniverseObject::GetMeter(METER_STOCKPILE)->ClampCurrentToRange(Meter::DEFAULT_VALUE, UniverseObject::GetMeter(METER_MAX_STOCKPILE)->Current()); UniverseObject::GetMeter(METER_REBEL_TROOPS)->ClampCurrentToRange(); UniverseObject::GetMeter(METER_DETECTION)->ClampCurrentToRange(); diff --git a/universe/Planet.h b/universe/Planet.h index e10306b6b84..83ecf316b9b 100644 --- a/universe/Planet.h +++ b/universe/Planet.h @@ -16,113 +16,106 @@ class FO_COMMON_API Planet : { public: /** \name Accessors */ //@{ - std::set Tags() const override; + std::set Tags() const override; + bool HasTag(const std::string& name) const override; + UniverseObjectType ObjectType() const override; - bool HasTag(const std::string& name) const override; + std::string Dump(unsigned short ntabs = 0) const override; - UniverseObjectType ObjectType() const override; + int ContainerObjectID() const override; + const std::set& ContainedObjectIDs() const override; + bool Contains(int object_id) const override; + bool ContainedBy(int object_id) const override; - std::string Dump() const override; - - int ContainerObjectID() const override; - - const std::set& ContainedObjectIDs() const override; - - bool Contains(int object_id) const override; - - bool ContainedBy(int object_id) const override; - - float CurrentMeterValue(MeterType type) const override; - - float InitialMeterValue(MeterType type) const override; - - float NextTurnCurrentMeterValue(MeterType type) const override; + float CurrentMeterValue(MeterType type) const override; + float InitialMeterValue(MeterType type) const override; std::shared_ptr Accept(const UniverseObjectVisitor& visitor) const override; - void Copy(std::shared_ptr copied_object, int empire_id = ALL_EMPIRES) override; + std::vectorAvailableFoci() const override; + const std::string& FocusIcon(const std::string& focus_name) const override; - Meter* GetMeter(MeterType type) override; - - std::vector AvailableFoci() const override; - - const std::string& FocusIcon(const std::string& focus_name) const override; + PlanetType Type() const { return m_type; } + PlanetType OriginalType() const { return m_original_type; } + int DistanceFromOriginalType() const { return TypeDifference(m_type, m_original_type); } + PlanetSize Size() const { return m_size; } + int HabitableSize() const; - void Reset() override; + bool HostileToEmpire(int empire_id) const override; - void Depopulate() override; + PlanetEnvironment EnvironmentForSpecies(const std::string& species_name = "") const; + PlanetType NextBetterPlanetTypeForSpecies(const std::string& species_name = "") const; + PlanetType NextCloserToOriginalPlanetType() const; + PlanetType ClockwiseNextPlanetType() const; + PlanetType CounterClockwiseNextPlanetType() const; + PlanetSize NextLargerPlanetSize() const; + PlanetSize NextSmallerPlanetSize() const; - PlanetType Type() const { return m_type; } - PlanetType OriginalType() const { return m_original_type; } - int DistanceFromOriginalType() const { return TypeDifference(m_type, m_original_type); } - PlanetSize Size() const { return m_size; } - int SizeAsInt() const; - - PlanetEnvironment EnvironmentForSpecies(const std::string& species_name = "") const; - PlanetType NextBetterPlanetTypeForSpecies(const std::string& species_name = "") const; - PlanetType NextCloserToOriginalPlanetType() const; - PlanetType ClockwiseNextPlanetType() const; - PlanetType CounterClockwiseNextPlanetType() const; - PlanetSize NextLargerPlanetSize() const; - PlanetSize NextSmallerPlanetSize() const; - - /** - * An orbital period is equal to a planets "year". A "year" is arbitrarily - * defined to be 4 turns. */ - float OrbitalPeriod() const; + /** An orbital period is equal to a planets "year". A "year" is arbitrarily + * defined to be 4 turns. */ + float OrbitalPeriod() const; /** @returns an angle in radians. */ - float InitialOrbitalPosition() const; + float InitialOrbitalPosition() const; /** @returns an angle in radians. */ - float OrbitalPositionOnTurn(int turn) const; - /** - * The rotational period represents a planets "day". A "day" is - * arbitrarily defined to be 1/360 of a "year", and 1/90 of a turn. */ - float RotationalPeriod() const; + float OrbitalPositionOnTurn(int turn) const; + /** The rotational period represents a planets "day". A "day" is + * arbitrarily defined to be 1/360 of a "year", and 1/90 of a turn. */ + float RotationalPeriod() const; /** @returns an angle in degree. */ - float AxialTilt() const; + float AxialTilt() const; - const std::set& BuildingIDs() const {return m_buildings;} + const std::set& BuildingIDs() const { return m_buildings; } - bool IsAboutToBeColonized() const { return m_is_about_to_be_colonized; } - bool IsAboutToBeInvaded() const { return m_is_about_to_be_invaded; } - bool IsAboutToBeBombarded() const { return m_is_about_to_be_bombarded; } - int OrderedGivenToEmpire() const { return m_ordered_given_to_empire_id; } - int LastTurnAttackedByShip() const { return m_last_turn_attacked_by_ship; } + bool IsAboutToBeColonized() const { return m_is_about_to_be_colonized; } + bool IsAboutToBeInvaded() const { return m_is_about_to_be_invaded; } + bool IsAboutToBeBombarded() const { return m_is_about_to_be_bombarded; } + int OrderedGivenToEmpire() const { return m_ordered_given_to_empire_id; } + int LastTurnAttackedByShip() const { return m_last_turn_attacked_by_ship; } + int LastTurnColonized() const { return m_turn_last_colonized; } + int LastTurnConquered() const { return m_turn_last_conquered; } - const std::string& SurfaceTexture() const { return m_surface_texture; } - std::string CardinalSuffix() const; ///< returns a roman number representing this planets orbit in relation to other planets + const std::string& SurfaceTexture() const { return m_surface_texture; } + std::string CardinalSuffix() const; ///< returns a roman number representing this planets orbit in relation to other planets //@} /** \name Mutators */ //@{ - void SetType(PlanetType type); ///< sets the type of this Planet to \a type - void SetOriginalType(PlanetType type); ///< sets the original type of this Planet to \a type - void SetSize(PlanetSize size); ///< sets the size of this Planet to \a size - - void SetRotationalPeriod(float days); ///< sets the rotational period of this planet - void SetHighAxialTilt(); ///< randomly generates a new, high axial tilt + void Copy(std::shared_ptr copied_object, + int empire_id = ALL_EMPIRES) override; - void AddBuilding(int building_id); ///< adds the building to the planet - bool RemoveBuilding(int building_id); ///< removes the building from the planet; returns false if no such building was found - - void Conquer(int conquerer); ///< Called during combat when a planet changes hands - bool Colonize(int empire_id, const std::string& species_name, double population); ///< Called during colonization handling to do the actual colonizing - void SetIsAboutToBeColonized(bool b); ///< Called during colonization when a planet is about to be colonized - void ResetIsAboutToBeColonized(); ///< Called after colonization, to reset the number of prospective colonizers to 0 - void SetIsAboutToBeInvaded(bool b); ///< Marks planet as being invaded or not, depending on whether \a b is true or false - void ResetIsAboutToBeInvaded(); ///< Marks planet as not being invaded - void SetIsAboutToBeBombarded(bool b); ///< Marks planet as being bombarded or not, depending on whether \a b is true or false - void ResetIsAboutToBeBombarded(); ///< Marks planet as not being bombarded - void SetGiveToEmpire(int empire_id); ///< Marks planet to be given to empire - void ClearGiveToEmpire(); ///< Marks planet not to be given to any empire - - void SetLastTurnAttackedByShip(int turn);///< Sets the last turn this planet was attacked by a ship - - void SetSurfaceTexture(const std::string& texture); + Meter* GetMeter(MeterType type) override; - void ResetTargetMaxUnpairedMeters() override; + void Reset() override; + void Depopulate() override; + void SetSpecies(const std::string& species_name) override; + + void SetType(PlanetType type); ///< sets the type of this Planet to \a type + void SetOriginalType(PlanetType type); ///< sets the original type of this Planet to \a type + void SetSize(PlanetSize size); ///< sets the size of this Planet to \a size + + void SetRotationalPeriod(float days); ///< sets the rotational period of this planet + void SetHighAxialTilt(); ///< randomly generates a new, high axial tilt + + void AddBuilding(int building_id); ///< adds the building to the planet + bool RemoveBuilding(int building_id); ///< removes the building from the planet; returns false if no such building was found + + void Conquer(int conquerer); ///< Called during combat when a planet changes hands + bool Colonize(int empire_id, const std::string& species_name, + double population); ///< Called during colonization handling to do the actual colonizing + void SetIsAboutToBeColonized(bool b); ///< Called during colonization when a planet is about to be colonized + void ResetIsAboutToBeColonized(); ///< Called after colonization, to reset the number of prospective colonizers to 0 + void SetIsAboutToBeInvaded(bool b); ///< Marks planet as being invaded or not, depending on whether \a b is true or false + void ResetIsAboutToBeInvaded(); ///< Marks planet as not being invaded + void SetIsAboutToBeBombarded(bool b); ///< Marks planet as being bombarded or not, depending on whether \a b is true or false + void ResetIsAboutToBeBombarded(); ///< Marks planet as not being bombarded + void SetGiveToEmpire(int empire_id); ///< Marks planet to be given to empire + void ClearGiveToEmpire(); ///< Marks planet not to be given to any empire + + void SetLastTurnAttackedByShip(int turn);///< Sets the last turn this planet was attacked by a ship + void SetSurfaceTexture(const std::string& texture); + void ResetTargetMaxUnpairedMeters() override; //@} - static int TypeDifference(PlanetType type1, PlanetType type2); + static int TypeDifference(PlanetType type1, PlanetType type2); protected: friend class Universe; @@ -131,14 +124,15 @@ class FO_COMMON_API Planet : /** \name Structors */ //@{ Planet(); +public: /** Create planet from @p type and @p size. */ Planet(PlanetType type, PlanetSize size); - - template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); - -public: ~Planet() {} +protected: + template + friend void boost::python::detail::value_destroyer::execute(T const volatile* p); + protected: /** returns new copy of this Planet. */ Planet* Clone(int empire_id = ALL_EMPIRES) const override; @@ -169,7 +163,8 @@ class FO_COMMON_API Planet : std::set m_buildings; - bool m_just_conquered = false; + int m_turn_last_colonized = INVALID_GAME_TURN; + int m_turn_last_conquered = INVALID_GAME_TURN; bool m_is_about_to_be_colonized = false; bool m_is_about_to_be_invaded = false; bool m_is_about_to_be_bombarded = false; @@ -179,7 +174,7 @@ class FO_COMMON_API Planet : std::string m_surface_texture; // intentionally not serialized; set by local effects friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/universe/PopCenter.cpp b/universe/PopCenter.cpp index 42a759e7e76..8e681ed3f0e 100644 --- a/universe/PopCenter.cpp +++ b/universe/PopCenter.cpp @@ -20,8 +20,7 @@ PopCenter::PopCenter(const std::string& species_name) : m_species_name(species_name) {} -PopCenter::PopCenter() : - m_species_name() +PopCenter::PopCenter() {} PopCenter::~PopCenter() @@ -44,14 +43,13 @@ void PopCenter::Copy(std::shared_ptr copied_object) { Copy(copied_object, VIS_FULL_VISIBILITY); } void PopCenter::Init() { - //DebugLogger() << "PopCenter::Init"; AddMeter(METER_POPULATION); AddMeter(METER_TARGET_POPULATION); AddMeter(METER_HAPPINESS); AddMeter(METER_TARGET_HAPPINESS); } -std::string PopCenter::Dump() const { +std::string PopCenter::Dump(unsigned short ntabs) const { std::stringstream os; os << " species: " << m_species_name << " "; return os.str(); @@ -65,56 +63,6 @@ float PopCenter::CurrentMeterValue(MeterType type) const { return meter->Current(); } -float PopCenter::PopCenterNextTurnMeterValue(MeterType meter_type) const { - const Meter* meter = GetMeter(meter_type); - if (!meter) { - throw std::invalid_argument("PopCenter::PopCenterNextTurnMeterValue passed meter type that the PopCenter does not have: " + boost::lexical_cast(meter_type)); - - } else if (meter_type == METER_POPULATION) { - return meter->Current() + NextTurnPopGrowth(); - - } else if (meter_type == METER_TARGET_POPULATION || - meter_type == METER_TARGET_HAPPINESS) - { - DebugLogger() << "PopCenter::PopCenterNextTurnMeterValue passed valid but unusual (TARGET) meter_type" << boost::lexical_cast(meter_type) << ". Returning meter->Current()"; - return meter->Current(); - - } else if (meter_type == METER_HAPPINESS) { - const Meter* target = GetMeter(METER_TARGET_HAPPINESS); - if (!target) - return meter->Current(); - float target_meter_value = target->Current(); - float current_meter_value = meter->Current(); - - // currently meter growth is one per turn. - if (target_meter_value > current_meter_value) - return std::min(current_meter_value + 1.0f, target_meter_value); - else if (target_meter_value < current_meter_value) - return std::max(target_meter_value, current_meter_value - 1.0f); - else - return current_meter_value; - } else { - ErrorLogger() << "PopCenter::PopCenterNextTurnMeterValue dealing with invalid meter type: " + boost::lexical_cast(meter_type); - return 0.0f; - } -} - -float PopCenter::NextTurnPopGrowth() const { - float target_pop = GetMeter(METER_TARGET_POPULATION)->Current(); - float cur_pop = GetMeter(METER_POPULATION)->Current(); - float pop_change = 0.0f; - - if (target_pop > cur_pop) { - pop_change = cur_pop * (target_pop + 1 - cur_pop) / 100; // Using target population slightly above actual population avoids excessively slow asymptotic growth towards target. - pop_change = std::min(pop_change, target_pop - cur_pop); - } else { - pop_change = -(cur_pop - target_pop) / 10; - pop_change = std::max(pop_change, target_pop - cur_pop); - } - - return pop_change; -} - void PopCenter::PopCenterResetTargetMaxUnpairedMeters() { GetMeter(METER_TARGET_POPULATION)->ResetCurrent(); GetMeter(METER_TARGET_HAPPINESS)->ResetCurrent(); @@ -126,21 +74,12 @@ void PopCenter::PopCenterPopGrowthProductionResearchPhase() { return; } - float cur_pop = CurrentMeterValue(METER_POPULATION); - float pop_growth = NextTurnPopGrowth(); // may be negative - float new_pop = cur_pop + pop_growth; - - //if (cur_pop > 0.0) - // DebugLogger() << "Planet Pop: " << cur_pop << " growth: " << pop_growth; + // Should be run after meter update but before a backpropagation, so check current, not initial, meter values - if (new_pop >= MINIMUM_POP_CENTER_POPULATION) { - GetMeter(METER_POPULATION)->SetCurrent(new_pop); - } else { + if (CurrentMeterValue(METER_POPULATION) < MINIMUM_POP_CENTER_POPULATION) { // if population falls below threshold, kill off the remainder Depopulate(); } - - GetMeter(METER_HAPPINESS)->SetCurrent(PopCenterNextTurnMeterValue(METER_HAPPINESS)); } void PopCenter::PopCenterClampMeters() @@ -160,9 +99,7 @@ void PopCenter::Depopulate() { } void PopCenter::SetSpecies(const std::string& species_name) { - const Species* species = GetSpecies(species_name); - if (!species && !species_name.empty()) { + if (!species_name.empty() && !GetSpecies(species_name)) ErrorLogger() << "PopCenter::SetSpecies couldn't get species with name " << species_name; - } m_species_name = species_name; } diff --git a/universe/PopCenter.h b/universe/PopCenter.h index bb8315a7cc4..392c6374406 100644 --- a/universe/PopCenter.h +++ b/universe/PopCenter.h @@ -23,60 +23,47 @@ class UniverseObject; * population and still travel between systems). */ class FO_COMMON_API PopCenter : virtual public std::enable_shared_from_this { public: - /** \name Structors */ //@{ + /** \name Structors */ //@ { PopCenter(); - explicit PopCenter(const std::string& species_name); - virtual ~PopCenter(); //@} - /** \name Accessors */ //@{ + /** \name Accessors */ //@ { const std::string& SpeciesName() const {return m_species_name;} ///< returns the name of the species that populates this planet - - std::string Dump() const; - - float NextTurnPopGrowth() const; ///< predicted pop growth next turn - + std::string Dump(unsigned short ntabs = 0) const; virtual float InitialMeterValue(MeterType type) const = 0; ///< implementation should return the initial value of the specified meter \a type virtual float CurrentMeterValue(MeterType type) const = 0; ///< implementation should current value of the specified meter \a type - virtual float NextTurnCurrentMeterValue(MeterType type) const = 0;///< implementation should return an estimate of the next turn's current value of the specified meter \a type //@} - /** \name Mutators */ //@{ - - void Copy(std::shared_ptr copied_object, Visibility vis); - - void Copy(std::shared_ptr copied_object); - - void SetSpecies(const std::string& species_name); ///< sets the species of the population to \a species_name + /** \name Mutators */ //@ { + void Copy(std::shared_ptr copied_object, Visibility vis); + void Copy(std::shared_ptr copied_object); + virtual void SetSpecies(const std::string& species_name); ///< sets the species of the population to \a species_name virtual void Reset(); ///< sets all meters to 0, clears race name virtual void Depopulate(); ///< removes population //@} protected: - void Init(); ///< initialization that needs to be called by derived class after derived class is constructed - - float PopCenterNextTurnMeterValue(MeterType meter_type) const;///< returns estimate of the next turn's current values of meters relevant to this PopCenter - void PopCenterResetTargetMaxUnpairedMeters(); - void PopCenterClampMeters(); - - void PopCenterPopGrowthProductionResearchPhase(); + void Init(); ///< initialization that needs to be called by derived class after derived class is constructed + void PopCenterResetTargetMaxUnpairedMeters(); + void PopCenterClampMeters(); + void PopCenterPopGrowthProductionResearchPhase(); private: virtual Meter* GetMeter(MeterType type) = 0; ///< implementation should return the requested Meter, or 0 if no such Meter of that type is found in this object virtual const Meter* GetMeter(MeterType type) const = 0; ///< implementation should return the requested Meter, or 0 if no such Meter of that type is found in this object virtual void AddMeter(MeterType meter_type) = 0; ///< implementation should add a meter to the object so that it can be accessed with the GetMeter() functions - std::string m_species_name; ///< the name of the species that occupies this planet + std::string m_species_name = ""; ///< the name of the species that occupies this planet friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; // template implementations -template +template void PopCenter::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_species_name); diff --git a/universe/Predicates.cpp b/universe/Predicates.cpp index 572e7ee570d..7da0f904b7c 100644 --- a/universe/Predicates.cpp +++ b/universe/Predicates.cpp @@ -94,3 +94,36 @@ std::shared_ptr MovingFleetVisitor::Visit(std::shared_ptr return obj; return nullptr; } + +//////////////////////////////////////////////// +// OwnedVisitor +//////////////////////////////////////////////// +OwnedVisitor::OwnedVisitor(int empire) : + empire_id(empire) +{} + +OwnedVisitor::~OwnedVisitor() = default; + +std::shared_ptr OwnedVisitor::Visit(std::shared_ptr obj) const +{ + if (obj->OwnedBy(empire_id)) + return obj; + return nullptr; +} + +//////////////////////////////////////////////// +// HostileVisitor +//////////////////////////////////////////////// +HostileVisitor::HostileVisitor(int viewing_empire, int owning_empire) : + viewing_empire_id(viewing_empire), + owning_empire_id(owning_empire) +{} + +HostileVisitor::~HostileVisitor() = default; + +std::shared_ptr HostileVisitor::Visit(std::shared_ptr obj) const +{ + if (obj->HostileToEmpire(viewing_empire_id)) + return obj; + return nullptr; +} diff --git a/universe/Predicates.h b/universe/Predicates.h index 4faa3488477..c6c48a40ad7 100644 --- a/universe/Predicates.h +++ b/universe/Predicates.h @@ -93,30 +93,27 @@ struct FO_COMMON_API MovingFleetVisitor : UniverseObjectVisitor }; /** returns obj iff \a obj is owned by the empire with id \a empire, and \a obj is of type T. */ -template -struct OwnedVisitor : UniverseObjectVisitor +struct FO_COMMON_API OwnedVisitor : UniverseObjectVisitor { OwnedVisitor(int empire = ALL_EMPIRES); - virtual ~OwnedVisitor() - {} + virtual ~OwnedVisitor(); - std::shared_ptr Visit(std::shared_ptr obj) const override; + std::shared_ptr Visit(std::shared_ptr obj) const override; const int empire_id; }; -template -OwnedVisitor::OwnedVisitor(int empire) : - empire_id(empire) -{} - -template -std::shared_ptr OwnedVisitor::Visit(std::shared_ptr obj) const +struct FO_COMMON_API HostileVisitor : UniverseObjectVisitor { - if (obj->OwnedBy(empire_id)) - return obj; - return nullptr; -} + HostileVisitor(int viewing_empire, int owning_empire = ALL_EMPIRES); + + virtual ~HostileVisitor(); + + std::shared_ptr Visit(std::shared_ptr obj) const override; + + const int viewing_empire_id; + const int owning_empire_id; +}; #endif // _Predicates_h_ diff --git a/universe/ResourceCenter.cpp b/universe/ResourceCenter.cpp index a6c3cfae0bb..c9d37295534 100644 --- a/universe/ResourceCenter.cpp +++ b/universe/ResourceCenter.cpp @@ -3,10 +3,10 @@ #include "../util/Directories.h" #include "../util/Logger.h" #include "../util/OptionsDB.h" +#include "../util/AppInterface.h" #include "../Empire/Empire.h" #include "Fleet.h" #include "Planet.h" -#include "ShipDesign.h" #include "System.h" #include "Building.h" #include "Enums.h" @@ -19,9 +19,9 @@ namespace { ResourceCenter::ResourceCenter() : m_focus(), - m_last_turn_focus_changed(BEFORE_FIRST_TURN), + m_last_turn_focus_changed(INVALID_GAME_TURN), m_focus_turn_initial(), - m_last_turn_focus_changed_turn_initial(BEFORE_FIRST_TURN) + m_last_turn_focus_changed_turn_initial(INVALID_GAME_TURN) {} ResourceCenter::~ResourceCenter() @@ -87,49 +87,12 @@ std::vector ResourceCenter::AvailableFoci() const const std::string& ResourceCenter::FocusIcon(const std::string& focus_name) const { return EMPTY_STRING; } -std::string ResourceCenter::Dump() const { +std::string ResourceCenter::Dump(unsigned short ntabs) const { std::stringstream os; os << "ResourceCenter focus: " << m_focus << " last changed on turn: " << m_last_turn_focus_changed; return os.str(); } -float ResourceCenter::ResourceCenterNextTurnMeterValue(MeterType type) const { - const Meter* meter = GetMeter(type); - if (!meter) - throw std::invalid_argument("ResourceCenter::ResourceCenterNextTurnMeterValue passed meter type that the ResourceCenter does not have: " + boost::lexical_cast(type)); - float current_meter_value = meter->Current(); - - MeterType target_meter_type = INVALID_METER_TYPE; - switch (type) { - case METER_TARGET_INDUSTRY: - case METER_TARGET_RESEARCH: - case METER_TARGET_TRADE: - case METER_TARGET_CONSTRUCTION: - return current_meter_value; - break; - case METER_INDUSTRY: target_meter_type = METER_TARGET_INDUSTRY; break; - case METER_RESEARCH: target_meter_type = METER_TARGET_RESEARCH; break; - case METER_TRADE: target_meter_type = METER_TARGET_TRADE; break; - case METER_CONSTRUCTION:target_meter_type = METER_TARGET_CONSTRUCTION; break; - default: - ErrorLogger() << "ResourceCenter::ResourceCenterNextTurnMeterValue dealing with invalid meter type"; - return 0.0f; - } - - const Meter* target_meter = GetMeter(target_meter_type); - if (!target_meter) - throw std::runtime_error("ResourceCenter::ResourceCenterNextTurnMeterValue dealing with invalid meter type"); - float target_meter_value = target_meter->Current(); - - // meter growth or decay towards target is one per turn. - if (target_meter_value > current_meter_value) - return std::min(current_meter_value + 1.0f, target_meter_value); - else if (target_meter_value < current_meter_value) - return std::max(target_meter_value, current_meter_value - 1.0f); - else - return current_meter_value; -} - void ResourceCenter::SetFocus(const std::string& focus) { if (focus == m_focus) return; @@ -137,8 +100,9 @@ void ResourceCenter::SetFocus(const std::string& focus) { ClearFocus(); return; } - std::vector avail_foci = AvailableFoci(); - if (std::find(avail_foci.begin(), avail_foci.end(), focus) != avail_foci.end()) { + auto avail_foci = AvailableFoci(); + auto foci_it = std::find(avail_foci.begin(), avail_foci.end(), focus); + if (foci_it != avail_foci.end()) { m_focus = focus; if (m_focus == m_focus_turn_initial) m_last_turn_focus_changed = m_last_turn_focus_changed_turn_initial; @@ -157,6 +121,9 @@ void ResourceCenter::ClearFocus() { } void ResourceCenter::UpdateFocusHistory() { + TraceLogger() << "ResourceCenter::UpdateFocusHistory: focus: " << m_focus + << " initial focus: " << m_focus_turn_initial + << " turns since change initial: " << m_last_turn_focus_changed_turn_initial; if (m_focus != m_focus_turn_initial) { m_focus_turn_initial = m_focus; m_last_turn_focus_changed_turn_initial = m_last_turn_focus_changed; @@ -170,13 +137,6 @@ void ResourceCenter::ResourceCenterResetTargetMaxUnpairedMeters() { GetMeter(METER_TARGET_CONSTRUCTION)->ResetCurrent(); } -void ResourceCenter::ResourceCenterPopGrowthProductionResearchPhase() { - GetMeter(METER_INDUSTRY)->SetCurrent(ResourceCenterNextTurnMeterValue(METER_INDUSTRY)); - GetMeter(METER_RESEARCH)->SetCurrent(ResourceCenterNextTurnMeterValue(METER_RESEARCH)); - GetMeter(METER_TRADE)->SetCurrent(ResourceCenterNextTurnMeterValue(METER_TRADE)); - GetMeter(METER_CONSTRUCTION)->SetCurrent(ResourceCenterNextTurnMeterValue(METER_CONSTRUCTION)); -} - void ResourceCenter::ResourceCenterClampMeters() { GetMeter(METER_TARGET_INDUSTRY)->ClampCurrentToRange(); GetMeter(METER_TARGET_RESEARCH)->ClampCurrentToRange(); diff --git a/universe/ResourceCenter.h b/universe/ResourceCenter.h index a0633aaf351..c803d99a82f 100644 --- a/universe/ResourceCenter.h +++ b/universe/ResourceCenter.h @@ -23,9 +23,7 @@ class FO_COMMON_API ResourceCenter : virtual public std::enable_shared_from_this public: /** \name Structors */ //@{ ResourceCenter(); - ResourceCenter(const ResourceCenter& rhs); - virtual ~ResourceCenter(); //@} @@ -35,11 +33,10 @@ class FO_COMMON_API ResourceCenter : virtual public std::enable_shared_from_this virtual std::vectorAvailableFoci() const; ///< focus settings available to this ResourceCenter virtual const std::string& FocusIcon(const std::string& focus_name) const; ///< icon representing focus with name \a focus_name for this ResourceCenter - std::string Dump() const; + std::string Dump(unsigned short ntabs = 0) const; virtual float InitialMeterValue(MeterType type) const = 0; ///< implementation should return the initial value of the specified meter \a type virtual float CurrentMeterValue(MeterType type) const = 0; ///< implementation should return the current value of the specified meter \a type - virtual float NextTurnCurrentMeterValue(MeterType type) const = 0; ///< implementation should return an estimate of the next turn's current value of the specified meter \a type /** the state changed signal object for this ResourceCenter */ mutable boost::signals2::signal ResourceCenterChangedSignal; @@ -47,12 +44,11 @@ class FO_COMMON_API ResourceCenter : virtual public std::enable_shared_from_this /** \name Mutators */ //@{ void Copy(std::shared_ptr copied_object, Visibility vis); - void Copy(std::shared_ptr copied_object); - void SetFocus(const std::string& focus); - void ClearFocus(); - void UpdateFocusHistory(); + void SetFocus(const std::string& focus); + void ClearFocus(); + void UpdateFocusHistory(); /** Resets the meters, etc. This should be called when a ResourceCenter is wiped out due to starvation, etc. */ @@ -60,14 +56,10 @@ class FO_COMMON_API ResourceCenter : virtual public std::enable_shared_from_this //@} protected: - void Init(); ///< initialization that needs to be called by derived class after derived class is constructed - - float ResourceCenterNextTurnMeterValue(MeterType meter_type) const; ///< returns estimate of the next turn's current values of meters relevant to this ResourceCenter - void ResourceCenterResetTargetMaxUnpairedMeters(); - void ResourceCenterClampMeters(); - - void ResourceCenterPopGrowthProductionResearchPhase(); + void Init(); ///< initialization that needs to be called by derived class after derived class is constructed + void ResourceCenterResetTargetMaxUnpairedMeters(); + void ResourceCenterClampMeters(); private: std::string m_focus; @@ -82,12 +74,12 @@ class FO_COMMON_API ResourceCenter : virtual public std::enable_shared_from_this virtual void AddMeter(MeterType meter_type) = 0; ///< implementation should add a meter to the object so that it can be accessed with the GetMeter() functions friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; // template implementations -template +template void ResourceCenter::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_focus) diff --git a/universe/ScriptingContext.h b/universe/ScriptingContext.h new file mode 100644 index 00000000000..edb2c6e6037 --- /dev/null +++ b/universe/ScriptingContext.h @@ -0,0 +1,148 @@ +#ifndef _ScriptingContext_h_ +#define _ScriptingContext_h_ + +#include "Universe.h" +#include "../util/AppInterface.h" + +#include + +#include + +class UniverseObject; + +/** combat/CombatInfo extends this ScriptingCombatInfo in order + * to give Conditions and ValueRefs access to combat related data */ +struct FO_COMMON_API ScriptingCombatInfo { + ScriptingCombatInfo() {} + ScriptingCombatInfo(int bout_, const Universe::EmpireObjectVisibilityMap& vis) : + bout(bout_), + empire_object_visibility(vis) + {} + + int bout = 0; ///< current combat bout, used with CombatBout ValueRef for implementing bout dependent targeting. First combat bout is 1 + ObjectMap objects; ///< actual state of objects relevant to combat + Universe::EmpireObjectVisibilityMap empire_object_visibility; ///< indexed by empire id and object id, the visibility level the empire has of each object. may be increased during battle +}; +const ScriptingCombatInfo EMPTY_COMBAT_INFO{}; + +struct ScriptingContext { + explicit ScriptingContext() : + objects(Objects()), + const_objects(Objects()) + {} + + explicit ScriptingContext(const ObjectMap& const_objects_) : + objects(Objects()), + const_objects(const_objects_) + {} + + explicit ScriptingContext(ObjectMap& objects_) : + objects(objects_), + const_objects(objects_) + {} + + explicit ScriptingContext(std::shared_ptr source_, + const ObjectMap& const_objects_ = Objects()) : + source(source_), + objects(Objects()), + const_objects(const_objects_) + {} + + ScriptingContext(std::shared_ptr source_, + const ScriptingContext& parent_context) : + source( source_), + effect_target( parent_context.effect_target), + condition_root_candidate( parent_context.condition_root_candidate), + condition_local_candidate( parent_context.condition_local_candidate), + current_value( parent_context.current_value), + combat_info( parent_context.combat_info), + objects( parent_context.objects), + const_objects( parent_context.const_objects) + {} + + ScriptingContext(std::shared_ptr source_, + ScriptingCombatInfo& combat_info_) : + source(source_), + combat_info(combat_info_), + objects(combat_info_.objects), + const_objects(combat_info_.objects) + {} + + ScriptingContext(std::shared_ptr source_, + std::shared_ptr target_, + ObjectMap& objects_ = Objects()) : + source(source_), + effect_target(target_), + objects(objects_), + const_objects(objects_) + {} + + ScriptingContext(const ScriptingContext& parent_context, + std::shared_ptr target_, + const boost::any& current_value_) : // TODO: Rework this so only specific types are accepted + source( parent_context.source), + effect_target( target_), + condition_root_candidate( parent_context.condition_root_candidate), + condition_local_candidate( parent_context.condition_local_candidate), + current_value( current_value_), + combat_info( parent_context.combat_info), + objects( parent_context.objects), + const_objects( parent_context.const_objects) + {} + + ScriptingContext(const ScriptingContext& parent_context, + const boost::any& current_value_) : // TODO: Rework this so only specific types are accepted + source( parent_context.source), + effect_target( parent_context.effect_target), + condition_root_candidate( parent_context.condition_root_candidate), + condition_local_candidate( parent_context.condition_local_candidate), + current_value( current_value_), + combat_info( parent_context.combat_info), + objects( parent_context.objects), + const_objects( parent_context.const_objects) + {} + + ScriptingContext(const ScriptingContext& parent_context, + std::shared_ptr condition_local_candidate_) : + source( parent_context.source), + effect_target( parent_context.effect_target), + condition_root_candidate( parent_context.condition_root_candidate ? + parent_context.condition_root_candidate : + condition_local_candidate_), // if parent context doesn't already have a root candidate, the new local candidate is the root + condition_local_candidate( condition_local_candidate_), // new local candidate + current_value( parent_context.current_value), + combat_info( parent_context.combat_info), + objects( parent_context.objects), + const_objects( parent_context.const_objects) + {} + + ScriptingContext(std::shared_ptr source_, + std::shared_ptr target_, + const boost::any& current_value_, // TODO: Rework this so only specific types are accepted + std::shared_ptr condition_root_candidate_ = nullptr, + std::shared_ptr condition_local_candidate_ = nullptr, + ObjectMap& objects_ = Objects()) : + source(source_), + condition_root_candidate(condition_root_candidate_), + condition_local_candidate(condition_local_candidate_), + current_value(current_value_), + objects(objects_), + const_objects(objects_) + {} + + const ObjectMap& ContextObjects() const { return const_objects; } + ObjectMap& ContextObjects() { return objects; } + + std::shared_ptr source; + std::shared_ptr effect_target; + std::shared_ptr condition_root_candidate; + std::shared_ptr condition_local_candidate; + const boost::any current_value; + const ScriptingCombatInfo& combat_info = EMPTY_COMBAT_INFO; + +private: + ObjectMap& objects; + const ObjectMap& const_objects; +}; + +#endif // _ScriptingContext_h_ diff --git a/universe/Ship.cpp b/universe/Ship.cpp index 04785f58b2e..5a8714dc07f 100644 --- a/universe/Ship.cpp +++ b/universe/Ship.cpp @@ -4,10 +4,12 @@ #include "../util/Logger.h" #include "../util/Random.h" #include "../util/AppInterface.h" -#include "../util/MultiplayerCommon.h" +#include "../util/GameRules.h" #include "Fleet.h" #include "Predicates.h" #include "ShipDesign.h" +#include "ShipPart.h" +#include "ShipHull.h" #include "Species.h" #include "Universe.h" #include "Enums.h" @@ -27,7 +29,8 @@ Ship::Ship(int empire_id, int design_id, const std::string& species_name, m_design_id(design_id), m_species_name(species_name), m_produced_by_empire_id(produced_by_empire_id), - m_arrived_on_turn(CurrentTurn()) + m_arrived_on_turn(CurrentTurn()), + m_last_resupplied_on_turn(CurrentTurn()) { if (!GetShipDesign(design_id)) throw std::invalid_argument("Attempted to construct a Ship with an invalid design id"); @@ -56,8 +59,8 @@ Ship::Ship(int empire_id, int design_id, const std::string& species_name, const std::vector& part_names = Design()->Parts(); for (const std::string& part_name : part_names) { - if (part_name != "") { - const PartType* part = GetPartType(part_name); + if (!part_name.empty()) { + const ShipPart* part = GetShipPart(part_name); if (!part) { ErrorLogger() << "Ship::Ship couldn't get part with name " << part_name; continue; @@ -66,18 +69,18 @@ Ship::Ship(int empire_id, int design_id, const std::string& species_name, switch (part->Class()) { case PC_COLONY: case PC_TROOPS: { - m_part_meters[std::make_pair(METER_CAPACITY, part->Name())]; + m_part_meters[{METER_CAPACITY, part->Name()}]; break; } case PC_DIRECT_WEAPON: // capacity is damage, secondary stat is shots per attack case PC_FIGHTER_HANGAR: { // capacity is how many fighters contained, secondary stat is damage per fighter attack - m_part_meters[std::make_pair(METER_SECONDARY_STAT, part->Name())]; - m_part_meters[std::make_pair(METER_MAX_SECONDARY_STAT, part->Name())]; + m_part_meters[{METER_SECONDARY_STAT, part->Name()}]; + m_part_meters[{METER_MAX_SECONDARY_STAT, part->Name()}]; // intentionally no break here } case PC_FIGHTER_BAY: { // capacity is how many fighters launched per combat round - m_part_meters[std::make_pair(METER_CAPACITY, part->Name())]; - m_part_meters[std::make_pair(METER_MAX_CAPACITY, part->Name())]; + m_part_meters[{METER_CAPACITY, part->Name()}]; + m_part_meters[{METER_MAX_CAPACITY, part->Name()}]; break; } default: @@ -101,7 +104,7 @@ Ship* Ship::Clone(int empire_id) const { void Ship::Copy(std::shared_ptr copied_object, int empire_id) { if (copied_object.get() == this) return; - std::shared_ptr copied_ship = std::dynamic_pointer_cast(copied_object); + auto copied_ship = std::dynamic_pointer_cast(copied_object); if (!copied_ship) { ErrorLogger() << "Ship::Copy passed an object that wasn't a Ship"; return; @@ -109,16 +112,16 @@ void Ship::Copy(std::shared_ptr copied_object, int empire_ int copied_object_id = copied_object->ID(); Visibility vis = GetUniverse().GetObjectVisibilityByEmpire(copied_object_id, empire_id); - std::set visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id); + auto visible_specials = GetUniverse().GetObjectVisibleSpecialsByEmpire(copied_object_id, empire_id); UniverseObject::Copy(copied_object, vis, visible_specials);; if (vis >= VIS_BASIC_VISIBILITY) { if (this->m_fleet_id != copied_ship->m_fleet_id) { // as with other containers, removal from the old container is triggered by the contained Object; removal from System is handled by UniverseObject::Copy - if (std::shared_ptr oldFleet = GetFleet(this->m_fleet_id)) - oldFleet->RemoveShip(this->ID()); - this->m_fleet_id = copied_ship->m_fleet_id; // as with other containers (Systems), actual insertion into fleet ships set is handled by the fleet + if (auto old_fleet = Objects().get(this->m_fleet_id)) + old_fleet->RemoveShips({this->ID()}); + this->m_fleet_id = copied_ship->m_fleet_id; // as with other containers (Systems), actual insertion into fleet ships set is handled by the fleet } if (vis >= VIS_PARTIAL_VISIBILITY) { @@ -132,6 +135,7 @@ void Ship::Copy(std::shared_ptr copied_object, int empire_ this->m_last_turn_active_in_combat= copied_ship->m_last_turn_active_in_combat; this->m_produced_by_empire_id = copied_ship->m_produced_by_empire_id; this->m_arrived_on_turn = copied_ship->m_arrived_on_turn; + this->m_last_resupplied_on_turn = copied_ship->m_last_resupplied_on_turn; if (vis >= VIS_FULL_VISIBILITY) { this->m_ordered_scrapped = copied_ship->m_ordered_scrapped; @@ -143,6 +147,14 @@ void Ship::Copy(std::shared_ptr copied_object, int empire_ } } +bool Ship::HostileToEmpire(int empire_id) const +{ + if (OwnedBy(empire_id)) + return false; + return empire_id == ALL_EMPIRES || Unowned() || + Empires().GetDiplomaticStatus(Owner(), empire_id) == DIPLO_WAR; +} + std::set Ship::Tags() const { std::set retval; @@ -150,7 +162,7 @@ std::set Ship::Tags() const { if (!design) return retval; - const HullType* hull = ::GetHullType(design->Hull()); + const ShipHull* hull = ::GetShipHull(design->Hull()); if (!hull) return retval; retval.insert(hull->Tags().begin(), hull->Tags().end()); @@ -160,7 +172,7 @@ std::set Ship::Tags() const { return retval; for (const std::string& part_name : parts) { - if (const PartType* part = GetPartType(part_name)) { + if (const ShipPart* part = GetShipPart(part_name)) { retval.insert(part->Tags().begin(), part->Tags().end()); } } @@ -172,13 +184,13 @@ bool Ship::HasTag(const std::string& name) const { const ShipDesign* design = GetShipDesign(m_design_id); if (design) { // check hull for tag - const HullType* hull = ::GetHullType(design->Hull()); + const ShipHull* hull = ::GetShipHull(design->Hull()); if (hull && hull->Tags().count(name)) return true; // check parts for tag for (const std::string& part_name : design->Parts()) { - const PartType* part = GetPartType(part_name); + const ShipPart* part = GetShipPart(part_name); if (part && part->Tags().count(name)) return true; } @@ -200,24 +212,23 @@ bool Ship::ContainedBy(int object_id) const { || object_id == this->SystemID()); } -std::string Ship::Dump() const { +std::string Ship::Dump(unsigned short ntabs) const { std::stringstream os; - os << UniverseObject::Dump(); + os << UniverseObject::Dump(ntabs); os << " design id: " << m_design_id << " fleet id: " << m_fleet_id << " species name: " << m_species_name << " produced by empire id: " << m_produced_by_empire_id - << " arrived on turn: " << m_arrived_on_turn; + << " arrived on turn: " << m_arrived_on_turn + << " last resupplied on turn: " << m_last_resupplied_on_turn; if (!m_part_meters.empty()) { os << " part meters: "; - for (const PartMeterMap::value_type& entry : m_part_meters) { - const std::string part_name = entry.first.second; - MeterType meter_type = entry.first.first; - const Meter& meter = entry.second; - os << part_name << " " - << meter_type - << ": " << meter.Current() << " "; - } + for (const auto& entry : m_part_meters) { + const std::string part_name = entry.first.second; + MeterType meter_type = entry.first.first; + const Meter& meter = entry.second; + os << part_name << " " << meter_type << ": " << meter.Current() << " "; + } } return os.str(); } @@ -234,19 +245,18 @@ bool Ship::IsMonster() const { } bool Ship::IsArmed() const { - const ShipDesign* design = Design(); - if (design) - return design->IsArmed(); - else - return false; + if (TotalWeaponsDamage(0.0f, false) > 0.0f) + return true; // has non-fighter weapons + if (HasFighters() && (TotalWeaponsDamage(0.0f, true) > 0.0f)) + return true; // has no non-fighter weapons but has launchable fighters that do damage + return false; } bool Ship::HasFighters() const { const ShipDesign* design = Design(); - if (design) - return design->HasFighters(); - else + if (!design || !design->HasFighters()) // ensures ship has ability to launch fighters return false; + return FighterCount() >= 1.0f; // ensures ship currently has fighters to launch } bool Ship::CanColonize() const { @@ -270,7 +280,7 @@ bool Ship::CanBombard() const { } float Ship::Speed() const -{ return CurrentMeterValue(METER_SPEED); } +{ return InitialMeterValue(METER_SPEED); } float Ship::ColonyCapacity() const { float retval = 0.0f; @@ -279,18 +289,17 @@ float Ship::ColonyCapacity() const { if (!design) return retval; - for (const std::string& part_name : design->Parts()) - { + for (const std::string& part_name : design->Parts()) { if (part_name.empty()) continue; - const PartType* part_type = GetPartType(part_name); - if (!part_type) + const ShipPart* part = GetShipPart(part_name); + if (!part) continue; - ShipPartClass part_class = part_type->Class(); + ShipPartClass part_class = part->Class(); if (part_class != PC_COLONY) continue; // add capacity for all instances of colony parts to accumulator - retval += this->CurrentPartMeterValue(METER_CAPACITY, part_name); + retval += this->InitialPartMeterValue(METER_CAPACITY, part_name); } return retval; @@ -306,14 +315,14 @@ float Ship::TroopCapacity() const { for (const std::string& part_name : design->Parts()) { if (part_name.empty()) continue; - const PartType* part_type = GetPartType(part_name); - if (!part_type) + const ShipPart* part = GetShipPart(part_name); + if (!part) continue; - ShipPartClass part_class = part_type->Class(); + ShipPartClass part_class = part->Class(); if (part_class != PC_TROOPS) continue; // add capacity for all instances of colony parts to accumulator - retval += this->CurrentPartMeterValue(METER_CAPACITY, part_name); + retval += this->InitialPartMeterValue(METER_CAPACITY, part_name); } return retval; @@ -347,58 +356,12 @@ const std::string& Ship::PublicName(int empire_id) const { std::shared_ptr Ship::Accept(const UniverseObjectVisitor& visitor) const { return visitor.Visit(std::const_pointer_cast(std::static_pointer_cast(shared_from_this()))); } -float Ship::NextTurnCurrentMeterValue(MeterType type) const { - const Meter* meter = UniverseObject::GetMeter(type); - if (!meter) - throw std::invalid_argument("Ship::NextTurnCurrentMeterValue passed meter type that the Ship does not have: " + boost::lexical_cast(type)); - float current_meter_value = meter->Current(); - - if (type == METER_SHIELD) { - if (m_last_turn_active_in_combat >= CurrentTurn()) - return std::max(0.0f, // battle just happened. shields limited to max shield, but don't regen - std::min(current_meter_value, - UniverseObject::GetMeter(METER_MAX_SHIELD)->Current())); - else // shields regneerate to max shield - return UniverseObject::GetMeter(METER_MAX_SHIELD)->Current(); - } - - - // ResourceCenter-like resource meter growth... - - MeterType target_meter_type = INVALID_METER_TYPE; - switch (type) { - case METER_TARGET_INDUSTRY: - case METER_TARGET_RESEARCH: - case METER_TARGET_TRADE: - return current_meter_value; - break; - case METER_INDUSTRY: target_meter_type = METER_TARGET_INDUSTRY; break; - case METER_RESEARCH: target_meter_type = METER_TARGET_RESEARCH; break; - case METER_TRADE: target_meter_type = METER_TARGET_TRADE; break; - default: - return UniverseObject::NextTurnCurrentMeterValue(type); - } - - const Meter* target_meter = UniverseObject::GetMeter(target_meter_type); - if (!target_meter) - throw std::runtime_error("Ship::NextTurnCurrentMeterValue dealing with invalid meter type: " + boost::lexical_cast(type)); - float target_meter_value = target_meter->Current(); - - // meter growth or decay towards target is one per turn. - if (target_meter_value > current_meter_value) - return std::min(current_meter_value + 1.0f, target_meter_value); - else if (target_meter_value < current_meter_value) - return std::max(target_meter_value, current_meter_value - 1.0f); - else - return current_meter_value; -} - const Meter* Ship::GetPartMeter(MeterType type, const std::string& part_name) const { return const_cast(this)->GetPartMeter(type, part_name); } Meter* Ship::GetPartMeter(MeterType type, const std::string& part_name) { Meter* retval = nullptr; - PartMeterMap::iterator it = m_part_meters.find(std::make_pair(type, part_name)); + auto it = m_part_meters.find({type, part_name}); if (it != m_part_meters.end()) retval = &it->second; return retval; @@ -423,7 +386,7 @@ float Ship::SumCurrentPartMeterValuesForPartClass(MeterType type, ShipPartClass if (!design) return retval; - const std::vector& parts = design->Parts(); + const auto& parts = design->Parts(); if (parts.empty()) return retval; @@ -431,16 +394,16 @@ float Ship::SumCurrentPartMeterValuesForPartClass(MeterType type, ShipPartClass for (const std::string& part : parts) part_counts[part]++; - for (const PartMeterMap::value_type& part_meter : m_part_meters) { + for (const auto& part_meter : m_part_meters) { if (part_meter.first.first != type) continue; const std::string& part_name = part_meter.first.second; if (part_counts[part_name] < 1) continue; - const PartType* part_type = GetPartType(part_name); - if (!part_type) + const ShipPart* part = GetShipPart(part_name); + if (!part) continue; - if (part_class == part_type->Class()) + if (part_class == part->Class()) retval += part_meter.second.Current() * part_counts[part_name]; } @@ -449,12 +412,11 @@ float Ship::SumCurrentPartMeterValuesForPartClass(MeterType type, ShipPartClass float Ship::FighterCount() const { float retval = 0.0f; - for (const PartMeterMap::value_type& entry : m_part_meters) { - //std::map, Meter> + for (const auto& entry : m_part_meters) { if (entry.first.first != METER_CAPACITY) continue; - const PartType* part_type = GetPartType(entry.first.second); - if (!part_type || part_type->Class() != PC_FIGHTER_HANGAR) + const ShipPart* part = GetShipPart(entry.first.second); + if (!part || part->Class() != PC_FIGHTER_HANGAR) continue; retval += entry.second.Current(); } @@ -464,12 +426,12 @@ float Ship::FighterCount() const { float Ship::FighterMax() const { float retval = 0.0f; - for (const PartMeterMap::value_type& entry : m_part_meters) { + for (const auto& entry : m_part_meters) { //std::map, Meter> if (entry.first.first != METER_MAX_CAPACITY) continue; - const PartType* part_type = GetPartType(entry.first.second); - if (!part_type || part_type->Class() != PC_FIGHTER_HANGAR) + const ShipPart* part = GetShipPart(entry.first.second); + if (!part || part->Class() != PC_FIGHTER_HANGAR) continue; retval += entry.second.Current(); } @@ -480,7 +442,7 @@ float Ship::FighterMax() const { float Ship::TotalWeaponsDamage(float shield_DR, bool include_fighters) const { // sum up all individual weapons' attack strengths float total_attack = 0.0f; - std::vector all_weapons_damage = AllWeaponsDamage(shield_DR, include_fighters); + auto all_weapons_damage = AllWeaponsDamage(shield_DR, include_fighters); for (float attack : all_weapons_damage) total_attack += attack; return total_attack; @@ -507,14 +469,14 @@ namespace { // for each weapon part, get its damage meter value for (const std::string& part_name : parts) { - const PartType* part = GetPartType(part_name); + const ShipPart* part = GetShipPart(part_name); if (!part) continue; ShipPartClass part_class = part->Class(); - // get the attack power for each weapon part + // get the attack power for each weapon part. if (part_class == PC_DIRECT_WEAPON) { - float part_attack = ship->CurrentPartMeterValue(METER, part_name); + float part_attack = ship->CurrentPartMeterValue(METER, part_name); // used within loop that updates meters, so need current, not initial values float part_shots = ship->CurrentPartMeterValue(SECONDARY_METER, part_name); if (part_attack > DR) retval.push_back((part_attack - DR)*part_shots); @@ -593,11 +555,13 @@ void Ship::BackPropagateMeters() { UniverseObject::BackPropagateMeters(); // ship part meter back propagation, since base class function doesn't do this... - for (PartMeterMap::value_type& entry : m_part_meters) + for (auto& entry : m_part_meters) entry.second.BackPropagate(); } void Ship::Resupply() { + m_last_resupplied_on_turn = CurrentTurn(); + Meter* fuel_meter = UniverseObject::GetMeter(METER_FUEL); const Meter* max_fuel_meter = UniverseObject::GetMeter(METER_MAX_FUEL); if (!fuel_meter || !max_fuel_meter) { @@ -610,13 +574,19 @@ void Ship::Resupply() { // set all part capacities equal to any associated max capacity // this "upgrades" any direct-fire weapon parts to their latest-allowed // strengths, and replaces any lost fighters - for (PartMeterMap::value_type& entry : m_part_meters) { - PartMeterMap::iterator max_it = m_part_meters.end(); - if (entry.first.first == METER_CAPACITY) { - max_it = m_part_meters.find(std::make_pair(METER_MAX_CAPACITY, entry.first.second)); - } else if (entry.first.first == METER_SECONDARY_STAT) { - max_it = m_part_meters.find(std::make_pair(METER_MAX_SECONDARY_STAT, entry.first.second)); + for (auto& entry : m_part_meters) { + const auto& part_name = entry.first.second; + MeterType meter_type = entry.first.first; + MeterType paired_meter_type = INVALID_METER_TYPE; + switch(meter_type) { + case METER_CAPACITY: paired_meter_type = METER_MAX_CAPACITY; break; + case METER_SECONDARY_STAT: paired_meter_type = METER_MAX_SECONDARY_STAT; break; + default: + break; } + if (paired_meter_type == INVALID_METER_TYPE) + continue; + auto max_it = m_part_meters.find({paired_meter_type, part_name}); if (max_it == m_part_meters.end()) continue; @@ -680,15 +650,31 @@ void Ship::ResetTargetMaxUnpairedMeters() { UniverseObject::GetMeter(METER_SPEED)->ResetCurrent(); //UniverseObject::GetMeter(METER_STEALTH)->ResetCurrent(); redundant with base class function - for (PartMeterMap::value_type& entry : m_part_meters) { - PartMeterMap::iterator max_it = m_part_meters.end(); - if (entry.first.first == METER_CAPACITY) { - max_it = m_part_meters.find(std::make_pair(METER_MAX_CAPACITY, entry.first.second)); - } else if (entry.first.first == METER_SECONDARY_STAT) { - max_it = m_part_meters.find(std::make_pair(METER_MAX_SECONDARY_STAT, entry.first.second)); + // max meters are always treated as target/max meters. + // other meters may be unpaired if there is no associated max or target meter + for (auto& entry : m_part_meters) { + const auto& part_name = entry.first.second; + MeterType meter_type = entry.first.first; + MeterType paired_meter_type = INVALID_METER_TYPE; + + switch(meter_type) { + case METER_MAX_CAPACITY: + case METER_MAX_SECONDARY_STAT: + entry.second.ResetCurrent(); + continue; + break; + case METER_CAPACITY: paired_meter_type = METER_MAX_CAPACITY; break; + case METER_SECONDARY_STAT: paired_meter_type = METER_MAX_SECONDARY_STAT; break; + default: + continue; + break; } + + auto max_it = m_part_meters.find({paired_meter_type, part_name}); if (max_it != m_part_meters.end()) - continue; + continue; // is a max/target meter associated with the meter, so don't treat this a target/max + + // no associated target/max meter, so treat this meter as unpaired entry.second.ResetCurrent(); } } @@ -696,15 +682,32 @@ void Ship::ResetTargetMaxUnpairedMeters() { void Ship::ResetPairedActiveMeters() { UniverseObject::ResetPairedActiveMeters(); - for (PartMeterMap::value_type& entry : m_part_meters) { - PartMeterMap::iterator max_it = m_part_meters.end(); - if (entry.first.first == METER_CAPACITY) { - max_it = m_part_meters.find(std::make_pair(METER_MAX_CAPACITY, entry.first.second)); - } else if (entry.first.first == METER_SECONDARY_STAT) { - max_it = m_part_meters.find(std::make_pair(METER_MAX_SECONDARY_STAT, entry.first.second)); + // meters are paired only if they are not max/target meters, and there is an + // associated max/target meter + for (auto& entry : m_part_meters) { + const auto& part_name = entry.first.second; + MeterType meter_type = entry.first.first; + MeterType paired_meter_type = INVALID_METER_TYPE; + + switch(meter_type) { + case METER_MAX_CAPACITY: + case METER_MAX_SECONDARY_STAT: + continue; // is a max/target meter + break; + case METER_CAPACITY: paired_meter_type = METER_MAX_CAPACITY; break; + case METER_SECONDARY_STAT: paired_meter_type = METER_MAX_SECONDARY_STAT; break; + default: + continue; // no associated max/target meter + break; } - if (max_it != m_part_meters.end()) - entry.second.SetCurrent(entry.second.Initial()); + + auto max_it = m_part_meters.find({paired_meter_type, part_name}); + if (max_it == m_part_meters.end()) + continue; // no associated max/target meter + + // has an associated max/target meter. + //std::map, Meter>::iterator + entry.second.SetCurrent(entry.second.Initial()); } } @@ -717,31 +720,8 @@ void Ship::SetShipMetersToMax() { UniverseObject::GetMeter(METER_STRUCTURE)->SetCurrent(Meter::LARGE_VALUE); // some part capacity meters may have an associated max capacity... - for (PartMeterMap::value_type& entry : m_part_meters) { - PartMeterMap::iterator max_it = m_part_meters.end(); - if (entry.first.first == METER_CAPACITY) { - max_it = m_part_meters.find(std::make_pair(METER_MAX_CAPACITY, entry.first.second)); - } else if (entry.first.first == METER_SECONDARY_STAT) { - max_it = m_part_meters.find(std::make_pair(METER_MAX_SECONDARY_STAT, entry.first.second)); - } - if (max_it == m_part_meters.end()) - continue; - max_it->second.SetCurrent(Meter::LARGE_VALUE); + for (auto& entry : m_part_meters) entry.second.SetCurrent(Meter::LARGE_VALUE); - } -} - -void Ship::PopGrowthProductionResearchPhase() { - UniverseObject::PopGrowthProductionResearchPhase(); - - UniverseObject::GetMeter(METER_SHIELD)->SetCurrent(Ship::NextTurnCurrentMeterValue(METER_SHIELD)); - UniverseObject::GetMeter(METER_INDUSTRY)->SetCurrent(Ship::NextTurnCurrentMeterValue(METER_INDUSTRY)); - UniverseObject::GetMeter(METER_RESEARCH)->SetCurrent(Ship::NextTurnCurrentMeterValue(METER_RESEARCH)); - UniverseObject::GetMeter(METER_TRADE)->SetCurrent(Ship::NextTurnCurrentMeterValue(METER_TRADE)); - - // part capacity meters set to max only by being in supply - - StateChangedSignal(); } void Ship::ClampMeters() { @@ -764,26 +744,34 @@ void Ship::ClampMeters() { UniverseObject::GetMeter(METER_SPEED)->ClampCurrentToRange(); // clamp most part meters to basic range limits - for (PartMeterMap::value_type& entry : m_part_meters) { - if (entry.first.first == METER_CAPACITY || entry.first.first == METER_SECONDARY_STAT) - continue; - entry.second.ClampCurrentToRange(); + for (auto& entry : m_part_meters) { + MeterType meter_type = entry.first.first; + switch(meter_type) { + case METER_MAX_CAPACITY: + case METER_MAX_SECONDARY_STAT: + entry.second.ClampCurrentToRange(); + default: + break; + } } - // special case extra clamping for paired CAPACTY meters dependent on their associated MAX_CAPACITY meter... - for (PartMeterMap::value_type& entry : m_part_meters) { - PartMeterMap::iterator max_it = m_part_meters.end(); - if (entry.first.first == METER_CAPACITY) { - max_it = m_part_meters.find(std::make_pair(METER_MAX_CAPACITY, entry.first.second)); - } else if (entry.first.first == METER_SECONDARY_STAT) { - max_it = m_part_meters.find(std::make_pair(METER_MAX_SECONDARY_STAT, entry.first.second)); + // special case extra clamping for paired active meters dependent + // on their associated max meter... + for (auto& entry : m_part_meters) { + const auto& part_name = entry.first.second; + MeterType meter_type = entry.first.first; + MeterType paired_meter_type = INVALID_METER_TYPE; + switch(meter_type) { + case METER_CAPACITY: paired_meter_type = METER_MAX_CAPACITY; break; + case METER_SECONDARY_STAT: paired_meter_type = METER_MAX_SECONDARY_STAT; break; + default: + break; } - - if (max_it == m_part_meters.end()) { - // no found max meter, revert to normal clamping for this meter - entry.second.ClampCurrentToRange(); + if (paired_meter_type == INVALID_METER_TYPE) + continue; + auto max_it = m_part_meters.find({paired_meter_type, part_name}); + if (max_it == m_part_meters.end()) continue; - } const Meter& max_meter = max_it->second; entry.second.ClampCurrentToRange(Meter::DEFAULT_VALUE, max_meter.Current()); diff --git a/universe/Ship.h b/universe/Ship.h index 14b3348925d..9b3bd2e3aef 100644 --- a/universe/Ship.h +++ b/universe/Ship.h @@ -9,6 +9,7 @@ FO_COMMON_API extern const int ALL_EMPIRES; FO_COMMON_API extern const int INVALID_DESIGN_ID; FO_COMMON_API extern const int INVALID_GAME_TURN; +FO_COMMON_API extern const int BEFORE_FIRST_TURN; FO_COMMON_API extern const int INVALID_OBJECT_ID; class ShipDesign; @@ -18,36 +19,26 @@ class FO_COMMON_API Ship : public UniverseObject { typedef std::map, Meter> PartMeterMap; /** \name Accessors */ //@{ + bool HostileToEmpire(int empire_id) const override; std::set Tags() const override; - bool HasTag(const std::string& name) const override; - UniverseObjectType ObjectType() const override; - - std::string Dump() const override; + std::string Dump(unsigned short ntabs = 0) const override; int ContainerObjectID() const override { return m_fleet_id; } bool ContainedBy(int object_id) const override; - - float NextTurnCurrentMeterValue(MeterType type) const override; - const std::string& PublicName(int empire_id) const override; - std::shared_ptr Accept(const UniverseObjectVisitor& visitor) const override; /** Back propagates part meters (which UniverseObject equivalent doesn't). */ void BackPropagateMeters() override; void ResetTargetMaxUnpairedMeters() override; - void ResetPairedActiveMeters() override; - void ClampMeters() override; - void PopGrowthProductionResearchPhase() override; - /** Returns new copy of this Ship. */ Ship* Clone(int empire_id = ALL_EMPIRES) const override; @@ -60,6 +51,7 @@ class FO_COMMON_API Ship : public UniverseObject { int ProducedByEmpireID() const { return m_produced_by_empire_id; } ///< returns the empire ID of the empire that produced this ship int ArrivedOnTurn() const { return m_arrived_on_turn; } ///< returns the turn on which this ship arrived in its current system + int LastResuppliedOnTurn() const{ return m_last_resupplied_on_turn;}///< returns the turn on which this ship was last resupplied / upgraded bool IsMonster() const; bool IsArmed() const; @@ -82,7 +74,7 @@ class FO_COMMON_API Ship : public UniverseObject { const PartMeterMap& PartMeters() const { return m_part_meters; } ///< returns this Ship's part meters const Meter* GetPartMeter(MeterType type, const std::string& part_name) const; ///< returns the requested part Meter, or 0 if no such part Meter of that type is found in this ship for that part name float CurrentPartMeterValue(MeterType type, const std::string& part_name) const; ///< returns current value of the specified part meter \a type for the specified part name - float InitialPartMeterValue(MeterType type, const std::string& part_name) const; ///< returns this turn's initial value for the speicified part meter \a type for the specified part name + float InitialPartMeterValue(MeterType type, const std::string& part_name) const; ///< returns this turn's initial value for the specified part meter \a type for the specified part name /** Returns sum of current value for part meter @p type of all parts with ShipPartClass @p part_class */ float SumCurrentPartMeterValuesForPartClass(MeterType type, ShipPartClass part_class) const; @@ -123,12 +115,14 @@ class FO_COMMON_API Ship : public UniverseObject { /** \name Structors */ //@{ Ship(); +public: /** Create a ship from an @p empire_id, @p design_id, @p species_name and @p production_by_empire_id. */ Ship(int empire_id, int design_id, const std::string& species_name, int produced_by_empire_id = ALL_EMPIRES); - template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); +protected: + template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); public: ~Ship() {} @@ -146,9 +140,10 @@ class FO_COMMON_API Ship : public UniverseObject { std::string m_species_name; int m_produced_by_empire_id = ALL_EMPIRES; int m_arrived_on_turn = INVALID_GAME_TURN; + int m_last_resupplied_on_turn = BEFORE_FIRST_TURN; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/universe/ShipDesign.cpp b/universe/ShipDesign.cpp index 9d3e884a89f..0d7292316c2 100644 --- a/universe/ShipDesign.cpp +++ b/universe/ShipDesign.cpp @@ -1,35 +1,25 @@ #include "ShipDesign.h" -#include "../util/OptionsDB.h" -#include "../util/Logger.h" -#include "../util/AppInterface.h" -#include "../util/MultiplayerCommon.h" -#include "../util/CheckSums.h" -#include "../parse/Parse.h" -#include "../Empire/Empire.h" -#include "../Empire/EmpireManager.h" +#include +#include #include "Condition.h" #include "Effect.h" #include "Planet.h" +#include "ScriptingContext.h" #include "Ship.h" -#include "Predicates.h" +#include "ShipHull.h" +#include "ShipPart.h" #include "Species.h" -#include "Universe.h" #include "ValueRef.h" -#include "Enums.h" +#include "../util/AppInterface.h" +#include "../util/CheckSums.h" +#include "../util/GameRules.h" +#include "../util/i18n.h" -#include -#include -#include -#include -#include -#include -#include -#include extern FO_COMMON_API const int INVALID_DESIGN_ID = -1; -using boost::io::str; +//using boost::io::str; namespace { void AddRules(GameRules& rules) { @@ -37,100 +27,11 @@ namespace { rules.Add("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION", "RULE_CHEAP_AND_FAST_SHIP_PRODUCTION_DESC", "", false, true); - rules.Add("RULE_SHIP_SPEED_FACTOR", "RULE_SHIP_SPEED_FACTOR_DESC", - "BALANCE", 1.0, true, RangedValidator(0.1, 10.0)); - rules.Add("RULE_SHIP_STRUCTURE_FACTOR", "RULE_SHIP_STRUCTURE_FACTOR_DESC", - "BALANCE", 1.0, true, RangedValidator(0.1, 10.0)); } bool temp_bool = RegisterGameRules(&AddRules); const std::string EMPTY_STRING; - - // create effectsgroup that increases the value of \a meter_type - // by the result of evalulating \a increase_vr - std::shared_ptr - IncreaseMeter(MeterType meter_type, - ValueRef::ValueRefBase* increase_vr) - { - typedef std::shared_ptr EffectsGroupPtr; - typedef std::vector Effects; - Condition::Source* scope = new Condition::Source; - Condition::Source* activation = new Condition::Source; - - ValueRef::ValueRefBase* vr = - new ValueRef::Operation( - ValueRef::PLUS, - new ValueRef::Variable(ValueRef::EFFECT_TARGET_VALUE_REFERENCE, std::vector()), - increase_vr - ); - return EffectsGroupPtr( - new Effect::EffectsGroup( - scope, activation, Effects(1, new Effect::SetMeter(meter_type, vr)))); - } - - // create effectsgroup that increases the value of \a meter_type - // by the specified amount \a fixed_increase - std::shared_ptr - IncreaseMeter(MeterType meter_type, float fixed_increase) { - ValueRef::ValueRefBase* increase_vr = - new ValueRef::Constant(fixed_increase); - return IncreaseMeter(meter_type, increase_vr); - } - - // create effectsgroup that increases the value of \a meter_type - // by the product of \a base_increase and the value of the game - // rule of type double with the name \a scaling_factor_rule_name - std::shared_ptr - IncreaseMeter(MeterType meter_type, float base_increase, - const std::string& scaling_factor_rule_name) - { - // if no rule specified, revert to fixed constant increase - if (scaling_factor_rule_name.empty()) - return IncreaseMeter(meter_type, base_increase); - - ValueRef::ValueRefBase* increase_vr = new ValueRef::Operation( - ValueRef::TIMES, - new ValueRef::Constant(base_increase), - new ValueRef::ComplexVariable( - "GameRule", nullptr, nullptr, nullptr, - new ValueRef::Constant(scaling_factor_rule_name) - ) - ); - - return IncreaseMeter(meter_type, increase_vr); - } - - // create effectsgroup that increases the value of the part meter - // of type \a meter_type for part name \a part_name by the fixed - // amount \a increase - std::shared_ptr - IncreaseMeter(MeterType meter_type, const std::string& part_name, - float increase, bool allow_stacking = true) - { - typedef std::shared_ptr EffectsGroupPtr; - typedef std::vector Effects; - Condition::Source* scope = new Condition::Source; - Condition::Source* activation = new Condition::Source; - - ValueRef::ValueRefBase* value_vr = - new ValueRef::Operation( - ValueRef::PLUS, - new ValueRef::Variable(ValueRef::EFFECT_TARGET_VALUE_REFERENCE, std::vector()), - new ValueRef::Constant(increase) - ); - - ValueRef::ValueRefBase* part_name_vr = - new ValueRef::Constant(part_name); - - std::string stacking_group = (allow_stacking ? "" : - (part_name + "_" + boost::lexical_cast(meter_type) + "_PartMeter")); - - return EffectsGroupPtr( - new Effect::EffectsGroup( - scope, activation, - Effects(1, new Effect::SetShipPartMeter(meter_type, part_name_vr, value_vr)), - part_name, stacking_group)); - } + const float ARBITRARY_LARGE_COST = 999999.9f; bool DesignsTheSame(const ShipDesign& one, const ShipDesign& two) { return ( @@ -149,563 +50,72 @@ namespace { } } -namespace CheckSums { - void CheckSumCombine(unsigned int& sum, const HullType::Slot& slot) { - TraceLogger() << "CheckSumCombine(Slot): " << typeid(slot).name(); - CheckSumCombine(sum, slot.x); - CheckSumCombine(sum, slot.y); - CheckSumCombine(sum, slot.type); - } -} - //////////////////////////////////////////////// // Free Functions // //////////////////////////////////////////////// -const PartTypeManager& GetPartTypeManager() -{ return PartTypeManager::GetPartTypeManager(); } - -const PartType* GetPartType(const std::string& name) -{ return GetPartTypeManager().GetPartType(name); } - -const HullTypeManager& GetHullTypeManager() -{ return HullTypeManager::GetHullTypeManager(); } - -const HullType* GetHullType(const std::string& name) -{ return GetHullTypeManager().GetHullType(name); } - const ShipDesign* GetShipDesign(int ship_design_id) { return GetUniverse().GetShipDesign(ship_design_id); } -///////////////////////////////////// -// PartTypeManager // -///////////////////////////////////// -// static -PartTypeManager* PartTypeManager::s_instance = nullptr; - -PartTypeManager::PartTypeManager() { - if (s_instance) - throw std::runtime_error("Attempted to create more than one PartTypeManager."); - - try { - parse::ship_parts(m_parts); - } catch (const std::exception& e) { - ErrorLogger() << "Failed parsing ship parts: error: " << e.what(); - throw; - } - - TraceLogger() << "Part Types:"; - for (const auto& entry : m_parts) { - const PartType* p = entry.second; - TraceLogger() << " ... " << p->Name() << " class: " << p->Class(); - } - - // Only update the global pointer on sucessful construction. - s_instance = this; - - DebugLogger() << "PartTypeManager checksum: " << GetCheckSum(); -} - -PartTypeManager::~PartTypeManager() { - for (std::map::value_type& entry : m_parts) { - delete entry.second; - } -} - -const PartType* PartTypeManager::GetPartType(const std::string& name) const { - std::map::const_iterator it = m_parts.find(name); - return it != m_parts.end() ? it->second : nullptr; -} - -const PartTypeManager& PartTypeManager::GetPartTypeManager() { - static PartTypeManager manager; - return manager; -} - -PartTypeManager::iterator PartTypeManager::begin() const -{ return m_parts.begin(); } - -PartTypeManager::iterator PartTypeManager::end() const -{ return m_parts.end(); } - -unsigned int PartTypeManager::GetCheckSum() const { - unsigned int retval{0}; - for (auto const& name_part_pair : m_parts) - CheckSums::CheckSumCombine(retval, name_part_pair); - CheckSums::CheckSumCombine(retval, m_parts.size()); - - return retval; -} - - //////////////////////////////////////////////// -// PartType +// CommonParams //////////////////////////////////////////////// - -PartType::PartType() : - m_name("invalid part type"), - m_description("indescribable"), - m_class(INVALID_SHIP_PART_CLASS), - m_capacity(0.0f), - m_secondary_stat(1.0f), - m_production_cost(0), - m_production_time(0), - m_producible(false), - m_mountable_slot_types(), - m_tags(), - m_production_meter_consumption(), - m_production_special_consumption(), - m_location(0), - m_exclusions(), - m_effects(), - m_icon(), - m_add_standard_capacity_effect(false) -{} - -PartType::PartType(ShipPartClass part_class, double capacity, double stat2, - const CommonParams& common_params, const MoreCommonParams& more_common_params, - std::vector mountable_slot_types, - const std::string& icon, bool add_standard_capacity_effect) : - m_name(more_common_params.name), - m_description(more_common_params.description), - m_class(part_class), - m_capacity(capacity), - m_secondary_stat(stat2), - m_production_cost(common_params.production_cost), - m_production_time(common_params.production_time), - m_producible(common_params.producible), - m_mountable_slot_types(mountable_slot_types), - m_tags(), - m_production_meter_consumption(common_params.production_meter_consumption), - m_production_special_consumption(common_params.production_special_consumption), - m_location(common_params.location), - m_exclusions(more_common_params.exclusions), - m_effects(), - m_icon(icon), - m_add_standard_capacity_effect(add_standard_capacity_effect) +CommonParams::CommonParams() {} + +CommonParams::CommonParams(std::unique_ptr>&& production_cost_, + std::unique_ptr>&& production_time_, + bool producible_, + const std::set& tags_, + std::unique_ptr&& location_, + std::vector>&& effects_, + ConsumptionMap&& production_meter_consumption_, + ConsumptionMap&& production_special_consumption_, + std::unique_ptr&& enqueue_location_) : + production_cost(std::move(production_cost_)), + production_time(std::move(production_time_)), + producible(producible_), + production_meter_consumption(std::move(production_meter_consumption_)), + production_special_consumption(std::move(production_special_consumption_)), + location(std::move(location_)), + enqueue_location(std::move(enqueue_location_)), + effects(std::move(effects_)) { - //TraceLogger() << "part type: " << m_name << " producible: " << m_producible << std::endl; - Init(common_params.effects); - for (const std::string& tag : common_params.tags) - m_tags.insert(boost::to_upper_copy(tag)); -} - -void PartType::Init(const std::vector>& effects) { - if ((m_capacity != 0 || m_secondary_stat != 0) && m_add_standard_capacity_effect) { - switch (m_class) { - case PC_COLONY: - case PC_TROOPS: - m_effects.push_back(IncreaseMeter(METER_CAPACITY, m_name, m_capacity, false)); - break; - case PC_FIGHTER_HANGAR: { // capacity indicates how many fighters are stored in this type of part (combined for all copies of the part) - m_effects.push_back(IncreaseMeter(METER_MAX_CAPACITY, m_name, m_capacity, true)); // stacking capacities allowed for this part, so each part contributes to the total capacity - m_effects.push_back(IncreaseMeter(METER_MAX_SECONDARY_STAT, m_name, m_secondary_stat, false)); // stacking damage not allowed, as damage per shot should be the same regardless of number of shots - break; - } - case PC_FIGHTER_BAY: // capacity indicates how many fighters each instance of the part can launch per combat bout... - case PC_DIRECT_WEAPON: { // capacity indicates weapon damage per shot - m_effects.push_back(IncreaseMeter(METER_MAX_CAPACITY, m_name, m_capacity, false)); - m_effects.push_back(IncreaseMeter(METER_MAX_SECONDARY_STAT, m_name, m_secondary_stat, false)); - break; - } - case PC_SHIELD: - m_effects.push_back(IncreaseMeter(METER_MAX_SHIELD, m_capacity)); - break; - case PC_DETECTION: - m_effects.push_back(IncreaseMeter(METER_DETECTION, m_capacity)); - break; - case PC_STEALTH: - m_effects.push_back(IncreaseMeter(METER_STEALTH, m_capacity)); - break; - case PC_FUEL: - m_effects.push_back(IncreaseMeter(METER_MAX_FUEL, m_capacity)); - break; - case PC_ARMOUR: - m_effects.push_back(IncreaseMeter(METER_MAX_STRUCTURE, m_capacity, "RULE_SHIP_STRUCTURE_FACTOR")); - break; - case PC_SPEED: - m_effects.push_back(IncreaseMeter(METER_SPEED, m_capacity, "RULE_SHIP_SPEED_FACTOR")); - break; - case PC_RESEARCH: - m_effects.push_back(IncreaseMeter(METER_TARGET_RESEARCH,m_capacity)); - break; - case PC_INDUSTRY: - m_effects.push_back(IncreaseMeter(METER_TARGET_INDUSTRY,m_capacity)); - break; - case PC_TRADE: - m_effects.push_back(IncreaseMeter(METER_TARGET_TRADE, m_capacity)); - break; - default: - break; - } - } - - for (std::shared_ptr effect : effects) { - effect->SetTopLevelContent(m_name); - m_effects.push_back(effect); - } -} - -PartType::~PartType() -{ delete m_location; } - -float PartType::Capacity() const { - switch (m_class) { - case PC_ARMOUR: - return m_capacity * GetGameRules().Get("RULE_SHIP_STRUCTURE_FACTOR"); - break; - case PC_SPEED: - return m_capacity * GetGameRules().Get("RULE_SHIP_SPEED_FACTOR"); - break; - default: - return m_capacity; - } -} - -float PartType::SecondaryStat() const -{ return m_secondary_stat; } - -std::string PartType::CapacityDescription() const { - std::string desc_string; - float main_stat = Capacity(); - float sdry_stat = SecondaryStat(); - - switch (m_class) { - case PC_FUEL: - case PC_TROOPS: - case PC_COLONY: - case PC_FIGHTER_BAY: - desc_string += str(FlexibleFormat(UserString("PART_DESC_CAPACITY")) % main_stat); - break; - case PC_DIRECT_WEAPON: - desc_string += str(FlexibleFormat(UserString("PART_DESC_DIRECT_FIRE_STATS")) % main_stat % sdry_stat); - break; - case PC_FIGHTER_HANGAR: - desc_string += str(FlexibleFormat(UserString("PART_DESC_HANGAR_STATS")) % main_stat % sdry_stat); - break; - case PC_SHIELD: - desc_string = str(FlexibleFormat(UserString("PART_DESC_SHIELD_STRENGTH")) % main_stat); - break; - case PC_DETECTION: - desc_string = str(FlexibleFormat(UserString("PART_DESC_DETECTION")) % main_stat); - break; - default: - desc_string = str(FlexibleFormat(UserString("PART_DESC_STRENGTH")) % main_stat); - break; - } - return desc_string; -} - -bool PartType::CanMountInSlotType(ShipSlotType slot_type) const { - if (INVALID_SHIP_SLOT_TYPE == slot_type) - return false; - for (ShipSlotType mountable_slot_type : m_mountable_slot_types) - if (mountable_slot_type == slot_type) - return true; - return false; -} - -bool PartType::ProductionCostTimeLocationInvariant() const { - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION")) - return true; - if (m_production_cost && !m_production_cost->TargetInvariant()) - return false; - if (m_production_time && !m_production_time->TargetInvariant()) - return false; - return true; -} - -float PartType::ProductionCost(int empire_id, int location_id) const { - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION") || !m_production_cost) { - return 1.0f; - } else { - if (m_production_cost->ConstantExpr()) - return static_cast(m_production_cost->Eval()); - else if (m_production_cost->SourceInvariant() && m_production_cost->TargetInvariant()) - return static_cast(m_production_cost->Eval()); - - const auto arbitrary_large_number = 999999.9f; - - std::shared_ptr location = GetUniverseObject(location_id); - if (!location && !m_production_cost->TargetInvariant()) - return arbitrary_large_number; - - std::shared_ptr source = Empires().GetSource(empire_id); - if (!source && !m_production_cost->SourceInvariant()) - return arbitrary_large_number; - - ScriptingContext context(source, location); - - return static_cast(m_production_cost->Eval(context)); - } -} - -int PartType::ProductionTime(int empire_id, int location_id) const { - const auto arbitrary_large_number = 9999; - - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION") || !m_production_time) { - return 1; - } else { - if (m_production_time->ConstantExpr()) - return m_production_time->Eval(); - else if (m_production_time->SourceInvariant() && m_production_time->TargetInvariant()) - return m_production_time->Eval(); - - std::shared_ptr location = GetUniverseObject(location_id); - if (!location && !m_production_time->TargetInvariant()) - return arbitrary_large_number; - - std::shared_ptr source = Empires().GetSource(empire_id); - if (!source && !m_production_time->SourceInvariant()) - return arbitrary_large_number; - - ScriptingContext context(source, location); - - return m_production_time->Eval(context); - } -} - -unsigned int PartType::GetCheckSum() const { - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, m_name); - CheckSums::CheckSumCombine(retval, m_description); - CheckSums::CheckSumCombine(retval, m_class); - CheckSums::CheckSumCombine(retval, m_capacity); - CheckSums::CheckSumCombine(retval, m_secondary_stat); - CheckSums::CheckSumCombine(retval, m_production_cost); - CheckSums::CheckSumCombine(retval, m_production_time); - CheckSums::CheckSumCombine(retval, m_producible); - CheckSums::CheckSumCombine(retval, m_mountable_slot_types); - CheckSums::CheckSumCombine(retval, m_tags); - CheckSums::CheckSumCombine(retval, m_production_meter_consumption); - CheckSums::CheckSumCombine(retval, m_production_special_consumption); - CheckSums::CheckSumCombine(retval, m_location); - CheckSums::CheckSumCombine(retval, m_exclusions); - CheckSums::CheckSumCombine(retval, m_effects); - CheckSums::CheckSumCombine(retval, m_icon); - CheckSums::CheckSumCombine(retval, m_add_standard_capacity_effect); - - return retval; -} - - -//////////////////////////////////////////////// -// HullType -//////////////////////////////////////////////// -HullType::Slot::Slot() : - type(INVALID_SHIP_SLOT_TYPE), x(0.5), y(0.5) -{} - -void HullType::Init(const std::vector>& effects) { - if (m_fuel != 0) - m_effects.push_back(IncreaseMeter(METER_MAX_FUEL, m_fuel)); - if (m_stealth != 0) - m_effects.push_back(IncreaseMeter(METER_STEALTH, m_stealth)); - if (m_structure != 0) - m_effects.push_back(IncreaseMeter(METER_MAX_STRUCTURE, m_structure, "RULE_SHIP_STRUCTURE_FACTOR")); - if (m_speed != 0) - m_effects.push_back(IncreaseMeter(METER_SPEED, m_speed, "RULE_SHIP_SPEED_FACTOR")); - - for (std::shared_ptr effect : effects) { - effect->SetTopLevelContent(m_name); - m_effects.push_back(effect); - } -} - -HullType::~HullType() -{ delete m_location; } - -float HullType::Speed() const -{ return m_speed * GetGameRules().Get("RULE_SHIP_SPEED_FACTOR"); } - -float HullType::Structure() const -{ return m_structure * GetGameRules().Get("RULE_SHIP_STRUCTURE_FACTOR"); } - -unsigned int HullType::NumSlots(ShipSlotType slot_type) const { - unsigned int count = 0; - for (const Slot& slot : m_slots) - if (slot.type == slot_type) - ++count; - return count; -} - -// HullType:: and PartType::ProductionCost and ProductionTime are almost identical. -// Chances are, the same is true of buildings and techs as well. -// TODO: Eliminate duplication -bool HullType::ProductionCostTimeLocationInvariant() const { - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION")) - return true; - if (m_production_cost && !m_production_cost->LocalCandidateInvariant()) - return false; - if (m_production_time && !m_production_time->LocalCandidateInvariant()) - return false; - return true; -} - -float HullType::ProductionCost(int empire_id, int location_id) const { - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION") || !m_production_cost) { - return 1.0f; - } else { - if (m_production_cost->ConstantExpr()) - return static_cast(m_production_cost->Eval()); - else if (m_production_cost->SourceInvariant() && m_production_cost->TargetInvariant()) - return static_cast(m_production_cost->Eval()); - - const auto arbitrary_large_number = 999999.9f; - - std::shared_ptr location = GetUniverseObject(location_id); - if (!location && !m_production_cost->TargetInvariant()) - return arbitrary_large_number; - - std::shared_ptr source = Empires().GetSource(empire_id); - if (!source && !m_production_cost->SourceInvariant()) - return arbitrary_large_number; - - ScriptingContext context(source, location); - - return static_cast(m_production_cost->Eval(context)); - } -} - -int HullType::ProductionTime(int empire_id, int location_id) const { - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION") || !m_production_time) { - return 1; - } else { - if (m_production_time->ConstantExpr()) - return m_production_time->Eval(); - else if (m_production_time->SourceInvariant() && m_production_time->TargetInvariant()) - return m_production_time->Eval(); - - const auto arbitrary_large_number = 999999; - - std::shared_ptr location = GetUniverseObject(location_id); - if (!location && !m_production_time->TargetInvariant()) - return arbitrary_large_number; - - std::shared_ptr source = Empires().GetSource(empire_id); - if (!source && !m_production_time->SourceInvariant()) - return arbitrary_large_number; - - ScriptingContext context(source, location); - - return m_production_time->Eval(context); - } + for (const std::string& tag : tags_) + tags.insert(boost::to_upper_copy(tag)); } -unsigned int HullType::GetCheckSum() const { - unsigned int retval{0}; - - //// test - //std::map> int_pBlah_map{ - // {103U, std::make_shared()}, {0, nullptr}}; - //CheckSumCombine(retval, int_pBlah_map); - //std::map metertype_string_map{{METER_INDUSTRY, "STRING!"}}; - //CheckSumCombine(retval, metertype_string_map); - //return retval; - //// end test - - CheckSums::CheckSumCombine(retval, m_name); - CheckSums::CheckSumCombine(retval, m_description); - CheckSums::CheckSumCombine(retval, m_speed); - CheckSums::CheckSumCombine(retval, m_fuel); - CheckSums::CheckSumCombine(retval, m_stealth); - CheckSums::CheckSumCombine(retval, m_structure); - CheckSums::CheckSumCombine(retval, m_production_cost); - CheckSums::CheckSumCombine(retval, m_production_time); - CheckSums::CheckSumCombine(retval, m_producible); - CheckSums::CheckSumCombine(retval, m_slots); - CheckSums::CheckSumCombine(retval, m_tags); - CheckSums::CheckSumCombine(retval, m_production_meter_consumption); - CheckSums::CheckSumCombine(retval, m_production_special_consumption); - CheckSums::CheckSumCombine(retval, m_location); - CheckSums::CheckSumCombine(retval, m_exclusions); - CheckSums::CheckSumCombine(retval, m_effects); - CheckSums::CheckSumCombine(retval, m_graphic); - CheckSums::CheckSumCombine(retval, m_icon); - - return retval; -} +CommonParams::~CommonParams() {} ///////////////////////////////////// -// HullTypeManager // +// ParsedShipDesign // ///////////////////////////////////// -// static -HullTypeManager* HullTypeManager::s_instance = nullptr; - -HullTypeManager::HullTypeManager() { - if (s_instance) - throw std::runtime_error("Attempted to create more than one HullTypeManager."); - - try { - parse::ship_hulls(m_hulls); - } catch (const std::exception& e) { - ErrorLogger() << "Failed parsing ship hulls: error: " << e.what(); - throw; - } - - TraceLogger() << "Hull Types:"; - for (const auto& entry : m_hulls) { - const HullType* h = entry.second; - TraceLogger() << " ... " << h->Name(); - } - - if (m_hulls.empty()) - ErrorLogger() << "HullTypeManager expects at least one hull type. All ship design construction will fail."; - - // Only update the global pointer on sucessful construction. - s_instance = this; - - DebugLogger() << "HullTypeManager checksum: " << GetCheckSum(); -} - -HullTypeManager::~HullTypeManager() { - for (std::map::value_type& entry : m_hulls) { - delete entry.second; - } -} - -const HullType* HullTypeManager::GetHullType(const std::string& name) const { - std::map::const_iterator it = m_hulls.find(name); - return it != m_hulls.end() ? it->second : nullptr; -} - -const HullTypeManager& HullTypeManager::GetHullTypeManager() { - static HullTypeManager manager; - return manager; -} - -HullTypeManager::iterator HullTypeManager::begin() const -{ return m_hulls.begin(); } - -HullTypeManager::iterator HullTypeManager::end() const -{ return m_hulls.end(); } - -unsigned int HullTypeManager::GetCheckSum() const { - unsigned int retval{0}; - for (auto const& name_hull_pair : m_hulls) - CheckSums::CheckSumCombine(retval, name_hull_pair); - CheckSums::CheckSumCombine(retval, m_hulls.size()); - - return retval; -} +ParsedShipDesign::ParsedShipDesign( + const std::string& name, const std::string& description, + int designed_on_turn, int designed_by_empire, + const std::string& hull, + const std::vector& parts, + const std::string& icon, const std::string& model, + bool name_desc_in_stringtable, bool monster, + const boost::uuids::uuid& uuid) : + m_name(name), + m_description(description), + m_uuid(uuid), + m_designed_on_turn(designed_on_turn), + m_designed_by_empire(designed_by_empire), + m_hull(hull), + m_parts(parts), + m_is_monster(monster), + m_icon(icon), + m_3D_model(model), + m_name_desc_in_stringtable(name_desc_in_stringtable) +{} //////////////////////////////////////////////// // ShipDesign //////////////////////////////////////////////// ShipDesign::ShipDesign() : - m_name(), - m_description(), - m_uuid(boost::uuids::nil_generator()()), - m_designed_on_turn(UniverseObject::INVALID_OBJECT_AGE), - m_designed_by_empire(ALL_EMPIRES), - m_hull(), - m_parts(), - m_is_monster(false), - m_icon(), - m_3D_model(), - m_name_desc_in_stringtable(false) + m_uuid(boost::uuids::nil_generator()()) {} ShipDesign::ShipDesign(const boost::optional& should_throw, @@ -732,14 +142,12 @@ ShipDesign::ShipDesign(const boost::optional& should_thro BuildStatCaches(); } -ShipDesign::ShipDesign(const std::string& name, const std::string& description, - int designed_on_turn, int designed_by_empire, const std::string& hull, - const std::vector& parts, - const std::string& icon, const std::string& model, - bool name_desc_in_stringtable, bool monster, - const boost::uuids::uuid& uuid /*= boost::uuids::nil_uuid()*/) : - ShipDesign(boost::none, name, description, designed_on_turn, designed_by_empire, hull, parts, - icon, model, name_desc_in_stringtable, monster, uuid) +ShipDesign::ShipDesign(const ParsedShipDesign& design) : + ShipDesign(boost::none, design.m_name, design.m_description, + design.m_designed_on_turn, design.m_designed_by_empire, + design.m_hull, design.m_parts, + design.m_icon, design.m_3D_model, design.m_name_desc_in_stringtable, + design.m_is_monster, design.m_uuid) {} const std::string& ShipDesign::Name(bool stringtable_lookup /* = true */) const { @@ -750,7 +158,7 @@ const std::string& ShipDesign::Name(bool stringtable_lookup /* = true */) const } void ShipDesign::SetName(const std::string& name) { - if (m_name != "") { + if (!name.empty() && !m_name.empty()) { m_name = name; } } @@ -765,24 +173,22 @@ const std::string& ShipDesign::Description(bool stringtable_lookup /* = true */) return m_description; } -void ShipDesign::SetDescription(const std::string& description) { - if (m_description != "") { - m_description = description; - } -} +void ShipDesign::SetDescription(const std::string& description) +{ m_description = description; } bool ShipDesign::ProductionCostTimeLocationInvariant() const { if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION")) return true; - // as seen in ShipDesign::ProductionCost, the location is passed as the - // local candidate in the ScriptingContext + // as seen in ShipDesign::ProductionCost, the production location is passed + // as the local candidate in the ScriptingContext // check hull and all parts - if (const HullType* hull = GetHullType(m_hull)) + if (const ShipHull* hull = GetShipHull(m_hull)) if (!hull->ProductionCostTimeLocationInvariant()) return false; + for (const std::string& part_name : m_parts) - if (const PartType* part = GetPartType(part_name)) + if (const ShipPart* part = GetShipPart(part_name)) if (!part->ProductionCostTimeLocationInvariant()) return false; @@ -791,41 +197,54 @@ bool ShipDesign::ProductionCostTimeLocationInvariant() const { } float ShipDesign::ProductionCost(int empire_id, int location_id) const { - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION")) { + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION")) return 1.0f; - } else { - float cost_accumulator = 0.0f; - if (const HullType* hull = GetHullType(m_hull)) - cost_accumulator += hull->ProductionCost(empire_id, location_id); - for (const std::string& part_name : m_parts) - if (const PartType* part = GetPartType(part_name)) - cost_accumulator += part->ProductionCost(empire_id, location_id); - return std::max(0.0f, cost_accumulator); + + float cost_accumulator = 0.0f; + if (const ShipHull* hull = GetShipHull(m_hull)) + cost_accumulator += hull->ProductionCost(empire_id, location_id, m_id); + + int part_count = 0; + for (const std::string& part_name : m_parts) { + if (const ShipPart* part = GetShipPart(part_name)) { + cost_accumulator += part->ProductionCost(empire_id, location_id, m_id); + part_count++; + } } + + // Assuming no reasonable combination of parts and hull will add up to more + // than ARBITRARY_LARGE_COST. Truncating cost here to return it to indicate + // an uncalculable cost (ie. due to lacking a valid location object) + + return std::min(std::max(0.0f, cost_accumulator), ARBITRARY_LARGE_COST); } float ShipDesign::PerTurnCost(int empire_id, int location_id) const { return ProductionCost(empire_id, location_id) / std::max(1, ProductionTime(empire_id, location_id)); } int ShipDesign::ProductionTime(int empire_id, int location_id) const { - if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION")) { + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION")) return 1; - } else { - int time_accumulator = 1; - if (const HullType* hull = GetHullType(m_hull)) - time_accumulator = std::max(time_accumulator, hull->ProductionTime(empire_id, location_id)); - for (const std::string& part_name : m_parts) - if (const PartType* part = GetPartType(part_name)) - time_accumulator = std::max(time_accumulator, part->ProductionTime(empire_id, location_id)); - return std::max(1, time_accumulator); - } + + int time_accumulator = 1; + if (const ShipHull* hull = GetShipHull(m_hull)) + time_accumulator = std::max(time_accumulator, hull->ProductionTime(empire_id, location_id)); + + for (const std::string& part_name : m_parts) + if (const ShipPart* part = GetShipPart(part_name)) + time_accumulator = std::max(time_accumulator, part->ProductionTime(empire_id, location_id)); + + // assuming that ARBITRARY_LARGE_TURNS is larger than any reasonable turns, + // so the std::max calls will preserve it be returned + + return std::max(1, time_accumulator); } bool ShipDesign::CanColonize() const { for (const std::string& part_name : m_parts) { if (part_name.empty()) continue; - if (const PartType* part = GetPartType(part_name)) + if (const ShipPart* part = GetShipPart(part_name)) if (part->Class() == PC_COLONY) return true; } @@ -835,9 +254,9 @@ bool ShipDesign::CanColonize() const { float ShipDesign::Defense() const { // accumulate defense from defensive parts in design. float total_defense = 0.0f; - const PartTypeManager& part_manager = GetPartTypeManager(); + const ShipPartManager& part_manager = GetShipPartManager(); for (const std::string& part_name : Parts()) { - const PartType* part = part_manager.GetPartType(part_name); + const ShipPart* part = part_manager.GetShipPart(part_name); if (part && (part->Class() == PC_SHIELD || part->Class() == PC_ARMOUR)) total_defense += part->Capacity(); } @@ -858,7 +277,7 @@ float ShipDesign::AdjustedAttack(float shield) const { float direct_attack = 0.0f; for (const std::string& part_name : m_parts) { - const PartType* part = GetPartType(part_name); + const ShipPart* part = GetShipPart(part_name); if (!part) continue; ShipPartClass part_class = part->Class(); @@ -898,12 +317,12 @@ float ShipDesign::AdjustedAttack(float shield) const { std::vector ShipDesign::Parts(ShipSlotType slot_type) const { std::vector retval; - const HullType* hull = GetHull(); + const ShipHull* hull = GetShipHullManager().GetShipHull(m_hull); if (!hull) { ErrorLogger() << "Design hull not found: " << m_hull; return retval; } - const std::vector& slots = hull->Slots(); + const auto& slots = hull->Slots(); if (m_parts.empty()) return retval; @@ -919,8 +338,8 @@ std::vector ShipDesign::Parts(ShipSlotType slot_type) const { std::vector ShipDesign::Weapons() const { std::vector retval; retval.reserve(m_parts.size()); - for (const std::string& part_name : m_parts) { - const PartType* part = GetPartType(part_name); + for (const auto& part_name : m_parts) { + const ShipPart* part = GetShipPart(part_name); if (!part) continue; ShipPartClass part_class = part->Class(); @@ -930,6 +349,13 @@ std::vector ShipDesign::Weapons() const { return retval; } +int ShipDesign::PartCount() const { + int count = 0; + for (auto& entry : m_num_part_classes) + count += entry.second; + return count; +} + bool ShipDesign::ProductionLocation(int empire_id, int location_id) const { Empire* empire = GetEmpire(empire_id); if (!empire) { @@ -938,11 +364,15 @@ bool ShipDesign::ProductionLocation(int empire_id, int location_id) const { } // must own the production location... - std::shared_ptr location = GetUniverseObject(location_id); + auto location = Objects().get(location_id); + if (!location) { + WarnLogger() << "ShipDesign::ProductionLocation unable to get location object with id " << location_id; + return false; + } if (!location->OwnedBy(empire_id)) return false; - std::shared_ptr planet = std::dynamic_pointer_cast(location); + auto planet = std::dynamic_pointer_cast(location); std::shared_ptr ship; if (!planet) ship = std::dynamic_pointer_cast(location); @@ -964,13 +394,13 @@ bool ShipDesign::ProductionLocation(int empire_id, int location_id) const { return false; // apply hull location conditions to potential location - const HullType* hull = GetHull(); + const ShipHull* hull = GetShipHull(m_hull); if (!hull) { ErrorLogger() << "ShipDesign::ProductionLocation ShipDesign couldn't get its own hull with name " << m_hull; return false; } // evaluate using location as the source, as it should be an object owned by this empire. - ScriptingContext location_as_source_context(location); + ScriptingContext location_as_source_context(location, location); if (!hull->Location()->Eval(location_as_source_context, location)) return false; @@ -979,7 +409,7 @@ bool ShipDesign::ProductionLocation(int empire_id, int location_id) const { if (part_name.empty()) continue; // empty slots don't limit build location - const PartType* part = GetPartType(part_name); + const ShipPart* part = GetShipPart(part_name); if (!part) { ErrorLogger() << "ShipDesign::ProductionLocation ShipDesign couldn't get part with name " << part_name; return false; @@ -1010,16 +440,16 @@ ShipDesign::MaybeInvalidDesign(const std::string& hull_in, auto parts = parts_in; // ensure hull type exists - const HullType* input_hull_type = GetHullTypeManager().GetHullType(hull); - HullType* fallback_hull_type; - if (!input_hull_type) { + auto ship_hull = GetShipHullManager().GetShipHull(hull); + if (!ship_hull) { is_valid = false; if (produce_log) WarnLogger() << "Invalid ShipDesign hull not found: " << hull; - const auto hull_it = GetHullTypeManager().begin(); - if (hull_it != GetHullTypeManager().end()) { - std::tie(hull, fallback_hull_type) = *hull_it; + const auto hull_it = GetShipHullManager().begin(); + if (hull_it != GetShipHullManager().end()) { + hull = hull_it->first; + ship_hull = hull_it->second.get(); if (produce_log) WarnLogger() << "Invalid ShipDesign hull falling back to: " << hull; } else { @@ -1031,29 +461,26 @@ ShipDesign::MaybeInvalidDesign(const std::string& hull_in, } } - const auto hull_type = input_hull_type ? input_hull_type : fallback_hull_type; - - // ensure hull type has at least enough slots for passed parts - if (parts.size() > hull_type->NumSlots()) { + if (parts.size() > ship_hull->NumSlots()) { is_valid = false; if (produce_log) WarnLogger() << "Invalid ShipDesign given " << parts.size() << " parts for hull with " - << hull_type->NumSlots() << " slots. Truncating last " - << (parts.size() - hull_type->NumSlots()) << " parts."; + << ship_hull->NumSlots() << " slots. Truncating last " + << (parts.size() - ship_hull->NumSlots()) << " parts."; } // If parts is smaller than the full hull size pad it and the incoming parts - if (parts.size() < hull_type->NumSlots()) - parts_in.resize(hull_type->NumSlots(), ""); + if (parts.size() < ship_hull->NumSlots()) + parts_in.resize(ship_hull->NumSlots(), ""); // Truncate or pad with "" parts. - parts.resize(hull_type->NumSlots(), ""); + parts.resize(ship_hull->NumSlots(), ""); - const auto& slots = hull_type->Slots(); + const auto& slots = ship_hull->Slots(); // check hull exclusions against all parts... - const auto& hull_exclusions = hull_type->Exclusions(); + const auto& hull_exclusions = ship_hull->Exclusions(); for (auto& part_name : parts) { if (part_name.empty()) continue; @@ -1061,40 +488,44 @@ ShipDesign::MaybeInvalidDesign(const std::string& hull_in, is_valid = false; if (produce_log) WarnLogger() << "Invalid ShipDesign part \"" << part_name << "\" is excluded by \"" - << hull_type->Name() << "\". Removing \"" << part_name <<"\""; + << ship_hull->Name() << "\". Removing \"" << part_name <<"\""; part_name.clear(); } } // check part exclusions against other parts and hull - std::unordered_set already_seen_component_names; - already_seen_component_names.insert(hull); + std::unordered_map component_name_counts; + component_name_counts[hull] = 1; + for (auto part_name : parts) + component_name_counts[part_name]++; + component_name_counts.erase(""); for (std::size_t ii = 0; ii < parts.size(); ++ii) { - auto& part_name = parts[ii]; - - // Ignore empty slots which are valid. + const auto part_name = parts[ii]; + // Ignore empty slots, which are valid. if (part_name.empty()) continue; - // Remove parts that don't exist - const auto part_type = GetPartType(part_name); - if (!part_type) { + // Parts must exist... + const auto ship_part = GetShipPart(part_name); + if (!ship_part) { if (produce_log) WarnLogger() << "Invalid ShipDesign part \"" << part_name << "\" not found" << ". Removing \"" << part_name <<"\""; is_valid = false; - part_name.clear(); continue; } - for (const auto& excluded : part_type->Exclusions()) { - if (already_seen_component_names.count(excluded)) { + for (const auto& excluded : ship_part->Exclusions()) { + // confict if a different excluded part is present, or if there are + // two or more of a part that excludes itself + if ((excluded == part_name && component_name_counts[excluded] > 1) || + (excluded != part_name && component_name_counts[excluded] > 0)) + { is_valid = false; if (produce_log) WarnLogger() << "Invalid ShipDesign part " << part_name << " conflicts with \"" << excluded << "\". Removing \"" << part_name <<"\""; - part_name.clear(); continue; } } @@ -1102,18 +533,13 @@ ShipDesign::MaybeInvalidDesign(const std::string& hull_in, // verify part can mount in indicated slot const ShipSlotType& slot_type = slots[ii].type; - if (!part_type->CanMountInSlotType(slot_type)) { + if (!ship_part->CanMountInSlotType(slot_type)) { if (produce_log) DebugLogger() << "Invalid ShipDesign part \"" << part_name << "\" can't be mounted in " - << boost::lexical_cast(slot_type) << " slot" - << ". Removing \"" << part_name <<"\""; + << slot_type << " slot. Removing \"" << part_name <<"\""; is_valid = false; - part_name.clear(); continue; } - - if (!part_name.empty()) - already_seen_component_names.insert(part_name); } if (is_valid) @@ -1156,7 +582,7 @@ void ShipDesign::ForceValidDesignOrThrow(const boost::optionalStructure(); m_speed = hull->Speed(); + bool has_fighter_bays = false; + bool has_fighter_hangars = false; + bool has_armed_fighters = false; + bool can_launch_fighters = false; + for (const std::string& part_name : m_parts) { if (part_name.empty()) continue; - const PartType* part = GetPartType(part_name); + const ShipPart* part = GetShipPart(part_name); if (!part) { ErrorLogger() << "ShipDesign::BuildStatCaches couldn't get part with name " << part_name; continue; @@ -1189,11 +620,19 @@ void ShipDesign::BuildStatCaches() { switch (part_class) { case PC_DIRECT_WEAPON: - m_is_armed = true; + m_has_direct_weapons = true; + if (part->Capacity() > 0.0f) + m_is_armed = true; break; case PC_FIGHTER_BAY: + has_fighter_bays = true; + if (part->Capacity() >= 1.0f) + can_launch_fighters = true; + break; case PC_FIGHTER_HANGAR: - m_has_fighters = true; + has_fighter_hangars = true; + if (part->SecondaryStat() > 0.0f && part->Capacity() >= 1.0f) + has_armed_fighters = true; break; case PC_COLONY: m_colony_capacity += part->Capacity(); @@ -1237,42 +676,40 @@ void ShipDesign::BuildStatCaches() { default: break; } + m_has_fighters = has_fighter_bays && has_fighter_hangars; + m_is_armed = m_is_armed || (can_launch_fighters && has_armed_fighters); - m_num_part_types[part_name]++; + m_num_ship_parts[part_name]++; if (part_class > INVALID_SHIP_PART_CLASS && part_class < NUM_SHIP_PART_CLASSES) m_num_part_classes[part_class]++; } } -std::string ShipDesign::Dump() const { - std::string retval = DumpIndent() + "ShipDesign\n"; - ++g_indent; - retval += DumpIndent() + "name = \"" + m_name + "\"\n"; - retval += DumpIndent() + "uuid = \"" + boost::uuids::to_string(m_uuid) + "\"\n"; - retval += DumpIndent() + "description = \"" + m_description + "\"\n"; +std::string ShipDesign::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "ShipDesign\n"; + retval += DumpIndent(ntabs+1) + "name = \"" + m_name + "\"\n"; + retval += DumpIndent(ntabs+1) + "uuid = \"" + boost::uuids::to_string(m_uuid) + "\"\n"; + retval += DumpIndent(ntabs+1) + "description = \"" + m_description + "\"\n"; if (!m_name_desc_in_stringtable) - retval += DumpIndent() + "NoStringtableLookup\n"; - retval += DumpIndent() + "hull = \"" + m_hull + "\"\n"; - retval += DumpIndent() + "parts = "; + retval += DumpIndent(ntabs+1) + "NoStringtableLookup\n"; + retval += DumpIndent(ntabs+1) + "hull = \"" + m_hull + "\"\n"; + retval += DumpIndent(ntabs+1) + "parts = "; if (m_parts.empty()) { retval += "[]\n"; } else if (m_parts.size() == 1) { retval += "\"" + *m_parts.begin() + "\"\n"; } else { retval += "[\n"; - ++g_indent; for (const std::string& part_name : m_parts) { - retval += DumpIndent() + "\"" + part_name + "\"\n"; + retval += DumpIndent(ntabs+2) + "\"" + part_name + "\"\n"; } - --g_indent; - retval += DumpIndent() + "]\n"; + retval += DumpIndent(ntabs+1) + "]\n"; } if (!m_icon.empty()) - retval += DumpIndent() + "icon = \"" + m_icon + "\"\n"; - retval += DumpIndent() + "model = \"" + m_3D_model + "\"\n"; - --g_indent; - return retval; + retval += DumpIndent(ntabs+1) + "icon = \"" + m_icon + "\"\n"; + retval += DumpIndent(ntabs+1) + "model = \"" + m_3D_model + "\"\n"; + return retval; } unsigned int ShipDesign::GetCheckSum() const { @@ -1300,6 +737,7 @@ bool operator ==(const ShipDesign& first, const ShipDesign& second) { std::map first_parts; std::map second_parts; + // don't care if order is different, as long as the types and numbers of parts is the same for (const std::string& part_name : first.Parts()) { ++first_parts[part_name]; } @@ -1309,74 +747,18 @@ bool operator ==(const ShipDesign& first, const ShipDesign& second) { return first_parts == second_parts; } - ///////////////////////////////////// // PredefinedShipDesignManager // ///////////////////////////////////// // static(s) PredefinedShipDesignManager* PredefinedShipDesignManager::s_instance = nullptr; -namespace { - template - void FillDesignsOrderingAndNameTables(const boost::filesystem::path& path, - Map1& designs, Ordering& ordering, Map2& name_to_uuid) - { - name_to_uuid.clear(); - - auto inconsistent_and_map_and_order_ships = - LoadShipDesignsAndManifestOrderFromFileSystem(path); - - ordering = std::get<2>(inconsistent_and_map_and_order_ships); - - auto& disk_designs = std::get<1>(inconsistent_and_map_and_order_ships); - - for (auto& uuid_and_design : disk_designs) { - auto& design = uuid_and_design.second.first; - - if (designs.count(design->UUID())) { - ErrorLogger() << design->Name() << " ship design does not have a unique UUID for " - << "its type monster or pre-defined. " - << designs[design->UUID()]->Name() << " has the same UUID."; - continue; - } - - if (name_to_uuid.count(design->Name())) { - ErrorLogger() << design->Name() << " ship design does not have a unique name for " - << "its type monster or pre-defined."; - continue; - } - - name_to_uuid.insert(std::make_pair(design->Name(), design->UUID())); - designs[design->UUID()] = std::move(design); - } - } -} - PredefinedShipDesignManager::PredefinedShipDesignManager() { if (s_instance) throw std::runtime_error("Attempted to create more than one PredefinedShipDesignManager."); - DebugLogger() << "Initializing PredefinedShipDesignManager"; - - m_designs.clear(); - - FillDesignsOrderingAndNameTables( - "scripting/ship_designs", m_designs, m_ship_ordering, m_name_to_ship_design); - FillDesignsOrderingAndNameTables( - "scripting/monster_designs", m_designs, m_monster_ordering, m_name_to_monster_design); - - // Make the monsters monstrous - for (const auto& uuid : m_monster_ordering) - m_designs[uuid]->SetMonster(true); - - TraceLogger() << "Predefined Ship Designs:"; - for (const auto& entry : m_designs) - TraceLogger() << " ... " << entry.second->Name(); - // Only update the global pointer on sucessful construction. s_instance = this; - - DebugLogger() << "PredefinedShipDesignManager checksum: " << GetCheckSum(); } namespace { @@ -1388,7 +770,7 @@ namespace { Universe& universe = GetUniverse(); /* check if there already exists this same design in the universe. */ - for (Universe::ship_design_iterator it = universe.beginShipDesigns(); + for (auto it = universe.beginShipDesigns(); it != universe.endShipDesigns(); ++it) { const ShipDesign* existing_design = it->second; @@ -1423,6 +805,7 @@ namespace { } void PredefinedShipDesignManager::AddShipDesignsToUniverse() const { + CheckPendingDesignsTypes(); m_design_generic_ids.clear(); for (const auto& uuid : m_ship_ordering) @@ -1439,6 +822,7 @@ PredefinedShipDesignManager& PredefinedShipDesignManager::GetPredefinedShipDesig std::vector PredefinedShipDesignManager::GetOrderedShipDesigns() const { + CheckPendingDesignsTypes(); std::vector retval; for (const auto& uuid : m_ship_ordering) retval.push_back(m_designs.at(uuid).get()); @@ -1446,6 +830,7 @@ std::vector PredefinedShipDesignManager::GetOrderedShipDesign } std::vector PredefinedShipDesignManager::GetOrderedMonsterDesigns() const { + CheckPendingDesignsTypes(); std::vector retval; for (const auto& uuid : m_monster_ordering) retval.push_back(m_designs.at(uuid).get()); @@ -1453,6 +838,7 @@ std::vector PredefinedShipDesignManager::GetOrderedMonsterDes } int PredefinedShipDesignManager::GetDesignID(const std::string& name) const { + CheckPendingDesignsTypes(); const auto& it = m_design_generic_ids.find(name); if (it == m_design_generic_ids.end()) return INVALID_DESIGN_ID; @@ -1460,13 +846,14 @@ int PredefinedShipDesignManager::GetDesignID(const std::string& name) const { } unsigned int PredefinedShipDesignManager::GetCheckSum() const { + CheckPendingDesignsTypes(); unsigned int retval{0}; auto build_checksum = [&retval, this](const std::vector& ordering){ for (auto const& uuid : ordering) { auto it = m_designs.find(uuid); if (it != m_designs.end()) - CheckSums::CheckSumCombine(retval, std::make_pair(it->second->Name(), *it->second)); + CheckSums::CheckSumCombine(retval, std::make_pair(it->second->Name(false), *it->second)); } CheckSums::CheckSumCombine(retval, ordering.size()); }; @@ -1474,14 +861,102 @@ unsigned int PredefinedShipDesignManager::GetCheckSum() const { build_checksum(m_ship_ordering); build_checksum(m_monster_ordering); + DebugLogger() << "PredefinedShipDesignManager checksum: " << retval; return retval; } +void PredefinedShipDesignManager::SetShipDesignTypes( + Pending::Pending&& pending_designs) +{ m_pending_designs = std::move(pending_designs); } + +void PredefinedShipDesignManager::SetMonsterDesignTypes( + Pending::Pending&& pending_designs) +{ m_pending_monsters = std::move(pending_designs); } + +namespace { + template + void FillDesignsOrderingAndNameTables( + PredefinedShipDesignManager::ParsedShipDesignsType& parsed_designs, + Map1& designs, Ordering& ordering, Map2& name_to_uuid) + { + // Remove the old designs + for (const auto& name_and_uuid: name_to_uuid) + designs.erase(name_and_uuid.second); + name_to_uuid.clear(); + + auto inconsistent_and_map_and_order_ships = + LoadShipDesignsAndManifestOrderFromParseResults(parsed_designs); + + ordering = std::get<2>(inconsistent_and_map_and_order_ships); + + auto& disk_designs = std::get<1>(inconsistent_and_map_and_order_ships); + + for (auto& uuid_and_design : disk_designs) { + auto& design = uuid_and_design.second.first; + + if (designs.count(design->UUID())) { + ErrorLogger() << design->Name() << " ship design does not have a unique UUID for " + << "its type monster or pre-defined. " + << designs[design->UUID()]->Name() << " has the same UUID."; + continue; + } + + if (name_to_uuid.count(design->Name())) { + ErrorLogger() << design->Name() << " ship design does not have a unique name for " + << "its type monster or pre-defined."; + continue; + } + + name_to_uuid.insert({design->Name(), design->UUID()}); + designs[design->UUID()] = std::move(design); + } + } + + template + void CheckPendingAndFillDesignsOrderingAndNameTables( + PendingShips& pending, Map1& designs, Ordering& ordering, Map2& name_to_uuid, bool are_monsters) + { + if (!pending) + return; + + auto parsed = Pending::WaitForPending(pending); + if (!parsed) + return; + + DebugLogger() << "Populating pre-defined ships with " + << std::string(are_monsters ? "monster" : "ship") << " designs."; + + FillDesignsOrderingAndNameTables( + *parsed, designs, ordering, name_to_uuid); + + // Make the monsters monstrous + if (are_monsters) + for (const auto& uuid : ordering) + designs[uuid]->SetMonster(true); + + TraceLogger() << [&designs, name_to_uuid]() { + std::stringstream ss; + ss << "Predefined Ship Designs:"; + for (const auto& entry : name_to_uuid) + ss << " ... " << designs[entry.second]->Name(); + return ss.str(); + }(); + } +} + +void PredefinedShipDesignManager::CheckPendingDesignsTypes() const { + CheckPendingAndFillDesignsOrderingAndNameTables( + m_pending_designs, m_designs, m_ship_ordering, m_name_to_ship_design, false); + + CheckPendingAndFillDesignsOrderingAndNameTables( + m_pending_monsters, m_designs, m_monster_ordering, m_name_to_monster_design, true); + } + /////////////////////////////////////////////////////////// // Free Functions // /////////////////////////////////////////////////////////// -const PredefinedShipDesignManager& GetPredefinedShipDesignManager() +PredefinedShipDesignManager& GetPredefinedShipDesignManager() { return PredefinedShipDesignManager::GetPredefinedShipDesignManager(); } const ShipDesign* GetPredefinedShipDesign(const std::string& name) @@ -1493,18 +968,19 @@ std::tuple< std::pair, boost::filesystem::path>, boost::hash>, std::vector> -LoadShipDesignsAndManifestOrderFromFileSystem(const boost::filesystem::path& dir) { +LoadShipDesignsAndManifestOrderFromParseResults( + PredefinedShipDesignManager::ParsedShipDesignsType& designs_paths_and_ordering) +{ std::unordered_map, boost::filesystem::path>, boost::hash> saved_designs; - std::vector disk_ordering; - std::vector, boost::filesystem::path>> designs_and_paths; - parse::ship_designs(dir, designs_and_paths, disk_ordering); + auto& designs_and_paths = designs_paths_and_ordering.first; + auto& disk_ordering = designs_paths_and_ordering.second; for (auto&& design_and_path : designs_and_paths) { - auto& design = design_and_path.first; + auto design = std::make_unique(*design_and_path.first); // If the UUID is nil this is a legacy design that needs a new UUID if(design->UUID() == boost::uuids::uuid{{0}}) { @@ -1524,12 +1000,12 @@ LoadShipDesignsAndManifestOrderFromFileSystem(const boost::filesystem::path& dir if (!saved_designs.count(design->UUID())) { TraceLogger() << "Added saved design UUID " << design->UUID() << " with name " << design->Name(); - saved_designs[design->UUID()] = std::move(design_and_path); + auto uuid = design->UUID(); + saved_designs[uuid] = std::make_pair(std::move(design), design_and_path.second); } else { WarnLogger() << "Duplicate ship design UUID " << design->UUID() << " found for ship design " << design->Name() - << " and " << saved_designs[design->UUID()].first->Name() - << " in " << dir; + << " and " << saved_designs[design->UUID()].first->Name(); } } diff --git a/universe/ShipDesign.h b/universe/ShipDesign.h index 53b19e4dbdb..685ea62e624 100644 --- a/universe/ShipDesign.h +++ b/universe/ShipDesign.h @@ -2,458 +2,47 @@ #define _ShipDesign_h_ -#include "ValueRefFwd.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include #include #include -#include #include -#include -#include #include - +#include #include "EnumsFwd.h" +#include "../util/Pending.h" -#include "../util/Export.h" FO_COMMON_API extern const int INVALID_OBJECT_ID; -namespace Condition { - struct ConditionBase; -} -namespace Effect { - class EffectsGroup; -} -class Empire; - -/** Common parameters for PartType and HullType constructors. Used as temporary - * storage for parsing to reduce number of sub-items parsed per item. */ -struct CommonParams { - CommonParams() : - production_cost(nullptr), - production_time(nullptr), - producible(false), - tags(), - production_meter_consumption(), - production_special_consumption(), - location(nullptr), - enqueue_location(nullptr), - effects() - {} - CommonParams(ValueRef::ValueRefBase* production_cost_, - ValueRef::ValueRefBase* production_time_, - bool producible_, - const std::set& tags_, - Condition::ConditionBase* location_, - const std::vector>& effects_, - std::map*, Condition::ConditionBase*>> production_meter_consumption_, - std::map*, Condition::ConditionBase*>> production_special_consumption_, - Condition::ConditionBase* enqueue_location_) : - production_cost(production_cost_), - production_time(production_time_), - producible(producible_), - tags(), - production_meter_consumption(production_meter_consumption_), - production_special_consumption(production_special_consumption_), - location(location_), - enqueue_location(enqueue_location_), - effects(effects_) - { - for (const std::string& tag : tags_) - tags.insert(boost::to_upper_copy(tag)); - } - - ValueRef::ValueRefBase* production_cost; - ValueRef::ValueRefBase* production_time; - bool producible; - std::set tags; - std::map*, Condition::ConditionBase*>> - production_meter_consumption; - std::map*, Condition::ConditionBase*>> - production_special_consumption; - Condition::ConditionBase* location; - Condition::ConditionBase* enqueue_location; - std::vector> effects; -}; - -struct MoreCommonParams { - MoreCommonParams() : - name(), - description(), - exclusions() - {} - MoreCommonParams(const std::string& name_, const std::string& description_, - const std::set& exclusions_) : - name(name_), - description(description_), - exclusions(exclusions_) - {} - std::string name; - std::string description; - std::set exclusions; -}; - -/** A type of ship part */ -class FO_COMMON_API PartType { -public: - /** \name Structors */ //@{ - PartType(); - PartType(ShipPartClass part_class, double capacity, double stat2, - const CommonParams& common_params, const MoreCommonParams& more_common_params, - std::vector mountable_slot_types, - const std::string& icon, bool add_standard_capacity_effect = true); - - ~PartType(); - //@} - - /** \name Accessors */ //@{ - const std::string& Name() const { return m_name; }; ///< returns name of part - const std::string& Description() const { return m_description; } ///< returns description string, generally a UserString key. - ShipPartClass Class() const { return m_class; } ///< returns that class of part that this is. - float Capacity() const; - std::string CapacityDescription() const; ///< returns a translated description of the part capacity, with numeric value - float SecondaryStat() const; - - bool CanMountInSlotType(ShipSlotType slot_type) const; ///< returns true if this part can be placed in a slot of the indicated type - const std::vector& - MountableSlotTypes() const { return m_mountable_slot_types; } - - bool ProductionCostTimeLocationInvariant() const; ///< returns true if the production cost and time are invariant (does not depend on) the location - float ProductionCost(int empire_id, int location_id) const; ///< returns the number of production points required to produce this part - int ProductionTime(int empire_id, int location_id) const; ///< returns the number of turns required to produce this part - bool Producible() const { return m_producible; } ///< returns whether this part type is producible by players and appears on the design screen - - const std::map*, Condition::ConditionBase*>>& - ProductionMeterConsumption() const { return m_production_meter_consumption; } - const std::map*, Condition::ConditionBase*>>& - ProductionSpecialConsumption() const{ return m_production_special_consumption; } - - const std::set& Tags() const { return m_tags; } - const Condition::ConditionBase* Location() const{ return m_location; } ///< returns the condition that determines the locations where ShipDesign containing part can be produced - const std::set& Exclusions() const { return m_exclusions; } ///< returns the names of other content that cannot be used in the same ship design as this part - - /** Returns the EffectsGroups that encapsulate the effects this part has. */ - const std::vector>& Effects() const - { return m_effects; } - - const std::string& Icon() const { return m_icon; } ///< returns icon graphic that represents part in UI - - /** Returns a number, calculated from the contained data, which should be - * different for different contained data, and must be the same for - * the same contained data, and must be the same on different platforms - * and executions of the program and the function. Useful to verify that - * the parsed content is consistent without sending it all between - * clients and server. */ - unsigned int GetCheckSum() const; - //@} - -private: - void Init(const std::vector>& effects); - - std::string m_name; - std::string m_description; - ShipPartClass m_class; - float m_capacity; - float m_secondary_stat; // damage for a hangar bay, shots per turn for a weapon, etc. - ValueRef::ValueRefBase* m_production_cost; - ValueRef::ValueRefBase* m_production_time; - bool m_producible; - std::vector m_mountable_slot_types; - std::set m_tags; - std::map*, Condition::ConditionBase*>> - m_production_meter_consumption; - std::map*, Condition::ConditionBase*>> - m_production_special_consumption; - Condition::ConditionBase* m_location; - std::set m_exclusions; - std::vector> m_effects; - std::string m_icon; - bool m_add_standard_capacity_effect; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Holds FreeOrion ship part types */ -class FO_COMMON_API PartTypeManager { -public: - typedef std::map::const_iterator iterator; - - /** \name Accessors */ //@{ - /** returns the part type with the name \a name; you should use the free function GetPartType() instead */ - const PartType* GetPartType(const std::string& name) const; - - /** iterator to the first part type */ - iterator begin() const; - - /** iterator to the last + 1th part type */ - iterator end() const; - - /** returns the instance of this singleton class; you should use the free function GetPartTypeManager() instead */ - static const PartTypeManager& GetPartTypeManager(); - - /** Returns a number, calculated from the contained data, which should be - * different for different contained data, and must be the same for - * the same contained data, and must be the same on different platforms - * and executions of the program and the function. Useful to verify that - * the parsed content is consistent without sending it all between - * clients and server. */ - unsigned int GetCheckSum() const; - //@} - -private: - PartTypeManager(); - ~PartTypeManager(); - - std::map m_parts; - static PartTypeManager* s_instance; -}; - - -/** returns the singleton part type manager */ -FO_COMMON_API const PartTypeManager& GetPartTypeManager(); - -/** Returns the ship PartType specification object with name \a name. If no - * such PartType exists, 0 is returned instead. */ -FO_COMMON_API const PartType* GetPartType(const std::string& name); - -/** Hull stats. Used by parser due to limits on number of sub-items per - * parsed main item. */ -struct HullTypeStats { - HullTypeStats() : - fuel(0.0f), - speed(0.0f), - stealth(0.0f), - structure(0.0f) - {} - - HullTypeStats(float fuel_, - float speed_, - float stealth_, - float structure_) : - fuel(fuel_), - speed(speed_), - stealth(stealth_), - structure(structure_) - {} - - float fuel; - float speed; - float stealth; - float structure; - - template - void serialize(Archive& ar, const unsigned int) { - ar & BOOST_SERIALIZATION_NVP(fuel) - & BOOST_SERIALIZATION_NVP(speed) - & BOOST_SERIALIZATION_NVP(stealth) - & BOOST_SERIALIZATION_NVP(structure); - } -}; - -/** Specification for the hull, or base, on which ship designs are created by - * adding parts. The hull determines some final design characteristics - * directly, and also determine how many parts can be added to the design. */ -class FO_COMMON_API HullType { -public: - struct Slot { - Slot(); - Slot(ShipSlotType slot_type, double x_, double y_) : - type(slot_type), x(x_), y(y_) - {} - ShipSlotType type; - double x, y; - }; - - /** \name Structors */ //@{ - HullType() : - m_name("generic hull type"), - m_description("indescribable"), - m_speed(1.0f), - m_fuel(0.0f), - m_stealth(0.0f), - m_structure(0.0f), - m_production_cost(nullptr), - m_production_time(nullptr), - m_producible(false), - m_slots(), - m_tags(), - m_production_meter_consumption(), - m_production_special_consumption(), - m_location(nullptr), - m_effects(), - m_graphic(), - m_icon() - {} - - HullType(const HullTypeStats& stats, const CommonParams& common_params, - const MoreCommonParams& more_common_params, - const std::vector& slots, - const std::string& icon, const std::string& graphic) : - m_name(more_common_params.name), - m_description(more_common_params.description), - m_speed(stats.speed), - m_fuel(stats.fuel), - m_stealth(stats.stealth), - m_structure(stats.structure), - m_production_cost(common_params.production_cost), - m_production_time(common_params.production_time), - m_producible(common_params.producible), - m_slots(slots), - m_tags(), - m_production_meter_consumption(common_params.production_meter_consumption), - m_production_special_consumption(common_params.production_special_consumption), - m_location(common_params.location), - m_exclusions(more_common_params.exclusions), - m_effects(), - m_graphic(graphic), - m_icon(icon) - { - //TraceLogger() << "hull type: " << m_name << " producible: " << m_producible << std::endl; - Init(common_params.effects); - for (const std::string& tag : common_params.tags) - m_tags.insert(boost::to_upper_copy(tag)); - } - - ~HullType(); - //@} +FO_COMMON_API extern const int INVALID_DESIGN_ID; +FO_COMMON_API extern const int ALL_EMPIRES; +FO_COMMON_API extern const int INVALID_GAME_TURN; + +/** ParsedShipDesign holds the results of a parsed ship design which can be + converted to a ShipDesign. */ +struct FO_COMMON_API ParsedShipDesign { + ParsedShipDesign(const std::string& name, const std::string& description, + int designed_on_turn, int designed_by_empire, const std::string& hull, + const std::vector& parts, + const std::string& icon, const std::string& model, + bool name_desc_in_stringtable = false, bool monster = false, + const boost::uuids::uuid& uuid = boost::uuids::nil_uuid()); - /** \name Accessors */ //@{ - const std::string& Name() const { return m_name; } ///< returns name of hull - const std::string& Description() const { return m_description; } ///< returns description, including a description of the stats and effects of this hull - - float Speed() const; ///< returns starlane speed of hull - float Fuel() const { return m_fuel; } ///< returns fuel capacity of hull - float Stealth() const { return m_stealth; } ///< returns stealth of hull - float Structure() const; ///< returns structure of hull - float Shields() const { return 0.0f; } ///< returns shields of hull - float ColonyCapacity() const { return 0.0f; } ///< returns colonist capacity of hull - float TroopCapacity() const { return 0.0f; } ///< returns the troop capacity of hull - float Detection() const { return 0.0f; } ///< returns detection ability of hull - - bool ProductionCostTimeLocationInvariant() const; ///< returns true if the production cost and time are invariant (does not depend on) the location - float ProductionCost(int empire_id, int location_id) const; ///< returns the number of production points required to produce this hull - int ProductionTime(int empire_id, int location_id) const; ///< returns the number of turns required to produce this hull - bool Producible() const { return m_producible; } ///< returns whether this hull type is producible by players and appears on the design screen - - const std::map*, Condition::ConditionBase*>>& - ProductionMeterConsumption() const { return m_production_meter_consumption; } - const std::map*, Condition::ConditionBase*>>& - ProductionSpecialConsumption() const{ return m_production_special_consumption; } - - unsigned int NumSlots() const { return m_slots.size(); } ///< returns total number of of slots in hull - unsigned int NumSlots(ShipSlotType slot_type) const; ///< returns number of of slots of indicated type in hull - const std::vector& Slots() const { return m_slots; } ///< returns vector of slots in hull - - const std::set& Tags() const { return m_tags; } - - bool HasTag(const std::string& tag) const { return m_tags.count(tag) != 0; } - - const Condition::ConditionBase* Location() const{ return m_location; } ///< returns the condition that determines the locations where ShipDesign containing hull can be produced - const std::set& Exclusions() const { return m_exclusions; } ///< returns the names of other content that cannot be used in the same ship design as this part - - /** Returns the EffectsGroups that encapsulate the effects this part hull - has. */ - const std::vector>& Effects() const - { return m_effects; } - - const std::string& Graphic() const { return m_graphic; } ///< returns the image that represents the hull on the design screen - const std::string& Icon() const { return m_icon; } ///< returns the small icon to represent hull - - /** Returns a number, calculated from the contained data, which should be - * different for different contained data, and must be the same for - * the same contained data, and must be the same on different platforms - * and executions of the program and the function. Useful to verify that - * the parsed content is consistent without sending it all between - * clients and server. */ - unsigned int GetCheckSum() const; - //@} - -private: - void Init(const std::vector>& effects); - - std::string m_name; - std::string m_description; - float m_speed; - float m_fuel; - float m_stealth; - float m_structure; - ValueRef::ValueRefBase* m_production_cost; - ValueRef::ValueRefBase* m_production_time; - bool m_producible; - std::vector m_slots; - std::set m_tags; - std::map*, Condition::ConditionBase*>> - m_production_meter_consumption; - std::map*, Condition::ConditionBase*>> - m_production_special_consumption; - Condition::ConditionBase* m_location; - std::set m_exclusions; - std::vector> m_effects; - std::string m_graphic; - std::string m_icon; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -namespace CheckSums { - FO_COMMON_API void CheckSumCombine(unsigned int& sum, const HullType::Slot& slot); -} - -/** Holds FreeOrion hull types */ -class FO_COMMON_API HullTypeManager { -public: - typedef std::map::const_iterator iterator; - - /** \name Accessors */ //@{ - /** returns the hull type with the name \a name; you should use the free function GetHullType() instead */ - const HullType* GetHullType(const std::string& name) const; - - /** iterator to the first hull type */ - iterator begin() const; - - /** iterator to the last + 1th hull type */ - iterator end() const; + std::string m_name; + std::string m_description; + boost::uuids::uuid m_uuid; - /** returns the instance of this singleton class; you should use the free function GetHullTypeManager() instead */ - static const HullTypeManager& GetHullTypeManager(); + int m_designed_on_turn = INVALID_GAME_TURN; + int m_designed_by_empire = ALL_EMPIRES; - /** Returns a number, calculated from the contained data, which should be - * different for different contained data, and must be the same for - * the same contained data, and must be the same on different platforms - * and executions of the program and the function. Useful to verify that - * the parsed content is consistent without sending it all between - * clients and server. */ - unsigned int GetCheckSum() const; - //@} + std::string m_hull; + std::vector m_parts; + bool m_is_monster = false; -private: - HullTypeManager(); - ~HullTypeManager(); + std::string m_icon; + std::string m_3D_model; - std::map m_hulls; - static HullTypeManager* s_instance; + bool m_name_desc_in_stringtable = false; }; -/** returns the singleton hull type manager */ -FO_COMMON_API const HullTypeManager& GetHullTypeManager(); - -/** Returns the ship HullType specification object with name \a name. If no such HullType exists, - * 0 is returned instead. */ -FO_COMMON_API const HullType* GetHullType(const std::string& name); - class FO_COMMON_API ShipDesign { public: /** \name Structors */ //@{ @@ -463,7 +52,7 @@ class FO_COMMON_API ShipDesign { ShipDesign(); public: /** The public ShipDesign constructor will only construct valid ship - designs, as long as the HullTypeManager has at least one hull. + designs, as long as the ShipHullManager has at least one hull. If \p should_throw is not boost::none and the passed in parameters (\p hull and \p parts) would result in an invalid design it generates an @@ -487,18 +76,10 @@ class FO_COMMON_API ShipDesign { const std::vector& parts, const std::string& icon, const std::string& model, bool name_desc_in_stringtable = false, bool monster = false, - const boost::uuids::uuid& uuid = boost::uuids::nil_uuid() - ); + const boost::uuids::uuid& uuid = boost::uuids::nil_uuid()); - /** The public ShipDesign constructor will only construct valid ship - designs, as long as the HullTypeManager has at least one hull. */ - ShipDesign(const std::string& name, const std::string& description, - int designed_on_turn, int designed_by_empire, const std::string& hull, - const std::vector& parts, - const std::string& icon, const std::string& model, - bool name_desc_in_stringtable = false, bool monster = false, - const boost::uuids::uuid& uuid = boost::uuids::nil_uuid() - ); + /** Convert a parsed ship design and do any required verification. */ + ShipDesign(const ParsedShipDesign& design); //@} /** \name Accessors */ //@{ @@ -548,6 +129,7 @@ class FO_COMMON_API ShipDesign { bool HasTroops() const { return (m_troop_capacity > 0.0f); } bool CanBombard() const { return m_can_bombard; } bool IsArmed() const { return m_is_armed; } + bool HasDirectWeapons() const{ return m_has_direct_weapons; } bool HasFighters() const { return m_has_fighters; } bool IsMonster() const { return m_is_monster; } @@ -556,8 +138,6 @@ class FO_COMMON_API ShipDesign { float Defense() const; const std::string& Hull() const { return m_hull; } ///< returns name of hull on which design is based - const HullType* GetHull() const - { return GetHullTypeManager().GetHullType(m_hull); } ///< returns HullType on which design is based const std::vector& Parts() const { return m_parts; } ///< returns vector of names of all parts in this design, with position in vector corresponding to slot positions std::vector Parts(ShipSlotType slot_type) const; ///< returns vector of names of parts in slots of indicated type in this design, unrelated to slot positions @@ -567,13 +147,14 @@ class FO_COMMON_API ShipDesign { const std::string& Model() const { return m_3D_model; } ///< returns filename of 3D model that represents ships of design bool LookupInStringtable() const { return m_name_desc_in_stringtable; } - /** returns number of parts in this ship design, indexed by PartType name */ - const std::map& PartTypeCount() const { return m_num_part_types; } + //! Returns number of parts in this ship design, indexed by ShipPart name + const std::map& ShipPartCount() const { return m_num_ship_parts; } + int PartCount() const; /** returns number of parts in this ship design, indexed by ShipPartClass */ const std::map& PartClassCount() const { return m_num_part_classes; } - std::string Dump() const; ///< returns a data file format representation of this object + std::string Dump(unsigned short ntabs = 0) const; ///< returns a data file format representation of this object /** Returns a number, calculated from the contained data, which should be * different for different contained data, and must be the same for @@ -624,22 +205,23 @@ class FO_COMMON_API ShipDesign { std::string m_description; boost::uuids::uuid m_uuid; - int m_designed_on_turn; - int m_designed_by_empire; + int m_designed_on_turn = INVALID_GAME_TURN; + int m_designed_by_empire = ALL_EMPIRES; std::string m_hull; std::vector m_parts; - bool m_is_monster; + bool m_is_monster = false; std::string m_icon; std::string m_3D_model; - bool m_name_desc_in_stringtable; + bool m_name_desc_in_stringtable = false; // Note that these are fine to compute on demand and cache here -- it is // not necessary to serialize them. - bool m_is_armed = false; + bool m_has_direct_weapons = false; bool m_has_fighters = false; + bool m_is_armed = false; bool m_can_bombard = false; float m_detection = 0.0f; float m_colony_capacity = 0.0f; @@ -653,12 +235,12 @@ class FO_COMMON_API ShipDesign { float m_industry_generation = 0.0f; float m_trade_generation = 0.0f; bool m_is_production_location = false; - std::map m_num_part_types; + std::map m_num_ship_parts; std::map m_num_part_classes; bool m_producible = false; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -670,9 +252,13 @@ FO_COMMON_API bool operator ==(const ShipDesign& first, const ShipDesign& second * or isn't know to this client), 0 is returned instead. */ FO_COMMON_API const ShipDesign* GetShipDesign(int ship_design_id); - class FO_COMMON_API PredefinedShipDesignManager { public: + using ParsedShipDesignsType = std::pair< + std::vector, boost::filesystem::path>>, // designs_and_paths, + std::vector // ordering + >; + /** Return pointers the ShipDesigns in order.*/ std::vector GetOrderedShipDesigns() const; @@ -701,83 +287,56 @@ class FO_COMMON_API PredefinedShipDesignManager { * ship design exists, 0 is returned instead. */ static PredefinedShipDesignManager& GetPredefinedShipDesignManager(); + /** Sets ship design types to the future value of \p pending_designs + found in \p subdir. */ + FO_COMMON_API void SetShipDesignTypes(Pending::Pending&& pending_designs); + + /** Sets monster design types to the future value of \p + pending_design_types found in \p subdir. */ + FO_COMMON_API void SetMonsterDesignTypes(Pending::Pending&& pending_designs); + private: PredefinedShipDesignManager(); - std::unordered_map, boost::hash> m_designs; + /** Assigns any m_pending_designs. */ + void CheckPendingDesignsTypes() const; + + /** Future ship design type being parsed by parser. mutable so that it can + be assigned to m_ship design_types when completed.*/ + mutable boost::optional> m_pending_designs = boost::none; + mutable boost::optional> m_pending_monsters = boost::none; + + mutable std::unordered_map, + boost::hash> m_designs; - std::unordered_map m_name_to_ship_design; - std::unordered_map m_name_to_monster_design; - mutable std::unordered_map m_design_generic_ids; // ids of designs from this manager that have been added to the universe with no empire as the creator + mutable std::unordered_map m_name_to_ship_design; + mutable std::unordered_map m_name_to_monster_design; + // ids of designs from this manager that have been added to the universe with no empire as the creator + mutable std::unordered_map m_design_generic_ids; - std::vector m_ship_ordering; - std::vector m_monster_ordering; + mutable std::vector m_ship_ordering; + mutable std::vector m_monster_ordering; static PredefinedShipDesignManager* s_instance; }; /** returns the singleton predefined ship design manager type manager */ -const FO_COMMON_API PredefinedShipDesignManager& GetPredefinedShipDesignManager(); +FO_COMMON_API PredefinedShipDesignManager& GetPredefinedShipDesignManager(); /** Returns the predefined ShipDesign with the name \a name. If no such * ship design exists, 0 is returned instead. */ FO_COMMON_API const ShipDesign* GetPredefinedShipDesign(const std::string& name); -// template implementations -template -void PartType::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_description) - & BOOST_SERIALIZATION_NVP(m_class) - & BOOST_SERIALIZATION_NVP(m_capacity) - & BOOST_SERIALIZATION_NVP(m_secondary_stat) - & BOOST_SERIALIZATION_NVP(m_production_cost) - & BOOST_SERIALIZATION_NVP(m_production_time) - & BOOST_SERIALIZATION_NVP(m_producible) - & BOOST_SERIALIZATION_NVP(m_mountable_slot_types) - & BOOST_SERIALIZATION_NVP(m_tags) - & BOOST_SERIALIZATION_NVP(m_production_meter_consumption) - & BOOST_SERIALIZATION_NVP(m_production_special_consumption) - & BOOST_SERIALIZATION_NVP(m_location) - & BOOST_SERIALIZATION_NVP(m_exclusions) - & BOOST_SERIALIZATION_NVP(m_effects) - & BOOST_SERIALIZATION_NVP(m_icon) - & BOOST_SERIALIZATION_NVP(m_add_standard_capacity_effect); -} - -template -void HullType::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_NVP(m_name) - & BOOST_SERIALIZATION_NVP(m_description) - & BOOST_SERIALIZATION_NVP(m_speed) - & BOOST_SERIALIZATION_NVP(m_fuel) - & BOOST_SERIALIZATION_NVP(m_stealth) - & BOOST_SERIALIZATION_NVP(m_structure) - & BOOST_SERIALIZATION_NVP(m_production_cost) - & BOOST_SERIALIZATION_NVP(m_production_time) - & BOOST_SERIALIZATION_NVP(m_producible) - & BOOST_SERIALIZATION_NVP(m_slots) - & BOOST_SERIALIZATION_NVP(m_tags) - & BOOST_SERIALIZATION_NVP(m_production_meter_consumption) - & BOOST_SERIALIZATION_NVP(m_production_special_consumption) - & BOOST_SERIALIZATION_NVP(m_location) - & BOOST_SERIALIZATION_NVP(m_exclusions) - & BOOST_SERIALIZATION_NVP(m_effects) - & BOOST_SERIALIZATION_NVP(m_graphic) - & BOOST_SERIALIZATION_NVP(m_icon); -} - -/** Load all ship designs in \p dir and return a tuple is_error, the map from uuid to ship design and path and the - ship ordering from the manifest. */ +/** Load all ship designs in \p parsed and return a tuple is_error, the map + from uuid to ship design and path and the ship ordering from the + manifest. */ FO_COMMON_API std::tuple< bool, std::unordered_map, boost::filesystem::path>, boost::hash>, std::vector> -LoadShipDesignsAndManifestOrderFromFileSystem(const boost::filesystem::path& dir); +LoadShipDesignsAndManifestOrderFromParseResults(PredefinedShipDesignManager::ParsedShipDesignsType& parsed); #endif // _ShipDesign_h_ diff --git a/universe/ShipHull.cpp b/universe/ShipHull.cpp new file mode 100644 index 00000000000..be944850b2c --- /dev/null +++ b/universe/ShipHull.cpp @@ -0,0 +1,324 @@ +#include "ShipHull.h" + + +#include "ConditionSource.h" +#include "Effects.h" +#include "Enums.h" +#include "ValueRefs.h" +#include "../Empire/EmpireManager.h" +#include "../util/GameRules.h" + + +namespace { + void AddRules(GameRules& rules) { + rules.Add("RULE_SHIP_SPEED_FACTOR", "RULE_SHIP_SPEED_FACTOR_DESC", + "BALANCE", 1.0, true, RangedValidator(0.1, 10.0)); + rules.Add("RULE_SHIP_STRUCTURE_FACTOR", "RULE_SHIP_STRUCTURE_FACTOR_DESC", + "BALANCE", 1.0, true, RangedValidator(0.1, 10.0)); + } + bool temp_bool = RegisterGameRules(&AddRules); + + const float ARBITRARY_LARGE_COST = 999999.9f; + const int ARBITRARY_LARGE_TURNS = 999999; + + // create effectsgroup that increases the value of \a meter_type + // by the result of evalulating \a increase_vr + std::shared_ptr + IncreaseMeter(MeterType meter_type, + std::unique_ptr>&& increase_vr) + { + typedef std::vector> Effects; + auto scope = std::make_unique(); + auto activation = std::make_unique(); + + auto vr = + std::make_unique>( + ValueRef::PLUS, + std::make_unique>( + ValueRef::EFFECT_TARGET_VALUE_REFERENCE, std::vector()), + std::move(increase_vr) + ); + auto effects = Effects(); + effects.push_back(std::make_unique(meter_type, std::move(vr))); + return std::make_shared(std::move(scope), std::move(activation), std::move(effects)); + } + + // create effectsgroup that increases the value of \a meter_type + // by the specified amount \a fixed_increase + std::shared_ptr + IncreaseMeter(MeterType meter_type, float fixed_increase) { + auto increase_vr = std::make_unique>(fixed_increase); + return IncreaseMeter(meter_type, std::move(increase_vr)); + } + + // create effectsgroup that increases the value of \a meter_type + // by the product of \a base_increase and the value of the game + // rule of type double with the name \a scaling_factor_rule_name + std::shared_ptr + IncreaseMeter(MeterType meter_type, float base_increase, + const std::string& scaling_factor_rule_name) + { + // if no rule specified, revert to fixed constant increase + if (scaling_factor_rule_name.empty()) + return IncreaseMeter(meter_type, base_increase); + + auto increase_vr = std::make_unique>( + ValueRef::TIMES, + std::make_unique>(base_increase), + std::make_unique>( + "GameRule", nullptr, nullptr, nullptr, + std::make_unique>(scaling_factor_rule_name) + ) + ); + + return IncreaseMeter(meter_type, std::move(increase_vr)); + } + +} + + +ShipHull::ShipHull() +{} + +ShipHull::ShipHull(const ShipHullStats& stats, + CommonParams&& common_params, + const MoreCommonParams& more_common_params, + const std::vector& slots, + const std::string& icon, const std::string& graphic) : + m_name(more_common_params.name), + m_description(more_common_params.description), + m_speed(stats.speed), + m_fuel(stats.fuel), + m_stealth(stats.stealth), + m_structure(stats.structure), + m_production_cost(std::move(common_params.production_cost)), + m_production_time(std::move(common_params.production_time)), + m_producible(common_params.producible), + m_slots(slots), + m_production_meter_consumption(std::move(common_params.production_meter_consumption)), + m_production_special_consumption(std::move(common_params.production_special_consumption)), + m_location(std::move(common_params.location)), + m_exclusions(more_common_params.exclusions), + m_graphic(graphic), + m_icon(icon) +{ + TraceLogger() << "hull type: " << m_name << " producible: " << m_producible << std::endl; + Init(std::move(common_params.effects), stats); + + for (const std::string& tag : common_params.tags) + m_tags.insert(boost::to_upper_copy(tag)); +} + +ShipHull::Slot::Slot() : + type(INVALID_SHIP_SLOT_TYPE) +{} + +ShipHull::~ShipHull() {} + +void ShipHull::Init(std::vector>&& effects, + const ShipHullStats& stats) +{ + if (stats.default_fuel_effects && m_fuel != 0) + m_effects.push_back(IncreaseMeter(METER_MAX_FUEL, m_fuel)); + if (stats.default_stealth_effects && m_stealth != 0) + m_effects.push_back(IncreaseMeter(METER_STEALTH, m_stealth)); + if (stats.default_structure_effects && m_structure != 0) + m_effects.push_back(IncreaseMeter(METER_MAX_STRUCTURE, m_structure, "RULE_SHIP_STRUCTURE_FACTOR")); + if (stats.default_speed_effects && m_speed != 0) + m_effects.push_back(IncreaseMeter(METER_SPEED, m_speed, "RULE_SHIP_SPEED_FACTOR")); + + if (m_production_cost) + m_production_cost->SetTopLevelContent(m_name); + if (m_production_time) + m_production_time->SetTopLevelContent(m_name); + if (m_location) + m_location->SetTopLevelContent(m_name); + for (auto&& effect : effects) { + effect->SetTopLevelContent(m_name); + m_effects.emplace_back(std::move(effect)); + } +} + +float ShipHull::Speed() const +{ return m_speed * GetGameRules().Get("RULE_SHIP_SPEED_FACTOR"); } + +float ShipHull::Structure() const +{ return m_structure * GetGameRules().Get("RULE_SHIP_STRUCTURE_FACTOR"); } + +unsigned int ShipHull::NumSlots(ShipSlotType slot_type) const { + unsigned int count = 0; + for (const Slot& slot : m_slots) + if (slot.type == slot_type) + ++count; + return count; +} + +// ShipHull:: and ShipPart::ProductionCost and ProductionTime are almost identical. +// Chances are, the same is true of buildings and techs as well. +// TODO: Eliminate duplication +bool ShipHull::ProductionCostTimeLocationInvariant() const { + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION")) + return true; + if (m_production_cost && !m_production_cost->LocalCandidateInvariant()) + return false; + if (m_production_time && !m_production_time->LocalCandidateInvariant()) + return false; + return true; +} + +float ShipHull::ProductionCost(int empire_id, int location_id, int in_design_id) const { + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION") || !m_production_cost) + return 1.0f; + + if (m_production_cost->ConstantExpr()) { + return static_cast(m_production_cost->Eval()); + } else if (m_production_cost->SourceInvariant() && m_production_cost->TargetInvariant()) { + ScriptingContext context(nullptr, nullptr, in_design_id); + return static_cast(m_production_cost->Eval(context)); + } + + auto location = Objects().get(location_id); + if (!location && !m_production_cost->TargetInvariant()) + return ARBITRARY_LARGE_COST; + + auto source = Empires().GetSource(empire_id); + if (!source && !m_production_cost->SourceInvariant()) + return ARBITRARY_LARGE_COST; + + ScriptingContext context(source, location, in_design_id); + return static_cast(m_production_cost->Eval(context)); +} + +int ShipHull::ProductionTime(int empire_id, int location_id, int in_design_id) const { + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION") || !m_production_time) + return 1; + + if (m_production_time->ConstantExpr()) { + return m_production_time->Eval(); + } else if (m_production_time->SourceInvariant() && m_production_time->TargetInvariant()) { + ScriptingContext context(nullptr, nullptr, in_design_id); + return m_production_time->Eval(context); + } + + auto location = Objects().get(location_id); + if (!location && !m_production_time->TargetInvariant()) + return ARBITRARY_LARGE_TURNS; + + auto source = Empires().GetSource(empire_id); + if (!source && !m_production_time->SourceInvariant()) + return ARBITRARY_LARGE_TURNS; + + ScriptingContext context(source, location, in_design_id); + return m_production_time->Eval(context); +} + +unsigned int ShipHull::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_description); + CheckSums::CheckSumCombine(retval, m_speed); + CheckSums::CheckSumCombine(retval, m_fuel); + CheckSums::CheckSumCombine(retval, m_stealth); + CheckSums::CheckSumCombine(retval, m_structure); + CheckSums::CheckSumCombine(retval, m_production_cost); + CheckSums::CheckSumCombine(retval, m_production_time); + CheckSums::CheckSumCombine(retval, m_producible); + CheckSums::CheckSumCombine(retval, m_slots); + CheckSums::CheckSumCombine(retval, m_tags); + CheckSums::CheckSumCombine(retval, m_production_meter_consumption); + CheckSums::CheckSumCombine(retval, m_production_special_consumption); + CheckSums::CheckSumCombine(retval, m_location); + CheckSums::CheckSumCombine(retval, m_exclusions); + CheckSums::CheckSumCombine(retval, m_effects); + CheckSums::CheckSumCombine(retval, m_graphic); + CheckSums::CheckSumCombine(retval, m_icon); + + return retval; +} + + +ShipHullManager* ShipHullManager::s_instance = nullptr; + +ShipHullManager::ShipHullManager() { + if (s_instance) + throw std::runtime_error("Attempted to create more than one ShipHullManager."); + + // Only update the global pointer on sucessful construction. + s_instance = this; +} + +const ShipHull* ShipHullManager::GetShipHull(const std::string& name) const { + CheckPendingShipHulls(); + auto it = m_hulls.find(name); + return it != m_hulls.end() ? it->second.get() : nullptr; +} + +ShipHullManager& ShipHullManager::GetShipHullManager() { + static ShipHullManager manager; + return manager; +} + +ShipHullManager::iterator ShipHullManager::begin() const { + CheckPendingShipHulls(); + return m_hulls.begin(); +} + +ShipHullManager::iterator ShipHullManager::end() const { + CheckPendingShipHulls(); + return m_hulls.end(); +} + +std::size_t ShipHullManager::size() const { + CheckPendingShipHulls(); + return m_hulls.size(); +} + +unsigned int ShipHullManager::GetCheckSum() const { + CheckPendingShipHulls(); + unsigned int retval{0}; + for (auto const& name_hull_pair : m_hulls) + CheckSums::CheckSumCombine(retval, name_hull_pair); + CheckSums::CheckSumCombine(retval, m_hulls.size()); + + DebugLogger() << "ShipHullManager checksum: " << retval; + return retval; +} + +void ShipHullManager::SetShipHulls(Pending::Pending&& pending_ship_hulls) +{ m_pending_ship_hulls = std::move(pending_ship_hulls); } + +void ShipHullManager::CheckPendingShipHulls() const { + if (!m_pending_ship_hulls) + return; + + Pending::SwapPending(m_pending_ship_hulls, m_hulls); + + TraceLogger() << [this]() { + std::string retval("Hull Types:"); + for (const auto& entry : m_hulls) { + retval.append("\n\t" + entry.second->Name()); + } + return retval; + }(); + + if (m_hulls.empty()) + ErrorLogger() << "ShipHullManager expects at least one hull type. All ship design construction will fail."; +} + + +namespace CheckSums { + void CheckSumCombine(unsigned int& sum, const ShipHull::Slot& slot) { + TraceLogger() << "CheckSumCombine(Slot): " << typeid(slot).name(); + CheckSumCombine(sum, slot.x); + CheckSumCombine(sum, slot.y); + CheckSumCombine(sum, slot.type); + } +} + + +ShipHullManager& GetShipHullManager() +{ return ShipHullManager::GetShipHullManager(); } + +const ShipHull* GetShipHull(const std::string& name) +{ return GetShipHullManager().GetShipHull(name); } diff --git a/universe/ShipHull.h b/universe/ShipHull.h new file mode 100644 index 00000000000..cda8f8bda4d --- /dev/null +++ b/universe/ShipHull.h @@ -0,0 +1,311 @@ +#ifndef _ShipHull_h_ +#define _ShipHull_h_ + +#include +#include + +#include "CommonParams.h" +#include "../util/Pending.h" + + +FO_COMMON_API extern const int INVALID_DESIGN_ID; + + +//! Hull stats. Used by parser due to limits on number of sub-items per parsed +//! parsed main item. +struct ShipHullStats { + ShipHullStats() = default; + + ShipHullStats(float fuel_, + float speed_, + float stealth_, + float structure_, + bool no_default_fuel_effects_, + bool no_default_speed_effects_, + bool no_default_stealth_effects_, + bool no_default_structure_effects_) : + fuel(fuel_), + speed(speed_), + stealth(stealth_), + structure(structure_), + default_fuel_effects(!no_default_fuel_effects_), + default_speed_effects(!no_default_speed_effects_), + default_stealth_effects(!no_default_stealth_effects_), + default_structure_effects(!no_default_structure_effects_) + {} + + float fuel = 0.0f; + float speed = 0.0f; + float stealth = 0.0f; + float structure = 0.0f; + bool default_fuel_effects = true; + bool default_speed_effects = true; + bool default_stealth_effects = true; + bool default_structure_effects = true; + + template + void serialize(Archive& ar, const unsigned int) { + ar & BOOST_SERIALIZATION_NVP(fuel) + & BOOST_SERIALIZATION_NVP(speed) + & BOOST_SERIALIZATION_NVP(stealth) + & BOOST_SERIALIZATION_NVP(structure) + & BOOST_SERIALIZATION_NVP(default_fuel_effects) + & BOOST_SERIALIZATION_NVP(default_speed_effects) + & BOOST_SERIALIZATION_NVP(default_stealth_effects) + & BOOST_SERIALIZATION_NVP(default_structure_effects); + } +}; + + +//! Specification for the hull, or base, on which ship designs are created by +//! adding parts. The hull determines some final design characteristics +//! directly, and also determine how many parts can be added to the design. +class FO_COMMON_API ShipHull { +public: + struct Slot { + Slot(); + + Slot(ShipSlotType slot_type, double x_, double y_) : + type(slot_type), x(x_), y(y_) + {} + + ShipSlotType type; + double x = 0.5, y = 0.5; + }; + + ShipHull(); + + ShipHull(const ShipHullStats& stats, + CommonParams&& common_params, + const MoreCommonParams& more_common_params, + const std::vector& slots, + const std::string& icon, const std::string& graphic); + + ~ShipHull(); + + //! Returns name of hull + auto Name() const -> const std::string& + { return m_name; } + + //! Returns description, including a description of the stats and effects + //! of this hull + auto Description() const -> const std::string& + { return m_description; } + + //! Returns starlane speed of hull + auto Speed() const -> float; + + //! Returns fuel capacity of hull + auto Fuel() const -> float + { return m_fuel; } + + //! Returns stealth of hull + auto Stealth() const -> float + { return m_stealth; } + + //! Returns structure of hull + auto Structure() const -> float; + + //! Returns shields of hull + auto Shields() const -> float + { return 0.0f; } + + //! Returns colonist capacity of hull + auto ColonyCapacity() const -> float + { return 0.0f; } + + //! Returns the troop capacity of hull + auto TroopCapacity() const -> float + { return 0.0f; } + + //! Returns detection ability of hull + auto Detection() const -> float + { return 0.0f; } + + //! Returns true if the production cost and time are invariant (does not + //! depend on) the location + auto ProductionCostTimeLocationInvariant() const -> bool; + + //! Returns the number of production points required to produce this hull + auto ProductionCost(int empire_id, int location_id, int in_design_id = INVALID_DESIGN_ID) const -> float; + + //! Returns the number of turns required to produce this hull + auto ProductionTime(int empire_id, int location_id, int in_design_id = INVALID_DESIGN_ID) const -> int; + + //! Returns whether this hull type is producible by players and appears on + //! the design screen + auto Producible() const -> bool + { return m_producible; } + + auto ProductionMeterConsumption() const -> const ConsumptionMap& + { return m_production_meter_consumption; } + + auto ProductionSpecialConsumption() const -> const ConsumptionMap& + { return m_production_special_consumption; } + + //! Returns total number of of slots in hull + auto NumSlots() const -> unsigned int + { return m_slots.size(); } + + //! Returns number of of slots of indicated type in hull + auto NumSlots(ShipSlotType slot_type) const -> unsigned int; + + //! Returns vector of slots in hull + auto Slots() const -> const std::vector& + { return m_slots; } + + auto Tags() const -> const std::set& + { return m_tags; } + + auto HasTag(const std::string& tag) const -> bool + { return m_tags.count(tag) != 0; } + + //! Returns the condition that determines the locations where ShipDesign + //! containing hull can be produced + auto Location() const -> const Condition::Condition* + { return m_location.get(); } + + //! Returns the names of other content that cannot be used in the same + //! ship design as this part + auto Exclusions() const -> const std::set& + { return m_exclusions; } + + //! Returns the EffectsGroups that encapsulate the effects this part hull + //! has. + auto Effects() const -> const std::vector>& + { return m_effects; } + + //! Returns the image that represents the hull on the design screen + auto Graphic() const -> const std::string& + { return m_graphic; } + + //! Returns the small icon to represent hull + auto Icon() const -> const std::string& + { return m_icon; } + + //! Returns a number, calculated from the contained data, which should be + //! different for different contained data, and must be the same for + //! the same contained data, and must be the same on different platforms + //! and executions of the program and the function. Useful to verify that + //! the parsed content is consistent without sending it all between + //! clients and server. + auto GetCheckSum() const -> unsigned int; + +private: + void Init(std::vector>&& effects, + const ShipHullStats& stats); + + std::string m_name; + std::string m_description; + float m_speed = 1.0f; + float m_fuel = 0.0f; + float m_stealth = 0.0f; + float m_structure = 0.0f; + + std::unique_ptr> m_production_cost; + std::unique_ptr> m_production_time; + bool m_producible = false; + std::vector m_slots; + std::set m_tags; + ConsumptionMap m_production_meter_consumption; + ConsumptionMap m_production_special_consumption; + std::unique_ptr m_location; + std::set m_exclusions; + std::vector> m_effects; + std::string m_graphic; + std::string m_icon; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + + +template +void ShipHull::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_description) + & BOOST_SERIALIZATION_NVP(m_speed) + & BOOST_SERIALIZATION_NVP(m_fuel) + & BOOST_SERIALIZATION_NVP(m_stealth) + & BOOST_SERIALIZATION_NVP(m_structure) + & BOOST_SERIALIZATION_NVP(m_production_cost) + & BOOST_SERIALIZATION_NVP(m_production_time) + & BOOST_SERIALIZATION_NVP(m_producible) + & BOOST_SERIALIZATION_NVP(m_slots) + & BOOST_SERIALIZATION_NVP(m_tags) + & BOOST_SERIALIZATION_NVP(m_production_meter_consumption) + & BOOST_SERIALIZATION_NVP(m_production_special_consumption) + & BOOST_SERIALIZATION_NVP(m_location) + & BOOST_SERIALIZATION_NVP(m_exclusions) + & BOOST_SERIALIZATION_NVP(m_effects) + & BOOST_SERIALIZATION_NVP(m_graphic) + & BOOST_SERIALIZATION_NVP(m_icon); +} + + +namespace CheckSums { + FO_COMMON_API void CheckSumCombine(unsigned int& sum, const ShipHull::Slot& slot); +} + + +//! Holds FreeOrion hull types +class FO_COMMON_API ShipHullManager { +public: + using container_type = std::map>; + using iterator = container_type::const_iterator; + + //! Returns the hull type with the name @a name; you should use the free + //! function GetShipHull() instead + auto GetShipHull(const std::string& name) const -> const ShipHull*; + + //! iterator to the first hull type + auto begin() const -> iterator; + + //! iterator to the last + 1th hull type + auto end() const -> iterator; + + //! How many hulls are known? + auto size() const -> std::size_t; + + //! Returns the instance of this singleton class; you should use the free + //! function GetShipHullManager() instead + static auto GetShipHullManager() -> ShipHullManager&; + + //! Returns a number, calculated from the contained data, which should be + //! different for different contained data, and must be the same for + //! the same contained data, and must be the same on different platforms + //! and executions of the program and the function. Useful to verify that + //! the parsed content is consistent without sending it all between + //! clients and server. + auto GetCheckSum() const -> unsigned int; + + //! Sets hull types to the future value of \p pending_ship_hulls. + FO_COMMON_API void SetShipHulls(Pending::Pending&& pending_ship_hulls); + +private: + ShipHullManager(); + + //! Assigns any m_pending_ship_hulls to m_bulding_types. + void CheckPendingShipHulls() const; + + //! Future hull type being parsed by parser. + mutable boost::optional> m_pending_ship_hulls = boost::none; + + //! Set of hull types. + mutable container_type m_hulls; + + static ShipHullManager* s_instance; +}; + + +//! Returns the singleton hull type manager +FO_COMMON_API auto GetShipHullManager() -> ShipHullManager&; + +//! Returns the ship ShipHull specification object with name @p name. If no +//! such ShipHull exists, nullptr is returned instead. +FO_COMMON_API auto GetShipHull(const std::string& name) -> const ShipHull*; + + +#endif // _ShipHull_h_ diff --git a/universe/ShipPart.cpp b/universe/ShipPart.cpp new file mode 100644 index 00000000000..b2301aa17e7 --- /dev/null +++ b/universe/ShipPart.cpp @@ -0,0 +1,426 @@ +#include "ShipPart.h" + +#include + +#include "Enums.h" +#include "ConditionSource.h" +#include "Effects.h" +#include "ValueRefs.h" +#include "../Empire/Empire.h" +#include "../Empire/EmpireManager.h" +#include "../util/CheckSums.h" +#include "../util/GameRules.h" + + +namespace { + const int ARBITRARY_LARGE_TURNS = 999999; + const float ARBITRARY_LARGE_COST = 999999.9f; + + // create effectsgroup that increases the value of \a meter_type + // by the result of evalulating \a increase_vr + std::shared_ptr + IncreaseMeter(MeterType meter_type, + std::unique_ptr>&& increase_vr) + { + typedef std::vector> Effects; + auto scope = std::make_unique(); + auto activation = std::make_unique(); + + auto vr = + std::make_unique>( + ValueRef::PLUS, + std::make_unique>( + ValueRef::EFFECT_TARGET_VALUE_REFERENCE, std::vector()), + std::move(increase_vr) + ); + auto effects = Effects(); + effects.push_back(std::make_unique(meter_type, std::move(vr))); + return std::make_shared(std::move(scope), std::move(activation), std::move(effects)); + } + + // create effectsgroup that increases the value of \a meter_type + // by the specified amount \a fixed_increase + std::shared_ptr + IncreaseMeter(MeterType meter_type, float fixed_increase) { + auto increase_vr = std::make_unique>(fixed_increase); + return IncreaseMeter(meter_type, std::move(increase_vr)); + } + + // create effectsgroup that increases the value of the part meter + // of type \a meter_type for part name \a part_name by the fixed + // amount \a increase + std::shared_ptr + IncreaseMeter(MeterType meter_type, const std::string& part_name, + float increase, bool allow_stacking = true) + { + typedef std::vector> Effects; + auto scope = std::make_unique(); + auto activation = std::make_unique(); + + auto value_vr = std::make_unique>( + ValueRef::PLUS, + std::make_unique>( + ValueRef::EFFECT_TARGET_VALUE_REFERENCE, std::vector()), + std::make_unique>(increase) + ); + + auto part_name_vr = + std::make_unique>(part_name); + + std::string stacking_group = (allow_stacking ? "" : + (part_name + "_" + boost::lexical_cast(meter_type) + "_PartMeter")); + + auto effects = Effects(); + effects.push_back(std::make_unique( + meter_type, std::move(part_name_vr), std::move(value_vr))); + + return std::make_shared( + std::move(scope), std::move(activation), std::move(effects), part_name, stacking_group); + } + + // create effectsgroup that increases the value of \a meter_type + // by the product of \a base_increase and the value of the game + // rule of type double with the name \a scaling_factor_rule_name + std::shared_ptr + IncreaseMeter(MeterType meter_type, float base_increase, + const std::string& scaling_factor_rule_name) + { + // if no rule specified, revert to fixed constant increase + if (scaling_factor_rule_name.empty()) + return IncreaseMeter(meter_type, base_increase); + + auto increase_vr = std::make_unique>( + ValueRef::TIMES, + std::make_unique>(base_increase), + std::make_unique>( + "GameRule", nullptr, nullptr, nullptr, + std::make_unique>(scaling_factor_rule_name) + ) + ); + + return IncreaseMeter(meter_type, std::move(increase_vr)); + } +} + + +ShipPart::ShipPart() : + m_class(INVALID_SHIP_PART_CLASS) +{} + +ShipPart::ShipPart(ShipPartClass part_class, double capacity, double stat2, + CommonParams& common_params, const MoreCommonParams& more_common_params, + std::vector mountable_slot_types, + const std::string& icon, bool add_standard_capacity_effect, + std::unique_ptr&& combat_targets) : + m_name(more_common_params.name), + m_description(more_common_params.description), + m_class(part_class), + m_capacity(capacity), + m_secondary_stat(stat2), + m_producible(common_params.producible), + m_production_cost(std::move(common_params.production_cost)), + m_production_time(std::move(common_params.production_time)), + m_mountable_slot_types(mountable_slot_types), + m_production_meter_consumption(std::move(common_params.production_meter_consumption)), + m_production_special_consumption(std::move(common_params.production_special_consumption)), + m_location(std::move(common_params.location)), + m_exclusions(more_common_params.exclusions), + m_icon(icon), + m_add_standard_capacity_effect(add_standard_capacity_effect), + m_combat_targets(std::move(combat_targets)) +{ + Init(std::move(common_params.effects)); + + for (const std::string& tag : common_params.tags) + m_tags.insert(boost::to_upper_copy(tag)); + + TraceLogger() << "ShipPart::ShipPart: name: " << m_name + << " description: " << m_description + << " class: " << m_class + << " capacity: " << m_capacity + << " secondary stat: " << m_secondary_stat + //<< " prod cost: " << m_production_cost + //<< " prod time: " << m_production_time + << " producible: " << m_producible + //<< " mountable slot types: " << m_mountable_slot_types + //<< " tags: " << m_tags + //<< " prod meter consump: " << m_production_meter_consumption + //<< " prod special consump: " << m_production_special_consumption + //<< " location: " << m_location + //<< " exclusions: " << m_exclusions + //<< " effects: " << m_effects + << " icon: " << m_icon + << " add standard cap effect: " << m_add_standard_capacity_effect; +} + +void ShipPart::Init(std::vector>&& effects) { + if ((m_capacity != 0 || m_secondary_stat != 0) && m_add_standard_capacity_effect) { + switch (m_class) { + case PC_COLONY: + case PC_TROOPS: + m_effects.push_back(IncreaseMeter(METER_CAPACITY, m_name, m_capacity, false)); + break; + case PC_FIGHTER_HANGAR: { // capacity indicates how many fighters are stored in this type of part (combined for all copies of the part) + m_effects.push_back(IncreaseMeter(METER_MAX_CAPACITY, m_name, m_capacity, true)); // stacking capacities allowed for this part, so each part contributes to the total capacity + m_effects.push_back(IncreaseMeter(METER_MAX_SECONDARY_STAT, m_name, m_secondary_stat, false)); // stacking damage not allowed, as damage per shot should be the same regardless of number of shots + break; + } + case PC_FIGHTER_BAY: // capacity indicates how many fighters each instance of the part can launch per combat bout... + case PC_DIRECT_WEAPON: { // capacity indicates weapon damage per shot + m_effects.push_back(IncreaseMeter(METER_MAX_CAPACITY, m_name, m_capacity, false)); + m_effects.push_back(IncreaseMeter(METER_MAX_SECONDARY_STAT, m_name, m_secondary_stat, false)); + break; + } + case PC_SHIELD: + m_effects.push_back(IncreaseMeter(METER_MAX_SHIELD, m_capacity)); + break; + case PC_DETECTION: + m_effects.push_back(IncreaseMeter(METER_DETECTION, m_capacity)); + break; + case PC_STEALTH: + m_effects.push_back(IncreaseMeter(METER_STEALTH, m_capacity)); + break; + case PC_FUEL: + m_effects.push_back(IncreaseMeter(METER_MAX_FUEL, m_capacity)); + break; + case PC_ARMOUR: + m_effects.push_back(IncreaseMeter(METER_MAX_STRUCTURE, m_capacity, "RULE_SHIP_STRUCTURE_FACTOR")); + break; + case PC_SPEED: + m_effects.push_back(IncreaseMeter(METER_SPEED, m_capacity, "RULE_SHIP_SPEED_FACTOR")); + break; + case PC_RESEARCH: + m_effects.push_back(IncreaseMeter(METER_TARGET_RESEARCH,m_capacity)); + break; + case PC_INDUSTRY: + m_effects.push_back(IncreaseMeter(METER_TARGET_INDUSTRY,m_capacity)); + break; + case PC_TRADE: + m_effects.push_back(IncreaseMeter(METER_TARGET_TRADE, m_capacity)); + break; + default: + break; + } + } + + if (m_production_cost) + m_production_cost->SetTopLevelContent(m_name); + if (m_production_time) + m_production_time->SetTopLevelContent(m_name); + if (m_location) + m_location->SetTopLevelContent(m_name); + if (m_combat_targets) + m_combat_targets->SetTopLevelContent(m_name); + for (auto&& effect : effects) { + effect->SetTopLevelContent(m_name); + m_effects.emplace_back(std::move(effect)); + } +} + +ShipPart::~ShipPart() +{} + +float ShipPart::Capacity() const { + switch (m_class) { + case PC_ARMOUR: + return m_capacity * GetGameRules().Get("RULE_SHIP_STRUCTURE_FACTOR"); + break; + case PC_SPEED: + return m_capacity * GetGameRules().Get("RULE_SHIP_SPEED_FACTOR"); + break; + default: + return m_capacity; + } +} + +float ShipPart::SecondaryStat() const +{ return m_secondary_stat; } + +std::string ShipPart::CapacityDescription() const { + std::string desc_string; + float main_stat = Capacity(); + float sdry_stat = SecondaryStat(); + + switch (m_class) { + case PC_FUEL: + case PC_TROOPS: + case PC_COLONY: + case PC_FIGHTER_BAY: + desc_string += str(FlexibleFormat(UserString("PART_DESC_CAPACITY")) % main_stat); + break; + case PC_DIRECT_WEAPON: + desc_string += str(FlexibleFormat(UserString("PART_DESC_DIRECT_FIRE_STATS")) % main_stat % sdry_stat); + break; + case PC_FIGHTER_HANGAR: + desc_string += str(FlexibleFormat(UserString("PART_DESC_HANGAR_STATS")) % main_stat % sdry_stat); + break; + case PC_SHIELD: + desc_string = str(FlexibleFormat(UserString("PART_DESC_SHIELD_STRENGTH")) % main_stat); + break; + case PC_DETECTION: + desc_string = str(FlexibleFormat(UserString("PART_DESC_DETECTION")) % main_stat); + break; + default: + desc_string = str(FlexibleFormat(UserString("PART_DESC_STRENGTH")) % main_stat); + break; + } + return desc_string; +} + +bool ShipPart::CanMountInSlotType(ShipSlotType slot_type) const { + if (INVALID_SHIP_SLOT_TYPE == slot_type) + return false; + for (ShipSlotType mountable_slot_type : m_mountable_slot_types) + if (mountable_slot_type == slot_type) + return true; + return false; +} + +bool ShipPart::ProductionCostTimeLocationInvariant() const { + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION")) + return true; + if (m_production_cost && !m_production_cost->TargetInvariant()) + return false; + if (m_production_time && !m_production_time->TargetInvariant()) + return false; + return true; +} + +float ShipPart::ProductionCost(int empire_id, int location_id, int in_design_id) const { + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION") || !m_production_cost) + return 1.0f; + + if (m_production_cost->ConstantExpr()) { + return static_cast(m_production_cost->Eval()); + } else if (m_production_cost->SourceInvariant() && m_production_cost->TargetInvariant()) { + ScriptingContext context(nullptr, nullptr, in_design_id); + return static_cast(m_production_cost->Eval(context)); + } + + auto location = Objects().get(location_id); + if (!location && !m_production_cost->TargetInvariant()) + return ARBITRARY_LARGE_COST; + + auto source = Empires().GetSource(empire_id); + if (!source && !m_production_cost->SourceInvariant()) + return ARBITRARY_LARGE_COST; + + ScriptingContext context(source, location, in_design_id); + return static_cast(m_production_cost->Eval(context)); +} + +int ShipPart::ProductionTime(int empire_id, int location_id, int in_design_id) const { + if (GetGameRules().Get("RULE_CHEAP_AND_FAST_SHIP_PRODUCTION") || !m_production_time) + return 1; + + if (m_production_time->ConstantExpr()) { + return m_production_time->Eval(); + } else if (m_production_time->SourceInvariant() && m_production_time->TargetInvariant()) { + ScriptingContext context(nullptr, nullptr, in_design_id); + return m_production_time->Eval(context); + } + + auto location = Objects().get(location_id); + if (!location && !m_production_time->TargetInvariant()) + return ARBITRARY_LARGE_TURNS; + + auto source = Empires().GetSource(empire_id); + if (!source && !m_production_time->SourceInvariant()) + return ARBITRARY_LARGE_TURNS; + + ScriptingContext context(source, location, in_design_id); + return m_production_time->Eval(context); +} + +unsigned int ShipPart::GetCheckSum() const { + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, m_name); + CheckSums::CheckSumCombine(retval, m_description); + CheckSums::CheckSumCombine(retval, m_class); + CheckSums::CheckSumCombine(retval, m_capacity); + CheckSums::CheckSumCombine(retval, m_secondary_stat); + CheckSums::CheckSumCombine(retval, m_production_cost); + CheckSums::CheckSumCombine(retval, m_production_time); + CheckSums::CheckSumCombine(retval, m_producible); + CheckSums::CheckSumCombine(retval, m_mountable_slot_types); + CheckSums::CheckSumCombine(retval, m_tags); + CheckSums::CheckSumCombine(retval, m_production_meter_consumption); + CheckSums::CheckSumCombine(retval, m_production_special_consumption); + CheckSums::CheckSumCombine(retval, m_location); + CheckSums::CheckSumCombine(retval, m_exclusions); + CheckSums::CheckSumCombine(retval, m_effects); + CheckSums::CheckSumCombine(retval, m_icon); + CheckSums::CheckSumCombine(retval, m_add_standard_capacity_effect); + + return retval; +} + + +ShipPartManager* ShipPartManager::s_instance = nullptr; + +ShipPartManager::ShipPartManager() { + if (s_instance) + throw std::runtime_error("Attempted to create more than one ShipPartManager."); + + // Only update the global pointer on sucessful construction. + s_instance = this; +} + +const ShipPart* ShipPartManager::GetShipPart(const std::string& name) const { + CheckPendingShipParts(); + auto it = m_parts.find(name); + return it != m_parts.end() ? it->second.get() : nullptr; +} + +ShipPartManager& ShipPartManager::GetShipPartManager() { + static ShipPartManager manager; + return manager; +} + +ShipPartManager::iterator ShipPartManager::begin() const { + CheckPendingShipParts(); + return m_parts.begin(); +} + +ShipPartManager::iterator ShipPartManager::end() const{ + CheckPendingShipParts(); + return m_parts.end(); +} + +unsigned int ShipPartManager::GetCheckSum() const { + CheckPendingShipParts(); + unsigned int retval{0}; + for (auto const& name_part_pair : m_parts) + CheckSums::CheckSumCombine(retval, name_part_pair); + CheckSums::CheckSumCombine(retval, m_parts.size()); + + + DebugLogger() << "ShipPartManager checksum: " << retval; + return retval; +} + +void ShipPartManager::SetShipParts(Pending::Pending&& pending_ship_parts) +{ m_pending_ship_parts = std::move(pending_ship_parts); } + +void ShipPartManager::CheckPendingShipParts() const { + if (!m_pending_ship_parts) + return; + + Pending::SwapPending(m_pending_ship_parts, m_parts); + + TraceLogger() << [this]() { + std::string retval("Part Types:"); + for (const auto& pair : m_parts) { + const auto& part = pair.second; + retval.append("\n\t" + part->Name() + " class: " + boost::lexical_cast(part->Class())); + } + return retval; + }(); +} + +ShipPartManager& GetShipPartManager() +{ return ShipPartManager::GetShipPartManager(); } + +const ShipPart* GetShipPart(const std::string& name) +{ return GetShipPartManager().GetShipPart(name); } diff --git a/universe/ShipPart.h b/universe/ShipPart.h new file mode 100644 index 00000000000..cf66445bcdb --- /dev/null +++ b/universe/ShipPart.h @@ -0,0 +1,243 @@ +#ifndef _ShipPart_h_ +#define _ShipPart_h_ + +#include + +#include +#include "CommonParams.h" +#include "../util/Pending.h" + + +FO_COMMON_API extern const int INVALID_DESIGN_ID; + + +//! Classifies ShipParts by general function. +GG_ENUM(ShipPartClass, + INVALID_SHIP_PART_CLASS = -1, + PC_DIRECT_WEAPON, //!< direct-fire weapons + PC_FIGHTER_BAY, //!< launch aparatus for fighters, which are self-propelled platforms that function independently of ships in combat, but don't exist on the main game map + PC_FIGHTER_HANGAR, //!< storage for fighters, also determines their weapon strength stat + PC_SHIELD, //!< energy-based defense + PC_ARMOUR, //!< defensive material on hull of ship + PC_TROOPS, //!< ground troops, used to conquer planets + PC_DETECTION, //!< range of vision and seeing through stealth + PC_STEALTH, //!< hiding from enemies + PC_FUEL, //!< distance that can be traveled away from resupply + PC_COLONY, //!< transports colonists and allows ships to make new colonies + PC_SPEED, //!< affects ship speed on starlanes + PC_GENERAL, //!< special purpose parts that don't fall into another class + PC_BOMBARD, //!< permit orbital bombardment by ships against planets + PC_INDUSTRY, //!< generates production points for owner at its location + PC_RESEARCH, //!< generates research points for owner + PC_TRADE, //!< generates trade points for owner + PC_PRODUCTION_LOCATION, //!< allows production items to be produced at its location + NUM_SHIP_PART_CLASSES +) + + +//! Describes an equipable part for a ship. +class FO_COMMON_API ShipPart { +public: + ShipPart(); + + ShipPart(ShipPartClass part_class, double capacity, double stat2, + CommonParams& common_params, const MoreCommonParams& more_common_params, + std::vector mountable_slot_types, + const std::string& icon, bool add_standard_capacity_effect = true, + std::unique_ptr&& combat_targets = nullptr); + + ~ShipPart(); + + //! Returns name of part + auto Name() const -> const std::string& + { return m_name; } + + //! Returns description string, generally a UserString key. + auto Description() const -> const std::string& + { return m_description; } + + //! Returns that class of part that this is. + auto Class() const -> ShipPartClass + { return m_class; } + + auto Capacity() const -> float; + + //! Returns a translated description of the part capacity, with numeric + //! value + auto CapacityDescription() const -> std::string; + + auto SecondaryStat() const -> float; + + //! Returns true if this part can be placed in a slot of the indicated type + auto CanMountInSlotType(ShipSlotType slot_type) const -> bool; + + //! Returns the condition for possible targets. may be nullptr if no + //! condition was specified. + auto CombatTargets() const -> const Condition::Condition* + { return m_combat_targets.get(); } + + auto MountableSlotTypes() const -> const std::vector& + { return m_mountable_slot_types; } + + //! Returns true if the production cost and time are invariant + //! (does not depend on) the location + auto ProductionCostTimeLocationInvariant() const -> bool; + + //! Returns the number of production points required to produce this part + auto ProductionCost(int empire_id, int location_id, int in_design_id = INVALID_DESIGN_ID) const -> float; + + //! Returns the number of turns required to produce this part + auto ProductionTime(int empire_id, int location_id, int in_design_id = INVALID_DESIGN_ID) const -> int; + + //! Returns whether this part type is producible by players and appears on + //! the design screen + auto Producible() const -> bool + { return m_producible; } + + auto ProductionMeterConsumption() const -> const ConsumptionMap& + { return m_production_meter_consumption; } + + auto ProductionSpecialConsumption() const -> const ConsumptionMap& + { return m_production_special_consumption; } + + auto Tags() const -> const std::set& + { return m_tags; } + + //! Returns the condition that determines the locations where ShipDesign + //! containing part can be produced + auto Location() const -> const Condition::Condition* + { return m_location.get(); } + + //! Returns the names of other content that cannot be used in the same + //! ship design as this part + auto Exclusions() const -> const std::set& + { return m_exclusions; } + + //! Returns the EffectsGroups that encapsulate the effects this part has. + auto Effects() const -> const std::vector>& + { return m_effects; } + + //! Returns icon graphic that represents part in UI + auto Icon() const -> const std::string& + { return m_icon; } + + //! Returns a number, calculated from the contained data, which should be + //! different for different contained data, and must be the same for + //! the same contained data, and must be the same on different platforms + //! and executions of the program and the function. Useful to verify that + //! the parsed content is consistent without sending it all between + //! clients and server. + unsigned int GetCheckSum() const; + //@} + +private: + void Init(std::vector>&& effects); + + std::string m_name; + std::string m_description; + ShipPartClass m_class; + float m_capacity = 0.0f; + //! Damage for a hangar bay, shots per turn for a weapon, etc. + float m_secondary_stat = 0.0f; + bool m_producible = false; + + std::unique_ptr> m_production_cost; + std::unique_ptr> m_production_time; + std::vector m_mountable_slot_types; + std::set m_tags; + ConsumptionMap m_production_meter_consumption; + ConsumptionMap m_production_special_consumption; + std::unique_ptr m_location; + std::set m_exclusions; + std::vector> m_effects; + std::string m_icon; + bool m_add_standard_capacity_effect = false; + std::unique_ptr m_combat_targets; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + + +template +void ShipPart::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_description) + & BOOST_SERIALIZATION_NVP(m_class) + & BOOST_SERIALIZATION_NVP(m_capacity) + & BOOST_SERIALIZATION_NVP(m_secondary_stat) + & BOOST_SERIALIZATION_NVP(m_production_cost) + & BOOST_SERIALIZATION_NVP(m_production_time) + & BOOST_SERIALIZATION_NVP(m_producible) + & BOOST_SERIALIZATION_NVP(m_mountable_slot_types) + & BOOST_SERIALIZATION_NVP(m_tags) + & BOOST_SERIALIZATION_NVP(m_production_meter_consumption) + & BOOST_SERIALIZATION_NVP(m_production_special_consumption) + & BOOST_SERIALIZATION_NVP(m_location) + & BOOST_SERIALIZATION_NVP(m_exclusions) + & BOOST_SERIALIZATION_NVP(m_effects) + & BOOST_SERIALIZATION_NVP(m_icon) + & BOOST_SERIALIZATION_NVP(m_add_standard_capacity_effect) + & BOOST_SERIALIZATION_NVP(m_combat_targets); +} + + +//! Holds FreeOrion available ShipParts +class FO_COMMON_API ShipPartManager { +public: + using ShipPartMap = std::map>; + using iterator = ShipPartMap::const_iterator; + + //! Returns the ShipPart with the name @p name; you should use the free + //! function GetShipPart() instead + auto GetShipPart(const std::string& name) const -> const ShipPart*; + + //! Iterator to the first ShipPart + auto begin() const -> iterator; + + //! Iterator to one after the last ShipPart. + auto end() const -> iterator; + + //! Returns the instance of this singleton class; you should use the free + //! function GetShipPartManager() instead. + static auto GetShipPartManager() -> ShipPartManager&; + + //! Returns a number, calculated from the contained data, which should be + //! different for different contained data, and must be the same for + //! the same contained data, and must be the same on different platforms + //! and executions of the program and the function. Useful to verify that + //! the parsed content is consistent without sending it all between + //! clients and server. + auto GetCheckSum() const -> unsigned int; + + //! Sets part types to the future value of @p pending_ship_parts. + FO_COMMON_API void SetShipParts(Pending::Pending&& pending_ship_parts); + +private: + ShipPartManager(); + + //! Assigns any m_pending_ship_parts to m_parts. + void CheckPendingShipParts() const; + + //! Future that provides all ShipPart%s after loaded by the parser. + mutable boost::optional> m_pending_ship_parts = boost::none; + + //! Map of ShipPart::Name to ShipPart%s. + mutable ShipPartMap m_parts; + + static ShipPartManager* s_instance; +}; + + +//! Returns the singleton ShipPart manager +FO_COMMON_API ShipPartManager& GetShipPartManager(); + + +//! Returns the ShipPart specification object with name @p name. If no +//! such ShipPart exists, nullptr is returned instead. +FO_COMMON_API const ShipPart* GetShipPart(const std::string& name); + + +#endif // _ShipPart_h_ diff --git a/universe/Special.cpp b/universe/Special.cpp index cca4317efd7..e74303ce3f5 100644 --- a/universe/Special.cpp +++ b/universe/Special.cpp @@ -4,78 +4,96 @@ #include "Effect.h" #include "UniverseObject.h" #include "ValueRef.h" -#include "../parse/Parse.h" #include "../util/OptionsDB.h" #include "../util/Logger.h" #include "../util/AppInterface.h" #include "../util/CheckSums.h" +#include "../util/ScopedTimer.h" +#include "../util/i18n.h" #include -namespace { - class SpecialManager { - public: - SpecialManager() { - try { - parse::specials(m_specials); - } catch (const std::exception& e) { - ErrorLogger() << "Failed parsing specials: error: " << e.what(); - throw e; - } - - TraceLogger() << "Specials:"; - for (const auto& entry : m_specials) - TraceLogger() << " ... " << entry.first; - - DebugLogger() << "SpecialManager checksum: " << GetCheckSum(); - } - ~SpecialManager() { - for (std::map::value_type& entry : m_specials) { - delete entry.second; - } - } - std::vector SpecialNames() const { - std::vector retval; - for (const std::map::value_type& entry : m_specials) { - retval.push_back(entry.first); - } - return retval; - } - const Special* GetSpecial(const std::string& name) const { - std::map::const_iterator it = m_specials.find(name); - return it != m_specials.end() ? it->second : nullptr; - } - unsigned int GetCheckSum() const { - unsigned int retval{0}; - for (auto const& name_type_pair : m_specials) - CheckSums::CheckSumCombine(retval, name_type_pair); - CheckSums::CheckSumCombine(retval, m_specials.size()); - return retval; - } - private: - std::map m_specials; - }; - const SpecialManager& GetSpecialManager() { - static SpecialManager special_manager; - return special_manager; +SpecialsManager::SpecialsManager() +{} + +SpecialsManager::~SpecialsManager() +{} + +std::vector SpecialsManager::SpecialNames() const { + CheckPendingSpecialsTypes(); + std::vector retval; + for (const auto& entry : m_specials) { + retval.push_back(entry.first); } + return retval; +} + +const Special* SpecialsManager::GetSpecial(const std::string& name) const { + CheckPendingSpecialsTypes(); + auto it = m_specials.find(name); + return it != m_specials.end() ? it->second.get() : nullptr; +} + +unsigned int SpecialsManager::GetCheckSum() const { + CheckPendingSpecialsTypes(); + unsigned int retval{0}; + for (auto const& name_type_pair : m_specials) + CheckSums::CheckSumCombine(retval, name_type_pair); + CheckSums::CheckSumCombine(retval, m_specials.size()); + DebugLogger() << "SpecialsManager checksum: " << retval; + return retval; +} + +void SpecialsManager::SetSpecialsTypes(Pending::Pending&& future) +{ m_pending_types = std::move(future); } + +void SpecialsManager::CheckPendingSpecialsTypes() const { + if (!m_pending_types) + return; + + Pending::SwapPending(m_pending_types, m_specials); +} + +SpecialsManager& GetSpecialsManager() { + static SpecialsManager special_manager; + return special_manager; } ///////////////////////////////////////////////// // Special // ///////////////////////////////////////////////// -Special::~Special() { - delete m_stealth; - delete m_initial_capacity; - delete m_location; +Special::Special(const std::string& name, const std::string& description, + std::unique_ptr>&& stealth, + std::vector>&& effects, + double spawn_rate /*= 1.0*/, int spawn_limit /*= 99999*/, + std::unique_ptr>&& initial_capaicty /*= nullptr*/, + std::unique_ptr&& location /*= nullptr*/, + const std::string& graphic /*= ""*/) : + m_name(name), + m_description(description), + m_stealth(std::move(stealth)), + m_effects(), + m_spawn_rate(spawn_rate), + m_spawn_limit(spawn_limit), + m_initial_capacity(std::move(initial_capaicty)), + m_location(std::move(location)), + m_graphic(graphic) +{ + for (auto&& effect : effects) + m_effects.emplace_back(std::move(effect)); + + Init(); } +Special::~Special() +{} + std::string Special::Description() const { std::stringstream result; result << UserString(m_description) << "\n"; - for (std::shared_ptr effect : m_effects) { + for (auto& effect : m_effects) { const std::string& description = effect->GetDescription(); if (!description.empty()) { @@ -89,7 +107,7 @@ std::string Special::Description() const { void Special::Init() { if (m_stealth) m_stealth->SetTopLevelContent(m_name); - for (std::shared_ptr effect : m_effects) { + for (auto& effect : m_effects) { effect->SetTopLevelContent(m_name); } if (m_initial_capacity) @@ -98,48 +116,37 @@ void Special::Init() { m_location->SetTopLevelContent(m_name); } -std::string Special::Dump() const { - std::string retval = DumpIndent() + "Special\n"; - ++g_indent; - retval += DumpIndent() + "name = \"" + m_name + "\"\n"; - retval += DumpIndent() + "description = \"" + m_description + "\"\n"; +std::string Special::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Special\n"; + retval += DumpIndent(ntabs+1) + "name = \"" + m_name + "\"\n"; + retval += DumpIndent(ntabs+1) + "description = \"" + m_description + "\"\n"; if (m_stealth) - retval += DumpIndent() + "stealth = " + m_stealth->Dump() + "\n"; + retval += DumpIndent(ntabs+1) + "stealth = " + m_stealth->Dump(ntabs+1) + "\n"; - retval += DumpIndent() + "spawnrate = " + std::to_string(m_spawn_rate) + "\n" - + DumpIndent() + "spawnlimit = " + std::to_string(m_spawn_limit) + "\n"; + retval += DumpIndent(ntabs+1) + "spawnrate = " + std::to_string(m_spawn_rate) + "\n" + + DumpIndent(ntabs+1) + "spawnlimit = " + std::to_string(m_spawn_limit) + "\n"; if (m_initial_capacity) { - retval += DumpIndent() + "initialcapacity = "; - ++g_indent; - retval += m_initial_capacity->Dump(); - --g_indent; + retval += DumpIndent(ntabs+1) + "initialcapacity = "; + retval += m_initial_capacity->Dump(ntabs+2); } if (m_location) { - retval += DumpIndent() + "location =\n"; - ++g_indent; - retval += m_location->Dump(); - --g_indent; + retval += DumpIndent(ntabs+1) + "location =\n"; + retval += m_location->Dump(ntabs+2); } if (m_effects.size() == 1) { - retval += DumpIndent() + "effectsgroups =\n"; - ++g_indent; - retval += m_effects[0]->Dump(); - --g_indent; + retval += DumpIndent(ntabs+1) + "effectsgroups =\n"; + retval += m_effects[0]->Dump(ntabs+2); } else { - retval += DumpIndent() + "effectsgroups = [\n"; - ++g_indent; - for (std::shared_ptr effect : m_effects) { - retval += effect->Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; + retval += DumpIndent(ntabs+1) + "effectsgroups = [\n"; + for (auto& effect : m_effects) + retval += effect->Dump(ntabs+2); + retval += DumpIndent(ntabs+1) + "]\n"; } - retval += DumpIndent() + "graphic = \"" + m_graphic + "\"\n"; - --g_indent; + retval += DumpIndent(ntabs+1) + "graphic = \"" + m_graphic + "\"\n"; return retval; } @@ -147,13 +154,11 @@ float Special::InitialCapacity(int object_id) const { if (!m_initial_capacity) return 0.0f; - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); if (!obj) return 0.0f; - ScriptingContext context(obj); - - return m_initial_capacity->Eval(context); + return m_initial_capacity->Eval(ScriptingContext(obj)); } unsigned int Special::GetCheckSum() const { @@ -173,7 +178,7 @@ unsigned int Special::GetCheckSum() const { } const Special* GetSpecial(const std::string& name) -{ return GetSpecialManager().GetSpecial(name); } +{ return GetSpecialsManager().GetSpecial(name); } std::vector SpecialNames() -{ return GetSpecialManager().SpecialNames(); } +{ return GetSpecialsManager().SpecialNames(); } diff --git a/universe/Special.h b/universe/Special.h index d7f2d34d174..1bd807abe3b 100644 --- a/universe/Special.h +++ b/universe/Special.h @@ -2,22 +2,27 @@ #define _Special_h_ -#include "ValueRefFwd.h" - #include +#include #include "../util/Export.h" +#include "../util/Pending.h" #include #include #include +#include namespace Effect { class EffectsGroup; } namespace Condition { - struct ConditionBase; + struct Condition; +} +namespace ValueRef { + template + struct ValueRef; } /** A predefined set of EffectsGroups that can be attached to a UniverseObject @@ -29,43 +34,33 @@ class FO_COMMON_API Special { public: /** \name Structors */ //@{ Special(const std::string& name, const std::string& description, - ValueRef::ValueRefBase* stealth, - const std::vector>& effects, + std::unique_ptr>&& stealth, + std::vector>&& effects, double spawn_rate = 1.0, int spawn_limit = 99999, - ValueRef::ValueRefBase* initial_capaicty = nullptr, - Condition::ConditionBase* location = nullptr, - const std::string& graphic = "") : - m_name(name), - m_description(description), - m_stealth(stealth), - m_effects(effects), - m_spawn_rate(spawn_rate), - m_spawn_limit(spawn_limit), - m_initial_capacity(initial_capaicty), - m_location(location), - m_graphic(graphic) - { Init(); } + std::unique_ptr>&& initial_capaicty = nullptr, + std::unique_ptr&& location = nullptr, + const std::string& graphic = ""); ~Special(); //@} /** \name Accessors */ //@{ - const std::string& Name() const { return m_name; } ///< returns the unique name for this type of special - std::string Description() const; ///< returns a text description of this type of special - std::string Dump() const; ///< returns a data file format representation of this object - const ValueRef::ValueRefBase* Stealth() const { return m_stealth; } ///< returns the stealth of the special, which determines how easily it is seen by empires + const std::string& Name() const { return m_name; } ///< returns the unique name for this type of special + std::string Description() const; ///< returns a text description of this type of special + std::string Dump(unsigned short ntabs = 0) const; ///< returns a data file format representation of this object + const ValueRef::ValueRef* Stealth() const { return m_stealth.get(); } ///< returns the stealth of the special, which determines how easily it is seen by empires /** Returns the EffectsGroups that encapsulate the effects that specials of this type have. */ const std::vector>& Effects() const { return m_effects; } - float SpawnRate() const { return m_spawn_rate; } - int SpawnLimit() const { return m_spawn_limit; } - const ValueRef::ValueRefBase* InitialCapacity() const { return m_initial_capacity; } ///< returns the ValueRef to use to set the initial capacity of the special when placed - float InitialCapacity(int object_id) const; ///< evaluates initial apacity ValueRef using the object with specified \a object_id as the object on which the special will be placed - const Condition::ConditionBase* Location() const { return m_location; } ///< returns the condition that determines whether an UniverseObject can have this special applied during universe creation - const std::string& Graphic() const { return m_graphic; }; ///< returns the name of the grapic file for this special + float SpawnRate() const { return m_spawn_rate; } + int SpawnLimit() const { return m_spawn_limit; } + const ValueRef::ValueRef* InitialCapacity() const { return m_initial_capacity.get(); }///< returns the ValueRef to use to set the initial capacity of the special when placed + float InitialCapacity(int object_id) const; ///< evaluates initial apacity ValueRef using the object with specified \a object_id as the object on which the special will be placed + const Condition::Condition* Location() const { return m_location.get(); } ///< returns the condition that determines whether an UniverseObject can have this special applied during universe creation + const std::string& Graphic() const { return m_graphic; }; ///< returns the name of the grapic file for this special /** Returns a number, calculated from the contained data, which should be * different for different contained data, and must be the same for @@ -73,26 +68,24 @@ class FO_COMMON_API Special { * and executions of the program and the function. Useful to verify that * the parsed content is consistent without sending it all between * clients and server. */ - unsigned int GetCheckSum() const; + unsigned int GetCheckSum() const; //@} private: - void Init(); - - std::string m_name; - std::string m_description; - ValueRef::ValueRefBase* m_stealth; - - std::vector> m_effects; - - float m_spawn_rate; - int m_spawn_limit; - ValueRef::ValueRefBase* m_initial_capacity; - Condition::ConditionBase* m_location; - std::string m_graphic; + void Init(); + + std::string m_name; + std::string m_description; + std::unique_ptr> m_stealth; + std::vector> m_effects; + float m_spawn_rate = 0.0f; + int m_spawn_limit = 99999; + std::unique_ptr> m_initial_capacity; + std::unique_ptr m_location; + std::string m_graphic; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -104,7 +97,7 @@ FO_COMMON_API const Special* GetSpecial(const std::string& name); FO_COMMON_API std::vector SpecialNames(); // template implementations -template +template void Special::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_name) @@ -118,4 +111,37 @@ void Special::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_graphic); } +/** Look up table for specials.*/ +class FO_COMMON_API SpecialsManager { +public: + using SpecialsTypeMap = std::map>; + + /** \name Structors */ //@{ + SpecialsManager(); + ~SpecialsManager(); + //@} + + /** \name Accessors */ //@{ + int NumSpecials() const { return m_specials.size(); } + std::vector SpecialNames() const; + const Special* GetSpecial(const std::string& name) const; + unsigned int GetCheckSum() const; + //@} + + /** Sets types to the value of \p future. */ + void SetSpecialsTypes(Pending::Pending&& future); + +private: + /** Assigns any m_pending_types to m_specials. */ + void CheckPendingSpecialsTypes() const; + + /** Future types being parsed by parser. mutable so that it can + be assigned to m_species_types when completed.*/ + mutable boost::optional> m_pending_types = boost::none; + + mutable SpecialsTypeMap m_specials; +}; + +FO_COMMON_API SpecialsManager& GetSpecialsManager(); + #endif // _Special_h_ diff --git a/universe/Species.cpp b/universe/Species.cpp index bf406692504..a2a47b8a444 100644 --- a/universe/Species.cpp +++ b/universe/Species.cpp @@ -1,18 +1,18 @@ #include "Species.h" -#include "Condition.h" +#include "Conditions.h" #include "Effect.h" #include "PopCenter.h" #include "Ship.h" #include "UniverseObject.h" -#include "ValueRef.h" +#include "ValueRefs.h" #include "Enums.h" -#include "../parse/Parse.h" #include "../util/OptionsDB.h" #include "../util/Logger.h" #include "../util/Random.h" #include "../util/AppInterface.h" #include "../util/CheckSums.h" +#include "../util/ScopedTimer.h" #include @@ -23,24 +23,24 @@ // FocusType // ///////////////////////////////////////////////// FocusType::FocusType(const std::string& name, const std::string& description, - const Condition::ConditionBase* location, const std::string& graphic) : + std::unique_ptr&& location, + const std::string& graphic) : m_name(name), m_description(description), - m_location(location), + m_location(std::move(location)), m_graphic(graphic) {} -std::string FocusType::Dump() const { - std::string retval = DumpIndent() + "FocusType\n"; - ++g_indent; - retval += DumpIndent() + "name = \"" + m_name + "\"\n"; - retval += DumpIndent() + "description = \"" + m_description + "\"\n"; - retval += DumpIndent() + "location = \n"; - ++g_indent; - retval += m_location->Dump(); - --g_indent; - retval += DumpIndent() + "graphic = \"" + m_graphic + "\"\n"; - --g_indent; +FocusType::~FocusType() +{} + +std::string FocusType::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "FocusType\n"; + retval += DumpIndent(ntabs+1) + "name = \"" + m_name + "\"\n"; + retval += DumpIndent(ntabs+1) + "description = \"" + m_description + "\"\n"; + retval += DumpIndent(ntabs+1) + "location = \n"; + retval += m_location->Dump(ntabs+2); + retval += DumpIndent(ntabs+1) + "graphic = \"" + m_graphic + "\"\n"; return retval; } @@ -87,77 +87,124 @@ namespace { } } +Species::Species(const SpeciesStrings& strings, + const std::vector& foci, + const std::string& preferred_focus, + const std::map& planet_environments, + std::vector>&& effects, + std::unique_ptr&& combat_targets, + const SpeciesParams& params, + const std::set& tags, + const std::string& graphic) : + m_name(strings.name), + m_description(strings.desc), + m_gameplay_description(strings.gameplay_desc), + m_foci(foci), + m_preferred_focus(preferred_focus), + m_planet_environments(planet_environments), + m_combat_targets(std::move(combat_targets)), + m_playable(params.playable), + m_native(params.native), + m_can_colonize(params.can_colonize), + m_can_produce_ships(params.can_produce_ships), + m_graphic(graphic) +{ + for (auto&& effect : effects) + m_effects.emplace_back(std::move(effect)); + + Init(); + + for (const std::string& tag : tags) + m_tags.insert(boost::to_upper_copy(tag)); +} + Species::~Species() -{ delete m_location; } +{} void Species::Init() { - if (m_location) - m_location->SetTopLevelContent(this->m_name); - for (std::shared_ptr effect : m_effects) { + for (auto& effect : m_effects) { effect->SetTopLevelContent(m_name); } + + if (!m_location) { + // set up a Condition structure to match popcenters that have + // (not uninhabitable) environment for this species + std::vector>> environments_vec; + environments_vec.push_back( + std::make_unique>( ::PE_UNINHABITABLE)); + auto this_species_name_ref = + std::make_unique>(m_name); // m_name specifies this species + auto enviro_cond = std::unique_ptr( + std::make_unique( + std::unique_ptr( + std::make_unique( + std::move(environments_vec), std::move(this_species_name_ref))))); + + auto type_cond = std::unique_ptr(std::make_unique( + std::make_unique>( ::OBJ_POP_CENTER))); + + std::vector> operands; + operands.push_back(std::move(enviro_cond)); + operands.push_back(std::move(type_cond)); + + m_location = std::unique_ptr(std::make_unique(std::move(operands))); + } + m_location->SetTopLevelContent(m_name); + + if (m_combat_targets) + m_combat_targets->SetTopLevelContent(m_name); + + TraceLogger() << "Species::Init: " << Dump(); } -std::string Species::Dump() const { - std::string retval = DumpIndent() + "Species\n"; - ++g_indent; - retval += DumpIndent() + "name = \"" + m_name + "\"\n"; - retval += DumpIndent() + "description = \"" + m_description + "\"\n"; - retval += DumpIndent() + "gameplay_description = \"" + m_gameplay_description + "\"\n"; +std::string Species::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Species\n"; + retval += DumpIndent(ntabs+1) + "name = \"" + m_name + "\"\n"; + retval += DumpIndent(ntabs+1) + "description = \"" + m_description + "\"\n"; + retval += DumpIndent(ntabs+1) + "gameplay_description = \"" + m_gameplay_description + "\"\n"; if (m_playable) - retval += DumpIndent() + "Playable\n"; + retval += DumpIndent(ntabs+1) + "Playable\n"; if (m_native) - retval += DumpIndent() + "Native\n"; + retval += DumpIndent(ntabs+1) + "Native\n"; if (m_can_produce_ships) - retval += DumpIndent() + "CanProduceShips\n"; + retval += DumpIndent(ntabs+1) + "CanProduceShips\n"; if (m_can_colonize) - retval += DumpIndent() + "CanColonize\n"; + retval += DumpIndent(ntabs+1) + "CanColonize\n"; if (m_foci.size() == 1) { - retval += DumpIndent() + "foci =\n"; - m_foci.begin()->Dump(); + retval += DumpIndent(ntabs+1) + "foci =\n"; + m_foci.begin()->Dump(ntabs+1); } else { - retval += DumpIndent() + "foci = [\n"; - ++g_indent; - for (const FocusType& focus : m_foci) { - retval += focus.Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; + retval += DumpIndent(ntabs+1) + "foci = [\n"; + for (const FocusType& focus : m_foci) + retval += focus.Dump(ntabs+2); + retval += DumpIndent(ntabs+1) + "]\n"; } if (m_effects.size() == 1) { - retval += DumpIndent() + "effectsgroups =\n"; - ++g_indent; - retval += m_effects[0]->Dump(); - --g_indent; + retval += DumpIndent(ntabs+1) + "effectsgroups =\n"; + retval += m_effects[0]->Dump(ntabs+2); } else { - retval += DumpIndent() + "effectsgroups = [\n"; - ++g_indent; - for (std::shared_ptr effect : m_effects) { - retval += effect->Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; + retval += DumpIndent(ntabs+1) + "effectsgroups = [\n"; + for (auto& effect : m_effects) + retval += effect->Dump(ntabs+2); + retval += DumpIndent(ntabs+1) + "]\n"; } + if (m_combat_targets) + retval += DumpIndent(ntabs+1) + "combatTargets = " + m_combat_targets->Dump(ntabs+2); if (m_planet_environments.size() == 1) { - retval += DumpIndent() + "environments =\n"; - ++g_indent; - retval += DumpIndent() + "type = " + PlanetTypeToString(m_planet_environments.begin()->first) - + " environment = " + PlanetEnvironmentToString(m_planet_environments.begin()->second) - + "\n"; - --g_indent; + retval += DumpIndent(ntabs+1) + "environments =\n"; + retval += DumpIndent(ntabs+2) + "type = " + PlanetTypeToString(m_planet_environments.begin()->first) + + " environment = " + PlanetEnvironmentToString(m_planet_environments.begin()->second) + + "\n"; } else { - retval += DumpIndent() + "environments = [\n"; - ++g_indent; - for (const std::map::value_type& entry : m_planet_environments) { - retval += DumpIndent() + "type = " + PlanetTypeToString(entry.first) - + " environment = " + PlanetEnvironmentToString(entry.second) - + "\n"; + retval += DumpIndent(ntabs+1) + "environments = [\n"; + for (const auto& entry : m_planet_environments) { + retval += DumpIndent(ntabs+2) + "type = " + PlanetTypeToString(entry.first) + + " environment = " + PlanetEnvironmentToString(entry.second) + + "\n"; } - --g_indent; - retval += DumpIndent() + "]\n"; + retval += DumpIndent(ntabs+1) + "]\n"; } - retval += DumpIndent() + "graphic = \"" + m_graphic + "\"\n"; - --g_indent; + retval += DumpIndent(ntabs+1) + "graphic = \"" + m_graphic + "\"\n"; return retval; } @@ -168,7 +215,7 @@ std::string Species::GameplayDescription() const { bool requires_separator = true; - for (std::shared_ptr effect : m_effects) { + for (auto& effect : m_effects) { const std::string& description = effect->GetDescription(); if (description.empty()) continue; @@ -184,27 +231,8 @@ std::string Species::GameplayDescription() const { return result.str(); } -const Condition::ConditionBase* Species::Location() const { - if (!m_location) { - // set up a Condition structure to match popcenters that have (not uninhabitable) environment for this species - std::vector*> environments_vec; - environments_vec.push_back(new ValueRef::Constant( ::PE_UNINHABITABLE)); - ValueRef::Constant* this_species_name_ref = new ValueRef::Constant(m_name); // m_name specifies this species - Condition::ConditionBase* enviro_cond = new Condition::Not(new Condition::PlanetEnvironment(environments_vec, this_species_name_ref)); - - Condition::ConditionBase* type_cond = new Condition::Type(new ValueRef::Constant( ::OBJ_POP_CENTER)); - - std::vector operands; - operands.push_back(enviro_cond); - operands.push_back(type_cond); - - m_location = new Condition::And(operands); - } - return m_location; -} - PlanetEnvironment Species::GetPlanetEnvironment(PlanetType planet_type) const { - std::map::const_iterator it = m_planet_environments.find(planet_type); + auto it = m_planet_environments.find(planet_type); if (it == m_planet_environments.end()) return PE_UNINHABITABLE; else @@ -226,8 +254,7 @@ namespace { } } -PlanetType Species::NextBetterPlanetType(PlanetType initial_planet_type) const -{ +PlanetType Species::NextBetterPlanetType(PlanetType initial_planet_type) const { // some types can't be terraformed if (initial_planet_type == PT_GASGIANT) return PT_GASGIANT; @@ -244,7 +271,7 @@ PlanetType Species::NextBetterPlanetType(PlanetType initial_planet_type) const // determine which environment rating is the best available for this species PlanetEnvironment best_environment = PE_UNINHABITABLE; //std::set best_types; - for (const std::map::value_type& entry : m_planet_environments) { + for (const auto& entry : m_planet_environments) { if (entry.second == best_environment) { //best_types.insert(entry.first); } else if (entry.second > best_environment) { @@ -279,16 +306,16 @@ PlanetType Species::NextBetterPlanetType(PlanetType initial_planet_type) const } void Species::AddHomeworld(int homeworld_id) { - if (!GetUniverseObject(homeworld_id)) + if (!Objects().get(homeworld_id)) DebugLogger() << "Species asked to add homeworld id " << homeworld_id << " but there is no such object in the Universe"; - if (m_homeworlds.find(homeworld_id) != m_homeworlds.end()) + if (m_homeworlds.count(homeworld_id)) return; m_homeworlds.insert(homeworld_id); // TODO if needed: StateChangedSignal(); } void Species::RemoveHomeworld(int homeworld_id) { - if (m_homeworlds.find(homeworld_id) == m_homeworlds.end()) { + if (!m_homeworlds.count(homeworld_id)) { DebugLogger() << "Species asked to remove homeworld id " << homeworld_id << " but doesn't have that id as a homeworld"; return; } @@ -325,6 +352,7 @@ unsigned int Species::GetCheckSum() const { CheckSums::CheckSumCombine(retval, m_foci); CheckSums::CheckSumCombine(retval, m_preferred_focus); CheckSums::CheckSumCombine(retval, m_planet_environments); + CheckSums::CheckSumCombine(retval, m_combat_targets); CheckSums::CheckSumCombine(retval, m_effects); CheckSums::CheckSumCombine(retval, m_location); CheckSums::CheckSumCombine(retval, m_playable); @@ -344,57 +372,36 @@ unsigned int Species::GetCheckSum() const { SpeciesManager* SpeciesManager::s_instance = nullptr; bool SpeciesManager::PlayableSpecies::operator()( - const std::map::value_type& species_map_iterator) const -{ return species_map_iterator.second->Playable(); } + const std::map>::value_type& species_entry) const +{ return species_entry.second->Playable(); } bool SpeciesManager::NativeSpecies::operator()( - const std::map::value_type& species_map_iterator) const -{ return species_map_iterator.second->Native(); } + const std::map>::value_type& species_entry) const +{ return species_entry.second->Native(); } SpeciesManager::SpeciesManager() { if (s_instance) throw std::runtime_error("Attempted to create more than one SpeciesManager."); - try { - parse::species(m_species); - } catch (const std::exception& e) { - ErrorLogger() << "Failed parsing species: error: " << e.what(); - throw e; - } - - TraceLogger() << "Species:"; - for (const auto& entry : m_species) { - const Species* s = entry.second; - TraceLogger() << " ... " << s->Name() << " \t" << - (s->Playable() ? "Playable " : " ") << - (s->Native() ? "Native " : " ") << - (s->CanProduceShips() ? "CanProduceShips " : " ") << - (s->CanColonize() ? "CanColonize " : " "); - } - // Only update the global pointer on sucessful construction. s_instance = this; - - DebugLogger() << "SpeciesManager checksum: " << GetCheckSum(); -} - -SpeciesManager::~SpeciesManager() { - for (std::map::value_type& entry : m_species) - delete entry.second; } const Species* SpeciesManager::GetSpecies(const std::string& name) const { - std::map::const_iterator it = m_species.find(name); - return it != m_species.end() ? it->second : nullptr; + CheckPendingSpeciesTypes(); + auto it = m_species.find(name); + return it != m_species.end() ? it->second.get() : nullptr; } Species* SpeciesManager::GetSpecies(const std::string& name) { - std::map::iterator it = m_species.find(name); - return it != m_species.end() ? it->second : nullptr; + CheckPendingSpeciesTypes(); + auto it = m_species.find(name); + return it != m_species.end() ? it->second.get() : nullptr; } int SpeciesManager::GetSpeciesID(const std::string& name) const { - iterator it = m_species.find(name); + CheckPendingSpeciesTypes(); + auto it = m_species.find(name); if (it == m_species.end()) return -1; return std::distance(m_species.begin(), it); @@ -405,29 +412,60 @@ SpeciesManager& SpeciesManager::GetSpeciesManager() { return manager; } -SpeciesManager::iterator SpeciesManager::begin() const -{ return m_species.begin(); } +void SpeciesManager::SetSpeciesTypes(Pending::Pending>&& future) +{ m_pending_types = std::move(future); } + +void SpeciesManager::CheckPendingSpeciesTypes() const { + if (!m_pending_types) { + if (m_species.empty()) + throw; + return; + } + + auto container = std::make_pair(std::move(m_species), m_census_order); -SpeciesManager::iterator SpeciesManager::end() const -{ return m_species.end(); } + Pending::SwapPending(m_pending_types, container); + + m_species = std::move(container.first); + m_census_order = std::move(container.second); +} + +SpeciesManager::iterator SpeciesManager::begin() const { + CheckPendingSpeciesTypes(); + return m_species.begin(); +} + +SpeciesManager::iterator SpeciesManager::end() const { + CheckPendingSpeciesTypes(); + return m_species.end(); +} SpeciesManager::playable_iterator SpeciesManager::playable_begin() const -{ return playable_iterator(PlayableSpecies(), m_species.begin(), m_species.end()); } +{ return playable_iterator(PlayableSpecies(), begin(), end()); } SpeciesManager::playable_iterator SpeciesManager::playable_end() const -{ return playable_iterator(PlayableSpecies(), m_species.end(), m_species.end()); } +{ return playable_iterator(PlayableSpecies(), end(), end()); } SpeciesManager::native_iterator SpeciesManager::native_begin() const -{ return native_iterator(NativeSpecies(), m_species.begin(), m_species.end()); } +{ return native_iterator(NativeSpecies(), begin(), end()); } SpeciesManager::native_iterator SpeciesManager::native_end() const -{ return native_iterator(NativeSpecies(), m_species.end(), m_species.end()); } +{ return native_iterator(NativeSpecies(), end(), end()); } -bool SpeciesManager::empty() const -{ return m_species.empty(); } +const SpeciesManager::CensusOrder& SpeciesManager::census_order() const { + CheckPendingSpeciesTypes(); + return m_census_order; +} -int SpeciesManager::NumSpecies() const -{ return m_species.size(); } +bool SpeciesManager::empty() const { + CheckPendingSpeciesTypes(); + return m_species.empty(); +} + +int SpeciesManager::NumSpecies() const { + CheckPendingSpeciesTypes(); + return m_species.size(); +} int SpeciesManager::NumPlayableSpecies() const { return std::distance(playable_begin(), playable_end()); } @@ -440,11 +478,12 @@ namespace { } const std::string& SpeciesManager::RandomSpeciesName() const { + CheckPendingSpeciesTypes(); if (m_species.empty()) return EMPTY_STRING; int species_idx = RandSmallInt(0, static_cast(m_species.size()) - 1); - return std::next(m_species.begin(), species_idx)->first; + return std::next(begin(), species_idx)->first; } const std::string& SpeciesManager::RandomPlayableSpeciesName() const { @@ -465,20 +504,22 @@ const std::string& SpeciesManager::SequentialPlayableSpeciesName(int id) const { } void SpeciesManager::ClearSpeciesHomeworlds() { - for (std::map::value_type& entry : m_species) + CheckPendingSpeciesTypes(); + for (auto& entry : m_species) entry.second->SetHomeworlds(std::set()); } void SpeciesManager::SetSpeciesHomeworlds(const std::map>& species_homeworld_ids) { + CheckPendingSpeciesTypes(); ClearSpeciesHomeworlds(); - for (const std::map>::value_type& entry : species_homeworld_ids) { + for (auto& entry : species_homeworld_ids) { const std::string& species_name = entry.first; const std::set& homeworlds = entry.second; Species* species = nullptr; - std::map::iterator species_it = m_species.find(species_name); - if (species_it != m_species.end()) - species = species_it->second; + auto species_it = m_species.find(species_name); + if (species_it != end()) + species = species_it->second.get(); if (species) { species->SetHomeworlds(homeworlds); @@ -501,10 +542,11 @@ void SpeciesManager::SetSpeciesSpeciesOpinion(const std::string& opinionated_spe { m_species_species_opinions[opinionated_species][rated_species] = opinion; } std::map> SpeciesManager::GetSpeciesHomeworldsMap(int encoding_empire/* = ALL_EMPIRES*/) const { + CheckPendingSpeciesTypes(); std::map> retval; - for (const std::map::value_type& entry : m_species) { + for (const auto& entry : m_species) { const std::string species_name = entry.first; - const Species* species = entry.second; + const Species* species = entry.second.get(); if (!species) { ErrorLogger() << "SpeciesManager::GetSpeciesHomeworldsMap found a null species pointer in SpeciesManager?!"; continue; @@ -522,22 +564,22 @@ const std::map>& SpeciesManager::GetSp { return m_species_species_opinions; } float SpeciesManager::SpeciesEmpireOpinion(const std::string& species_name, int empire_id) const { - std::map>::const_iterator sp_it = m_species_empire_opinions.find(species_name); + auto sp_it = m_species_empire_opinions.find(species_name); if (sp_it == m_species_empire_opinions.end()) return 0.0f; const std::map& emp_map = sp_it->second; - std::map::const_iterator emp_it = emp_map.find(empire_id); + auto emp_it = emp_map.find(empire_id); if (emp_it == emp_map.end()) return 0.0f; return emp_it->second; } float SpeciesManager::SpeciesSpeciesOpinion(const std::string& opinionated_species_name, const std::string& rated_species_name) const { - std::map>::const_iterator sp_it = m_species_species_opinions.find(opinionated_species_name); + auto sp_it = m_species_species_opinions.find(opinionated_species_name); if (sp_it == m_species_species_opinions.end()) return 0.0f; - const std::map& ra_sp_map = sp_it->second; - std::map::const_iterator ra_sp_it = ra_sp_map.find(rated_species_name); + const auto& ra_sp_map = sp_it->second; + auto ra_sp_it = ra_sp_map.find(rated_species_name); if (ra_sp_it == ra_sp_map.end()) return 0.0f; return ra_sp_it->second; @@ -551,12 +593,12 @@ void SpeciesManager::ClearSpeciesOpinions() { void SpeciesManager::UpdatePopulationCounter() { // ships of each species and design m_species_object_populations.clear(); - for (const std::map>::value_type& entry : Objects().ExistingObjects()) { - std::shared_ptr obj = entry.second; + for (const auto& entry : Objects().ExistingObjects()) { + auto obj = entry.second; if (obj->ObjectType() != OBJ_PLANET && obj->ObjectType() != OBJ_POP_CENTER) continue; - std::shared_ptr pop_center = std::dynamic_pointer_cast(obj); + auto pop_center = std::dynamic_pointer_cast(obj); if (!pop_center) continue; @@ -579,11 +621,13 @@ std::map>& SpeciesManager::SpeciesShipsD { return m_species_species_ships_destroyed; } unsigned int SpeciesManager::GetCheckSum() const { + CheckPendingSpeciesTypes(); unsigned int retval{0}; for (auto const& name_type_pair : m_species) CheckSums::CheckSumCombine(retval, name_type_pair); CheckSums::CheckSumCombine(retval, m_species.size()); + DebugLogger() << "SpeciesManager checksum: " << retval; return retval; } diff --git a/universe/Species.h b/universe/Species.h index 5cd2ad37201..2931ac4acf2 100644 --- a/universe/Species.h +++ b/universe/Species.h @@ -5,10 +5,12 @@ #include "EnumsFwd.h" #include "../util/Export.h" #include "../util/Serialize.h" +#include "../util/Pending.h" #include #include #include +#include #include #include @@ -18,7 +20,7 @@ namespace Condition { - struct ConditionBase; + struct Condition; } namespace Effect { class EffectsGroup; @@ -43,15 +45,18 @@ class FO_COMMON_API FocusType { {} FocusType(const std::string& name, const std::string& description, - const Condition::ConditionBase* location, const std::string& graphic); + std::unique_ptr&& location, + const std::string& graphic); + + ~FocusType(); //@} /** \name Accessors */ //@{ const std::string& Name() const { return m_name; } ///< returns the name for this focus type const std::string& Description() const { return m_description; } ///< returns a text description of this focus type - const Condition::ConditionBase* Location() const { return m_location.get(); }///< returns the condition that determines whether an UniverseObject can use this FocusType + const Condition::Condition* Location() const { return m_location.get(); }///< returns the condition that determines whether an UniverseObject can use this FocusType const std::string& Graphic() const { return m_graphic; } ///< returns the name of the grapic file for this focus type - std::string Dump() const; ///< returns a data file format representation of this object + std::string Dump(unsigned short ntabs = 0) const; ///< returns a data file format representation of this object /** Returns a number, calculated from the contained data, which should be * different for different contained data, and must be the same for @@ -65,17 +70,13 @@ class FO_COMMON_API FocusType { private: std::string m_name; std::string m_description; - std::shared_ptr m_location; + std::shared_ptr m_location; std::string m_graphic; }; /** Used by parser due to limits on number of sub-items per parsed main item. */ struct SpeciesParams { - SpeciesParams() : - playable(false), - native(false), - can_colonize(false), - can_produce_ships(false) + SpeciesParams() {} SpeciesParams(bool playable_, bool native_, bool can_colonize_, bool can_produce_ships_) : playable(playable_), @@ -83,18 +84,15 @@ struct SpeciesParams { can_colonize(can_colonize_), can_produce_ships(can_produce_ships_) {} - bool playable; - bool native; - bool can_colonize; - bool can_produce_ships; + bool playable = false; + bool native = false; + bool can_colonize = false; + bool can_produce_ships = false; }; /** Used by parser due to limits on number of sub-items per parsed main item. */ struct SpeciesStrings { - SpeciesStrings() : - name(), - desc(), - gameplay_desc() + SpeciesStrings() {} SpeciesStrings(const std::string& name_, const std::string& desc_, const std::string& gameplay_desc_) : @@ -102,9 +100,9 @@ struct SpeciesStrings { desc(desc_), gameplay_desc(gameplay_desc_) {} - std::string name; - std::string desc; - std::string gameplay_desc; + std::string name; + std::string desc; + std::string gameplay_desc; }; /** A predefined type of population that can exist on a PopulationCenter. @@ -119,29 +117,11 @@ class FO_COMMON_API Species { const std::vector& foci, const std::string& preferred_focus, const std::map& planet_environments, - const std::vector>& effects, + std::vector>&& effects, + std::unique_ptr&& combat_targets, const SpeciesParams& params, const std::set& tags, - const std::string& graphic) : - m_name(strings.name), - m_description(strings.desc), - m_gameplay_description(strings.gameplay_desc), - m_foci(foci), - m_preferred_focus(preferred_focus), - m_planet_environments(planet_environments), - m_effects(effects), - m_location(nullptr), - m_playable(params.playable), - m_native(params.native), - m_can_colonize(params.can_colonize), - m_can_produce_ships(params.can_produce_ships), - m_tags(), - m_graphic(graphic) - { - Init(); - for (const std::string& tag : tags) - m_tags.insert(boost::to_upper_copy(tag)); - } + const std::string& graphic); ~Species(); //@} @@ -156,14 +136,15 @@ class FO_COMMON_API Species { const std::map& EmpireOpinions() const { return m_empire_opinions; } ///< returns the positive/negative opinions of this species about empires const std::map& OtherSpeciesOpinions() const{ return m_other_species_opinions; }///< returns the positive/negative opinions of this species about other species - const Condition::ConditionBase* Location() const; + const Condition::Condition* Location() const { return m_location.get(); } ///< returns the condition determining what planets on which this species may spawn + const Condition::Condition* CombatTargets() const { return m_combat_targets.get(); } ///< returns the condition for possible targets. may be nullptr if no condition was specified. - std::string Dump() const; ///< returns a data file format representation of this object - const std::vector& Foci() const { return m_foci; } ///< returns the focus types this species can use - const std::string& PreferredFocus() const { return m_preferred_focus; } ///< returns the name of the planetary focus this species prefers. Default for new colonies and may affect happiness if on a different focus? + std::string Dump(unsigned short ntabs = 0) const; ///< returns a data file format representation of this object + const std::vector& Foci() const { return m_foci; } ///< returns the focus types this species can use + const std::string& PreferredFocus() const { return m_preferred_focus; } ///< returns the name of the planetary focus this species prefers. Default for new colonies and may affect happiness if on a different focus? const std::map& PlanetEnvironments() const { return m_planet_environments; } ///< returns a map from PlanetType to the PlanetEnvironment this Species has on that PlanetType - PlanetEnvironment GetPlanetEnvironment(PlanetType planet_type) const; ///< returns the PlanetEnvironment this species has on PlanetType \a planet_type - PlanetType NextBetterPlanetType(PlanetType initial_planet_type) const; ///< returns the next better PlanetType for this species from the \a initial_planet_type specified + PlanetEnvironment GetPlanetEnvironment(PlanetType planet_type) const; ///< returns the PlanetEnvironment this species has on PlanetType \a planet_type + PlanetType NextBetterPlanetType(PlanetType initial_planet_type) const; ///< returns the next better PlanetType for this species from the \a initial_planet_type specified /** Returns the EffectsGroups that encapsulate the effects that species of this type have. */ @@ -187,13 +168,13 @@ class FO_COMMON_API Species { //@} /** \name Mutators */ //@{ - void AddHomeworld(int homeworld_id); - void RemoveHomeworld(int homeworld_id); - void SetHomeworlds(const std::set& homeworld_ids); - void SetEmpireOpinions(const std::map& opinions); - void SetEmpireOpinion(int empire_id, double opinion); - void SetOtherSpeciesOpinions(const std::map& opinions); - void SetOtherSpeciesOpinion(const std::string& species_name, double opinion); + void AddHomeworld(int homeworld_id); + void RemoveHomeworld(int homeworld_id); + void SetHomeworlds(const std::set& homeworld_ids); + void SetEmpireOpinions(const std::map& opinions); + void SetEmpireOpinion(int empire_id, double opinion); + void SetOtherSpeciesOpinions(const std::map& opinions); + void SetOtherSpeciesOpinion(const std::string& species_name, double opinion); //@} private: @@ -211,9 +192,9 @@ class FO_COMMON_API Species { std::string m_preferred_focus; std::map m_planet_environments; - std::vector> m_effects; - - mutable Condition::ConditionBase* m_location; + std::vector> m_effects; + std::unique_ptr m_location; + std::unique_ptr m_combat_targets; bool m_playable; bool m_native; @@ -228,12 +209,14 @@ class FO_COMMON_API Species { class FO_COMMON_API SpeciesManager { private: struct FO_COMMON_API PlayableSpecies - { bool operator()(const std::map::value_type& species_map_iterator) const; }; + { bool operator()(const std::map>::value_type& species_entry) const; }; struct FO_COMMON_API NativeSpecies - { bool operator()(const std::map::value_type& species_map_iterator) const; }; + { bool operator()(const std::map>::value_type& species_entry) const; }; public: - typedef std::map::const_iterator iterator; + using SpeciesTypeMap = std::map>; + using CensusOrder = std::vector; + using iterator = SpeciesTypeMap::const_iterator; typedef boost::filter_iterator playable_iterator; typedef boost::filter_iterator native_iterator; @@ -258,6 +241,9 @@ class FO_COMMON_API SpeciesManager { native_iterator native_begin() const; native_iterator native_end() const; + /** returns an ordered list of tags that should be considered for census listings. */ + const CensusOrder& census_order() const; + /** returns true iff this SpeciesManager is empty. */ bool empty() const; @@ -317,36 +303,46 @@ class FO_COMMON_API SpeciesManager { /** sets all species to have no homeworlds. this is useful when generating * a new game, when any homeworlds species had in the previous game should * be removed before the new game's homeworlds are added. */ - void ClearSpeciesHomeworlds(); + void ClearSpeciesHomeworlds(); /** sets the opinions of species (indexed by name string) of empires (indexed * by id) as a double-valued number. */ - void SetSpeciesEmpireOpinions(const std::map>& species_empire_opinions); - void SetSpeciesEmpireOpinion(const std::string& species_name, int empire_id, float opinion); + void SetSpeciesEmpireOpinions(const std::map>& species_empire_opinions); + void SetSpeciesEmpireOpinion(const std::string& species_name, int empire_id, float opinion); /** sets the opinions of species (indexed by name string) of other species * (indexed by name string) as a double-valued number. */ - void SetSpeciesSpeciesOpinions(const std::map>& species_species_opinions); - void SetSpeciesSpeciesOpinion(const std::string& opinionated_species, const std::string& rated_species, float opinion); + void SetSpeciesSpeciesOpinions(const std::map>& species_species_opinions); + void SetSpeciesSpeciesOpinion(const std::string& opinionated_species, const std::string& rated_species, float opinion); /** clears all species opinion data */ - void ClearSpeciesOpinions(); + void ClearSpeciesOpinions(); - void UpdatePopulationCounter(); + void UpdatePopulationCounter(); std::map>& SpeciesObjectPopulations(int encoding_empire = ALL_EMPIRES); std::map>& SpeciesShipsDestroyed(int encoding_empire = ALL_EMPIRES); + + /** Sets species types to the value of \p future. */ + void SetSpeciesTypes(Pending::Pending>&& future); //@} private: SpeciesManager(); - ~SpeciesManager(); /** sets the homeworld ids of species in this SpeciesManager to those * specified in \a species_homeworld_ids */ - void SetSpeciesHomeworlds(const std::map>& species_homeworld_ids); + void SetSpeciesHomeworlds(const std::map>& species_homeworld_ids); + + /** Assigns any m_pending_types to m_species. */ + void CheckPendingSpeciesTypes() const; + + /** Future types being parsed by parser. mutable so that it can + be assigned to m_species_types when completed.*/ + mutable boost::optional>> m_pending_types = boost::none; - std::map m_species; + mutable SpeciesTypeMap m_species; + mutable CensusOrder m_census_order; std::map> m_species_empire_opinions; std::map> m_species_species_opinions; @@ -356,7 +352,7 @@ class FO_COMMON_API SpeciesManager { static SpeciesManager* s_instance; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/universe/System.cpp b/universe/System.cpp index 308aaccec33..2f3b3e0c49f 100644 --- a/universe/System.cpp +++ b/universe/System.cpp @@ -14,9 +14,7 @@ #include "../util/i18n.h" #include "../util/Logger.h" #include "../util/OptionsDB.h" - -#include -#include +#include "../util/AppInterface.h" System::System() : m_star(INVALID_STAR_TYPE) @@ -26,10 +24,8 @@ System::System(StarType star, const std::string& name, double x, double y) : UniverseObject(name, x, y), m_star(star) { - //DebugLogger() << "System::System(" << star << ", " << orbits << ", " << name << ", " << x << ", " << y << ")"; if (m_star < INVALID_STAR_TYPE || NUM_STAR_TYPES < m_star) - throw std::invalid_argument("System::System : Attempted to create a system \"" + - Name() + "\" with an invalid star type."); + m_star = INVALID_STAR_TYPE; m_orbits.assign(SYSTEM_ORBITS, INVALID_OBJECT_ID); @@ -42,10 +38,8 @@ System::System(StarType star, const std::map& lanes_and_holes, m_star(star), m_starlanes_wormholes(lanes_and_holes) { - //DebugLogger() << "System::System(" << star << ", " << orbits << ", (StarlaneMap), " << name << ", " << x << ", " << y << ")"; if (m_star < INVALID_STAR_TYPE || NUM_STAR_TYPES < m_star) - throw std::invalid_argument("System::System : Attempted to create a system \"" + - Name() + "\" with an invalid star type."); + m_star = INVALID_STAR_TYPE; m_orbits.assign(SYSTEM_ORBITS, INVALID_OBJECT_ID); @@ -83,7 +77,7 @@ void System::Copy(std::shared_ptr copied_object, int empir if (vis >= VIS_BASIC_VISIBILITY) { // add any visible lanes, without removing existing entries std::map visible_lanes_holes = copied_system->VisibleStarlanesWormholes(empire_id); - for (const std::map::value_type& entry : visible_lanes_holes) + for (const auto& entry : visible_lanes_holes) { this->m_starlanes_wormholes[entry.first] = entry.second; } // copy visible info of visible contained objects @@ -93,41 +87,40 @@ void System::Copy(std::shared_ptr copied_object, int empir size_t orbits_size = m_orbits.size(); m_orbits.clear(); m_orbits.assign(orbits_size, INVALID_OBJECT_ID); - for (int o = 0; o < static_cast(copied_system->m_orbits.size()); ++o) { + for (std::size_t o = 0; o < copied_system->m_orbits.size(); ++o) { int planet_id = copied_system->m_orbits[o]; - if (m_objects.find(planet_id) != m_objects.end()) + if (m_objects.count(planet_id)) m_orbits[o] = planet_id; } // copy visible contained object per-type info m_planets.clear(); - for (int planet_id : copied_system->m_planets) - { - if (m_objects.find(planet_id) != m_objects.end()) + for (int planet_id : copied_system->m_planets) { + if (m_objects.count(planet_id)) m_planets.insert(planet_id); } m_buildings.clear(); for (int building_id : copied_system->m_buildings) { - if (m_objects.find(building_id) != m_objects.end()) + if (m_objects.count(building_id)) m_buildings.insert(building_id); } m_fleets.clear(); for (int fleet_id : copied_system->m_fleets) { - if (m_objects.find(fleet_id) != m_objects.end()) + if (m_objects.count(fleet_id)) m_fleets.insert(fleet_id); } m_ships.clear(); for (int ship_id : copied_system->m_ships) { - if (m_objects.find(ship_id) != m_objects.end()) + if (m_objects.count(ship_id)) m_ships.insert(ship_id); } m_fields.clear(); for (int field_id : copied_system->m_fields) { - if (m_objects.find(field_id) != m_objects.end()) + if (m_objects.count(field_id)) m_fields.insert(field_id); } @@ -146,7 +139,7 @@ void System::Copy(std::shared_ptr copied_object, int empir /* conditional increment in deleting loop */) { int lane_end_sys_id = entry_it->first; - if (visible_lanes_holes.find(lane_end_sys_id) == visible_lanes_holes.end()) { + if (!visible_lanes_holes.count(lane_end_sys_id)) { entry_it = m_starlanes_wormholes.erase(entry_it); } else { ++entry_it; @@ -159,9 +152,9 @@ void System::Copy(std::shared_ptr copied_object, int empir UniverseObjectType System::ObjectType() const { return OBJ_SYSTEM; } -std::string System::Dump() const { +std::string System::Dump(unsigned short ntabs) const { std::stringstream os; - os << UniverseObject::Dump(); + os << UniverseObject::Dump(ntabs); os << " star type: " << m_star << " last combat on turn: " << m_last_turn_battle_here << " total orbits: " << m_orbits.size(); @@ -170,7 +163,7 @@ std::string System::Dump() const { os << " objects per orbit: "; int orbit_index = 0; - for (std::vector::const_iterator it = m_orbits.begin(); + for (auto it = m_orbits.begin(); it != m_orbits.end();) { os << "[" << orbit_index << "]" << *it; @@ -182,7 +175,7 @@ std::string System::Dump() const { } os << " starlanes: "; - for (std::map::const_iterator it = m_starlanes_wormholes.begin(); + for (auto it = m_starlanes_wormholes.begin(); it != m_starlanes_wormholes.end();) { int lane_end_id = it->first; @@ -191,7 +184,7 @@ std::string System::Dump() const { } os << " objects: "; - for (std::set::const_iterator it = m_objects.begin(); it != m_objects.end();) { + for (auto it = m_objects.begin(); it != m_objects.end();) { int obj_id = *it; ++it; if (obj_id == INVALID_OBJECT_ID) @@ -208,8 +201,8 @@ const std::string& System::ApparentName(int empire_id, bool blank_unexplored_and return this->PublicName(empire_id); // has the indicated empire ever detected this system? - const Universe::VisibilityTurnMap& vtm = GetUniverse().GetObjectVisibilityTurnMapByEmpire(this->ID(), empire_id); - if (vtm.find(VIS_PARTIAL_VISIBILITY) == vtm.end()) { + const auto& vtm = GetUniverse().GetObjectVisibilityTurnMapByEmpire(this->ID(), empire_id); + if (!vtm.count(VIS_PARTIAL_VISIBILITY)) { if (blank_unexplored_and_none) return EMPTY_STRING; @@ -221,7 +214,7 @@ const std::string& System::ApparentName(int empire_id, bool blank_unexplored_and if (m_star == STAR_NONE) { // determine if there are any planets in the system - for (const std::map>::value_type& entry : Objects().ExistingPlanets()) { + for (const auto& entry : Objects().ExistingPlanets()) { if (entry.second->SystemID() == this->ID()) return this->PublicName(empire_id); } @@ -240,25 +233,23 @@ StarType System::NextOlderStarType() const { if (m_star <= INVALID_STAR_TYPE || m_star >= NUM_STAR_TYPES) return INVALID_STAR_TYPE; if (m_star >= STAR_RED) - return m_star; // STAR_RED, STAR_NEUTRON, STAR_BLACK, STAR_NONE - if (m_star <= STAR_BLUE) - return STAR_BLUE; - return StarType(m_star + 1); + return m_star; // STAR_RED, STAR_NEUTRON, STAR_BLACK, STAR_NONE + return StarType(m_star + 1); // STAR_BLUE -> STAR_WHITE -> STAR_YELLOW -> STAR_ORANGE -> STAR_RED } StarType System::NextYoungerStarType() const { if (m_star <= INVALID_STAR_TYPE || m_star >= NUM_STAR_TYPES) return INVALID_STAR_TYPE; - if (m_star >= STAR_RED) - return m_star; // STAR_RED, STAR_NEUTRON, STAR_BLACK, STAR_NONE + if (m_star > STAR_RED) + return m_star; // STAR_NEUTRON, STAR_BLACK, STAR_NONE if (m_star <= STAR_BLUE) - return STAR_BLUE; - return StarType(m_star - 1); + return STAR_BLUE; // STAR_BLUE + return StarType(m_star - 1); // STAR_BLUE <- STAR_WHITE <- STAR_YELLOW <- STAR_ORANGE <- STAR_RED } int System::NumStarlanes() const { int retval = 0; - for (const std::map::value_type& entry : m_starlanes_wormholes) { + for (const auto& entry : m_starlanes_wormholes) { if (!entry.second) ++retval; } @@ -267,7 +258,7 @@ int System::NumStarlanes() const { int System::NumWormholes() const { int retval = 0; - for (const std::map::value_type& entry : m_starlanes_wormholes) { + for (const auto& entry : m_starlanes_wormholes) { if (entry.second) ++retval; } @@ -275,28 +266,28 @@ int System::NumWormholes() const { } bool System::HasStarlaneTo(int id) const { - std::map::const_iterator it = m_starlanes_wormholes.find(id); + auto it = m_starlanes_wormholes.find(id); return (it == m_starlanes_wormholes.end() ? false : it->second == false); } bool System::HasWormholeTo(int id) const { - std::map::const_iterator it = m_starlanes_wormholes.find(id); + auto it = m_starlanes_wormholes.find(id); return (it == m_starlanes_wormholes.end() ? false : it->second == true); } int System::Owner() const { // Check if all of the owners are the same empire. int first_owner_found(ALL_EMPIRES); - for (int planet_id : m_planets) { - if (std::shared_ptr planet = GetPlanet(planet_id)) { - const int owner(planet->Owner()); - if (owner == ALL_EMPIRES) - continue; - if (first_owner_found == ALL_EMPIRES) - first_owner_found = owner; - if (first_owner_found != owner) - return ALL_EMPIRES; - } + for (const auto& planet : Objects().find(m_planets)) { + if (!planet) + continue; + const int owner = planet->Owner(); + if (owner == ALL_EMPIRES) + continue; + if (first_owner_found == ALL_EMPIRES) + first_owner_found = owner; + if (first_owner_found != owner) + return ALL_EMPIRES; } return first_owner_found; } @@ -307,7 +298,7 @@ const std::set& System::ContainedObjectIDs() const bool System::Contains(int object_id) const { if (object_id == INVALID_OBJECT_ID) return false; - return m_objects.find(object_id) != m_objects.end(); + return m_objects.count(object_id); } std::shared_ptr System::Accept(const UniverseObjectVisitor& visitor) const @@ -388,9 +379,7 @@ void System::Insert(std::shared_ptr obj, int orbit/* = -1*/) { } case OBJ_FLEET: { m_fleets.insert(obj->ID()); - std::vector> fleets; - fleets.push_back(std::dynamic_pointer_cast(obj)); - FleetsInsertedSignal(fleets); + FleetsInsertedSignal({std::dynamic_pointer_cast(obj)}); break; } case OBJ_PLANET: @@ -419,7 +408,7 @@ void System::Remove(int id) { bool removed_fleet = false; - std::set::iterator it = m_fleets.find(id); + auto it = m_fleets.find(id); if (it != m_fleets.end()) { m_fleets.erase(it); removed_fleet = true; @@ -439,11 +428,8 @@ void System::Remove(int id) { m_objects.erase(id); if (removed_fleet) { - if (std::shared_ptr fleet = GetFleet(id)) { - std::vector> fleets; - fleets.push_back(fleet); - FleetsRemovedSignal(fleets); - } + if (auto fleet = Objects().get(id)) + FleetsRemovedSignal({fleet}); } StateChangedSignal(); } @@ -451,7 +437,7 @@ void System::Remove(int id) { void System::SetStarType(StarType type) { m_star = type; if (m_star <= INVALID_STAR_TYPE || NUM_STAR_TYPES <= m_star) - ErrorLogger() << "System::SetStarType set star type to " << boost::lexical_cast(type); + ErrorLogger() << "System::SetStarType set star type to " << type; StateChangedSignal(); } @@ -559,7 +545,7 @@ std::map System::VisibleStarlanesWormholes(int empire_id) const { // check if any of the adjacent systems are partial or better visible - for (const std::map::value_type& entry : m_starlanes_wormholes) { + for (const auto& entry : m_starlanes_wormholes) { int lane_end_sys_id = entry.first; if (universe.GetObjectVisibilityByEmpire(lane_end_sys_id, empire_id) >= VIS_PARTIAL_VISIBILITY) retval[lane_end_sys_id] = entry.second; @@ -575,14 +561,14 @@ std::map System::VisibleStarlanesWormholes(int empire_id) const { // get moving fleets owned by empire std::vector> moving_empire_fleets; - for (std::shared_ptr object : objects.FindObjects(MovingFleetVisitor())) { - if (std::shared_ptr fleet = std::dynamic_pointer_cast(object)) + for (auto& object : objects.find(MovingFleetVisitor())) { + if (auto fleet = std::dynamic_pointer_cast(object)) if (fleet->OwnedBy(empire_id)) moving_empire_fleets.push_back(fleet); } // add any lanes an owned fleet is moving along that connect to this system - for (std::shared_ptr fleet : moving_empire_fleets) { + for (auto& fleet : moving_empire_fleets) { if (fleet->SystemID() != INVALID_OBJECT_ID) { ErrorLogger() << "System::VisibleStarlanesWormholes somehow got a moving fleet that had a valid system id?"; continue; @@ -602,7 +588,7 @@ std::map System::VisibleStarlanesWormholes(int empire_id) const { other_lane_end_sys_id = prev_sys_id; if (other_lane_end_sys_id != INVALID_OBJECT_ID) { - std::map::const_iterator lane_it = m_starlanes_wormholes.find(other_lane_end_sys_id); + auto lane_it = m_starlanes_wormholes.find(other_lane_end_sys_id); if (lane_it == m_starlanes_wormholes.end()) { ErrorLogger() << "System::VisibleStarlanesWormholes found an owned fleet moving along a starlane connected to this system that isn't also connected to one of this system's starlane-connected systems...?"; continue; diff --git a/universe/System.h b/universe/System.h index c7884e5035d..0e5e07cfd5c 100644 --- a/universe/System.h +++ b/universe/System.h @@ -2,12 +2,11 @@ #define _System_h_ #include "UniverseObject.h" - -#include "../util/AppInterface.h" #include "../util/Export.h" #include +class Fleet; FO_COMMON_API extern const int INVALID_OBJECT_ID; namespace { @@ -38,7 +37,7 @@ class FO_COMMON_API System : public UniverseObject { /** \name Accessors */ //@{ UniverseObjectType ObjectType() const override; - std::string Dump() const override; + std::string Dump(unsigned short ntabs = 0) const override; const std::set& ContainedObjectIDs() const override; @@ -113,17 +112,17 @@ class FO_COMMON_API System : public UniverseObject { void Insert(std::shared_ptr obj, int orbit = -1); /** removes the object with ID number \a id from this system. */ - void Remove(int id); + void Remove(int id); - void SetStarType(StarType type); ///< sets the type of the star in this Systems to \a StarType - void AddStarlane(int id); ///< adds a starlane between this system and the system with ID number \a id. \note Adding a starlane to a system to which there is already a wormhole erases the wormhole; you may want to check for a wormhole before calling this function. - void AddWormhole(int id); ///< adds a wormhole between this system and the system with ID number \a id \note Adding a wormhole to a system to which there is already a starlane erases the starlane; you may want to check for a starlane before calling this function. - bool RemoveStarlane(int id); ///< removes a starlane between this system and the system with ID number \a id. Returns false if there was no starlane from this system to system \a id. - bool RemoveWormhole(int id); ///< removes a wormhole between this system and the system with ID number \a id. Returns false if there was no wormhole from this system to system \a id. + void SetStarType(StarType type); ///< sets the type of the star in this Systems to \a StarType + void AddStarlane(int id); ///< adds a starlane between this system and the system with ID number \a id. \note Adding a starlane to a system to which there is already a wormhole erases the wormhole; you may want to check for a wormhole before calling this function. + void AddWormhole(int id); ///< adds a wormhole between this system and the system with ID number \a id \note Adding a wormhole to a system to which there is already a starlane erases the starlane; you may want to check for a starlane before calling this function. + bool RemoveStarlane(int id); ///< removes a starlane between this system and the system with ID number \a id. Returns false if there was no starlane from this system to system \a id. + bool RemoveWormhole(int id); ///< removes a wormhole between this system and the system with ID number \a id. Returns false if there was no wormhole from this system to system \a id. - void SetLastTurnBattleHere(int turn);///< Sets the last turn there was a battle at this system + void SetLastTurnBattleHere(int turn);///< Sets the last turn there was a battle at this system - void SetOverlayTexture(const std::string& texture, double size); + void SetOverlayTexture(const std::string& texture, double size); //@} protected: @@ -131,22 +130,16 @@ class FO_COMMON_API System : public UniverseObject { friend class ObjectMap; /** \name Structors */ //@{ - System(); + explicit System(); - /** general ctor. \throw std::invalid_arugment May throw - * std::invalid_arugment if \a star is out of the range of StarType, - * \a orbits is negative, or either x or y coordinate is outside the map - * area.*/ +public: System(StarType star, const std::string& name, double x, double y); - /** general ctor. \throw std::invalid_arugment May throw - * std::invalid_arugment if \a star is out of the range of StarType, - * \a orbits is negative, or either x or y coordinate is outside the map - * area.*/ +protected: System(StarType star, const std::map& lanes_and_holes, const std::string& name, double x, double y); - template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); + template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); public: ~System() {} @@ -172,7 +165,7 @@ class FO_COMMON_API System : public UniverseObject { double m_overlay_size = 1.0; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/universe/Tech.cpp b/universe/Tech.cpp index 88b2ee85b6a..2bb93ecaa5c 100644 --- a/universe/Tech.cpp +++ b/universe/Tech.cpp @@ -2,13 +2,14 @@ #include "Effect.h" #include "UniverseObject.h" +#include "UnlockableItem.h" #include "ObjectMap.h" -#include "../parse/Parse.h" #include "../util/OptionsDB.h" #include "../util/Logger.h" #include "../util/AppInterface.h" -#include "../util/MultiplayerCommon.h" +#include "../util/GameRules.h" #include "../util/CheckSums.h" +#include "../util/ScopedTimer.h" #include "../Empire/Empire.h" #include "../Empire/EmpireManager.h" #include "ValueRef.h" @@ -31,22 +32,22 @@ namespace { std::set& checked_techs, TechManager::iterator it, TechManager::iterator end_it) { - if (checked_techs.find(*it) != checked_techs.end()) + if (checked_techs.count(it->get())) return; - if (known_techs.find((*it)->Name()) == known_techs.end() && it != end_it) { + if (!known_techs.count((*it)->Name()) && it != end_it) { std::vector stack; - stack.push_back(*it); + stack.push_back(it->get()); while (!stack.empty()) { const Tech* current_tech = stack.back(); unsigned int starting_stack_size = stack.size(); bool all_prereqs_known = true; for (const std::string& prereq_name : current_tech->Prerequisites()) { const Tech* prereq_tech = GetTech(prereq_name); - bool prereq_unknown = known_techs.find(prereq_tech->Name()) == known_techs.end(); + bool prereq_unknown = !known_techs.count(prereq_tech->Name()); if (prereq_unknown) all_prereqs_known = false; - if (checked_techs.find(prereq_tech) == checked_techs.end() && prereq_unknown) + if (!checked_techs.count(prereq_tech) && prereq_unknown) stack.push_back(prereq_tech); } if (starting_stack_size == stack.size()) { @@ -78,12 +79,6 @@ namespace { } namespace CheckSums { - void CheckSumCombine(unsigned int& sum, const ItemSpec& item) { - TraceLogger() << "CheckSumCombine(Slot): " << typeid(item).name(); - CheckSumCombine(sum, item.type); - CheckSumCombine(sum, item.name); - } - void CheckSumCombine(unsigned int& sum, const TechCategory& cat) { TraceLogger() << "CheckSumCombine(Slot): " << typeid(cat).name(); CheckSumCombine(sum, cat.name); @@ -95,79 +90,148 @@ namespace CheckSums { } } +/////////////////////////////////////////////////////////// +// Tech Info // +/////////////////////////////////////////////////////////// +Tech::TechInfo::TechInfo() : + research_cost(nullptr), + research_turns(nullptr), + researchable(false) +{} + +Tech::TechInfo::TechInfo(const std::string& name_, const std::string& description_, const std::string& short_description_, + const std::string& category_, + std::unique_ptr>&& research_cost_, + std::unique_ptr>&& research_turns_, + bool researchable_, + const std::set& tags_) : + name(name_), + description(description_), + short_description(short_description_), + category(category_), + research_cost(std::move(research_cost_)), + research_turns(std::move(research_turns_)), + researchable(researchable_), + tags(tags_) +{} + +Tech::TechInfo::~TechInfo() +{} + /////////////////////////////////////////////////////////// // Tech // /////////////////////////////////////////////////////////// +Tech::Tech(const std::string& name, const std::string& description, const std::string& short_description, + const std::string& category, + std::unique_ptr>&& research_cost, + std::unique_ptr>&& research_turns, + bool researchable, + const std::set& tags, + const std::vector>& effects, + const std::set& prerequisites, const std::vector& unlocked_items, + const std::string& graphic) : + m_name(name), + m_description(description), + m_short_description(short_description), + m_category(category), + m_research_cost(std::move(research_cost)), + m_research_turns(std::move(research_turns)), + m_researchable(researchable), + m_tags(), + m_effects(effects), + m_prerequisites(prerequisites), + m_unlocked_items(unlocked_items), + m_graphic(graphic) +{ + for (const std::string& tag : tags) + m_tags.insert(boost::to_upper_copy(tag)); + Init(); +} + +Tech::Tech(TechInfo& tech_info, + std::vector>&& effects, + const std::set& prerequisites, const std::vector& unlocked_items, + const std::string& graphic) : + m_name(tech_info.name), + m_description(tech_info.description), + m_short_description(tech_info.short_description), + m_category(tech_info.category), + m_research_cost(std::move(tech_info.research_cost)), + m_research_turns(std::move(tech_info.research_turns)), + m_researchable(tech_info.researchable), + m_tags(), + m_effects(), + m_prerequisites(prerequisites), + m_unlocked_items(unlocked_items), + m_graphic(graphic) +{ + for (auto&& effect : effects) + m_effects.emplace_back(std::move(effect)); + for (const std::string& tag : tech_info.tags) + m_tags.insert(boost::to_upper_copy(tag)); + Init(); +} + +Tech::~Tech() +{} + void Tech::Init() { if (m_research_cost) m_research_cost->SetTopLevelContent(m_name); if (m_research_turns) m_research_turns->SetTopLevelContent(m_name); - for (std::shared_ptr effect : m_effects) + for (auto& effect : m_effects) { effect->SetTopLevelContent(m_name); } } -std::string Tech::Dump() const { - std::string retval = DumpIndent() + "Tech\n"; - ++g_indent; - retval += DumpIndent() + "name = \"" + m_name + "\"\n"; - retval += DumpIndent() + "description = \"" + m_description + "\"\n"; - retval += DumpIndent() + "shortdescription = \"" + m_short_description + "\"\n"; - retval += DumpIndent() + "category = \"" + m_category + "\"\n"; - retval += DumpIndent() + "researchcost = " + m_research_cost->Dump() + "\n"; - retval += DumpIndent() + "researchturns = " + m_research_turns->Dump() + "\n"; - retval += DumpIndent() + "prerequisites = "; +std::string Tech::Dump(unsigned short ntabs) const { + std::string retval = DumpIndent(ntabs) + "Tech\n"; + retval += DumpIndent(ntabs+1) + "name = \"" + m_name + "\"\n"; + retval += DumpIndent(ntabs+1) + "description = \"" + m_description + "\"\n"; + retval += DumpIndent(ntabs+1) + "shortdescription = \"" + m_short_description + "\"\n"; + retval += DumpIndent(ntabs+1) + "category = \"" + m_category + "\"\n"; + retval += DumpIndent(ntabs+1) + "researchcost = " + m_research_cost->Dump(ntabs+1) + "\n"; + retval += DumpIndent(ntabs+1) + "researchturns = " + m_research_turns->Dump(ntabs+1) + "\n"; + retval += DumpIndent(ntabs+1) + "prerequisites = "; if (m_prerequisites.empty()) { retval += "[]\n"; } else if (m_prerequisites.size() == 1) { retval += "\"" + *m_prerequisites.begin() + "\"\n"; } else { retval += "[\n"; - ++g_indent; - for (const std::string& prerequisite : m_prerequisites) { - retval += DumpIndent() + "\"" + prerequisite + "\"\n"; - } - --g_indent; - retval += DumpIndent() + "]\n"; + for (const std::string& prerequisite : m_prerequisites) + retval += DumpIndent(ntabs+2) + "\"" + prerequisite + "\"\n"; + retval += DumpIndent(ntabs+1) + "]\n"; } - retval += DumpIndent() + "unlock = "; + retval += DumpIndent(ntabs+1) + "unlock = "; if (m_unlocked_items.empty()) { retval += "[]\n"; } else if (m_unlocked_items.size() == 1) { retval += m_unlocked_items[0].Dump(); } else { retval += "[\n"; - ++g_indent; - for (const ItemSpec& unlocked_item : m_unlocked_items) { - retval += DumpIndent() + unlocked_item.Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; + for (const UnlockableItem& unlocked_item : m_unlocked_items) + retval += DumpIndent(ntabs+2) + unlocked_item.Dump(); + retval += DumpIndent(ntabs+1) + "]\n"; } if (!m_effects.empty()) { if (m_effects.size() == 1) { - retval += DumpIndent() + "effectsgroups =\n"; - ++g_indent; - retval += m_effects[0]->Dump(); - --g_indent; + retval += DumpIndent(ntabs+1) + "effectsgroups =\n"; + retval += m_effects[0]->Dump(ntabs+2); } else { - retval += DumpIndent() + "effectsgroups = [\n"; - ++g_indent; - for (std::shared_ptr effect : m_effects) { - retval += effect->Dump(); - } - --g_indent; - retval += DumpIndent() + "]\n"; + retval += DumpIndent(ntabs+1) + "effectsgroups = [\n"; + for (auto& effect : m_effects) + retval += effect->Dump(ntabs+2); + retval += DumpIndent(ntabs+1) + "]\n"; } } - retval += DumpIndent() + "graphic = \"" + m_graphic + "\"\n"; - --g_indent; + retval += DumpIndent(ntabs+1) + "graphic = \"" + m_graphic + "\"\n"; return retval; } float Tech::ResearchCost(int empire_id) const { - const auto arbitrary_large_number = 999999.9f; + const auto ARBITRARY_LARGE_COST = 999999.9f; if (GetGameRules().Get("RULE_CHEAP_AND_FAST_TECH_RESEARCH") || !m_research_cost) { return 1.0; @@ -179,15 +243,17 @@ float Tech::ResearchCost(int empire_id) const { return m_research_cost->Eval(); } else if (empire_id == ALL_EMPIRES) { - return arbitrary_large_number; + return ARBITRARY_LARGE_COST; } else { - std::shared_ptr source = Empires().GetSource(empire_id); - if (!source && !m_research_cost->SourceInvariant()) - return arbitrary_large_number; + if (m_research_cost->SourceInvariant()) + return m_research_cost->Eval(); - ScriptingContext context(source); - return m_research_cost->Eval(context); + auto source = Empires().GetSource(empire_id); + if (!source) + return ARBITRARY_LARGE_COST; + + return m_research_cost->Eval(ScriptingContext(source)); } } @@ -195,7 +261,7 @@ float Tech::PerTurnCost(int empire_id) const { return ResearchCost(empire_id) / std::max(1, ResearchTime(empire_id)); } int Tech::ResearchTime(int empire_id) const { - const auto arbitrary_large_number = 9999; + const auto ARBITRARY_LARGE_TURNS = 9999; if (GetGameRules().Get("RULE_CHEAP_AND_FAST_TECH_RESEARCH") || !m_research_turns) { return 1; @@ -207,16 +273,14 @@ int Tech::ResearchTime(int empire_id) const { return m_research_turns->Eval(); } else if (empire_id == ALL_EMPIRES) { - return arbitrary_large_number; + return ARBITRARY_LARGE_TURNS; } else { - std::shared_ptr source = Empires().GetSource(empire_id); + auto source = Empires().GetSource(empire_id); if (!source && !m_research_turns->SourceInvariant()) - return arbitrary_large_number; - - ScriptingContext context(source); + return ARBITRARY_LARGE_TURNS; - return m_research_turns->Eval(context); + return m_research_turns->Eval(ScriptingContext(source)); } } @@ -240,35 +304,6 @@ unsigned int Tech::GetCheckSum() const { return retval; } -/////////////////////////////////////////////////////////// -// ItemSpec // -/////////////////////////////////////////////////////////// -ItemSpec::ItemSpec() : - type(INVALID_UNLOCKABLE_ITEM_TYPE), - name() -{} - -std::string ItemSpec::Dump() const { - std::string retval = "Item type = "; - switch (type) { - case UIT_BUILDING: retval += "Building"; break; - case UIT_SHIP_PART: retval += "ShipPart"; break; - case UIT_SHIP_HULL: retval += "ShipHull"; break; - case UIT_SHIP_DESIGN: retval += "ShipDesign"; break; - case UIT_TECH: retval += "Tech" ; break; - default: retval += "?" ; break; - } - retval += " name = \"" + name + "\"\n"; - return retval; -} - -bool operator==(const ItemSpec& lhs, const ItemSpec& rhs) { - return lhs.type == rhs.type && - lhs.name == rhs.name; -} - -bool operator!=(const ItemSpec& lhs, const ItemSpec& rhs) -{ return !(lhs == rhs); } /////////////////////////////////////////////////////////// // TechManager // @@ -277,30 +312,35 @@ bool operator!=(const ItemSpec& lhs, const ItemSpec& rhs) TechManager* TechManager::s_instance = nullptr; const Tech* TechManager::GetTech(const std::string& name) const { + CheckPendingTechs(); iterator it = m_techs.get().find(name); - return it == m_techs.get().end() ? nullptr : *it; + return it == m_techs.get().end() ? nullptr : it->get(); } const TechCategory* TechManager::GetTechCategory(const std::string& name) const { - std::map::const_iterator it = m_categories.find(name); - return it == m_categories.end() ? nullptr : it->second; + CheckPendingTechs(); + auto it = m_categories.find(name); + return it == m_categories.end() ? nullptr : it->second.get(); } std::vector TechManager::CategoryNames() const { + CheckPendingTechs(); std::vector retval; - for (const std::map::value_type& entry : m_categories) + for (const auto& entry : m_categories) retval.push_back(entry.first); return retval; } std::vector TechManager::TechNames() const { + CheckPendingTechs(); std::vector retval; - for (const TechContainer::value_type& tech : m_techs.get()) + for (const auto& tech : m_techs.get()) retval.push_back(tech->Name()); return retval; } std::vector TechManager::TechNames(const std::string& name) const { + CheckPendingTechs(); std::vector retval; for (TechManager::category_iterator it = category_begin(name); it != category_end(name); ++it) { retval.push_back((*it)->Name()); @@ -309,6 +349,7 @@ std::vector TechManager::TechNames(const std::string& name) const { } std::vector TechManager::AllNextTechs(const std::set& known_techs) { + CheckPendingTechs(); std::vector retval; std::set checked_techs; iterator end_it = m_techs.get().end(); @@ -318,13 +359,16 @@ std::vector TechManager::AllNextTechs(const std::set& return retval; } -const Tech* TechManager::CheapestNextTech(const std::set& known_techs, int empire_id) -{ return Cheapest(AllNextTechs(known_techs), empire_id); } +const Tech* TechManager::CheapestNextTech(const std::set& known_techs, int empire_id) { + CheckPendingTechs(); + return Cheapest(AllNextTechs(known_techs), empire_id); +} std::vector TechManager::NextTechsTowards(const std::set& known_techs, const std::string& desired_tech, int empire_id) { + CheckPendingTechs(); std::vector retval; std::set checked_techs; NextTechs(retval, known_techs, checked_techs, m_techs.get().find(desired_tech), @@ -337,34 +381,52 @@ const Tech* TechManager::CheapestNextTechTowards(const std::set& kn int empire_id) { return Cheapest(NextTechsTowards(known_techs, desired_tech, empire_id), empire_id); } -TechManager::iterator TechManager::begin() const -{ return m_techs.get().begin(); } +TechManager::iterator TechManager::begin() const { + CheckPendingTechs(); + return m_techs.get().begin(); +} -TechManager::iterator TechManager::end() const -{ return m_techs.get().end(); } +TechManager::iterator TechManager::end() const { + CheckPendingTechs(); + return m_techs.get().end(); +} -TechManager::category_iterator TechManager::category_begin(const std::string& name) const -{ return m_techs.get().lower_bound(name); } +TechManager::category_iterator TechManager::category_begin(const std::string& name) const { + CheckPendingTechs(); + return m_techs.get().lower_bound(name); +} -TechManager::category_iterator TechManager::category_end(const std::string& name) const -{ return m_techs.get().upper_bound(name); } +TechManager::category_iterator TechManager::category_end(const std::string& name) const { + CheckPendingTechs(); + return m_techs.get().upper_bound(name); +} TechManager::TechManager() { if (s_instance) throw std::runtime_error("Attempted to create more than one TechManager."); + // Only update the global pointer on sucessful construction. + s_instance = this; +} + +void TechManager::SetTechs(Pending::Pending&& future) +{ m_pending_types = std::move(future); } + +void TechManager::CheckPendingTechs() const { + if (!m_pending_types) + return; + + auto parsed = WaitForPending(m_pending_types); + if (!parsed) + return; + std::set categories_seen_in_techs; + std::tie(m_techs, m_categories, categories_seen_in_techs) = std::move(*parsed); - try { - parse::techs(m_techs, m_categories, categories_seen_in_techs); - } catch (const std::exception& e) { - ErrorLogger() << "Failed parsing techs: error: " << e.what(); - throw e; - } std::set empty_defined_categories; - for (const std::map::value_type& map : m_categories) { - std::set::iterator set_it = categories_seen_in_techs.find(map.first); + for (const auto& map : m_categories) { + auto set_it = categories_seen_in_techs.find(map.first); if (set_it == categories_seen_in_techs.end()) { empty_defined_categories.insert(map.first); } else { @@ -407,7 +469,7 @@ TechManager::TechManager() { } // fill in the unlocked techs data for each loaded tech - for (const Tech* tech : m_techs) { + for (const auto& tech : m_techs) { for (const std::string& prereq : tech->Prerequisites()) { if (Tech* prereq_tech = const_cast(GetTech(prereq))) prereq_tech->m_unlocked_techs.insert(tech->Name()); @@ -420,29 +482,18 @@ TechManager::TechManager() { #ifdef OUTPUT_TECH_LIST for (const Tech* tech : m_techs.get()) { - std::cerr << UserString(tech->Name()) << " (" + std::cerr << UserString(tech->Name()) << " (" << UserString(tech->Category()) << ") - " << tech->Graphic() << std::endl; } #endif - - // Only update the global pointer on sucessful construction. - s_instance = this; - - DebugLogger() << "TechManager checksum: " << GetCheckSum(); } -TechManager::~TechManager() { - for (std::map::value_type& entry : m_categories) - delete entry.second; - for (const Tech* tech : m_techs) - delete tech; -} - -std::string TechManager::FindIllegalDependencies() { +std::string TechManager::FindIllegalDependencies() const { + CheckPendingTechs(); assert(!m_techs.empty()); std::string retval; - for (const Tech* tech : m_techs) { + for (const auto& tech : m_techs) { if (!tech) { std::stringstream stream; stream << "ERROR: Missing tech referenced in other tech, for unknown reasons..."; @@ -461,17 +512,18 @@ std::string TechManager::FindIllegalDependencies() { return retval; } -std::string TechManager::FindFirstDependencyCycle() { +std::string TechManager::FindFirstDependencyCycle() const { + CheckPendingTechs(); assert(!m_techs.empty()); static const std::set EMPTY_STRING_SET; // used in case an invalid tech is processed std::set checked_techs; // the list of techs that are not part of any cycle - for (const Tech* tech : *this) { - if (checked_techs.find(tech) != checked_techs.end()) + for (const auto& tech : *this) { + if (checked_techs.count(tech.get())) continue; std::vector stack; - stack.push_back(tech); + stack.push_back(tech.get()); while (!stack.empty()) { // Examine the tech on top of the stack. If the tech has no prerequisite techs, or if all // of its prerequisite techs have already been checked, pop it off the stack and mark it as @@ -482,7 +534,7 @@ std::string TechManager::FindFirstDependencyCycle() { const std::set& prereqs = (current_tech ? current_tech->Prerequisites() : EMPTY_STRING_SET); for (const std::string& prereq_name : prereqs) { const Tech* prereq_tech = GetTech(prereq_name); - if (!prereq_tech || checked_techs.find(prereq_tech) != checked_techs.end()) + if (!prereq_tech || checked_techs.count(prereq_tech)) continue; // since this is not a checked prereq, see if it is already in the stack somewhere; if so, we have a cycle @@ -496,7 +548,7 @@ std::string TechManager::FindFirstDependencyCycle() { for (std::vector::reverse_iterator stack_it = stack.rbegin(); stack_it != stack_duplicate_it; ++stack_it) { - if ((*stack_it)->Prerequisites().find(current_tech_name) != (*stack_it)->Prerequisites().end()) { + if ((*stack_it)->Prerequisites().count(current_tech_name)) { current_tech_name = (*stack_it)->Name(); stream << " <-- \"" << current_tech_name << "\""; } @@ -517,10 +569,11 @@ std::string TechManager::FindFirstDependencyCycle() { return ""; } -std::string TechManager::FindRedundantDependency() { +std::string TechManager::FindRedundantDependency() const { + CheckPendingTechs(); assert(!m_techs.empty()); - for (const Tech* tech : m_techs) { + for (const auto& tech : m_techs) { if (!tech) { std::stringstream stream; stream << "ERROR: Missing referenced tech for unknown reasons..."; @@ -538,7 +591,7 @@ std::string TechManager::FindRedundantDependency() { AllChildren(prereq_tech, techs_unlocked_by_prereqs); } for (const std::string& prereq_name : prereqs) { - std::map::const_iterator map_it = techs_unlocked_by_prereqs.find(prereq_name); + auto map_it = techs_unlocked_by_prereqs.find(prereq_name); if (map_it != techs_unlocked_by_prereqs.end()) { std::stringstream stream; stream << "ERROR: Redundant tech dependency found (A <-- B means A is a prerequisite of B): " @@ -553,7 +606,7 @@ std::string TechManager::FindRedundantDependency() { return ""; } -void TechManager::AllChildren(const Tech* tech, std::map& children) { +void TechManager::AllChildren(const Tech* tech, std::map& children) const { for (const std::string& unlocked_tech : tech->UnlockedTechs()) { if (unlocked_tech == tech->Name()) { // infinite loop @@ -590,7 +643,8 @@ std::vector TechManager::RecursivePrereqs(const std::string& tech_n const Tech* cur_tech = this->GetTech(cur_name); // check if this tech is already in the map of prereqs. If so, it has already been processed, and can be skipped. - if (prereqs_set.find(cur_name) != prereqs_set.end()) continue; + if (prereqs_set.count(cur_name)) + continue; // if this tech is already known and min_required==true, can skip. if (min_required && empire && (empire->GetTechStatus(cur_name) == TS_COMPLETE)) @@ -608,13 +662,14 @@ std::vector TechManager::RecursivePrereqs(const std::string& tech_n // extract sorted techs into vector, to be passed to signal... std::vector retval; - for (const std::multimap::value_type& tech_to_add : techs_to_add_map) + for (const auto& tech_to_add : techs_to_add_map) { retval.push_back(tech_to_add.second); } return retval; } unsigned int TechManager::GetCheckSum() const { + CheckPendingTechs(); unsigned int retval{0}; for (auto const& name_type_pair : m_categories) CheckSums::CheckSumCombine(retval, name_type_pair); @@ -624,6 +679,7 @@ unsigned int TechManager::GetCheckSum() const { CheckSums::CheckSumCombine(retval, tech); CheckSums::CheckSumCombine(retval, m_techs.size()); + DebugLogger() << "TechManager checksum: " << retval; return retval; } diff --git a/universe/Tech.h b/universe/Tech.h index c0fa7ba6dbf..e4d4b29e4c7 100644 --- a/universe/Tech.h +++ b/universe/Tech.h @@ -1,15 +1,15 @@ #ifndef _Tech_h_ #define _Tech_h_ -#include "ValueRefFwd.h" - #include "EnumsFwd.h" #include "../util/Export.h" +#include "../util/Pending.h" #include #include #include #include +#include #include #include @@ -20,100 +20,65 @@ namespace Effect { class EffectsGroup; } +namespace ValueRef { + template + struct ValueRef; +} class TechManager; -struct ItemSpec; - +struct UnlockableItem; /** encasulates the data for a single FreeOrion technology */ class FO_COMMON_API Tech { public: /** Helper struct for parsing tech definitions */ struct TechInfo { - TechInfo() - {} - TechInfo(const std::string& name_, const std::string& description_, const std::string& short_description_, - const std::string& category_, - ValueRef::ValueRefBase* research_cost_, - ValueRef::ValueRefBase* research_turns_, + TechInfo(); + TechInfo(const std::string& name_, const std::string& description_, + const std::string& short_description_, const std::string& category_, + std::unique_ptr>&& research_cost_, + std::unique_ptr>&& research_turns_, bool researchable_, - const std::set& tags_) : - name(name_), - description(description_), - short_description(short_description_), - category(category_), - research_cost(research_cost_), - research_turns(research_turns_), - researchable(researchable_), - tags(tags_) - {} + const std::set& tags_); + ~TechInfo(); + std::string name; std::string description; std::string short_description; std::string category; - ValueRef::ValueRefBase* research_cost; - ValueRef::ValueRefBase* research_turns; + std::unique_ptr> research_cost; + std::unique_ptr> research_turns; bool researchable; std::set tags; }; /** \name Structors */ //@{ - Tech(const std::string& name, const std::string& description, const std::string& short_description, - const std::string& category, - ValueRef::ValueRefBase* research_cost, - ValueRef::ValueRefBase* research_turns, + Tech(const std::string& name, const std::string& description, + const std::string& short_description, const std::string& category, + std::unique_ptr>&& research_cost, + std::unique_ptr>&& research_turns, bool researchable, const std::set& tags, const std::vector>& effects, - const std::set& prerequisites, const std::vector& unlocked_items, - const std::string& graphic) : - m_name(name), - m_description(description), - m_short_description(short_description), - m_category(category), - m_research_cost(research_cost), - m_research_turns(research_turns), - m_researchable(researchable), - m_tags(), - m_effects(effects), - m_prerequisites(prerequisites), - m_unlocked_items(unlocked_items), - m_graphic(graphic) - { - for (const std::string& tag : tags) - m_tags.insert(boost::to_upper_copy(tag)); - Init(); - } + const std::set& prerequisites, + const std::vector& unlocked_items, + const std::string& graphic); /** basic ctor taking helper struct to reduce number of direct parameters * in order to making parsing work. */ - Tech(const TechInfo& tech_info, - const std::vector>& effects, - const std::set& prerequisites, const std::vector& unlocked_items, - const std::string& graphic) : - m_name(tech_info.name), - m_description(tech_info.description), - m_short_description(tech_info.short_description), - m_category(tech_info.category), - m_research_cost(tech_info.research_cost), - m_research_turns(tech_info.research_turns), - m_researchable(tech_info.researchable), - m_tags(), - m_effects(effects), - m_prerequisites(prerequisites), - m_unlocked_items(unlocked_items), - m_graphic(graphic) - { - for (const std::string& tag : tech_info.tags) - m_tags.insert(boost::to_upper_copy(tag)); - Init(); - } + Tech(TechInfo& tech_info, + std::vector>&& effects, + const std::set& prerequisites, + const std::vector& unlocked_items, + const std::string& graphic); + + ~Tech(); //@} /** \name Accessors */ //@{ const std::string& Name() const { return m_name; } //!< returns name of this tech const std::string& Description() const { return m_description; } //!< Returns the text description of this tech const std::string& ShortDescription() const { return m_short_description; } //!< Returns the single-line short text description of this tech - std::string Dump() const; //!< Returns a text representation of this object + std::string Dump(unsigned short ntabs = 0) const; //!< Returns a text representation of this object const std::string& Category() const { return m_category; } //!< retursn the name of the category to which this tech belongs float ResearchCost(int empire_id) const; //!< returns the total research cost in RPs required to research this tech float PerTurnCost(int empire_id) const; //!< returns the maximum number of RPs per turn allowed to be spent on researching this tech @@ -130,7 +95,11 @@ class FO_COMMON_API Tech { const std::set& Prerequisites() const { return m_prerequisites; } //!< returns the set of names of all techs required before this one can be researched const std::string& Graphic() const { return m_graphic; } //!< returns the name of the grapic file for this tech - const std::vector& UnlockedItems() const { return m_unlocked_items; } //!< returns the set all items that are unlocked by researching this tech + + //! Returns the set all items that are unlocked by researching this tech + const std::vector& UnlockedItems() const + { return m_unlocked_items; } + const std::set& UnlockedTechs() const { return m_unlocked_techs; } //!< returns the set of names of all techs for which this one is a prerequisite /** Returns a number, calculated from the contained data, which should be @@ -151,13 +120,13 @@ class FO_COMMON_API Tech { std::string m_description; std::string m_short_description; std::string m_category; - ValueRef::ValueRefBase* m_research_cost; - ValueRef::ValueRefBase* m_research_turns; + std::unique_ptr> m_research_cost; + std::unique_ptr> m_research_turns; bool m_researchable; std::set m_tags; std::vector> m_effects; std::set m_prerequisites; - std::vector m_unlocked_items; + std::vector m_unlocked_items; std::string m_graphic; std::set m_unlocked_techs; @@ -165,24 +134,6 @@ class FO_COMMON_API Tech { }; -/** specifies a single item of game content that may be unlocked for an empire. The \a type field - * stores the type of item that is being unlocked, such as a building or ship component, and the - * \a name field contains the name of the actual item (e.g. (UIT_BUILDING, "Superfarm") or - * (UIT_SHIP_PART, "Death Ray")). */ -struct FO_COMMON_API ItemSpec { - ItemSpec(); - ItemSpec(UnlockableItemType type_, const std::string& name_) : - type(type_), - name(name_) - {} - std::string Dump() const; ///< returns a data file format representation of this object - UnlockableItemType type; ///< the kind of item this is - std::string name; ///< the exact item this is -}; - -FO_COMMON_API bool operator==(const ItemSpec& lhs, const ItemSpec& rhs); -bool operator!=(const ItemSpec& lhs, const ItemSpec& rhs); - /** specifies a category of techs, with associated \a name, \a graphic (icon), and \a colour.*/ struct FO_COMMON_API TechCategory { TechCategory() : @@ -202,7 +153,6 @@ struct FO_COMMON_API TechCategory { }; namespace CheckSums { - FO_COMMON_API void CheckSumCombine(unsigned int& sum, const ItemSpec& item); FO_COMMON_API void CheckSumCombine(unsigned int& sum, const TechCategory& cat); } @@ -213,7 +163,7 @@ class FO_COMMON_API TechManager { struct CategoryIndex {}; struct NameIndex {}; typedef boost::multi_index_container< - const Tech*, + std::unique_ptr, boost::multi_index::indexed_by< boost::multi_index::ordered_non_unique< boost::multi_index::tag, @@ -234,6 +184,8 @@ class FO_COMMON_API TechManager { > > TechContainer; + using TechCategoryMap = std::map>; + /** iterator that runs over techs within a category */ typedef TechContainer::index::type::const_iterator category_iterator; @@ -298,37 +250,59 @@ class FO_COMMON_API TechManager { unsigned int GetCheckSum() const; //@} + using TechParseTuple = std::tuple< + TechManager::TechContainer, // techs_ + std::map>, // tech_categories, + std::set // categories_seen + >; + /** Sets types to the value of \p future. */ + FO_COMMON_API void SetTechs(Pending::Pending&& future); + + /** returns the instance of this singleton class; you should use the free function GetTechManager() instead */ static TechManager& GetTechManager(); private: TechManager(); - ~TechManager(); + + /** Assigns any m_pending_types to m_techs. */ + void CheckPendingTechs() const; /** returns an error string indicating the first instance of an illegal prerequisite relationship between two techs in m_techs, or an empty string if there are no illegal dependencies */ - std::string FindIllegalDependencies(); + std::string FindIllegalDependencies() const; /** returns an error string indicating the first prerequisite dependency cycle found in m_techs, or an empty string if there are no dependency cycles */ - std::string FindFirstDependencyCycle(); + std::string FindFirstDependencyCycle() const; /** returns an error string indicating the first instance of a redundant dependency, or an empty string if there are no redundant dependencies. An example of a redundant dependency is A --> C, if A --> B and B --> C. */ - std::string FindRedundantDependency(); + std::string FindRedundantDependency() const; + + void AllChildren(const Tech* tech, std::map& children) const; - void AllChildren(const Tech* tech, std::map& children); + /** Future types being parsed by parser. mutable so that it can + be assigned to m_species_types when completed.*/ + mutable boost::optional> m_pending_types = boost::none; - std::map m_categories; - TechContainer m_techs; + mutable TechCategoryMap m_categories; + mutable TechContainer m_techs; - static TechManager* s_instance; + static TechManager* s_instance; }; /** returns the singleton tech manager */ FO_COMMON_API TechManager& GetTechManager(); -/** returns a pointer to the tech with the name \a name, or 0 if no such tech exists */ +//! @brief Returns the ::Tech identified by @p name +//! +//! @param name +//! The identifying name of the requested ::Tech. +//! +//! @return +//! A pointer to the ::Tech matching @p name or nullptr if no ::Tech with that +//! name was found. FO_COMMON_API const Tech* GetTech(const std::string& name); /** returns a pointer to the tech category with the name \a name, or 0 if no such category exists */ diff --git a/universe/Universe.cpp b/universe/Universe.cpp index f66697731a4..f8c1b29bc4b 100644 --- a/universe/Universe.cpp +++ b/universe/Universe.cpp @@ -4,37 +4,56 @@ #include "../util/i18n.h" #include "../util/Logger.h" #include "../util/Random.h" -#include "../util/RunQueue.h" #include "../util/ScopedTimer.h" #include "../util/CheckSums.h" -#include "../util/MultiplayerCommon.h" -#include "../parse/Parse.h" +#include "../util/GameRules.h" #include "../Empire/Empire.h" #include "../Empire/EmpireManager.h" #include "IDAllocator.h" #include "Building.h" +#include "BuildingType.h" +#include "Effect.h" #include "Fleet.h" +#include "FleetPlan.h" #include "Planet.h" #include "Ship.h" #include "System.h" #include "Field.h" +#include "FieldType.h" #include "UniverseObject.h" -#include "Effect.h" +#include "UnlockableItem.h" #include "Predicates.h" +#include "ShipPart.h" +#include "ShipHull.h" +#include "ShipDesign.h" #include "Special.h" #include "Species.h" -#include "Condition.h" +#include "Tech.h" +#include "Conditions.h" #include "ValueRef.h" #include "Enums.h" #include "Pathfinder.h" +#include "Encyclopedia.h" #include -#include +#if BOOST_VERSION >= 106600 +# include +# include +#else +namespace boost { namespace asio { + struct thread_pool { + thread_pool(int) {} + void join() {} + }; + void post(thread_pool, std::function func) { func(); } + } } +#endif FO_COMMON_API extern const int INVALID_DESIGN_ID; namespace { DeclareThreadSafeLogger(effects); + DeclareThreadSafeLogger(conditions); } #if defined(_MSC_VER) @@ -49,17 +68,24 @@ namespace { const bool ENABLE_VISIBILITY_EMPIRE_MEMORY = true; // toggles using memory with visibility, so that empires retain knowledge of objects viewed on previous turns void AddOptions(OptionsDB& db) { - db.Add("effects-threads-ui", UserStringNop("OPTIONS_DB_EFFECTS_THREADS_UI_DESC"), 8, RangedValidator(1, 32)); - db.Add("effects-threads-ai", UserStringNop("OPTIONS_DB_EFFECTS_THREADS_AI_DESC"), 2, RangedValidator(1, 32)); - db.Add("effects-threads-server", UserStringNop("OPTIONS_DB_EFFECTS_THREADS_SERVER_DESC"), 8, RangedValidator(1, 32)); - db.Add("effect-accounting", UserStringNop("OPTIONS_DB_EFFECT_ACCOUNTING"), true, Validator()); + auto HardwareThreads = []() -> int { + int cores = std::thread::hardware_concurrency(); + return cores > 0 ? cores : 4; + }; + + db.Add("effects.ui.threads", UserStringNop("OPTIONS_DB_EFFECTS_THREADS_UI_DESC"), HardwareThreads(), RangedValidator(1, 32)); + db.Add("effects.ai.threads", UserStringNop("OPTIONS_DB_EFFECTS_THREADS_AI_DESC"), 2, RangedValidator(1, 32)); + db.Add("effects.server.threads", UserStringNop("OPTIONS_DB_EFFECTS_THREADS_SERVER_DESC"), HardwareThreads(), RangedValidator(1, 32)); + db.Add("effects.accounting.enabled", UserStringNop("OPTIONS_DB_EFFECT_ACCOUNTING"), true, Validator()); } bool temp_bool = RegisterOptions(&AddOptions); void AddRules(GameRules& rules) { // makes all PRNG be reseeded frequently - rules.Add("RULE_RESEED_PRNG_SERVER", "RULE_RESEED_PRNG_SERVER_DESC", + rules.Add("RULE_RESEED_PRNG_SERVER", "RULE_RESEED_PRNG_SERVER_DESC", "", true, true); + rules.Add("RULE_STARLANES_EVERYWHERE","RULE_STARLANES_EVERYWHERE_DESC", + "TEST", false, true); } bool temp_bool2 = RegisterGameRules(&AddRules); @@ -68,48 +94,30 @@ namespace { // determining how much of their speed is consumed by the jump // unused variable const double WORMHOLE_TRAVEL_DISTANCE = 0.1; - template struct constant_property + template + struct constant_property { Value m_value; }; } namespace boost { - template struct property_traits> { + template + struct property_traits> { typedef Value value_type; typedef Key key_type; typedef readable_property_map_tag category; }; - template const Value& get(const constant_property& pmap, const Key&) { return pmap.m_value; } + template + const Value& get(const constant_property& pmap, const Key&) { return pmap.m_value; } } -extern FO_COMMON_API const int ALL_EMPIRES = -1; - -namespace EmpireStatistics { - const std::map*>& GetEmpireStats() { - static std::map*> s_stats; - if (s_stats.empty()) { - try { - parse::statistics(s_stats); - - unsigned int checksum{0}; - CheckSums::CheckSumCombine(checksum, s_stats); - DebugLogger() << "Empire Statistics checksum: " << checksum; - - } catch (const std::exception& e) { - ErrorLogger() << "Failed parsing empire statistics: error: " << e.what(); - throw e; - } - } - return s_stats; - } -} - +extern FO_COMMON_API const int ALL_EMPIRES = -1; ///////////////////////////////////////////// // class Universe ///////////////////////////////////////////// Universe::Universe() : - m_pathfinder(new Pathfinder), + m_pathfinder(std::make_shared()), m_universe_width(1000.0), m_inhibit_universe_object_signals(false), m_encoding_empire(ALL_EMPIRES), @@ -120,47 +128,48 @@ Universe::Universe() : TEMPORARY_OBJECT_ID, INVALID_DESIGN_ID)) {} -Universe::~Universe() { - Clear(); -} +Universe::~Universe() +{ Clear(); } void Universe::Clear() { // empty object maps - m_objects.Clear(); - for (EmpireObjectMap::value_type& entry : m_empire_latest_known_objects) - entry.second.Clear(); - m_empire_latest_known_objects.clear(); + m_objects.clear(); + + ResetAllIDAllocation(); + + m_marked_destroyed.clear(); + m_destroyed_object_ids.clear(); // clean up ship designs - for (ShipDesignMap::value_type& entry : m_ship_designs) + for (auto& entry : m_ship_designs) delete entry.second; m_ship_designs.clear(); - m_destroyed_object_ids.clear(); - m_empire_object_visibility.clear(); m_empire_object_visibility_turns.clear(); - m_empire_object_visible_specials.clear(); + m_empire_known_destroyed_object_ids.clear(); + m_empire_latest_known_objects.clear(); + m_empire_stale_knowledge_object_ids.clear(); + m_empire_known_ship_design_ids.clear(); + m_effect_accounting_map.clear(); m_effect_discrepancy_map.clear(); + m_effect_specified_empire_object_visibilities.clear(); - ResetAllIDAllocation(); - - m_empire_known_destroyed_object_ids.clear(); - m_empire_stale_knowledge_object_ids.clear(); + m_stat_records.clear(); - m_empire_known_ship_design_ids.clear(); + m_universe_width = 1000.0; - m_marked_destroyed.clear(); + m_pathfinder = std::make_shared(); } void Universe::ResetAllIDAllocation(const std::vector& empire_ids) { // Find the highest already allocated id for saved games that did not partition ids by client int highest_allocated_id = INVALID_OBJECT_ID; - for (const auto& obj: m_objects) + for (const auto& obj: m_objects.all()) highest_allocated_id = std::max(highest_allocated_id, obj->ID()); *m_object_id_allocator = IDAllocator(ALL_EMPIRES, empire_ids, INVALID_OBJECT_ID, @@ -178,11 +187,51 @@ void Universe::ResetAllIDAllocation(const std::vector& empire_ids) { << " and highest design id = " << highest_allocated_design_id; } +void Universe::SetInitiallyUnlockedItems(Pending::Pending>&& future) +{ m_pending_items = std::move(future); } + +const std::vector& Universe::InitiallyUnlockedItems() const +{ return Pending::SwapPending(m_pending_items, m_unlocked_items); } + +void Universe::SetInitiallyUnlockedBuildings(Pending::Pending>&& future) +{ m_pending_buildings = std::move(future); } + +const std::vector& Universe::InitiallyUnlockedBuildings() const +{ return Pending::SwapPending(m_pending_buildings, m_unlocked_buildings); } + +void Universe::SetInitiallyUnlockedFleetPlans(Pending::Pending>>&& future) +{ m_pending_fleet_plans = std::move(future);} + +const std::vector Universe::InitiallyUnlockedFleetPlans() const { + Pending::SwapPending(m_pending_fleet_plans, m_unlocked_fleet_plans); + std::vector retval; + for (const auto& plan : m_unlocked_fleet_plans) + retval.push_back(plan.get()); + return retval; +} + +void Universe::SetMonsterFleetPlans(Pending::Pending>>&& future) +{ m_pending_monster_fleet_plans = std::move(future); } + +const std::vector Universe::MonsterFleetPlans() const { + Pending::SwapPending(m_pending_monster_fleet_plans, m_monster_fleet_plans); + std::vector retval; + for (const auto& plan : m_monster_fleet_plans) + retval.push_back(plan.get()); + return retval; +} + +void Universe::SetEmpireStats(Pending::Pending future) +{ m_pending_empire_stats = std::move(future); } + +const Universe::EmpireStatsMap& Universe::EmpireStats() const +{ return Pending::SwapPending(m_pending_empire_stats, m_empire_stats); } + const ObjectMap& Universe::EmpireKnownObjects(int empire_id) const { if (empire_id == ALL_EMPIRES) return m_objects; - EmpireObjectMap::const_iterator it = m_empire_latest_known_objects.find(empire_id); + auto it = m_empire_latest_known_objects.find(empire_id); if (it != m_empire_latest_known_objects.end()) return it->second; @@ -194,12 +243,12 @@ ObjectMap& Universe::EmpireKnownObjects(int empire_id) { if (empire_id == ALL_EMPIRES) return m_objects; - EmpireObjectMap::iterator it = m_empire_latest_known_objects.find(empire_id); + auto it = m_empire_latest_known_objects.find(empire_id); if (it != m_empire_latest_known_objects.end()) return it->second; static ObjectMap empty_map; - empty_map.Clear(); + empty_map.clear(); return empty_map; } @@ -211,17 +260,16 @@ std::set Universe::EmpireVisibleObjectIDs(int empire_id/* = ALL_EMPIRES*/) if (empire_id != ALL_EMPIRES) empire_ids.insert(empire_id); else - for (const std::map::value_type& empire_entry : Empires()) + for (const auto& empire_entry : Empires()) empire_ids.insert(empire_entry.first); // check each object's visibility against all empires, including the object // if an empire has visibility of it - for (ObjectMap::const_iterator<> obj_it = m_objects.const_begin(); obj_it != m_objects.const_end(); ++obj_it) { - int id = obj_it->ID(); - for (int empire_id : empire_ids) { - Visibility vis = GetObjectVisibilityByEmpire(id, empire_id); + for (const auto& obj : m_objects.all()) { + for (int detector_empire_id : empire_ids) { + Visibility vis = GetObjectVisibilityByEmpire(obj->ID(), detector_empire_id); if (vis >= VIS_BASIC_VISIBILITY) { - retval.insert(id); + retval.insert(obj->ID()); break; } } @@ -233,15 +281,21 @@ std::set Universe::EmpireVisibleObjectIDs(int empire_id/* = ALL_EMPIRES*/) const std::set& Universe::DestroyedObjectIds() const { return m_destroyed_object_ids; } +int Universe::HighestDestroyedObjectID() const { + if (m_destroyed_object_ids.empty()) + return INVALID_OBJECT_ID; + return *m_destroyed_object_ids.rbegin(); +} + const std::set& Universe::EmpireKnownDestroyedObjectIDs(int empire_id) const { - ObjectKnowledgeMap::const_iterator it = m_empire_known_destroyed_object_ids.find(empire_id); + auto it = m_empire_known_destroyed_object_ids.find(empire_id); if (it != m_empire_known_destroyed_object_ids.end()) return it->second; return m_destroyed_object_ids; } const std::set& Universe::EmpireStaleKnowledgeObjectIDs(int empire_id) const { - ObjectKnowledgeMap::const_iterator it = m_empire_stale_knowledge_object_ids.find(empire_id); + auto it = m_empire_stale_knowledge_object_ids.find(empire_id); if (it != m_empire_stale_knowledge_object_ids.end()) return it->second; static const std::set empty_set; @@ -255,26 +309,24 @@ const ShipDesign* Universe::GetShipDesign(int ship_design_id) const { return (it != m_ship_designs.end() ? it->second : nullptr); } -void Universe::RenameShipDesign(int design_id, const std::string& name/* = ""*/, const std::string& description/* = ""*/) { - ShipDesignMap::iterator design_it = m_ship_designs.find(design_id); +void Universe::RenameShipDesign(int design_id, const std::string& name/* = ""*/, + const std::string& description/* = ""*/) +{ + auto design_it = m_ship_designs.find(design_id); if (design_it == m_ship_designs.end()) { DebugLogger() << "Universe::RenameShipDesign tried to rename a ship design that doesn't exist!"; return; } ShipDesign* design = design_it->second; - if (name != "") { - design->SetName(name); - } - if (description != "") { - design->SetDescription(description); - } + design->SetName(name); + design->SetDescription(description); } const ShipDesign* Universe::GetGenericShipDesign(const std::string& name) const { if (name.empty()) return nullptr; - for (const std::map::value_type& entry : m_ship_designs) { + for (const auto& entry : m_ship_designs) { const ShipDesign* design = entry.second; const std::string& design_name = design->Name(false); if (name == design_name) @@ -284,7 +336,7 @@ const ShipDesign* Universe::GetGenericShipDesign(const std::string& name) const } const std::set& Universe::EmpireKnownShipDesignIDs(int empire_id) const { - std::map>::const_iterator it = m_empire_known_ship_design_ids.find(empire_id); + auto it = m_empire_known_ship_design_ids.find(empire_id); if (it != m_empire_known_ship_design_ids.end()) return it->second; static const std::set empty_set; @@ -295,13 +347,13 @@ Visibility Universe::GetObjectVisibilityByEmpire(int object_id, int empire_id) c if (empire_id == ALL_EMPIRES || GetUniverse().AllObjectsVisible()) return VIS_FULL_VISIBILITY; - EmpireObjectVisibilityMap::const_iterator empire_it = m_empire_object_visibility.find(empire_id); + auto empire_it = m_empire_object_visibility.find(empire_id); if (empire_it == m_empire_object_visibility.end()) return VIS_NO_VISIBILITY; const ObjectVisibilityMap& vis_map = empire_it->second; - ObjectVisibilityMap::const_iterator vis_map_it = vis_map.find(object_id); + auto vis_map_it = vis_map.find(object_id); if (vis_map_it == vis_map.end()) return VIS_NO_VISIBILITY; @@ -311,12 +363,12 @@ Visibility Universe::GetObjectVisibilityByEmpire(int object_id, int empire_id) c const Universe::VisibilityTurnMap& Universe::GetObjectVisibilityTurnMapByEmpire(int object_id, int empire_id) const { static const std::map empty_map; - EmpireObjectVisibilityTurnMap::const_iterator empire_it = m_empire_object_visibility_turns.find(empire_id); + auto empire_it = m_empire_object_visibility_turns.find(empire_id); if (empire_it == m_empire_object_visibility_turns.end()) return empty_map; const ObjectVisibilityTurnMap& obj_vis_turn_map = empire_it->second; - ObjectVisibilityTurnMap::const_iterator object_it = obj_vis_turn_map.find(object_id); + auto object_it = obj_vis_turn_map.find(object_id); if (object_it == obj_vis_turn_map.end()) return empty_map; @@ -325,21 +377,21 @@ const Universe::VisibilityTurnMap& Universe::GetObjectVisibilityTurnMapByEmpire( std::set Universe::GetObjectVisibleSpecialsByEmpire(int object_id, int empire_id) const { if (empire_id != ALL_EMPIRES) { - EmpireObjectSpecialsMap::const_iterator empire_it = m_empire_object_visible_specials.find(empire_id); + auto empire_it = m_empire_object_visible_specials.find(empire_id); if (empire_it == m_empire_object_visible_specials.end()) return std::set(); const ObjectSpecialsMap& object_specials_map = empire_it->second; - ObjectSpecialsMap::const_iterator object_it = object_specials_map.find(object_id); + auto object_it = object_specials_map.find(object_id); if (object_it == object_specials_map.end()) return std::set(); return object_it->second; } else { - std::shared_ptr obj = m_objects.Object(object_id); + auto obj = m_objects.get(object_id); if (!obj) return std::set(); // all specials visible std::set retval; - for (const std::map>::value_type& entry : obj->Specials()) { + for (const auto& entry : obj->Specials()) { retval.insert(entry.first); } return retval; @@ -363,15 +415,10 @@ void Universe::ObfuscateIDGenerator() { bool Universe::VerifyUnusedObjectID(const int empire_id, const int id) { auto good_id_and_possible_legacy = m_object_id_allocator->IsIDValidAndUnused(id, empire_id); - if (!good_id_and_possible_legacy.second) { - WarnLogger() << "object id = " << id << " should not have been assigned by empire = " - << empire_id << ". It is probably from loading an old saved game. " - << "In future this will be promoted to an error."; - m_object_id_allocator->FixLegacyOrderIDs(id); - //TODO before version change to v0.4.8 make this a hard failure; - } + if (!good_id_and_possible_legacy.second) // Possibly from old save game + ErrorLogger() << "object id = " << id << " should not have been assigned by empire = " << empire_id; - return good_id_and_possible_legacy.first; + return good_id_and_possible_legacy.first && good_id_and_possible_legacy.second; } void Universe::InsertIDCore(std::shared_ptr obj, int id) { @@ -386,74 +433,69 @@ void Universe::InsertIDCore(std::shared_ptr obj, int id) { } obj->SetID(id); - m_objects.Insert(std::forward>(obj)); + m_objects.insert(std::forward>(obj)); } bool Universe::InsertShipDesign(ShipDesign* ship_design) { if (!ship_design || (ship_design->ID() != INVALID_DESIGN_ID && m_ship_designs.count(ship_design->ID()))) - return false; - - int id = GenerateDesignID(); + { return false; } - // TODO move this check int InsertShipDesignID(design, id) before the version change to v0.4.8 - // **************************************** Non Legacy Check Below ******************** - m_design_id_allocator->UpdateIDAndCheckIfOwned(id); - // **************************************** Non Legacy Check Above ******************** - - return InsertShipDesignID(ship_design, boost::none, id); + return InsertShipDesignID(ship_design, boost::none, GenerateDesignID()); } bool Universe::InsertShipDesignID(ShipDesign* ship_design, boost::optional empire_id, int id) { if (!ship_design) return false; - // TODO remove this check before the version change to v0.4.8 - if (empire_id) { - auto good_id_and_possible_legacy = m_design_id_allocator->IsIDValidAndUnused(id, *empire_id); - if (!good_id_and_possible_legacy.first) { - ErrorLogger() << "Ship design id = " << id << " is invalid."; - return false; - } - - if (!good_id_and_possible_legacy.second) { - WarnLogger() << "design id = " << id << " should not have been assigned by empire = " - << *empire_id << ". It is probably from loading an old saved game. " - << "In future this will be promoted to an error."; - } - m_design_id_allocator->FixLegacyOrderIDs(id); + if (!m_design_id_allocator->UpdateIDAndCheckIfOwned(id)) { + ErrorLogger() << "Ship design id " << id << " is invalid."; + return false; } - // TODO replace the above code with this check before the version change to v0.4.8 - // **************************************** Non Legacy Check Below ******************** - else { - auto valid = m_design_id_allocator->UpdateIDAndCheckIfOwned(id); - if (!valid) { - ErrorLogger() << "Ship design id = " << id << " is invalid."; - return false; - } + if (m_ship_designs.count(id)) { + ErrorLogger() << "Ship design id " << id << " already exists."; + return false; } - // **************************************** Non Legacy Check Above ******************** - ship_design->SetID(id); m_ship_designs[id] = ship_design; return true; } bool Universe::DeleteShipDesign(int design_id) { - ShipDesignMap::iterator it = m_ship_designs.find(design_id); + auto it = m_ship_designs.find(design_id); if (it != m_ship_designs.end()) { m_ship_designs.erase(it); return true; } else { return false; } } +void Universe::ResetAllObjectMeters(bool target_max_unpaired, bool active) { + for (const auto& object : m_objects.all()) { + if (target_max_unpaired) + object->ResetTargetMaxUnpairedMeters(); + if (active) + object->ResetPairedActiveMeters(); + } +} + +void Universe::ResetObjectMeters(const std::vector>& objects, + bool target_max_unpaired, bool active) +{ + for (const auto& object : objects) { + if (target_max_unpaired) + object->ResetTargetMaxUnpairedMeters(); + if (active) + object->ResetPairedActiveMeters(); + } +} + void Universe::ApplyAllEffectsAndUpdateMeters(bool do_accounting) { ScopedTimer timer("Universe::ApplyAllEffectsAndUpdateMeters"); if (do_accounting) { // override if option disabled - do_accounting = GetOptionsDB().Get("effect-accounting"); + do_accounting = GetOptionsDB().Get("effects.accounting.enabled"); } m_effect_specified_empire_object_visibilities.clear(); @@ -461,25 +503,22 @@ void Universe::ApplyAllEffectsAndUpdateMeters(bool do_accounting) { // cache all activation and scoping condition results before applying // Effects, since the application of these Effects may affect the activation // and scoping evaluations - Effect::TargetsCauses targets_causes; - GetEffectsAndTargets(targets_causes); + std::map source_effects_targets_causes; + GetEffectsAndTargets(source_effects_targets_causes, false); // revert all current meter values (which are modified by effects) to // their initial state for this turn, so that max/target/unpaired meter // value can be calculated (by accumulating all effects' modifications this // turn) and active meters have the proper baseline from which to // accumulate changes from effects - for (std::shared_ptr object : m_objects) { - object->ResetTargetMaxUnpairedMeters(); - object->ResetPairedActiveMeters(); - } - for (std::map::value_type& entry : Empires()) + ResetAllObjectMeters(true, true); + for (auto& entry : Empires()) entry.second->ResetMeters(); - ExecuteEffects(targets_causes, do_accounting, false, false, true); + ExecuteEffects(source_effects_targets_causes, do_accounting, false, false, true); // clamp max meters to [DEFAULT_VALUE, LARGE_VALUE] and current meters to [DEFAULT_VALUE, max] // clamp max and target meters to [DEFAULT_VALUE, LARGE_VALUE] and current meters to [DEFAULT_VALUE, max] - for (std::shared_ptr object : m_objects) + for (const auto& object : m_objects.all()) object->ClampMeters(); } @@ -489,31 +528,28 @@ void Universe::ApplyMeterEffectsAndUpdateMeters(const std::vector& object_i ScopedTimer timer("Universe::ApplyMeterEffectsAndUpdateMeters on " + std::to_string(object_ids.size()) + " objects"); if (do_accounting) { // override if disabled - do_accounting = GetOptionsDB().Get("effect-accounting"); + do_accounting = GetOptionsDB().Get("effects.accounting.enabled"); } // cache all activation and scoping condition results before applying Effects, since the application of // these Effects may affect the activation and scoping evaluations - Effect::TargetsCauses targets_causes; - GetEffectsAndTargets(targets_causes, object_ids); + std::map source_effects_targets_causes; + GetEffectsAndTargets(source_effects_targets_causes, object_ids, true); - std::vector> objects = m_objects.FindObjects(object_ids); + std::vector> objects = m_objects.find(object_ids); // revert all current meter values (which are modified by effects) to // their initial state for this turn, so meter // value can be calculated (by accumulating all effects' modifications this // turn) and active meters have the proper baseline from which to // accumulate changes from effects - for (std::shared_ptr object : objects) { - object->ResetTargetMaxUnpairedMeters(); - object->ResetPairedActiveMeters(); - } + ResetObjectMeters(objects, true, true); // could also reset empire meters here, but unless all objects have meters // recalculated, some targets that lead to empire meters being modified may // be missed, and estimated empire meters would be inaccurate - ExecuteEffects(targets_causes, do_accounting, true); + ExecuteEffects(source_effects_targets_causes, do_accounting, true); - for (std::shared_ptr object : objects) + for (auto& object : objects) object->ClampMeters(); } @@ -521,40 +557,32 @@ void Universe::ApplyMeterEffectsAndUpdateMeters(bool do_accounting) { ScopedTimer timer("Universe::ApplyMeterEffectsAndUpdateMeters on all objects"); if (do_accounting) { // override if disabled - do_accounting = GetOptionsDB().Get("effect-accounting"); + do_accounting = GetOptionsDB().Get("effects.accounting.enabled"); } - Effect::TargetsCauses targets_causes; - GetEffectsAndTargets(targets_causes); + std::map source_effects_targets_causes; + GetEffectsAndTargets(source_effects_targets_causes, true); - for (std::shared_ptr object : m_objects) { + TraceLogger(effects) << "Universe::ApplyMeterEffectsAndUpdateMeters resetting..."; + for (const auto& object : m_objects.all()) { + TraceLogger(effects) << "object " << object->Name() << " (" << object->ID() << ") before resetting meters: "; + for (auto const& meter_pair : object->Meters()) { + TraceLogger(effects) << " meter: " << meter_pair.first + << " value: " << meter_pair.second.Current(); + } object->ResetTargetMaxUnpairedMeters(); object->ResetPairedActiveMeters(); + TraceLogger(effects) << "object " << object->Name() << " (" << object->ID() << ") after resetting meters: "; + for (auto const& meter_pair : object->Meters()) { + TraceLogger(effects) << " meter: " << meter_pair.first + << " value: " << meter_pair.second.Current(); + } } - for (std::map::value_type& entry : Empires()) + for (auto& entry : Empires()) entry.second->ResetMeters(); - ExecuteEffects(targets_causes, do_accounting, true, false, true); - - for (std::shared_ptr object : m_objects) - object->ClampMeters(); -} - -void Universe::ApplyMeterEffectsAndUpdateTargetMaxUnpairedMeters(bool do_accounting) { - ScopedTimer timer("Universe::ApplyMeterEffectsAndUpdateMeters on all objects"); - if (do_accounting) { - // override if disabled - do_accounting = GetOptionsDB().Get("effect-accounting"); - } - Effect::TargetsCauses targets_causes; - GetEffectsAndTargets(targets_causes); - - for (std::shared_ptr object : m_objects) { - object->ResetTargetMaxUnpairedMeters(); - } - - ExecuteEffects(targets_causes, do_accounting, true, false, true); + ExecuteEffects(source_effects_targets_causes, do_accounting, true, false, true); - for (std::shared_ptr object : m_objects) + for (const auto& object : m_objects.all()) object->ClampMeters(); } @@ -566,9 +594,9 @@ void Universe::ApplyAppearanceEffects(const std::vector& object_ids) { // cache all activation and scoping condition results before applying // Effects, since the application of these Effects may affect the // activation and scoping evaluations - Effect::TargetsCauses targets_causes; - GetEffectsAndTargets(targets_causes, object_ids); - ExecuteEffects(targets_causes, false, false, true); + std::map source_effects_targets_causes; + GetEffectsAndTargets(source_effects_targets_causes, object_ids, false); + ExecuteEffects(source_effects_targets_causes, false, false, true); } void Universe::ApplyAppearanceEffects() { @@ -577,9 +605,9 @@ void Universe::ApplyAppearanceEffects() { // cache all activation and scoping condition results before applying // Effects, since the application of Effects in general (even if not these // particular Effects) may affect the activation and scoping evaluations - Effect::TargetsCauses targets_causes; - GetEffectsAndTargets(targets_causes); - ExecuteEffects(targets_causes, false, false, true); + std::map source_effects_targets_causes; + GetEffectsAndTargets(source_effects_targets_causes, false); + ExecuteEffects(source_effects_targets_causes, false, false, true); } void Universe::ApplyGenerateSitRepEffects() { @@ -588,106 +616,165 @@ void Universe::ApplyGenerateSitRepEffects() { // cache all activation and scoping condition results before applying // Effects, since the application of Effects in general (even if not these // particular Effects) may affect the activation and scoping evaluations - Effect::TargetsCauses targets_causes; - GetEffectsAndTargets(targets_causes); - ExecuteEffects(targets_causes, false, false, false, false, true); + std::map source_effects_targets_causes; + GetEffectsAndTargets(source_effects_targets_causes, false); + ExecuteEffects(source_effects_targets_causes, false, false, false, false, true); } void Universe::InitMeterEstimatesAndDiscrepancies() { - DebugLogger() << "Universe::InitMeterEstimatesAndDiscrepancies"; - ScopedTimer timer("Universe::InitMeterEstimatesAndDiscrepancies"); + DebugLogger(effects) << "Universe::InitMeterEstimatesAndDiscrepancies"; + ScopedTimer timer("Universe::InitMeterEstimatesAndDiscrepancies", true, std::chrono::microseconds(1)); // clear old discrepancies and accounting m_effect_discrepancy_map.clear(); m_effect_accounting_map.clear(); + m_effect_discrepancy_map.reserve(m_objects.size()); + m_effect_accounting_map.reserve(m_objects.size()); + + TraceLogger(effects) << "IMEAD: updating meter estimates"; + + // save starting meter vales + DiscrepancyMap starting_current_meter_values; + starting_current_meter_values.reserve(m_objects.size()); + for (const auto& obj : m_objects.all()) { + auto& obj_discrep = starting_current_meter_values[obj->ID()]; + obj_discrep.reserve(obj->Meters().size()); + for (const auto& meter_pair : obj->Meters()) { + // inserting in order into initially-empty map should always put next item efficiently at end + obj_discrep.emplace_hint(obj_discrep.end(), meter_pair.first, meter_pair.second.Current()); + } + } - DebugLogger() << "IMEAD: updating meter estimates"; // generate new estimates (normally uses discrepancies, but in this case will find none) UpdateMeterEstimates(); - DebugLogger() << "IMEAD: determining discrepancies"; + + TraceLogger(effects) << "IMEAD: determining discrepancies"; + TraceLogger(effects) << "Initial accounting map size: " << m_effect_accounting_map.size() + << " and discrepancy map size: " << m_effect_discrepancy_map.size(); + // determine meter max discrepancies - for (Effect::AccountingMap::value_type& entry : m_effect_accounting_map) { + for (auto& entry : m_effect_accounting_map) { int object_id = entry.first; // skip destroyed objects - if (m_destroyed_object_ids.find(object_id) != m_destroyed_object_ids.end()) + if (m_destroyed_object_ids.count(object_id)) continue; // get object - std::shared_ptr obj = m_objects.Object(object_id); + auto obj = m_objects.get(object_id); if (!obj) { - ErrorLogger() << "Universe::InitMeterEstimatesAndDiscrepancies couldn't find an object that was in the effect accounting map...?"; + ErrorLogger(effects) << "Universe::InitMeterEstimatesAndDiscrepancies couldn't find an object that was in the effect accounting map...?"; continue; } + if (obj->Meters().empty()) + continue; + + TraceLogger(effects) << "... discrepancies for " << obj->Name() << " (" << obj->ID() << "):"; - // every meter has a value at the start of the turn, and a value after updating with known effects - for (std::map::value_type& entry : obj->Meters()) { - MeterType type = entry.first; - Meter& meter = entry.second; + auto& account_map = entry.second; + account_map.reserve(obj->Meters().size()); - // discrepancy is the difference between expected and actual meter values at start of turn - float discrepancy = meter.Initial() - meter.Current(); + // discrepancies should be empty before this loop, so emplacing / assigning should be fine here (without overwriting existing data) + auto dis_map_it = m_effect_discrepancy_map.emplace_hint(m_effect_discrepancy_map.end(), object_id, boost::container::flat_map{}); + auto& discrep_map = dis_map_it->second; + discrep_map.reserve(obj->Meters().size()); + auto& start_map = starting_current_meter_values[object_id]; + start_map.reserve(obj->Meters().size()); + + TraceLogger(effects) << "For object " << object_id << " initial accounting map size: " + << account_map.size() << " discrep map size: " << discrep_map.size() + << " and starting meters map size: " << start_map.size(); + + // every meter has a value at the start of the turn, and a value after + // updating with known effects + for (auto& meter_pair : obj->Meters()) { + MeterType type = meter_pair.first; + // skip paired active meters, as differences in these are expected and persistent, and not a "discrepancy" + if (type >= METER_POPULATION && type <= METER_TROOPS) + continue; + Meter& meter = meter_pair.second; + + // discrepancy is the difference between expected and actual meter + // values at start of turn. here "expected" is what the meter value + // was before updating the meters, and actual is what it is now + // after updating the meters based on the known universe. + float discrepancy = start_map[type] - meter.Current(); if (discrepancy == 0.0f) continue; // no discrepancy for this meter - // add to discrepancy map - m_effect_discrepancy_map[object_id][type] = discrepancy; + // add to discrepancy map. as above, should have been empty before this loop. + discrep_map.emplace_hint(discrep_map.end(), type, discrepancy); // correct current max meter estimate for discrepancy meter.AddToCurrent(discrepancy); // add discrepancy adjustment to meter accounting - Effect::AccountingInfo info; - info.cause_type = ECT_UNKNOWN_CAUSE; - info.meter_change = discrepancy; - info.running_meter_total = meter.Current(); + account_map[type].emplace_back(INVALID_OBJECT_ID, ECT_UNKNOWN_CAUSE, discrepancy, meter.Current()); - m_effect_accounting_map[object_id][type].push_back(info); + TraceLogger(effects) << "... ... " << type << ": " << discrepancy; } } } void Universe::UpdateMeterEstimates() -{ UpdateMeterEstimates(INVALID_OBJECT_ID, false); } +{ UpdateMeterEstimates(GetOptionsDB().Get("effects.accounting.enabled")); } -void Universe::UpdateMeterEstimates(int object_id, bool update_contained_objects) { - if (object_id == INVALID_OBJECT_ID) { - for (int obj_id : m_objects.FindExistingObjectIDs()) - m_effect_accounting_map[obj_id].clear(); - // update meters for all objects. Value of updated_contained_objects is irrelivant and is ignored in this case. - UpdateMeterEstimatesImpl(std::vector());// will cause it to process all existing objects - return; - } +void Universe::UpdateMeterEstimates(bool do_accounting) { + for (int obj_id : m_objects.FindExistingObjectIDs()) + m_effect_accounting_map[obj_id].clear(); + // update meters for all objects. + UpdateMeterEstimatesImpl(std::vector(), do_accounting); +} - // collect objects to update meter for. this may be a single object, a group of related objects, or all objects - // in the (known) universe. also clear effect accounting for meters that are to be updated. - std::set objects_set; - std::list objects_list; - objects_list.push_back(object_id); +void Universe::UpdateMeterEstimates(int object_id, bool update_contained_objects) { + // ids of the object and all valid contained objects + std::unordered_set collected_ids; + + // Collect objects ids to update meter for. This may be a single object, a + // group of related objects. Return true if all collected ids are valid. + std::function collect_ids = + [this, &collected_ids, update_contained_objects, &collect_ids] + (int cur_id, int container_id) + { + // Ignore if already in the set + if (collected_ids.count(cur_id)) + return true; - for (int cur_object_id : objects_list) { - std::shared_ptr cur_object = m_objects.Object(cur_object_id); + auto cur_object = m_objects.get(cur_id); if (!cur_object) { - ErrorLogger() << "Universe::UpdateMeterEstimates tried to get an invalid object..."; - return; + ErrorLogger() << "Universe::UpdateMeterEstimates tried to get an invalid object for id " << cur_id + << " in container " << container_id + << ". All meter estimates will be updated."; + UpdateMeterEstimates(); + return false; } - // add object and clear effect accounting for all its meters - objects_set.insert(cur_object_id); - m_effect_accounting_map[cur_object_id].clear(); + // add object + collected_ids.insert(cur_id); // add contained objects to list of objects to process, if requested. - // assumes no objects contain themselves (which could cause infinite loops) - if (update_contained_objects) { - const std::set& contained_objects = cur_object->ContainedObjectIDs(); - std::copy(contained_objects.begin(), contained_objects.end(), std::back_inserter(objects_list)); - } - } + if (update_contained_objects) + for (const auto& contained_id : cur_object->ContainedObjectIDs()) + if (!collect_ids(contained_id, cur_id)) + return false; + return true; + }; + + if (!collect_ids(object_id, INVALID_OBJECT_ID)) + return; + + if (collected_ids.empty()) + return; + + // Clear ids that will be updated + for (auto cur_id : collected_ids) + m_effect_accounting_map[cur_id].clear(); + + // Convert to a vector std::vector objects_vec; - objects_vec.reserve(objects_set.size()); - std::copy(objects_set.begin(), objects_set.end(), std::back_inserter(objects_vec)); - if (!objects_vec.empty()) - UpdateMeterEstimatesImpl(objects_vec); + objects_vec.reserve(collected_ids.size()); + std::copy(collected_ids.begin(), collected_ids.end(), std::back_inserter(objects_vec)); + UpdateMeterEstimatesImpl(objects_vec, GetOptionsDB().Get("effects.accounting.enabled")); } void Universe::UpdateMeterEstimates(const std::vector& objects_vec) { @@ -695,112 +782,107 @@ void Universe::UpdateMeterEstimates(const std::vector& objects_vec) { for (int object_id : objects_vec) { // skip destroyed objects - if (m_destroyed_object_ids.find(object_id) != m_destroyed_object_ids.end()) + if (m_destroyed_object_ids.count(object_id)) continue; m_effect_accounting_map[object_id].clear(); objects_set.insert(object_id); } std::vector final_objects_vec; + final_objects_vec.reserve(objects_set.size()); std::copy(objects_set.begin(), objects_set.end(), std::back_inserter(final_objects_vec)); if (!final_objects_vec.empty()) - UpdateMeterEstimatesImpl(final_objects_vec); + UpdateMeterEstimatesImpl(final_objects_vec, GetOptionsDB().Get("effects.accounting.enabled")); } -void Universe::UpdateMeterEstimatesImpl(const std::vector& objects_vec) { - ScopedTimer timer("Universe::UpdateMeterEstimatesImpl on " + std::to_string(objects_vec.size()) + " objects", true); - bool do_accounting = GetOptionsDB().Get("effect-accounting"); +void Universe::UpdateMeterEstimatesImpl(const std::vector& objects_vec, bool do_accounting) { + auto number_text = std::to_string(objects_vec.empty() ? m_objects.ExistingObjects().size() : objects_vec.size()); + ScopedTimer timer("Universe::UpdateMeterEstimatesImpl on " + number_text + " objects", true); // get all pointers to objects once, to avoid having to do so repeatedly // when iterating over the list in the following code - std::vector> object_ptrs = m_objects.FindObjects(objects_vec); + auto object_ptrs = m_objects.find(objects_vec); if (objects_vec.empty()) { object_ptrs.reserve(m_objects.ExistingObjects().size()); - std::transform(Objects().ExistingObjects().begin(), Objects().ExistingObjects().end(), - std::back_inserter(object_ptrs), - boost::bind(&std::map>::value_type::second, _1)); + std::transform(m_objects.ExistingObjects().begin(), m_objects.ExistingObjects().end(), + std::back_inserter(object_ptrs), [](const std::map>::value_type& p) { + return std::const_pointer_cast(p.second); + }); } - for (std::shared_ptr obj : object_ptrs) { - int obj_id = obj->ID(); - - // Reset max meters to DEFAULT_VALUE and current meters to initial value at start of this turn + for (auto& obj : object_ptrs) { + // Reset max meters to DEFAULT_VALUE and current meters to initial value + // at start of this turn obj->ResetTargetMaxUnpairedMeters(); obj->ResetPairedActiveMeters(); if (!do_accounting) continue; - // record current value(s) of meters after resetting - for (MeterType type = MeterType(0); type != NUM_METER_TYPES; type = MeterType(type + 1)) { - if (Meter* meter = obj->GetMeter(type)) { - Effect::AccountingInfo info; - info.source_id = INVALID_OBJECT_ID; - info.cause_type = ECT_INHERENT; - info.meter_change = meter->Current() - Meter::DEFAULT_VALUE; - info.running_meter_total = meter->Current(); - - if (info.meter_change != 0.0f) - m_effect_accounting_map[obj_id][type].push_back(info); - } + auto& meters = obj->Meters(); + auto& account_map = m_effect_accounting_map[obj->ID()]; + account_map.clear(); // remove any old accounting info. this should be redundant here. + account_map.reserve(meters.size()); + + for (auto& meter_pair : meters) { + MeterType type = meter_pair.first; + const auto& meter = meter_pair.second; + float meter_change = meter.Current() - Meter::DEFAULT_VALUE; + if (meter_change != 0.0f) + account_map[type].emplace_back(INVALID_OBJECT_ID, ECT_INHERENT, meter_change, meter.Current()); } } TraceLogger(effects) << "UpdateMeterEstimatesImpl after resetting meters objects:"; - for (std::shared_ptr obj : object_ptrs) - TraceLogger(effects) << obj->Dump(); + for (auto& obj : object_ptrs) + TraceLogger(effects) << obj->Dump(); // cache all activation and scoping condition results before applying Effects, since the application of // these Effects may affect the activation and scoping evaluations - Effect::TargetsCauses targets_causes; - GetEffectsAndTargets(targets_causes, objects_vec); + std::map source_effects_targets_causes; + GetEffectsAndTargets(source_effects_targets_causes, objects_vec, true); // Apply and record effect meter adjustments - ExecuteEffects(targets_causes, do_accounting, true, false, false, false); + ExecuteEffects(source_effects_targets_causes, do_accounting, true, false, false, false); TraceLogger(effects) << "UpdateMeterEstimatesImpl after executing effects objects:"; - for (std::shared_ptr obj : object_ptrs) + for (auto& obj : object_ptrs) TraceLogger(effects) << obj->Dump(); // Apply known discrepancies between expected and calculated meter maxes at start of turn. This // accounts for the unknown effects on the meter, and brings the estimate in line with the actual // max at the start of the turn if (!m_effect_discrepancy_map.empty() && do_accounting) { - for (std::shared_ptr obj : object_ptrs) { - int obj_id = obj->ID(); - + for (auto& obj : object_ptrs) { // check if this object has any discrepancies - Effect::DiscrepancyMap::iterator dis_it = m_effect_discrepancy_map.find(obj_id); + auto dis_it = m_effect_discrepancy_map.find(obj->ID()); if (dis_it == m_effect_discrepancy_map.end()) continue; // no discrepancy, so skip to next object + auto& account_map = m_effect_accounting_map[obj->ID()]; // reserving space now should be redundant with previous manipulations + // apply all meters' discrepancies - for (std::map::value_type& entry : dis_it->second) { + for (auto& entry : dis_it->second) { MeterType type = entry.first; double discrepancy = entry.second; //if (discrepancy == 0.0) continue; Meter* meter = obj->GetMeter(type); + if (!meter) + continue; - if (meter) { - TraceLogger(effects) << "object " << obj_id << " has meter " << type - << ": discrepancy: " << discrepancy << " and : " << meter->Dump(); - - meter->AddToCurrent(discrepancy); + TraceLogger(effects) << "object " << obj->ID() << " has meter " << type + << ": discrepancy: " << discrepancy << " and : " << meter->Dump(); - Effect::AccountingInfo info; - info.cause_type = ECT_UNKNOWN_CAUSE; - info.meter_change = discrepancy; - info.running_meter_total = meter->Current(); + meter->AddToCurrent(discrepancy); - m_effect_accounting_map[obj_id][type].push_back(info); - } + account_map[type].emplace_back(INVALID_OBJECT_ID, ECT_UNKNOWN_CAUSE, discrepancy, meter->Current()); } } } // clamp meters to valid range of max values, and so current is less than max - for (std::shared_ptr obj : object_ptrs) { + for (auto& obj : object_ptrs) { // currently this clamps all meters, even if not all meters are being processed by this function... // but that shouldn't be a problem, as clamping meters that haven't changed since they were last // updated should have no effect @@ -808,331 +890,340 @@ void Universe::UpdateMeterEstimatesImpl(const std::vector& objects_vec) { } TraceLogger(effects) << "UpdateMeterEstimatesImpl after discrepancies and clamping objects:"; - for (std::shared_ptr obj : object_ptrs) + for (auto& obj : object_ptrs) TraceLogger(effects) << obj->Dump(); - } -void Universe::BackPropagateObjectMeters(const std::vector& object_ids) { - // copy current meter values to initial values - for (std::shared_ptr obj : m_objects.FindObjects(object_ids)) +void Universe::BackPropagateObjectMeters() { + for (const auto& obj : m_objects.all()) obj->BackPropagateMeters(); } -void Universe::BackPropagateObjectMeters() -{ BackPropagateObjectMeters(m_objects.FindObjectIDs()); } - namespace { - /** Used by GetEffectsAndTargets to process a vector of effects groups. - * Stores target set of specified \a effects_groups and \a source_object_id - * in \a targets_causes - * NOTE: this method will modify target_objects temporarily, but restore - * its contents before returning. - * This is a calleable class instead of an ordinary method so that we can - * use it as work item in parallel scheduling. - */ - class StoreTargetsAndCausesOfEffectsGroupsWorkItem { - public: - struct ConditionCache : public boost::noncopyable { - public: - std::pair* Find(const Condition::ConditionBase* cond, bool insert); - void MarkComplete(std::pair* cache_entry); - void LockShared(boost::shared_lock& guard); - private: - std::map> m_entries; - boost::shared_mutex m_mutex; - boost::condition_variable_any m_state_changed; - }; - StoreTargetsAndCausesOfEffectsGroupsWorkItem( - const std::shared_ptr& the_effects_group, - const std::vector>& the_sources, - EffectsCauseType the_effect_cause_type, - const std::string& the_specific_cause_name, - Effect::TargetSet& the_target_objects, - Effect::TargetsCauses& the_targets_causes, - std::map>& the_source_cached_condition_matches, - ConditionCache& the_invariant_cached_condition_matches, - boost::shared_mutex& the_global_mutex - ); - void operator ()(); - - /** Return a report of that state of this work item. */ - std::string GenerateReport() const; - private: - // WARNING: do NOT copy the shared_pointers! Use raw pointers, shared_ptr may not be thread-safe. - std::shared_ptr m_effects_group; - const std::vector>* m_sources; - EffectsCauseType m_effect_cause_type; - const std::string m_specific_cause_name; - Effect::TargetSet* m_target_objects; - Effect::TargetsCauses* m_targets_causes; - std::map>* m_source_cached_condition_matches; - ConditionCache* m_invariant_cached_condition_matches; - boost::shared_mutex* m_global_mutex; - - static Effect::TargetSet& GetConditionMatches( - const Condition::ConditionBase* cond, - ConditionCache& cached_condition_matches, - std::shared_ptr source, - const ScriptingContext& source_context, - Effect::TargetSet& target_objects); - }; - - StoreTargetsAndCausesOfEffectsGroupsWorkItem::StoreTargetsAndCausesOfEffectsGroupsWorkItem( - const std::shared_ptr& the_effects_group, - const std::vector>& the_sources, - EffectsCauseType the_effect_cause_type, - const std::string& the_specific_cause_name, - Effect::TargetSet& the_target_objects, - Effect::TargetsCauses& the_targets_causes, - std::map>& the_source_cached_condition_matches, - ConditionCache& the_invariant_cached_condition_matches, - boost::shared_mutex& the_global_mutex - ) : - m_effects_group (the_effects_group), - m_sources (&the_sources), - m_effect_cause_type (the_effect_cause_type), - // create a deep copy just in case string methods do unlocked copy-on-write or other unsafe things - m_specific_cause_name (the_specific_cause_name.c_str()), - m_target_objects (&the_target_objects), - m_targets_causes (&the_targets_causes), - m_source_cached_condition_matches (&the_source_cached_condition_matches), - m_invariant_cached_condition_matches (&the_invariant_cached_condition_matches), - m_global_mutex (&the_global_mutex) - {} - - std::pair* StoreTargetsAndCausesOfEffectsGroupsWorkItem::ConditionCache::Find( - const Condition::ConditionBase* cond, bool insert) + /** Evaluate activation, and scope conditions of \a effects_group for + * each of the objects in \a source_objects for the candidate target + * objects in \a candidate_objects_in (unless it is empty, in which case + * the default candidate objects for the scope condition is used. Stores + * the objects that matched the effects group's scope condition, for each + * source object, as a separate entry in \a targets_cases_out */ + void StoreTargetsAndCausesOfEffectsGroup( + const ObjectMap& object_map, + const Effect::EffectsGroup* effects_group, + const Condition::ObjectSet& source_objects, + EffectsCauseType effect_cause_type, + const std::string& specific_cause_name, + const std::unordered_set& candidate_object_ids, // TODO: Can this be removed along with scope is source test? + Condition::ObjectSet& candidate_objects_in, // may be empty: indicates to test for full universe of objects + Effect::SourcesEffectsTargetsAndCausesVec& source_effects_targets_causes_out, + int n) { - // have to iterate through cached condition matches, rather than using - // find, since there is no operator< for comparing conditions by value - // and by pointer is irrelivant. - boost::unique_lock unique_guard(m_mutex, boost::defer_lock_t()); - boost::shared_lock shared_guard(m_mutex, boost::defer_lock_t()); - - if (insert) unique_guard.lock(); else shared_guard.lock(); - - for (std::map>::value_type& entry : m_entries) { - if (*cond == *(entry.first)) { - //DebugLogger() << "Reused target set!"; - - if (insert) { - // no need to insert. downgrade lock - unique_guard.unlock(); - shared_guard.lock(); - } + TraceLogger(effects) << "StoreTargetsAndCausesOfEffectsGroup < " << n << " >" + << " cause type: " << effect_cause_type + << " specific cause: " << specific_cause_name << " )"; - // wait for cache fill - while (!entry.second.first) - m_state_changed.wait(shared_guard); + auto scope = effects_group->Scope(); + if (!scope) + return; + bool scope_is_just_source = dynamic_cast(scope); + + auto message{"StoreTargetsAndCausesOfEffectsGroup < " + std::to_string(n) + " >" + + " cause type: " + boost::lexical_cast(effect_cause_type) + + " specific cause: " + specific_cause_name + + (scope_is_just_source ? " [Scope = Source]" : "") + + " sources: " + std::to_string(source_objects.size())}; + + ScopedTimer timer(message, std::chrono::milliseconds(5)); + + source_effects_targets_causes_out.reserve(source_objects.size()); + ScriptingContext source_context(object_map); + + for (auto& source : source_objects) { + // assuming input sources objects set was already filtered with activation condition + source_context.source = source; + // construct output in-place + + // SourcedEffectsGroup {int source_object_id; const EffectsGroup* effects_group;} + // EffectCause {EffectsCauseType cause_type; std::string specific_cause; std::string custom_label; } + // TargetsAndCause {TargetSet target_set; EffectCause effect_cause;} + // typedef std::vector> SourcesEffectsTargetsAndCausesVec; + source_effects_targets_causes_out.emplace_back( + Effect::SourcedEffectsGroup{source->ID(), effects_group}, + Effect::TargetsAndCause{ + {}, // empty Effect::TargetSet + Effect::EffectCause{effect_cause_type, specific_cause_name, effects_group->AccountingLabel()}}); + + // extract output Effect::TargetSet and alias to receive condition matches + Effect::TargetSet& matched_targets{source_effects_targets_causes_out.back().second.target_set}; + Condition::ObjectSet& matches = reinterpret_cast(matched_targets); + + // move scope condition matches into output matches + if (candidate_objects_in.empty()) { + // condition default candidates will be tested + scope->Eval(source_context, matches); + + } else if (scope_is_just_source) { + // special case for condition that is just source when a set of candidates is specified: only need to put the source in if it is in the candidates + if (candidate_object_ids.count(source->ID())) + matches.push_back(source); - return &entry.second; + } else { + // input candidates will all be tested + scope->Eval(source_context, matches, candidate_objects_in); } - } - - // nothing found - if (insert) - // set up storage - return &m_entries[cond]; - - return nullptr; - } - void StoreTargetsAndCausesOfEffectsGroupsWorkItem::ConditionCache::MarkComplete(std::pair* cache_entry) + TraceLogger(effects) << "Scope Results " << n << " source: " << source->ID() + << " matches: " << [&]() { + std::stringstream ss; + for (auto& obj : matches) + ss << obj->ID() << ", "; + return ss.str(); + }(); + } + } + + + /** Collect info for scope condition evaluations and dispatch those + * evaluations to \a thread_pool. Not thread-safe, but the individual + * condition evaluations should be safe to evaluate in parallel. */ + void DispatchEffectsGroupScopeEvaluations( + EffectsCauseType effect_cause_type, + const std::string& specific_cause_name, + const Condition::ObjectSet& source_objects, + const std::vector>& effects_groups, + bool only_meter_effects, + const ObjectMap& object_map, + const Condition::ObjectSet& potential_targets, + const std::unordered_set& potential_target_ids, + std::list>& source_effects_targets_causes_reorder_buffer_out, + boost::asio::thread_pool& thread_pool, + int& n) { - boost::unique_lock cache_guard(m_mutex); // make sure threads are waiting for completion, not checking for completion - cache_entry->first = true; - m_state_changed.notify_all(); // signal cachefill - } - - void StoreTargetsAndCausesOfEffectsGroupsWorkItem::ConditionCache::LockShared(boost::shared_lock& guard) { - boost::shared_lock tmp_guard(m_mutex); - guard.swap(tmp_guard); - } - - Effect::TargetSet EMPTY_TARGET_SET; - - Effect::TargetSet& StoreTargetsAndCausesOfEffectsGroupsWorkItem::GetConditionMatches( - const Condition::ConditionBase* cond, - StoreTargetsAndCausesOfEffectsGroupsWorkItem::ConditionCache& cached_condition_matches, - std::shared_ptr source, - const ScriptingContext& source_context, - Effect::TargetSet& target_objects) - { - std::pair* cache_entry = nullptr; - - if (!cond) - return EMPTY_TARGET_SET; - - // the passed-in cached_condition_matches are here expected be specific for the current source object - cache_entry = cached_condition_matches.Find(cond, false); - if (cache_entry) - return cache_entry->second; - - // no cached result (yet). create cache entry - cache_entry = cached_condition_matches.Find(cond, true); - if (cache_entry->first) - return cache_entry->second; // some other thread was faster creating the cache entry + std::vector> already_evaluated_activation_condition_idx; + already_evaluated_activation_condition_idx.reserve(effects_groups.size()); + + TraceLogger(effects) << "Checking activation condition for " << source_objects.size() + << " sources and " + << (potential_targets.empty() ? "full universe" : std::to_string(potential_targets.size())) + << " potential targets"; + + // evaluate activation conditions of effects_groups on input source objects + std::vector active_sources{effects_groups.size()}; + ScriptingContext source_context{object_map}; + for (std::size_t i = 0; i < effects_groups.size(); ++i) { + const auto* effects_group = effects_groups.at(i).get(); + if (only_meter_effects && !effects_group->HasMeterEffects()) + continue; + if (!effects_group->Scope()) + continue; - // no cached result. calculate it... + if (!effects_group->Activation()) { + // no activation condition, leave all sources active + active_sources[i] = source_objects; + continue; - Effect::TargetSet* target_set = &cache_entry->second; - Condition::ObjectSet& matched_target_objects = - *reinterpret_cast(target_set); - if (target_objects.empty()) { - // move matches from default target candidates into target_set - cond->Eval(source_context, matched_target_objects); - } else { - // move matches from candidates in target_objects into target_set - Condition::ObjectSet& potential_target_objects = - *reinterpret_cast(&target_objects); + } else { + // check if this activation condition has already been evaluated + bool cache_hit = false; + for (const auto& cond_idx : already_evaluated_activation_condition_idx) { + if (*cond_idx.first == *(effects_group->Activation())) { + active_sources[i] = active_sources[cond_idx.second]; // copy previous condition evaluation result + cache_hit = true; + break; + } + } + if (cache_hit) + continue; // don't need to evaluate activation condition on these sources again + + // no cache hit; need to evaluate activation condition on input source objects + if (effects_group->Activation()->SourceInvariant()) { + // can apply condition to all source objects simultaneously + Condition::ObjectSet rejected; + rejected.reserve(source_objects.size()); + active_sources[i] = source_objects; // copy input source objects set + source_context.source = nullptr; + effects_group->Activation()->Eval(source_context, active_sources[i], rejected, Condition::MATCHES); + + } else { + // need to apply separately to each source object + active_sources[i].reserve(source_objects.size()); + for (auto& obj : source_objects) { + source_context.source = obj; + if (effects_group->Activation()->Eval(source_context, obj)) + active_sources[i].push_back(obj); + } + } - // move matches from candidates in target_objects into target_set - cond->Eval(source_context, matched_target_objects, potential_target_objects); - // restore target_objects by copying objects back from targets to target_objects - // this should be cheaper than doing a full copy because target_set is usually small - target_objects.insert(target_objects.end(), target_set->begin(), target_set->end()); + // save evaluation lookup index in cache + already_evaluated_activation_condition_idx.emplace_back(effects_group->Activation(), i); + } } - cached_condition_matches.MarkComplete(cache_entry); - - //DebugLogger() << "Generated new target set!"; - return *target_set; - } - std::string StoreTargetsAndCausesOfEffectsGroupsWorkItem::GenerateReport() const { - boost::unique_lock guard(*m_global_mutex); - std::stringstream ss; - ss << "StoreTargetsAndCausesOfEffectsGroups: effects_group: " - << m_effects_group->AccountingLabel() - << " specific_cause: " << m_specific_cause_name - << " sources: "; - for (const auto& obj : *m_sources) - ss << obj->Name() << " (" << std::to_string(obj->ID()) << ") "; - ss << ")"; - return ss.str(); - } + TraceLogger(effects) << "After activation condition, for " << effects_groups.size() << " effects groups " + << "have # sources: " << [&active_sources]() + { + std::stringstream ss; + for (auto& src_set : active_sources) + ss << src_set.size() << ", "; + return ss.str(); + }(); - void StoreTargetsAndCausesOfEffectsGroupsWorkItem::operator()() - { - ScopedTimer timer("StoreTargetsAndCausesOfEffectsGroups"); - TraceLogger(effects) << GenerateReport(); + // TODO: is it faster to index by scope and activation condition or scope and filtered sources set? + std::vector> already_dispatched_scope_condition_ptrs; + already_evaluated_activation_condition_idx.reserve(effects_groups.size()); - // get objects matched by scope - const Condition::ConditionBase* scope = m_effects_group->Scope(); - if (!scope) - return; - // create temporary container for concurrent work - Effect::TargetSet target_objects(*m_target_objects); - // process all sources in set provided - for (std::shared_ptr source : *m_sources) { - ScriptingContext source_context(source); - int source_object_id = (source ? source->ID() : INVALID_OBJECT_ID); - ScopedTimer update_timer("... StoreTargetsAndCausesOfEffectsGroups done processing source " + - std::to_string(source_object_id) + - " cause: " + m_specific_cause_name); - - // skip inactive sources - // FIXME: is it safe to move this out of the loop? - // Activation condition must not contain "Source" subconditions in that case - const Condition::ConditionBase* activation = m_effects_group->Activation(); - if (activation && !activation->Eval(source_context, source)) + // evaluate scope conditions for source objects that are active + for (std::size_t i = 0; i < effects_groups.size(); ++i) { + if (active_sources[i].empty()) { + TraceLogger(effects) << "Skipping empty active sources set"; continue; - - // if scope is source-invariant, use the source-invariant cache of condition results. - // if scope depends on the source object, use a cache of condition results for that souce object. - bool source_invariant = !source || scope->SourceInvariant(); - ConditionCache* condition_cache = source_invariant ? m_invariant_cached_condition_matches : (*m_source_cached_condition_matches)[source_object_id].get(); - // look up scope condition in the cache. if not found, calculate it and store in the cache. either way, return the result. - Effect::TargetSet& target_set = GetConditionMatches(scope, - *condition_cache, - source, - source_context, - target_objects); - { - boost::shared_lock cache_guard; - - condition_cache->LockShared(cache_guard); - if (target_set.empty()) - continue; } + TraceLogger(effects) << "Handing active sources set of size: " << active_sources[i].size(); - { - // NOTE: std::shared_ptr copying is not thread-safe. - // FIXME: use std::shared_ptr here, or a dedicated lock - boost::unique_lock guard(*m_global_mutex); + const auto* effects_group = effects_groups.at(i).get(); + n++; - // combine effects group and source object id into a sourced effects group - Effect::SourcedEffectsGroup sourced_effects_group(source_object_id, m_effects_group); + // allocate space to store output of effectsgroup targets evaluation + // for the sources and this effects group + source_effects_targets_causes_reorder_buffer_out.emplace_back(); + source_effects_targets_causes_reorder_buffer_out.back().second = nullptr; // default, may be overwritten - // combine cause type and specific cause into effect cause - Effect::EffectCause effect_cause(m_effect_cause_type, m_specific_cause_name, - m_effects_group->AccountingLabel()); - // combine target set and effect cause - Effect::TargetsAndCause target_and_cause(target_set, effect_cause); + // check if the scope-condition + sources set has already been dispatched + bool cache_hit = false; + if (effects_group->Scope()) { + for (const auto& cond_sources_ptr : already_dispatched_scope_condition_ptrs) { + if (*std::get<0>(cond_sources_ptr) == *(effects_group->Scope()) && + std::get<1>(cond_sources_ptr) == active_sources[i]) + { + TraceLogger(effects) << "scope condition cache hit !"; + + // record pointer to previously-dispatched result struct + // that will contain the results to copy later, after + // all dispatched condition evauations have resolved + source_effects_targets_causes_reorder_buffer_out.back().second = std::get<2>(cond_sources_ptr); + + // allocate result structs that contain empty + // Effect::TargetSets that will be filled later + auto& vec_out{source_effects_targets_causes_reorder_buffer_out.back().first}; + for (auto& source : active_sources[i]) { + source_context.source = source; + vec_out.emplace_back( + Effect::SourcedEffectsGroup{source->ID(), effects_group}, + Effect::TargetsAndCause{ + {}, // empty Effect::TargetSet + Effect::EffectCause{effect_cause_type, specific_cause_name, + effects_group->AccountingLabel()}}); + } - // store effect cause and targets info in map, indexed by sourced effects group - m_targets_causes->push_back(std::make_pair(sourced_effects_group, target_and_cause)); + cache_hit = true; + break; + } + } + } + if (cache_hit) + continue; + if (!cache_hit) { + TraceLogger(effects) << "scope condition cache miss idx: " << n; + + // add cache entry for this combination, with pointer to the + // storage that will contain the to-be-dispatched scope + // condition evaluation results + already_dispatched_scope_condition_ptrs.emplace_back( + effects_group->Scope(), active_sources[i], + &source_effects_targets_causes_reorder_buffer_out.back().first); } + + + TraceLogger(effects) << "Dispatching Scope Evaluations < " << n << " > sources: " << [&]() { + std::stringstream ss; + for (auto& obj : active_sources[i]) + ss << obj->ID() << ", "; + return ss.str(); + }() + << " cause type: " << effect_cause_type + << " specific cause: " << specific_cause_name + << " candidates: " << [&]() { + std::stringstream ss; + for (auto& obj : potential_targets) + ss << obj->ID() << ", "; + return ss.str(); + }(); + + // asynchronously evaluate targetset for effectsgroup for each source using worker threads + boost::asio::post( + thread_pool, + [ + &object_map, + effects_group, + active_source_objects{active_sources[i]}, + effect_cause_type, + specific_cause_name, + &potential_target_ids, + local_potential_targets{potential_targets}, + &source_effects_targets_causes_vec_out = source_effects_targets_causes_reorder_buffer_out.back().first, + n + ]() mutable + { + StoreTargetsAndCausesOfEffectsGroup(object_map, effects_group, active_source_objects, + effect_cause_type, specific_cause_name, + potential_target_ids, local_potential_targets, + source_effects_targets_causes_vec_out, n); + }); } } - } // namespace -void Universe::GetEffectsAndTargets(Effect::TargetsCauses& targets_causes) { - targets_causes.clear(); - GetEffectsAndTargets(targets_causes, std::vector()); +void Universe::GetEffectsAndTargets(std::map& source_effects_targets_causes, + bool only_meter_effects) const +{ + source_effects_targets_causes.clear(); + GetEffectsAndTargets(source_effects_targets_causes, std::vector(), only_meter_effects); } -void Universe::GetEffectsAndTargets(Effect::TargetsCauses& targets_causes, - const std::vector& target_objects) +void Universe::GetEffectsAndTargets(std::map& source_effects_targets_causes, + const std::vector& target_object_ids, + bool only_meter_effects) const { - ScopedTimer timer("Universe::GetEffectsAndTargets"); + SectionedScopedTimer type_timer("Effect TargetSets Evaluation", std::chrono::microseconds(0)); - // transfer target objects from input vector to a set - Effect::TargetSet all_potential_targets = m_objects.FindObjects(target_objects); + // assemble target objects from input vector of IDs + Condition::ObjectSet potential_targets{m_objects.find(target_object_ids)}; + std::unordered_set potential_ids_set{target_object_ids.begin(), target_object_ids.end()}; - TraceLogger(effects) << "target objects:"; - for (std::shared_ptr obj : all_potential_targets) + TraceLogger(effects) << "GetEffectsAndTargets input candidate target objects:"; + for (auto& obj : potential_targets) TraceLogger(effects) << obj->Dump(); - // caching space for each source object's results of finding matches for - // scope conditions. Index INVALID_OBJECT_ID stores results for - // source-invariant conditions - typedef StoreTargetsAndCausesOfEffectsGroupsWorkItem::ConditionCache ConditionCache; - std::map> cached_source_condition_matches; - // prepopulate the cache for safe concurrent access - for (int obj_id : m_objects.FindObjectIDs()) { - cached_source_condition_matches[obj_id] = std::make_shared(); - } - - cached_source_condition_matches[INVALID_OBJECT_ID] = std::make_shared(); - - ConditionCache& invariant_condition_matches = *cached_source_condition_matches[INVALID_OBJECT_ID]; + // list, not vector, to avoid invaliding iterators when pushing more items + // onto list due to vector reallocation. + // .first are results of evaluating an effectsgroups's activation and source + // conditions for a set of candidate source objects + // .second may be nullptr, in which case it is ignored, or may be a pointer + // to another earlier entry in this list, which contains the results of + // evaluating the same scope condition on the same set of activation-passing + // source objects, and which should be copied into the paired Vec + std::list> source_effects_targets_causes_reorder_buffer; - boost::timer type_timer; - boost::timer eval_timer; + const unsigned int num_threads = static_cast(std::max(1, EffectsProcessingThreads())); + boost::asio::thread_pool thread_pool(num_threads); - std::list targets_causes_reorder_buffer; // create before run_queue, destroy after run_queue - unsigned int num_threads = static_cast(std::max(1, EffectsProcessingThreads())); - RunQueue run_queue(num_threads); - boost::shared_mutex global_mutex; - boost::unique_lock global_lock(global_mutex); // create after run_queue, destroy before run_queue + int n = 1; // count dispatched condition evaluations - eval_timer.restart(); // 1) EffectsGroups from Species + type_timer.EnterSection("species"); TraceLogger(effects) << "Universe::GetEffectsAndTargets for SPECIES"; - type_timer.restart(); - - // find each species planets in single pass, maintaining object map order per-species std::map>> species_objects; - for (std::shared_ptr planet : m_objects.FindObjects()) { - if (m_destroyed_object_ids.find(planet->ID()) != m_destroyed_object_ids.end()) + // find each species planets in single pass, maintaining object map order per-species + for (auto& planet : m_objects.all()) { + if (m_destroyed_object_ids.count(planet->ID())) continue; const std::string& species_name = planet->SpeciesName(); if (species_name.empty()) @@ -1144,13 +1235,9 @@ void Universe::GetEffectsAndTargets(Effect::TargetsCauses& targets_causes, } species_objects[species_name].push_back(planet); } - - double planet_species_time = type_timer.elapsed(); - type_timer.restart(); - // find each species ships in single pass, maintaining object map order per-species - for (std::shared_ptr ship : m_objects.FindObjects()) { - if (m_destroyed_object_ids.find(ship->ID()) != m_destroyed_object_ids.end()) + for (auto& ship : m_objects.all()) { + if (m_destroyed_object_ids.count(ship->ID())) continue; const std::string& species_name = ship->SpeciesName(); if (species_name.empty()) @@ -1162,39 +1249,36 @@ void Universe::GetEffectsAndTargets(Effect::TargetsCauses& targets_causes, } species_objects[species_name].push_back(ship); } - double ship_species_time = type_timer.elapsed(); - - // enforce species effects order - for (const std::map::value_type& entry : GetSpeciesManager()) { + // allocate storage for target sets and dispatch condition evaluations + for (const auto& entry : GetSpeciesManager()) { const std::string& species_name = entry.first; - const Species* species = entry.second; - std::map>>::iterator species_objects_it = - species_objects.find(species_name); - + const auto& species = entry.second; + auto species_objects_it = species_objects.find(species_name); if (species_objects_it == species_objects.end()) continue; + const auto& source_objects = species_objects_it->second; + if (source_objects.empty()) + continue; - for (std::shared_ptr effects_group : species->Effects()) { - targets_causes_reorder_buffer.push_back(Effect::TargetsCauses()); - run_queue.AddWork(new StoreTargetsAndCausesOfEffectsGroupsWorkItem( - effects_group, species_objects_it->second, ECT_SPECIES, species_name, - all_potential_targets, targets_causes_reorder_buffer.back(), - cached_source_condition_matches, - invariant_condition_matches, - global_mutex)); - } + DispatchEffectsGroupScopeEvaluations(ECT_SPECIES, species_name, + source_objects, species->Effects(), + only_meter_effects, + m_objects, potential_targets, + potential_ids_set, + source_effects_targets_causes_reorder_buffer, + thread_pool, n); } + // 2) EffectsGroups from Specials + type_timer.EnterSection("specials"); TraceLogger(effects) << "Universe::GetEffectsAndTargets for SPECIALS"; - type_timer.restart(); std::map>> specials_objects; // determine objects with specials in a single pass - for (std::shared_ptr obj : m_objects) { - int source_object_id = obj->ID(); - if (m_destroyed_object_ids.find(source_object_id) != m_destroyed_object_ids.end()) + for (const auto& obj : m_objects.all()) { + if (m_destroyed_object_ids.count(obj->ID())) continue; - for (const std::map>::value_type& entry : obj->Specials()) { + for (const auto& entry : obj->Specials()) { const std::string& special_name = entry.first; const Special* special = GetSpecial(special_name); if (!special) { @@ -1204,64 +1288,66 @@ void Universe::GetEffectsAndTargets(Effect::TargetsCauses& targets_causes, specials_objects[special_name].push_back(obj); } } - // enforce specials effects order + // dispatch condition evaluations for (const std::string& special_name : SpecialNames()) { - const Special* special = GetSpecial(special_name); - std::map>>::iterator specials_objects_it = specials_objects.find(special_name); - + const Special* special = GetSpecial(special_name); + auto specials_objects_it = specials_objects.find(special_name); if (specials_objects_it == specials_objects.end()) continue; + const auto& source_objects = specials_objects_it->second; + if (source_objects.empty()) + continue; - for (std::shared_ptr effects_group : special->Effects()) { - targets_causes_reorder_buffer.push_back(Effect::TargetsCauses()); - run_queue.AddWork(new StoreTargetsAndCausesOfEffectsGroupsWorkItem( - effects_group, specials_objects_it->second, ECT_SPECIAL, special_name, - all_potential_targets, targets_causes_reorder_buffer.back(), - cached_source_condition_matches, - invariant_condition_matches, - global_mutex)); - } + DispatchEffectsGroupScopeEvaluations(ECT_SPECIAL, special_name, + source_objects, special->Effects(), + only_meter_effects, + m_objects, potential_targets, + potential_ids_set, + source_effects_targets_causes_reorder_buffer, + thread_pool, n); } - double special_time = type_timer.elapsed(); + // 3) EffectsGroups from Techs + type_timer.EnterSection("techs"); TraceLogger(effects) << "Universe::GetEffectsAndTargets for TECHS"; - type_timer.restart(); - std::list>> tech_sources; - for (std::map::value_type& entry : Empires()) { + std::list tech_sources; // for each empire, a set with a single source object for all its techs + // select a source object for each empire and dispatch condition evaluations + for (auto& entry : Empires()) { const Empire* empire = entry.second; - std::shared_ptr source = empire->Source(); + auto source = empire->Source(); if (!source) continue; - tech_sources.push_back(std::vector>(1U, source)); - for (const std::string& tech_name : empire->AvailableTechs()) { + // unlike species and special effectsgroups, all techs for an empire have the same source object + tech_sources.emplace_back(Condition::ObjectSet{1U, source}); + const auto& source_objects = tech_sources.back(); + + for (const auto tech_entry : empire->ResearchedTechs()) { + const std::string& tech_name{tech_entry.first}; const Tech* tech = GetTech(tech_name); if (!tech) continue; - for (std::shared_ptr effects_group : tech->Effects()) { - targets_causes_reorder_buffer.push_back(Effect::TargetsCauses()); - run_queue.AddWork(new StoreTargetsAndCausesOfEffectsGroupsWorkItem( - effects_group, tech_sources.back(), ECT_TECH, tech->Name(), - all_potential_targets, targets_causes_reorder_buffer.back(), - cached_source_condition_matches, - invariant_condition_matches, - global_mutex)); - } + DispatchEffectsGroupScopeEvaluations(ECT_TECH, tech_name, + source_objects, tech->Effects(), + only_meter_effects, + m_objects, potential_targets, + potential_ids_set, + source_effects_targets_causes_reorder_buffer, + thread_pool, n); } } - double tech_time = type_timer.elapsed(); + // 4) EffectsGroups from Buildings + type_timer.EnterSection("buildings"); TraceLogger(effects) << "Universe::GetEffectsAndTargets for BUILDINGS"; - type_timer.restart(); - // determine buildings of each type in a single pass std::map>> buildings_by_type; - for (std::shared_ptr building : m_objects.FindObjects()) { - if (m_destroyed_object_ids.find(building->ID()) != m_destroyed_object_ids.end()) + for (auto& building : m_objects.all()) { + if (m_destroyed_object_ids.count(building->ID())) continue; - const std::string& building_type_name = building->BuildingTypeName(); + const std::string& building_type_name = building->BuildingTypeName(); const BuildingType* building_type = GetBuildingType(building_type_name); if (!building_type) { ErrorLogger() << "GetEffectsAndTargets couldn't get BuildingType " << building->BuildingTypeName(); @@ -1270,114 +1356,109 @@ void Universe::GetEffectsAndTargets(Effect::TargetsCauses& targets_causes, buildings_by_type[building_type_name].push_back(building); } - - // enforce building types effects order + // dispatch condition evaluations for (const auto& entry : GetBuildingTypeManager()) { - const std::string& building_type_name = entry.first; - const BuildingType* building_type = entry.second.get(); - std::map>>::iterator buildings_by_type_it = - buildings_by_type.find(building_type_name); - + const std::string& building_type_name = entry.first; + const BuildingType* building_type = entry.second.get(); + auto buildings_by_type_it = buildings_by_type.find(building_type_name); if (buildings_by_type_it == buildings_by_type.end()) continue; + const auto& source_objects = buildings_by_type_it->second; + if (source_objects.empty()) + continue; - for (std::shared_ptr effects_group : building_type->Effects()) { - targets_causes_reorder_buffer.push_back(Effect::TargetsCauses()); - run_queue.AddWork(new StoreTargetsAndCausesOfEffectsGroupsWorkItem( - effects_group, buildings_by_type_it->second, ECT_BUILDING, building_type_name, - all_potential_targets, targets_causes_reorder_buffer.back(), - cached_source_condition_matches, - invariant_condition_matches, - global_mutex)); - } + DispatchEffectsGroupScopeEvaluations(ECT_BUILDING, building_type_name, + source_objects, building_type->Effects(), + only_meter_effects, + m_objects, potential_targets, + potential_ids_set, + source_effects_targets_causes_reorder_buffer, + thread_pool, n); } - double building_time = type_timer.elapsed(); + // 5) EffectsGroups from Ship Hull and Ship Parts + type_timer.EnterSection("ship hull/parts"); TraceLogger(effects) << "Universe::GetEffectsAndTargets for SHIPS hulls and parts"; - type_timer.restart(); // determine ship hulls and parts of each type in a single pass // the same ship might be added multiple times if it contains the part multiple times // recomputing targets for the same ship and part is kind of silly here, but shouldn't hurt - std::map>> ships_by_hull_type; - std::map>> ships_by_part_type; - for (std::shared_ptr ship : m_objects.FindObjects()) { - if (m_destroyed_object_ids.find(ship->ID()) != m_destroyed_object_ids.end()) - continue; + std::map>> ships_by_ship_hull; + std::map>> ships_by_ship_part; + for (auto& ship : m_objects.all()) { + if (m_destroyed_object_ids.count(ship->ID())) + continue; const ShipDesign* ship_design = ship->Design(); if (!ship_design) continue; - const HullType* hull_type = ship_design->GetHull(); - if (!hull_type) { - ErrorLogger() << "GetEffectsAndTargets couldn't get HullType"; + const ShipHull* ship_hull = GetShipHull(ship_design->Hull()); + if (!ship_hull) { + ErrorLogger() << "GetEffectsAndTargets couldn't get ShipHull"; continue; } - - ships_by_hull_type[hull_type->Name()].push_back(ship); + ships_by_ship_hull[ship_hull->Name()].push_back(ship); for (const std::string& part : ship_design->Parts()) { if (part.empty()) continue; - const PartType* part_type = GetPartType(part); - if (!part_type) { - ErrorLogger() << "GetEffectsAndTargets couldn't get PartType"; + const ShipPart* ship_part = GetShipPart(part); + if (!ship_part) { + ErrorLogger() << "GetEffectsAndTargets couldn't get ShipPart"; continue; } - - ships_by_part_type[part].push_back(ship); + ships_by_ship_part[part].push_back(ship); } } - // enforce hull types effects order - for (const std::map::value_type& entry : GetHullTypeManager()) { - const std::string& hull_type_name = entry.first; - const HullType* hull_type = entry.second; - std::map>>::iterator ships_by_hull_type_it = ships_by_hull_type.find(hull_type_name); - - if (ships_by_hull_type_it == ships_by_hull_type.end()) + // dispatch hull condition evaluations + for (const auto& entry : GetShipHullManager()) { + const std::string& ship_hull_name = entry.first; + const auto& ship_hull = entry.second; + auto ships_by_hull_it = ships_by_ship_hull.find(ship_hull_name); + if (ships_by_hull_it == ships_by_ship_hull.end()) + continue; + const auto& source_objects = ships_by_hull_it->second; + if (source_objects.empty()) continue; - for (std::shared_ptr effects_group : hull_type->Effects()) { - targets_causes_reorder_buffer.push_back(Effect::TargetsCauses()); - run_queue.AddWork(new StoreTargetsAndCausesOfEffectsGroupsWorkItem( - effects_group, ships_by_hull_type_it->second, ECT_SHIP_HULL, hull_type_name, - all_potential_targets, targets_causes_reorder_buffer.back(), - cached_source_condition_matches, - invariant_condition_matches, - global_mutex)); - } - } - // enforce part types effects order - for (const std::map::value_type& entry : GetPartTypeManager()) { - const std::string& part_type_name = entry.first; - const PartType* part_type = entry.second; - std::map>>::iterator ships_by_part_type_it = ships_by_part_type.find(part_type_name); - - if (ships_by_part_type_it == ships_by_part_type.end()) + DispatchEffectsGroupScopeEvaluations(ECT_SHIP_HULL, ship_hull_name, + source_objects, ship_hull->Effects(), + only_meter_effects, + m_objects, potential_targets, + potential_ids_set, + source_effects_targets_causes_reorder_buffer, + thread_pool, n); + } + // dispatch part condition evaluations + for (const auto& entry : GetShipPartManager()) { + const std::string& ship_part_name = entry.first; + const ShipPart* ship_part = entry.second.get(); + auto ships_by_ship_part_it = ships_by_ship_part.find(ship_part_name); + if (ships_by_ship_part_it == ships_by_ship_part.end()) + continue; + const auto& source_objects = ships_by_ship_part_it->second; + if (source_objects.empty()) continue; - for (std::shared_ptr effects_group : part_type->Effects()) { - targets_causes_reorder_buffer.push_back(Effect::TargetsCauses()); - run_queue.AddWork(new StoreTargetsAndCausesOfEffectsGroupsWorkItem( - effects_group, ships_by_part_type_it->second, ECT_SHIP_PART, part_type_name, - all_potential_targets, targets_causes_reorder_buffer.back(), - cached_source_condition_matches, - invariant_condition_matches, - global_mutex)); - } + DispatchEffectsGroupScopeEvaluations(ECT_SHIP_PART, ship_part_name, + source_objects, ship_part->Effects(), + only_meter_effects, + m_objects, potential_targets, + potential_ids_set, + source_effects_targets_causes_reorder_buffer, + thread_pool, n); } - double ships_time = type_timer.elapsed(); + // 6) EffectsGroups from Fields + type_timer.EnterSection("fields"); TraceLogger(effects) << "Universe::GetEffectsAndTargets for FIELDS"; - type_timer.restart(); // determine fields of each type in a single pass std::map>> fields_by_type; - for (std::shared_ptr field : m_objects.FindObjects()) { - if (m_destroyed_object_ids.find(field->ID()) != m_destroyed_object_ids.end()) + for (auto& field : m_objects.all()) { + if (m_destroyed_object_ids.count(field->ID())) continue; - const std::string& field_type_name = field->FieldTypeName(); const FieldType* field_type = GetFieldType(field_type_name); if (!field_type) { @@ -1388,52 +1469,66 @@ void Universe::GetEffectsAndTargets(Effect::TargetsCauses& targets_causes, fields_by_type[field_type_name].push_back(field); } - // enforce field types effects order - for (const std::map::value_type& entry : GetFieldTypeManager()) { + // dispatch field condition evaluations + for (const auto& entry : GetFieldTypeManager()) { const std::string& field_type_name = entry.first; - const FieldType* field_type = entry.second; - std::map>>::iterator fields_by_type_it = fields_by_type.find(field_type_name); - + const FieldType* field_type = entry.second.get(); + auto fields_by_type_it = fields_by_type.find(field_type_name); if (fields_by_type_it == fields_by_type.end()) continue; + const auto& source_objects = fields_by_type_it->second; + if (source_objects.empty()) + continue; - for (std::shared_ptr effects_group : field_type->Effects()) { - targets_causes_reorder_buffer.push_back(Effect::TargetsCauses()); - run_queue.AddWork(new StoreTargetsAndCausesOfEffectsGroupsWorkItem( - effects_group, fields_by_type_it->second, ECT_FIELD, field_type_name, - all_potential_targets, targets_causes_reorder_buffer.back(), - cached_source_condition_matches, - invariant_condition_matches, - global_mutex)); - } - } - double fields_time = type_timer.elapsed(); - - run_queue.Wait(global_lock); - double eval_time = eval_timer.elapsed(); + DispatchEffectsGroupScopeEvaluations(ECT_FIELD, field_type_name, + source_objects, field_type->Effects(), + only_meter_effects, + m_objects, potential_targets, + potential_ids_set, + source_effects_targets_causes_reorder_buffer, + thread_pool, n); + } + + + // wait for evaluation of conditions dispatched above + type_timer.EnterSection("eval waiting"); + thread_pool.join(); + + + // add results to source_effects_targets_causes, sorted by effect priority, then in issue order + type_timer.EnterSection("reordering"); + for (const auto& job_results : source_effects_targets_causes_reorder_buffer) { + if (job_results.second) { + // entry in reorder buffer contains empty Effect::TargetSets that + // should be populated from the pointed-to earlier entry + const auto& resolved_scope_target_sets{*job_results.second}; + TraceLogger(effects) << "Reordering using cached result of size " << resolved_scope_target_sets.size() + << " for expected result of size: " << job_results.first.size(); + + for (std::size_t i = 0; i < std::min(job_results.first.size(), resolved_scope_target_sets.size()); ++i) { + // create entry in output with empty TargetSet + auto& result{job_results.first[i]}; + int priority = result.first.effects_group->Priority(); + source_effects_targets_causes[priority].push_back(result); + + // overwrite empty placeholder TargetSet with contents of + // pointed-to earlier entry + source_effects_targets_causes[priority].back().second.target_set = + resolved_scope_target_sets.at(i).second.target_set; + } - eval_timer.restart(); - // add results to targets_causes in issue order - // FIXME: each job is an effectsgroup, and we need that separation for - // execution anyway, so maintain it here instead of merging. - for (const Effect::TargetsCauses& job_results : targets_causes_reorder_buffer) { - for (const std::pair& result : job_results) { - targets_causes.push_back(result); + } else { + // entry in reorder buffer contains the results of an effectsgroup + // scope/activation being evaluatied with a set of source objects + for (const auto& result : job_results.first) { + int priority = result.first.effects_group->Priority(); + source_effects_targets_causes[priority].push_back(result); + } } } - double reorder_time = eval_timer.elapsed(); - DebugLogger() << "Issue times: planet species: " << planet_species_time*1000 - << " ship species: " << ship_species_time*1000 - << " specials: " << special_time*1000 - << " techs: " << tech_time*1000 - << " buildings: " << building_time*1000 - << " hulls/parts: " << ships_time*1000 - << " fields: " << fields_time*1000; - DebugLogger() << "Evaluation time: " << eval_time*1000 - << " reorder time: " << reorder_time*1000; } -void Universe::ExecuteEffects(const Effect::TargetsCauses& targets_causes, +void Universe::ExecuteEffects(std::map& source_effects_targets_causes, bool update_effect_accounting, bool only_meter_effects/* = false*/, bool only_appearance_effects/* = false*/, @@ -1443,33 +1538,22 @@ void Universe::ExecuteEffects(const Effect::TargetsCauses& targets_causes, ScopedTimer timer("Universe::ExecuteEffects", true); m_marked_destroyed.clear(); - std::map< std::string, std::set> executed_nonstacking_effects; + std::map> executed_nonstacking_effects; // for each stacking group, which objects have had effects executed on them - // grouping targets causes by effects group - // sorting by effects group has already been done in GetEffectsAndTargets() - // FIXME: GetEffectsAndTargets already produces this separation, exploit that - std::map>> dispatched_targets_causes; - { - const Effect::EffectsGroup* last_effects_group = nullptr; - Effect::TargetsCauses* group_targets_causes = nullptr; - for (const std::pair& targets_cause : targets_causes) { - const Effect::SourcedEffectsGroup& sourced_effects_group = targets_cause.first; - Effect::EffectsGroup* effects_group = sourced_effects_group.effects_group.get(); + // within each priority group, execute effects in dispatch order + for (auto& priority_group : source_effects_targets_causes) { + int priority = priority_group.first; + Effect::SourcesEffectsTargetsAndCausesVec& setc{priority_group.second}; - if (effects_group != last_effects_group) { - last_effects_group = effects_group; - dispatched_targets_causes[effects_group->Priority()].push_back(std::make_pair(effects_group, Effect::TargetsCauses())); - group_targets_causes = &dispatched_targets_causes[effects_group->Priority()].back().second; - } - group_targets_causes->push_back(targets_cause); - } - } + // construct a source context, which is updated for each entry in sources-effects-targets. + // execute each effectsgroup on its target set + ScriptingContext source_context; + for (std::pair& effect_group_entry : setc) { + Effect::TargetsAndCause& targets_and_cause{effect_group_entry.second}; + Effect::TargetSet& target_set{targets_and_cause.target_set}; - // execute each effects group one by one - for (std::map>>::value_type& priority_group : dispatched_targets_causes) { - for (std::vector>::value_type& effect_group_entry : priority_group.second) { - Effect::EffectsGroup* effects_group = effect_group_entry.first; + const Effect::EffectsGroup* effects_group = effect_group_entry.first.effects_group; if (only_meter_effects && !effects_group->HasMeterEffects()) continue; @@ -1478,59 +1562,44 @@ void Universe::ExecuteEffects(const Effect::TargetsCauses& targets_causes, if (only_generate_sitrep_effects && !effects_group->HasSitrepEffects()) continue; - Effect::TargetsCauses& group_targets_causes = effect_group_entry.second; - std::string stacking_group = effects_group->StackingGroup(); - ScopedTimer update_timer( - "Universe::ExecuteEffects effgrp (" + effects_group->AccountingLabel() + ") from " - + std::to_string(group_targets_causes.size()) + " sources" - ); + const std::string& stacking_group{effects_group->StackingGroup()}; - // if other EffectsGroups or sources with the same stacking group have affected some of the - // targets in the scope of the current EffectsGroup, skip them - // and add the remaining objects affected by it to executed_nonstacking_effects + // 1) If other EffectsGroups or sources with the same stacking group + // have acted on some of the targets in the scope of the current + // EffectsGroup, skip them. + // 2) Add remaining objects to executed_nonstacking_effects, as effects + // with the starting group are now acting on them if (!stacking_group.empty()) { std::set& non_stacking_targets = executed_nonstacking_effects[stacking_group]; - for (Effect::TargetsCauses::iterator targets_it = group_targets_causes.begin(); - targets_it != group_targets_causes.end();) - { - Effect::TargetsAndCause& targets_and_cause = targets_it->second; - Effect::TargetSet& targets = targets_and_cause.target_set; - - // this is a set difference/union algorithm: - // targets -= non_stacking_targets - // non_stacking_targets += targets - for (Effect::TargetSet::iterator object_it = targets.begin(); - object_it != targets.end(); ) - { - int object_id = (*object_it)->ID(); - std::set::const_iterator it = non_stacking_targets.find(object_id); - - if (it != non_stacking_targets.end()) { - *object_it = targets.back(); - targets.pop_back(); - } else { - non_stacking_targets.insert(object_id); - ++object_it; - } - } + // this is a set difference/union algorithm: + // targets -= non_stacking_targets + // non_stacking_targets += targets + for (auto object_it = target_set.begin(); object_it != target_set.end();) { + int object_id = (*object_it)->ID(); + auto it = non_stacking_targets.find(object_id); - if (targets.empty()) { - *targets_it = group_targets_causes.back(); - group_targets_causes.pop_back(); + if (it != non_stacking_targets.end()) { + *object_it = target_set.back(); + target_set.pop_back(); } else { - ++targets_it; + non_stacking_targets.insert(object_id); + ++object_it; } } } - if (group_targets_causes.empty()) + // were all objects in target set removed due to stacking? If so, skip to next effect / source / target set + if (target_set.empty()) continue; - TraceLogger(effects) << "\n\n * * * * * * * * * * * (new effects group log entry)"; + TraceLogger(effects) << "\n\n * * * * * * * * * * * (new effects group log entry)(" << effects_group->TopLevelContent() + << " " << effects_group->AccountingLabel() << " " << effects_group->StackingGroup() << ")"; // execute Effects in the EffectsGroup - effects_group->Execute(group_targets_causes, + source_context.source = source_context.ContextObjects().get(effect_group_entry.first.source_object_id); + effects_group->Execute(source_context, + targets_and_cause, update_effect_accounting ? &m_effect_accounting_map : nullptr, only_meter_effects, only_appearance_effects, @@ -1546,10 +1615,9 @@ void Universe::ExecuteEffects(const Effect::TargetsCauses& targets_causes, // but, do now collect info about source objects for destruction, to sure // their info is available even if they are destroyed by the upcoming effect // destruction - - for (std::map>::value_type& entry : m_marked_destroyed) { + for (auto& entry : m_marked_destroyed) { int obj_id = entry.first; - std::shared_ptr obj = GetUniverseObject(obj_id); + auto obj = m_objects.get(obj_id); if (!obj) continue; @@ -1577,18 +1645,14 @@ namespace { static const std::string EMPTY_STRING; const std::string& GetSpeciesFromObject(std::shared_ptr obj) { - std::shared_ptr obj_fleet; - std::shared_ptr obj_ship; - std::shared_ptr obj_building; - switch (obj->ObjectType()) { case OBJ_PLANET: { - std::shared_ptr obj_planet = std::static_pointer_cast(obj); + auto obj_planet = std::static_pointer_cast(obj); return obj_planet->SpeciesName(); break; } case OBJ_SHIP: { - std::shared_ptr obj_ship = std::static_pointer_cast(obj); + auto obj_ship = std::static_pointer_cast(obj); return obj_ship->SpeciesName(); break; } @@ -1600,16 +1664,16 @@ namespace { int GetDesignIDFromObject(std::shared_ptr obj) { if (obj->ObjectType() != OBJ_SHIP) return INVALID_DESIGN_ID; - std::shared_ptr shp = std::static_pointer_cast(obj); + auto shp = std::static_pointer_cast(obj); return shp->DesignID(); } } void Universe::CountDestructionInStats(int object_id, int source_object_id) { - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = m_objects.get(object_id); if (!obj) return; - std::shared_ptr source = GetUniverseObject(source_object_id); + auto source = m_objects.get(source_object_id); if (!source) return; @@ -1639,25 +1703,65 @@ void Universe::CountDestructionInStats(int object_id, int source_object_id) { } } -void Universe::SetEffectDerivedVisibility(int empire_id, int object_id, Visibility vis) { +void Universe::SetEffectDerivedVisibility(int empire_id, int object_id, int source_id, + const ValueRef::ValueRef* vis) +{ if (empire_id == ALL_EMPIRES) return; if (object_id <= INVALID_OBJECT_ID) return; - if (vis == INVALID_VISIBILITY) + if (!vis) return; - m_effect_specified_empire_object_visibilities[empire_id][object_id] = vis; + m_effect_specified_empire_object_visibilities[empire_id][object_id].push_back({source_id, vis}); } void Universe::ApplyEffectDerivedVisibilities() { + EmpireObjectVisibilityMap new_empire_object_visibilities; // for each empire with a visibility map - for (std::map::value_type& empire_entry : m_effect_specified_empire_object_visibilities) { + for (auto& empire_entry : m_effect_specified_empire_object_visibilities) { if (empire_entry.first == ALL_EMPIRES) continue; // can't set a non-empire's visibility - for (const ObjectVisibilityMap::value_type& object_entry : empire_entry.second) { + for (const auto& object_entry : empire_entry.second) { if (object_entry.first <= INVALID_OBJECT_ID) - continue; - m_empire_object_visibility[empire_entry.first][object_entry.first] = object_entry.second; + continue; // can't set a non-object's visibility + auto target = m_objects.get(object_entry.first); + if (!target) + continue; // don't need to set a non-gettable object's visibility + + // if already have an entry in new_empire_object_visibilities then + // use that as the target initial visibility for purposes of + // evaluating this ValueRef. If not, use the object's current + // in-universe Visibility for the specified empire + Visibility target_initial_vis = + m_empire_object_visibility[empire_entry.first][object_entry.first]; + auto neov_it = new_empire_object_visibilities[empire_entry.first].find(object_entry.first); + if (neov_it != new_empire_object_visibilities[empire_entry.first].end()) + target_initial_vis = neov_it->second; + + // evaluate valuerefs and and store visibility of object + for (auto& source_ref_entry : object_entry.second) { + // set up context for executing ValueRef to determine visibility to set + auto source = m_objects.get(source_ref_entry.first); + ScriptingContext context(source, target, target_initial_vis, nullptr, nullptr, m_objects); + + const auto val_ref = source_ref_entry.second; + + // evaluate and store actual new visibility level + Visibility vis = val_ref->Eval(context); + target_initial_vis = vis; // store for next iteration's context + new_empire_object_visibilities[empire_entry.first][object_entry.first] = vis; + } + } + } + + // copy newly determined visibility levels into actual gamestate, without + // erasing visibilities that aren't affected by the effects + for (auto empire_entry : new_empire_object_visibilities) { + int empire_id = empire_entry.first; + for (auto object_entry : empire_entry.second) { + int object_id = object_entry.first; + Visibility vis = object_entry.second; + m_empire_object_visibility[empire_id][object_id] = vis; } } } @@ -1667,10 +1771,10 @@ void Universe::ForgetKnownObject(int empire_id, int object_id) { // immediately forget information without waiting for the turn update. ObjectMap& objects(EmpireKnownObjects(empire_id)); - if (objects.Empty()) + if (objects.empty()) return; - std::shared_ptr obj = objects.Object(object_id); + auto obj = objects.get(object_id); if (!obj) { ErrorLogger() << "ForgetKnownObject empire: " << empire_id << " bad object id: " << object_id; @@ -1684,23 +1788,27 @@ void Universe::ForgetKnownObject(int empire_id, int object_id) { return; } - for (int child_id : obj->VisibleContainedObjectIDs(empire_id)) { - if (std::shared_ptr child = objects.Object(child_id)) - ForgetKnownObject(empire_id, child->ID()); - } + // Remove all contained objects to avoid breaking fleet+ship, system+planet invariants + auto contained_ids = obj->ContainedObjectIDs(); + for (int child_id : contained_ids) + ForgetKnownObject(empire_id, child_id); - if (int container_id = obj->ContainerObjectID() != INVALID_OBJECT_ID) { - if (std::shared_ptr container = objects.Object(container_id)) { - if (std::shared_ptr system = std::dynamic_pointer_cast(container)) + int container_id = obj->ContainerObjectID(); + if (container_id != INVALID_OBJECT_ID) { + if (auto container = objects.get(container_id)) { + if (auto system = std::dynamic_pointer_cast(container)) system->Remove(object_id); - else if (std::shared_ptr planet = std::dynamic_pointer_cast(container)) + else if (auto planet = std::dynamic_pointer_cast(container)) planet->RemoveBuilding(object_id); - else if (std::shared_ptr fleet = std::dynamic_pointer_cast(container)) - fleet->RemoveShip(object_id); + else if (auto fleet = std::dynamic_pointer_cast(container)) { + fleet->RemoveShips({object_id}); + if (fleet->Empty()) + objects.erase(fleet->ID()); + } } } - objects.Remove(object_id); + objects.erase(object_id); } void Universe::SetEmpireObjectVisibility(int empire_id, int object_id, Visibility vis) { @@ -1708,8 +1816,8 @@ void Universe::SetEmpireObjectVisibility(int empire_id, int object_id, Visibilit return; // get visibility map for empire and find object in it - Universe::ObjectVisibilityMap& vis_map = m_empire_object_visibility[empire_id]; - Universe::ObjectVisibilityMap::iterator vis_map_it = vis_map.find(object_id); + auto& vis_map = m_empire_object_visibility[empire_id]; + auto vis_map_it = vis_map.find(object_id); // if object not already present, store default value (which may be replaced) if (vis_map_it == vis_map.end()) { @@ -1725,7 +1833,7 @@ void Universe::SetEmpireObjectVisibility(int empire_id, int object_id, Visibilit // if object is a ship, empire also gets knowledge of its design if (vis >= VIS_PARTIAL_VISIBILITY) { - if (std::shared_ptr ship = GetShip(object_id)) + if (auto ship = m_objects.get(object_id)) SetEmpireKnowledgeOfShipDesign(ship->DesignID(), empire_id); } } @@ -1736,7 +1844,7 @@ void Universe::SetEmpireSpecialVisibility(int empire_id, int object_id, { if (empire_id == ALL_EMPIRES || special_name.empty() || object_id == INVALID_OBJECT_ID) return; - //std::shared_ptr obj = GetUniverseObject(object_id); + //auto obj = m_objects.get(object_id); //if (!obj) // return; //if (!obj->HasSpecial(special_name)) @@ -1747,6 +1855,7 @@ void Universe::SetEmpireSpecialVisibility(int empire_id, int object_id, m_empire_object_visible_specials[empire_id][object_id].erase(special_name); } + namespace { /** for each empire: for each position where the empire has detector objects, * what is the empire's detection range at that location? (this is the @@ -1754,7 +1863,7 @@ namespace { std::map, float>> GetEmpiresPositionDetectionRanges() { std::map, float>> retval; - for (std::shared_ptr obj : Objects()) { + for (const auto& obj : Objects().all()) { // skip unowned objects, which can't provide detection to any empire if (obj->Unowned()) continue; @@ -1772,9 +1881,9 @@ namespace { if (obj->ObjectType() == OBJ_FLEET) { fleet = std::dynamic_pointer_cast(obj); } else if (obj->ObjectType() == OBJ_SHIP) { - std::shared_ptr ship = std::dynamic_pointer_cast(obj); + auto ship = std::dynamic_pointer_cast(obj); if (ship) - fleet = Objects().Object(ship->FleetID()); + fleet = Objects().get(ship->FleetID()); } if (fleet) { int cur_id = fleet->SystemID(); @@ -1787,8 +1896,8 @@ namespace { std::pair object_pos(obj->X(), obj->Y()); // store range in output map (if new for location or larger than any // previously-found range at this location) - std::map, float>& retval_empire_pos_range = retval[object_owner_empire_id]; - std::map, float>::iterator retval_pos_it = retval_empire_pos_range.find(object_pos); + auto& retval_empire_pos_range = retval[object_owner_empire_id]; + auto retval_pos_it = retval_empire_pos_range.find(object_pos); if (retval_pos_it == retval_empire_pos_range.end()) retval_empire_pos_range[object_pos] = object_detection_range; else @@ -1800,7 +1909,7 @@ namespace { std::map GetEmpiresDetectionStrengths(int empire_id = ALL_EMPIRES) { std::map retval; if (empire_id == ALL_EMPIRES) { - for (const std::map::value_type& empire_entry : Empires()) { + for (const auto& empire_entry : Empires()) { const Meter* meter = empire_entry.second->GetMeter("METER_DETECTION_STRENGTH"); float strength = meter ? meter->Current() : 0.0f; retval[empire_entry.first] = strength; @@ -1821,14 +1930,11 @@ namespace { { std::map, std::vector>> retval; - std::map empire_detection_strengths = GetEmpiresDetectionStrengths(empire_id); + auto empire_detection_strengths = GetEmpiresDetectionStrengths(empire_id); // filter objects as detectors for this empire or detectable objects - for (ObjectMap::const_iterator<> object_it = objects.const_begin(); - object_it != objects.const_end(); ++object_it) + for (const auto& obj : objects.all()) { - std::shared_ptr obj = *object_it; - int object_id = object_it->ID(); const Meter* stealth_meter = obj->GetMeter(METER_STEALTH); if (!stealth_meter) continue; @@ -1839,10 +1945,9 @@ namespace { // detected by the empire if the empire has a detector in range. // being detectable by an empire requires the object to have // low enough stealth (0 or below the empire's detection strength) - for (const std::map::value_type& empire_entry : empire_detection_strengths) { - int empire_id = empire_entry.first; - if (object_stealth <= empire_entry.second || object_stealth == 0.0f || obj->OwnedBy(empire_id)) - retval[empire_id][object_pos].push_back(object_id); + for (const auto& empire_entry : empire_detection_strengths) { + if (object_stealth <= empire_entry.second || object_stealth == 0.0f || obj->OwnedBy(empire_entry.first)) + retval[empire_entry.first][object_pos].push_back(obj->ID()); } } return retval; @@ -1856,14 +1961,14 @@ namespace { { std::vector retval; // check each detector position and range against each object position - for (const std::map, std::vector>::value_type& object_position_entry : object_positions) { - const std::pair& object_pos = object_position_entry.first; - const std::vector& objects = object_position_entry.second; + for (const auto& object_position_entry : object_positions) { + const auto& object_pos = object_position_entry.first; + const auto& objects = object_position_entry.second; // search through detector positions until one is found in range - for (const std::map, float>::value_type& detector_position_entry : detector_position_ranges) { + for (const auto& detector_position_entry : detector_position_ranges) { // check range for this detector location for this detectables location float detector_range2 = detector_position_entry.second * detector_position_entry.second; - const std::pair& detector_pos = detector_position_entry.first; + const auto& detector_pos = detector_position_entry.first; double x_dist = detector_pos.first - object_pos.first; double y_dist = detector_pos.second - object_pos.second; double dist2 = x_dist*x_dist + y_dist*y_dist; @@ -1884,16 +1989,15 @@ namespace { { if (empire_id == ALL_EMPIRES) return; - for (std::vector::iterator it = object_ids.begin(); it != object_ids.end();) { + for (auto it = object_ids.begin(); it != object_ids.end();) { int object_id = *it; - std::map>::const_iterator obj_it = - empire_known_destroyed_object_ids.find(object_id); + auto obj_it = empire_known_destroyed_object_ids.find(object_id); if (obj_it == empire_known_destroyed_object_ids.end()) { ++it; continue; } const std::set& empires_that_know = obj_it->second; - if (empires_that_know.find(empire_id) == empires_that_know.end()) { + if (!empires_that_know.count(empire_id)) { ++it; continue; } @@ -1915,8 +2019,7 @@ namespace { { Universe& universe = GetUniverse(); - for (const std::map, float>>::value_type& detecting_empire_entry : empire_location_detection_ranges) - { + for (const auto& detecting_empire_entry : empire_location_detection_ranges) { int detecting_empire_id = detecting_empire_entry.first; double detection_strength = 0.0; const Empire* empire = GetEmpire(detecting_empire_id); @@ -1928,22 +2031,21 @@ namespace { detection_strength = meter->Current(); // get empire's locations of detection ranges - const std::map, float>& detector_position_ranges = - detecting_empire_entry.second; + const auto& detector_position_ranges = detecting_empire_entry.second; // for each field, try to find a detector position in range for this empire - for (std::shared_ptr field : objects.FindObjects()) { + for (auto& field : objects.all()) { if (field->GetMeter(METER_STEALTH)->Current() > detection_strength) continue; double field_size = field->GetMeter(METER_SIZE)->Current(); const std::pair object_pos(field->X(), field->Y()); // search through detector positions until one is found in range - for (const std::map, float>::value_type& detector_position_entry : detector_position_ranges) { + for (const auto& detector_position_entry : detector_position_ranges) { // check range for this detector location, for field of this // size, against distance between field and detector float detector_range = detector_position_entry.second; - const std::pair& detector_pos = detector_position_entry.first; + const auto& detector_pos = detector_position_entry.first; double x_dist = detector_pos.first - object_pos.first; double y_dist = detector_pos.second - object_pos.second; double dist = std::sqrt(x_dist*x_dist + y_dist*y_dist); @@ -1969,19 +2071,16 @@ namespace { { Universe& universe = GetUniverse(); - for (const std::map, float>>::value_type& detecting_empire_entry : empire_location_detection_ranges) - { + for (const auto& detecting_empire_entry : empire_location_detection_ranges) { int detecting_empire_id = detecting_empire_entry.first; // get empire's locations of detection ability - const std::map, float>& detector_position_ranges = - detecting_empire_entry.second; + const auto& detector_position_ranges = detecting_empire_entry.second; // for this empire, get objects it could potentially detect - const std::map, std::vector>>::const_iterator - empire_detectable_objects_it = empire_location_potentially_detectable_objects.find(detecting_empire_id); + const auto empire_detectable_objects_it = + empire_location_potentially_detectable_objects.find(detecting_empire_id); if (empire_detectable_objects_it == empire_location_potentially_detectable_objects.end()) continue; // empire can't detect anything! - const std::map, std::vector>& detectable_position_objects = - empire_detectable_objects_it->second; + const auto& detectable_position_objects = empire_detectable_objects_it->second; if (detectable_position_objects.empty()) continue; @@ -2005,7 +2104,7 @@ namespace { /** sets visibility of objects that empires own for those objects */ void SetEmpireOwnedObjectVisibilities() { Universe& universe = GetUniverse(); - for (std::shared_ptr obj : Objects()) { + for (const auto& obj : universe.Objects().all()) { if (obj->Unowned()) continue; universe.SetEmpireObjectVisibility(obj->Owner(), obj->ID(), VIS_FULL_VISIBILITY); @@ -2016,15 +2115,13 @@ namespace { void SetAllObjectsVisibleToAllEmpires() { Universe& universe = GetUniverse(); // set every object visible to all empires - for (ObjectMap::const_iterator<> obj_it = Objects().const_begin(); - obj_it != Objects().const_end(); ++obj_it) - { - for (std::map::value_type& empire_entry : Empires()) { + for (const auto& obj : universe.Objects().all()) { + for (auto& empire_entry : Empires()) { // objects - universe.SetEmpireObjectVisibility(empire_entry.first, obj_it->ID(), VIS_FULL_VISIBILITY); + universe.SetEmpireObjectVisibility(empire_entry.first, obj->ID(), VIS_FULL_VISIBILITY); // specials on objects - for (const std::map>::value_type& special_entry : obj_it->Specials()) { - universe.SetEmpireSpecialVisibility(empire_entry.first, obj_it->ID(), special_entry.first); + for (const auto& special_entry : obj->Specials()) { + universe.SetEmpireSpecialVisibility(empire_entry.first, obj->ID(), special_entry.first); } } } @@ -2037,15 +2134,14 @@ namespace { // map from empire ID to ID of systems where those empires own at least one object std::map> empires_systems_with_owned_objects; // get systems where empires have owned objects - for (ObjectMap::const_iterator<> it = objects.const_begin(); it != objects.const_end(); ++it) { - std::shared_ptr obj = *it; + for (const auto& obj : objects.all()) { if (obj->Unowned() || obj->SystemID() == INVALID_OBJECT_ID) continue; empires_systems_with_owned_objects[obj->Owner()].insert(obj->SystemID()); } // set system visibility - for (std::map>::value_type& empire_entry : empires_systems_with_owned_objects) { + for (const auto& empire_entry : empires_systems_with_owned_objects) { int empire_id = empire_entry.first; for (int system_id : empire_entry.second) { @@ -2054,17 +2150,16 @@ namespace { } // get planets, check their locations... - std::vector> planets = objects.FindObjects(); - for (std::shared_ptr planet : objects.FindObjects()) { + for (const auto& planet : objects.all()) { int system_id = planet->SystemID(); if (system_id == INVALID_OBJECT_ID) continue; int planet_id = planet->ID(); - for (const std::map>::value_type& empire_entry : empires_systems_with_owned_objects) { + for (const auto& empire_entry : empires_systems_with_owned_objects) { int empire_id = empire_entry.first; - const std::set& empire_systems = empire_entry.second; - if (empire_systems.find(system_id) == empire_systems.end()) + const auto& empire_systems = empire_entry.second; + if (!empire_systems.count(system_id)) continue; // ensure planets are at least basicaly visible. does not // overwrite higher visibility levels @@ -2077,13 +2172,8 @@ namespace { Universe::EmpireObjectVisibilityMap& empire_object_visibility) { // propagate visibility from contained to container objects - for (ObjectMap::const_iterator<> container_object_it = objects.const_begin(); - container_object_it != objects.const_end(); ++container_object_it) + for (const auto& container_obj : objects.all()) { - int container_obj_id = container_object_it->ID(); - - // get container object - std::shared_ptr container_obj = *container_object_it; if (!container_obj) continue; // shouldn't be necessary, but I like to be safe... @@ -2097,19 +2187,19 @@ namespace { //DebugLogger() << " ... contained object (" << contained_obj_id << ")"; // for each empire with a visibility map - for (Universe::EmpireObjectVisibilityMap::value_type& empire_entry : empire_object_visibility) { - Universe::ObjectVisibilityMap& vis_map = empire_entry.second; + for (auto& empire_entry : empire_object_visibility) { + auto& vis_map = empire_entry.second; //DebugLogger() << " ... ... empire id " << empire_entry.first; // find current empire's visibility entry for current container object - Universe::ObjectVisibilityMap::iterator container_vis_it = vis_map.find(container_obj_id); + auto container_vis_it = vis_map.find(container_obj->ID()); // if no entry yet stored for this object, default to not visible if (container_vis_it == vis_map.end()) { - vis_map[container_obj_id] = VIS_NO_VISIBILITY; + vis_map[container_obj->ID()] = VIS_NO_VISIBILITY; // get iterator pointing at newly-created entry - container_vis_it = vis_map.find(container_obj_id); + container_vis_it = vis_map.find(container_obj->ID()); } else { // check whether having a contained object would change container's visibility if (container_fleet) { @@ -2129,7 +2219,7 @@ namespace { // find contained object's entry in visibility map - Universe::ObjectVisibilityMap::iterator contained_vis_it = vis_map.find(contained_obj_id); + auto contained_vis_it = vis_map.find(contained_obj_id); if (contained_vis_it != vis_map.end()) { // get contained object's visibility for current empire Visibility contained_obj_vis = contained_vis_it->second; @@ -2149,7 +2239,7 @@ namespace { // special case for fleets: grant partial visibility if // visible contained object is partially or better visible - // this way fleet ownership is known to players who can + // this way fleet ownership is known to players who can // see ships with partial or better visibility (and thus // know the owner of the ships and thus should know the // owners of the fleet) @@ -2162,17 +2252,18 @@ namespace { } // end for container objects } - void PropagateVisibilityToSystemsAlongStarlanes(const ObjectMap& objects, - Universe::EmpireObjectVisibilityMap& empire_object_visibility) { - for (std::shared_ptr system : objects.FindObjects()) { + void PropagateVisibilityToSystemsAlongStarlanes( + const ObjectMap& objects, Universe::EmpireObjectVisibilityMap& empire_object_visibility) + { + for (auto& system : objects.all()) { int system_id = system->ID(); // for each empire with a visibility map - for (Universe::EmpireObjectVisibilityMap::value_type& empire_entry : empire_object_visibility) { - Universe::ObjectVisibilityMap& vis_map = empire_entry.second; + for (auto& empire_entry : empire_object_visibility) { + auto& vis_map = empire_entry.second; // find current system's visibility - Universe::ObjectVisibilityMap::iterator system_vis_it = vis_map.find(system_id); + auto system_vis_it = vis_map.find(system_id); if (system_vis_it == vis_map.end()) continue; @@ -2182,7 +2273,7 @@ namespace { continue; // get all starlanes emanating from this system, and loop through them - for (const std::map::value_type& lane : system->StarlanesWormholes()) { + for (auto& lane : system->StarlanesWormholes()) { bool is_wormhole = lane.second; if (is_wormhole) continue; @@ -2192,7 +2283,7 @@ namespace { // leve, so that starlanes will be visible if either system it // ends at is partially visible or better int lane_end_sys_id = lane.first; - Universe::ObjectVisibilityMap::iterator lane_end_vis_it = vis_map.find(lane_end_sys_id); + auto lane_end_vis_it = vis_map.find(lane_end_sys_id); if (lane_end_vis_it == vis_map.end()) vis_map[lane_end_sys_id] = VIS_BASIC_VISIBILITY; else if (lane_end_vis_it->second < VIS_BASIC_VISIBILITY) @@ -2209,11 +2300,10 @@ namespace { // ensure systems on either side of a starlane along which a fleet is // moving are at least basically visible, so that the starlane itself can / // will be visible - std::vector> moving_fleets; - for (std::shared_ptr obj : objects.FindObjects(MovingFleetVisitor())) { + for (auto& obj : objects.find(MovingFleetVisitor())) { if (obj->Unowned() || obj->SystemID() == INVALID_OBJECT_ID || obj->ObjectType() != OBJ_FLEET) continue; - std::shared_ptr fleet = std::dynamic_pointer_cast(obj); + auto fleet = std::dynamic_pointer_cast(obj); if (!fleet) continue; @@ -2222,9 +2312,9 @@ namespace { // ensure fleet's owner has at least basic visibility of the next // and previous systems on the fleet's path - Universe::ObjectVisibilityMap& vis_map = empire_object_visibility[fleet->Owner()]; + auto& vis_map = empire_object_visibility[fleet->Owner()]; - Universe::ObjectVisibilityMap::iterator system_vis_it = vis_map.find(prev); + auto system_vis_it = vis_map.find(prev); if (system_vis_it == vis_map.end()) { vis_map[prev] = VIS_BASIC_VISIBILITY; } else { @@ -2242,16 +2332,16 @@ namespace { } } - void SetEmpireSpecialVisibilities(const ObjectMap& objects, + void SetEmpireSpecialVisibilities(ObjectMap& objects, Universe::EmpireObjectVisibilityMap& empire_object_visibility, Universe::EmpireObjectSpecialsMap& empire_object_visible_specials) { // after setting object visibility, similarly set visibility of objects' // specials for each empire - for (std::map::value_type& empire_entry : Empires()) { + for (auto& empire_entry : Empires()) { int empire_id = empire_entry.first; - Universe::ObjectVisibilityMap& obj_vis_map = empire_object_visibility[empire_id]; - Universe::ObjectSpecialsMap& obj_specials_map = empire_object_visible_specials[empire_id]; + auto& obj_vis_map = empire_object_visibility[empire_id]; + auto& obj_specials_map = empire_object_visible_specials[empire_id]; const Empire* empire = empire_entry.second; const Meter* detection_meter = empire->GetMeter("METER_DETECTION_STRENGTH"); @@ -2260,30 +2350,30 @@ namespace { float detection_strength = detection_meter->Current(); // every object empire has visibility of might have specials - for (Universe::ObjectVisibilityMap::value_type& obj_entry : obj_vis_map) { + for (auto& obj_entry : obj_vis_map) { if (obj_entry.second <= VIS_NO_VISIBILITY) continue; int object_id = obj_entry.first; - std::shared_ptr obj = objects.Object(object_id); + auto obj = objects.get(object_id); if (!obj) continue; if (obj->Specials().empty()) continue; - std::set& visible_specials = obj_specials_map[object_id]; + auto& visible_specials = obj_specials_map[object_id]; // check all object's specials. - for (const std::map>::value_type& special_entry : obj->Specials()) { + for (const auto& special_entry : obj->Specials()) { const Special* special = GetSpecial(special_entry.first); if (!special) continue; float stealth = 0.0f; - const ValueRef::ValueRefBase* special_stealth = special->Stealth(); + const auto special_stealth = special->Stealth(); if (special_stealth) - stealth = special_stealth->Eval(ScriptingContext(obj)); + stealth = special_stealth->Eval(ScriptingContext(obj, objects)); // if special is 0 stealth, or has stealth less than empire's detection strength, mark as visible if (stealth <= 0.0f || stealth <= detection_strength) { @@ -2299,18 +2389,18 @@ namespace { Universe::EmpireObjectSpecialsMap& empire_object_visible_specials) { // make copy of input vis map, iterate over that, not the output as - // iterating over the output while modifying it would result in + // iterating over the output while modifying it would result in // second-order visibility sharing (but only through allies with lower // empire id) - Universe::EmpireObjectVisibilityMap input_eov_copy = empire_object_visibility; - Universe::EmpireObjectSpecialsMap input_eovs_copy = empire_object_visible_specials; + auto input_eov_copy = empire_object_visibility; + auto input_eovs_copy = empire_object_visible_specials; Universe& universe = GetUniverse(); - for (std::map::value_type& empire_entry : Empires()) { + for (auto& empire_entry : Empires()) { int empire_id = empire_entry.first; // output maps for this empire - Universe::ObjectVisibilityMap& obj_vis_map = empire_object_visibility[empire_id]; - // unused variable Universe::ObjectSpecialsMap& obj_specials_map = empire_object_visible_specials[empire_id]; + auto& obj_vis_map = empire_object_visibility[empire_id]; + auto& obj_specials_map = empire_object_visible_specials[empire_id]; for (auto allied_empire_id : Empires().GetEmpireIDsWithDiplomaticStatusWithEmpire(empire_id, DIPLO_ALLIED)) { if (empire_id == allied_empire_id) { @@ -2319,8 +2409,8 @@ namespace { } // input maps for this ally empire - Universe::ObjectVisibilityMap& allied_obj_vis_map = input_eov_copy[allied_empire_id]; - // unused variable Universe::ObjectSpecialsMap& allied_obj_specials_map = input_eovs_copy[allied_empire_id]; + auto& allied_obj_vis_map = input_eov_copy[allied_empire_id]; + auto& allied_obj_specials_map = input_eovs_copy[allied_empire_id]; // add allied visibilities to outer-loop empire visibilities // whenever the ally has better visibility of an object @@ -2328,15 +2418,23 @@ namespace { for (auto const& allied_obj_id_vis_pair : allied_obj_vis_map) { int obj_id = allied_obj_id_vis_pair.first; Visibility allied_vis = allied_obj_id_vis_pair.second; - std::map::iterator it = obj_vis_map.find(obj_id); + auto it = obj_vis_map.find(obj_id); if (it == obj_vis_map.end() || it->second < allied_vis) { obj_vis_map[obj_id] = allied_vis; if (allied_vis < VIS_PARTIAL_VISIBILITY) continue; - if (std::shared_ptr ship = GetShip(obj_id)) + if (auto ship = Objects().get(obj_id)) universe.SetEmpireKnowledgeOfShipDesign(ship->DesignID(), empire_id); } } + + // add allied visibilities of specials to outer-loop empire + // visibilities as well + for (const auto& allied_obj_special_vis_pair : allied_obj_specials_map) { + int obj_id = allied_obj_special_vis_pair.first; + const auto& specials = allied_obj_special_vis_pair.second; + obj_specials_map[obj_id].insert(specials.begin(), specials.end()); + } } } } @@ -2344,7 +2442,7 @@ namespace { void Universe::UpdateEmpireObjectVisibilities() { // ensure Universe knows empires have knowledge of designs the empire is specifically remembering - for (std::map::value_type& empire_entry : Empires()) { + for (auto& empire_entry : Empires()) { int empire_id = empire_entry.first; const Empire* empire = empire_entry.second; for (int design_id : empire->ShipDesigns()) { @@ -2363,28 +2461,26 @@ void Universe::UpdateEmpireObjectVisibilities() { SetEmpireOwnedObjectVisibilities(); - std::map, float>> - empire_position_detection_ranges = GetEmpiresPositionDetectionRanges(); + auto empire_position_detection_ranges = GetEmpiresPositionDetectionRanges(); - std::map, std::vector>> - empire_position_potentially_detectable_objects = - GetEmpiresPositionsPotentiallyDetectableObjects(Objects()); + auto empire_position_potentially_detectable_objects = + GetEmpiresPositionsPotentiallyDetectableObjects(m_objects); SetEmpireObjectVisibilitiesFromRanges(empire_position_detection_ranges, empire_position_potentially_detectable_objects); - SetEmpireFieldVisibilitiesFromRanges(empire_position_detection_ranges, Objects()); + SetEmpireFieldVisibilitiesFromRanges(empire_position_detection_ranges, m_objects); - SetSameSystemPlanetsVisible(Objects()); + SetSameSystemPlanetsVisible(m_objects); ApplyEffectDerivedVisibilities(); - PropagateVisibilityToContainerObjects(Objects(), m_empire_object_visibility); + PropagateVisibilityToContainerObjects(m_objects, m_empire_object_visibility); - PropagateVisibilityToSystemsAlongStarlanes(Objects(), m_empire_object_visibility); + PropagateVisibilityToSystemsAlongStarlanes(m_objects, m_empire_object_visibility); - SetTravelledStarlaneEndpointsVisible(Objects(), m_empire_object_visibility); + SetTravelledStarlaneEndpointsVisible(m_objects, m_empire_object_visibility); - SetEmpireSpecialVisibilities(Objects(), m_empire_object_visibility, m_empire_object_visible_specials); + SetEmpireSpecialVisibilities(m_objects, m_empire_object_visibility, m_empire_object_visible_specials); ShareVisbilitiesBetweenAllies(m_empire_object_visibility, m_empire_object_visible_specials); } @@ -2404,7 +2500,7 @@ void Universe::UpdateEmpireLatestKnownObjectsAndVisibilityTurns() { return; // for each object in universe - for (std::shared_ptr full_object : m_objects) { + for (const auto& full_object : m_objects.all()) { if (!full_object) { ErrorLogger() << "UpdateEmpireLatestKnownObjectsAndVisibilityTurns found null object in m_objects"; continue; @@ -2412,10 +2508,10 @@ void Universe::UpdateEmpireLatestKnownObjectsAndVisibilityTurns() { int object_id = full_object->ID(); // for each empire with a visibility map - for (EmpireObjectVisibilityMap::value_type& empire_entry : m_empire_object_visibility) { + for (auto& empire_entry : m_empire_object_visibility) { // can empire see object? - const ObjectVisibilityMap& vis_map = empire_entry.second; // stores level of visibility empire has for each object it can detect this turn - ObjectVisibilityMap::const_iterator vis_it = vis_map.find(object_id); + const auto& vis_map = empire_entry.second; // stores level of visibility empire has for each object it can detect this turn + auto vis_it = vis_map.find(object_id); if (vis_it == vis_map.end()) continue; // empire can't see current object, so move to next empire const Visibility vis = vis_it->second; @@ -2436,11 +2532,11 @@ void Universe::UpdateEmpireLatestKnownObjectsAndVisibilityTurns() { // update empire's latest known data about object, based on current visibility and historical visibility and knowledge of object // is there already last known version of an UniverseObject stored for this empire? - if (std::shared_ptr known_obj = known_object_map.Object(object_id)) { + if (auto known_obj = known_object_map.get(object_id)) { known_obj->Copy(full_object, empire_id); // already a stored version of this object for this empire. update it, limited by visibility this empire has for this object this turn } else { if (auto new_obj = std::shared_ptr(full_object->Clone(empire_id))) // no previously-recorded version of this object for this empire. create a new one, copying only the information limtied by visibility, leaving the rest as default values - known_object_map.Insert(new_obj); + known_object_map.insert(new_obj); } //DebugLogger() << "Empire " << empire_id << " can see object " << object_id << " with vis level " << vis; @@ -2470,10 +2566,9 @@ void Universe::UpdateEmpireStaleObjectKnowledge() { // detectable by that empire, then the latest known state of the objects // (including stealth and position) appears to be stale / out of date. - const std::map, float>> - empire_location_detection_ranges = GetEmpiresPositionDetectionRanges(); + const auto empire_location_detection_ranges = GetEmpiresPositionDetectionRanges(); - for (const EmpireObjectMap::value_type& empire_entry : m_empire_latest_known_objects) { + for (const auto& empire_entry : m_empire_latest_known_objects) { int empire_id = empire_entry.first; const ObjectMap& latest_known_objects = empire_entry.second; const ObjectVisibilityMap& vis_map = m_empire_object_visibility[empire_id]; @@ -2481,34 +2576,27 @@ void Universe::UpdateEmpireStaleObjectKnowledge() { const std::set& destroyed_set = m_empire_known_destroyed_object_ids[empire_id]; // remove stale marking for any known destroyed or currently visible objects - for (std::set::iterator stale_it = stale_set.begin(); stale_it != stale_set.end();) { + for (auto stale_it = stale_set.begin(); stale_it != stale_set.end();) { int object_id = *stale_it; - if (vis_map.find(object_id) != vis_map.end() || - destroyed_set.find(object_id) != destroyed_set.end()) - { - stale_set.erase(stale_it++); - } else { + if (vis_map.count(object_id) || destroyed_set.count(object_id)) + stale_it = stale_set.erase(stale_it); + else ++stale_it; - } } // get empire latest known objects that are potentially detectable - std::map, std::vector>> - empires_latest_known_objects_that_should_be_detectable = - GetEmpiresPositionsPotentiallyDetectableObjects(latest_known_objects, empire_id); - std::map, std::vector>& - empire_latest_known_should_be_still_detectable_objects = - empires_latest_known_objects_that_should_be_detectable[empire_id]; + auto empires_latest_known_objects_that_should_be_detectable = + GetEmpiresPositionsPotentiallyDetectableObjects(latest_known_objects, empire_id); + auto& empire_latest_known_should_be_still_detectable_objects = + empires_latest_known_objects_that_should_be_detectable[empire_id]; // get empire detection ranges - std::map, float>>::const_iterator - empire_detectors_it = empire_location_detection_ranges.find(empire_id); + auto empire_detectors_it = empire_location_detection_ranges.find(empire_id); if (empire_detectors_it == empire_location_detection_ranges.end()) continue; - const std::map, float>& empire_detector_positions_ranges = - empire_detectors_it->second; + const auto& empire_detector_positions_ranges = empire_detectors_it->second; // filter should-be-still-detectable objects by whether they are @@ -2529,7 +2617,7 @@ void Universe::UpdateEmpireStaleObjectKnowledge() { // represent out-of-date info in empire's latest known objects. these // entries need to be removed / flagged to indicate this for (int object_id : should_still_be_detectable_latest_known_objects) { - ObjectVisibilityMap::const_iterator vis_it = vis_map.find(object_id); + auto vis_it = vis_map.find(object_id); if (vis_it == vis_map.end() || vis_it->second < VIS_BASIC_VISIBILITY) { // object not visible even though the latest known info about it // for this empire suggests it should be. info is stale. @@ -2539,63 +2627,54 @@ void Universe::UpdateEmpireStaleObjectKnowledge() { // fleets that are not visible and that contain no ships or only stale ships are stale - for (ObjectMap::const_iterator<> obj_it = latest_known_objects.const_begin(); - obj_it != latest_known_objects.const_end(); ++obj_it) + for (const auto& fleet : latest_known_objects.all()) { - if (obj_it->ObjectType() != OBJ_FLEET) - continue; - if (obj_it->GetVisibility(empire_id) >= VIS_BASIC_VISIBILITY) + if (fleet->GetVisibility(empire_id) >= VIS_BASIC_VISIBILITY) continue; - std::shared_ptr fleet = std::dynamic_pointer_cast(*obj_it); - if (!fleet) - continue; - int fleet_id = obj_it->ID(); // destroyed? not stale - if (destroyed_set.find(fleet_id) != destroyed_set.end()) { - stale_set.insert(fleet_id); + if (destroyed_set.count(fleet->ID())) { + stale_set.insert(fleet->ID()); continue; } // no ships? -> stale if (fleet->Empty()) { - stale_set.insert(fleet_id); + stale_set.insert(fleet->ID()); continue; } bool fleet_stale = true; // check each ship. if any are visible or not visible but not stale, // fleet is not stale - for (int ship_id : fleet->ShipIDs()) { - std::shared_ptr ship = latest_known_objects.Object(ship_id); - + for (const auto& ship : latest_known_objects.find(fleet->ShipIDs())) { // if ship doesn't think it's in this fleet, doesn't count. - if (!ship || ship->FleetID() != fleet_id) + if (!ship || ship->FleetID() != fleet->ID()) continue; // if ship is destroyed, doesn't count - if (destroyed_set.find(ship_id) != destroyed_set.end()) + if (destroyed_set.count(ship->ID())) continue; // is contained ship visible? If so, fleet is not stale. - ObjectVisibilityMap::const_iterator vis_it = vis_map.find(ship_id); + auto vis_it = vis_map.find(ship->ID()); if (vis_it != vis_map.end() && vis_it->second > VIS_NO_VISIBILITY) { fleet_stale = false; break; } // is contained ship not visible and not stale? if so, fleet is not stale - if (stale_set.find(ship_id) == stale_set.end()) { + if (!stale_set.count(ship->ID())) { fleet_stale = false; break; } } if (fleet_stale) - stale_set.insert(fleet_id); + stale_set.insert(fleet->ID()); } //for (int stale_id : stale_set) { - // std::shared_ptr obj = latest_known_objects.Object(stale_id); + // auto obj = latest_known_objects.Object(stale_id); // DebugLogger() << "Object " << stale_id << " : " << (obj ? obj->Name() : "(unknown)") << " is stale for empire " << empire_id ; //} } @@ -2628,7 +2707,7 @@ void Universe::SetEmpireKnowledgeOfShipDesign(int ship_design_id, int empire_id) void Universe::Destroy(int object_id, bool update_destroyed_object_knowers/* = true*/) { // remove object from any containing UniverseObject - std::shared_ptr obj = m_objects.Object(object_id); + auto obj = m_objects.get(object_id); if (!obj) { ErrorLogger() << "Universe::Destroy called for nonexistant object with id: " << object_id; return; @@ -2638,7 +2717,7 @@ void Universe::Destroy(int object_id, bool update_destroyed_object_knowers/* = t if (update_destroyed_object_knowers) { // record empires that know this object has been destroyed - for (std::map::value_type& empire_entry : Empires()) { + for (auto& empire_entry : Empires()) { int empire_id = empire_entry.first; if (obj->GetVisibility(empire_id) >= VIS_BASIC_VISIBILITY) { SetEmpireKnowledgeOfDestroyedObject(object_id, empire_id); @@ -2649,25 +2728,25 @@ void Universe::Destroy(int object_id, bool update_destroyed_object_knowers/* = t // signal that an object has been deleted UniverseObjectDeleteSignal(obj); - m_objects.Remove(object_id); + m_objects.erase(object_id); } std::set Universe::RecursiveDestroy(int object_id) { std::set retval; - std::shared_ptr obj = m_objects.Object(object_id); + auto obj = m_objects.get(object_id); if (!obj) { DebugLogger() << "Universe::RecursiveDestroy asked to destroy nonexistant object with id " << object_id; return retval; } - std::shared_ptr system = GetSystem(obj->SystemID()); + auto system = m_objects.get(obj->SystemID()); - if (std::shared_ptr ship = std::dynamic_pointer_cast(obj)) { + if (auto ship = std::dynamic_pointer_cast(obj)) { // if a ship is being deleted, and it is the last ship in its fleet, then the empty fleet should also be deleted - std::shared_ptr fleet = GetFleet(ship->FleetID()); + auto fleet = m_objects.get(ship->FleetID()); if (fleet) { - fleet->RemoveShip(ship->ID()); + fleet->RemoveShips({ship->ID()}); if (fleet->Empty()) { if (system) system->Remove(fleet->ID()); @@ -2680,8 +2759,8 @@ std::set Universe::RecursiveDestroy(int object_id) { Destroy(object_id); retval.insert(object_id); - } else if (std::shared_ptr fleet = std::dynamic_pointer_cast(obj)) { - for (int ship_id : fleet->ShipIDs()) { + } else if (auto obj_fleet = std::dynamic_pointer_cast(obj)) { + for (int ship_id : obj_fleet->ShipIDs()) { if (system) system->Remove(ship_id); Destroy(ship_id); @@ -2692,8 +2771,8 @@ std::set Universe::RecursiveDestroy(int object_id) { Destroy(object_id); retval.insert(object_id); - } else if (std::shared_ptr planet = std::dynamic_pointer_cast(obj)) { - for (int building_id : planet->BuildingIDs()) { + } else if (auto obj_planet = std::dynamic_pointer_cast(obj)) { + for (int building_id : obj_planet->BuildingIDs()) { if (system) system->Remove(building_id); Destroy(building_id); @@ -2704,7 +2783,7 @@ std::set Universe::RecursiveDestroy(int object_id) { Destroy(object_id); retval.insert(object_id); - } else if (std::shared_ptr obj_system = std::dynamic_pointer_cast(obj)) { + } else if (auto obj_system = std::dynamic_pointer_cast(obj)) { // destroy all objects in system for (int system_id : obj_system->ObjectIDs()) { Destroy(system_id); @@ -2713,17 +2792,20 @@ std::set Universe::RecursiveDestroy(int object_id) { // remove any starlane connections to this system int this_sys_id = obj_system->ID(); - for (std::shared_ptr sys : m_objects.FindObjects()) { + for (auto& sys : m_objects.all()) { sys->RemoveStarlane(this_sys_id); } // remove fleets / ships moving along destroyed starlane - for (std::shared_ptr fleet : m_objects.FindObjects()) { + std::vector> fleets_to_destroy; + for (auto& fleet : m_objects.all()) { if (fleet->SystemID() == INVALID_OBJECT_ID && ( fleet->NextSystemID() == this_sys_id || fleet->PreviousSystemID() == this_sys_id)) - { RecursiveDestroy(fleet->ID()); } + { fleets_to_destroy.push_back(fleet); } } + for (auto& fleet : fleets_to_destroy) + RecursiveDestroy(fleet->ID()); // then destroy system itself Destroy(object_id); @@ -2731,8 +2813,8 @@ std::set Universe::RecursiveDestroy(int object_id) { // don't need to bother with removing things from system, fleets, or // ships, since everything in system is being destroyed - } else if (std::shared_ptr building = std::dynamic_pointer_cast(obj)) { - std::shared_ptr planet = GetPlanet(building->PlanetID()); + } else if (auto building = std::dynamic_pointer_cast(obj)) { + auto planet = m_objects.get(building->PlanetID()); if (planet) planet->RemoveBuilding(object_id); if (system) @@ -2754,7 +2836,7 @@ bool Universe::Delete(int object_id) { DebugLogger() << "Universe::Delete with ID: " << object_id; // find object amongst existing objects and delete directly, without storing // any info about the previous object (as is done for destroying an object) - std::shared_ptr obj = m_objects.Object(object_id); + auto obj = m_objects.get(object_id); if (!obj) { ErrorLogger() << "Tried to delete a nonexistant object with id: " << object_id; return false; @@ -2764,7 +2846,7 @@ bool Universe::Delete(int object_id) { // contained it and propagating associated signals obj->MoveTo(UniverseObject::INVALID_POSITION, UniverseObject::INVALID_POSITION); // remove from existing objects set - m_objects.Remove(object_id); + m_objects.erase(object_id); // TODO: Should this also remove the object from the latest known objects // and known destroyed objects for each empire? @@ -2773,17 +2855,15 @@ bool Universe::Delete(int object_id) { } void Universe::EffectDestroy(int object_id, int source_object_id) { - if (m_marked_destroyed.find(object_id) != m_marked_destroyed.end()) + if (m_marked_destroyed.count(object_id)) return; m_marked_destroyed[object_id].insert(source_object_id); } void Universe::InitializeSystemGraph(int for_empire_id) { - std::vector system_ids = ::EmpireKnownObjects(for_empire_id).FindObjectIDs(); - std::vector> systems; - for (size_t system1_index = 0; system1_index < system_ids.size(); ++system1_index) { - int system1_id = system_ids[system1_index]; - systems.push_back(GetEmpireKnownSystem(system1_id, for_empire_id)); + std::vector system_ids; + for (const auto& system : ::EmpireKnownObjects(for_empire_id).all()) { + system_ids.push_back(system->ID()); } m_pathfinder->InitializeSystemGraph(system_ids, for_empire_id); @@ -2815,10 +2895,10 @@ void Universe::UpdateStatRecords() { m_stat_records.clear(); std::map> empire_sources; - for (std::map::value_type& empire_entry : Empires()) { + for (const auto& empire_entry : Empires()) { if (empire_entry.second->Eliminated()) continue; - std::shared_ptr source = empire_entry.second->Source(); + auto source = empire_entry.second->Source(); if (!source) { ErrorLogger() << "Universe::UpdateStatRecords() unable to find source for empire, id = " << empire_entry.second->EmpireID(); @@ -2828,44 +2908,45 @@ void Universe::UpdateStatRecords() { } // process each stat - for (const std::map*>::value_type& stat_entry : EmpireStatistics::GetEmpireStats()) - { + for (const auto& stat_entry : EmpireStats()) { const std::string& stat_name = stat_entry.first; - const ValueRef::ValueRefBase* value_ref = stat_entry.second; + const auto& value_ref = stat_entry.second; if (!value_ref) continue; - std::map>& stat_records = m_stat_records[stat_name]; + auto& stat_records = m_stat_records[stat_name]; // calculate stat for each empire, store in records for current turn - for (std::map>::value_type entry : empire_sources) { + for (const auto& entry : empire_sources) { int empire_id = entry.first; if (value_ref->SourceInvariant()) { stat_records[empire_id][current_turn] = value_ref->Eval(); } else if (entry.second) { - stat_records[empire_id][current_turn] = value_ref->Eval(ScriptingContext(entry.second)); + stat_records[empire_id][current_turn] = value_ref->Eval(ScriptingContext(entry.second, m_objects)); } } } } -void Universe::GetShipDesignsToSerialize(ShipDesignMap& designs_to_serialize, int encoding_empire) const { +void Universe::GetShipDesignsToSerialize(ShipDesignMap& designs_to_serialize, + int encoding_empire) const +{ if (encoding_empire == ALL_EMPIRES) { designs_to_serialize = m_ship_designs; } else { designs_to_serialize.clear(); // add generic monster ship designs so they always appear in players' pedias - for (const ShipDesignMap::value_type& ship_design_entry : m_ship_designs) { + for (const auto& ship_design_entry : m_ship_designs) { ShipDesign* design = ship_design_entry.second; if (design->IsMonster() && design->DesignedByEmpire() == ALL_EMPIRES) designs_to_serialize[design->ID()] = design; } // get empire's known ship designs - std::map>::const_iterator it = m_empire_known_ship_design_ids.find(encoding_empire); + auto it = m_empire_known_ship_design_ids.find(encoding_empire); if (it == m_empire_known_ship_design_ids.end()) return; // no known designs to serialize @@ -2873,7 +2954,7 @@ void Universe::GetShipDesignsToSerialize(ShipDesignMap& designs_to_serialize, in // add all ship designs of ships this empire knows about for (int design_id : empire_designs) { - ShipDesignMap::const_iterator universe_design_it = m_ship_designs.find(design_id); + auto universe_design_it = m_ship_designs.find(design_id); if (universe_design_it != m_ship_designs.end()) designs_to_serialize[design_id] = universe_design_it->second; else @@ -2886,7 +2967,7 @@ void Universe::GetObjectsToSerialize(ObjectMap& objects, int encoding_empire) co if (&objects == &m_objects) return; - objects.Clear(); + objects.clear(); if (encoding_empire == ALL_EMPIRES) { // if encoding for all empires, copy true full universe state, and use the @@ -2901,23 +2982,25 @@ void Universe::GetObjectsToSerialize(ObjectMap& objects, int encoding_empire) co // if encoding for a specific empire with memory... // find indicated empire's knowledge about objects, current and previous - EmpireObjectMap::const_iterator it = m_empire_latest_known_objects.find(encoding_empire); + auto it = m_empire_latest_known_objects.find(encoding_empire); if (it == m_empire_latest_known_objects.end()) return; // empire has no object knowledge, so there is nothing to send //the empire_latest_known_objects are already processed for visibility, so can be copied streamlined objects.CopyForSerialize(it->second); - std::map>::const_iterator destroyed_ids_it = - m_empire_known_destroyed_object_ids.find(encoding_empire); + auto destroyed_ids_it = m_empire_known_destroyed_object_ids.find(encoding_empire); bool map_avail = (destroyed_ids_it != m_empire_known_destroyed_object_ids.end()); - const std::set& destroyed_object_ids = map_avail ? destroyed_ids_it->second : std::set(); + const auto& destroyed_object_ids = map_avail ? + destroyed_ids_it->second : std::set(); objects.AuditContainment(destroyed_object_ids); } } -void Universe::GetDestroyedObjectsToSerialize(std::set& destroyed_object_ids, int encoding_empire) const { +void Universe::GetDestroyedObjectsToSerialize(std::set& destroyed_object_ids, + int encoding_empire) const +{ if (&destroyed_object_ids == &m_destroyed_object_ids) return; @@ -2927,20 +3010,22 @@ void Universe::GetDestroyedObjectsToSerialize(std::set& destroyed_object_id } else { destroyed_object_ids.clear(); // get empire's known destroyed objects - ObjectKnowledgeMap::const_iterator it = m_empire_known_destroyed_object_ids.find(encoding_empire); + auto it = m_empire_known_destroyed_object_ids.find(encoding_empire); if (it != m_empire_known_destroyed_object_ids.end()) destroyed_object_ids = it->second; } } -void Universe::GetEmpireKnownObjectsToSerialize(EmpireObjectMap& empire_latest_known_objects, int encoding_empire) const { +void Universe::GetEmpireKnownObjectsToSerialize(EmpireObjectMap& empire_latest_known_objects, + int encoding_empire) const +{ if (&empire_latest_known_objects == &m_empire_latest_known_objects) return; DebugLogger() << "GetEmpireKnownObjectsToSerialize"; - for (EmpireObjectMap::value_type& entry : empire_latest_known_objects) - entry.second.Clear(); + for (auto& entry : empire_latest_known_objects) + entry.second.clear(); empire_latest_known_objects.clear(); @@ -2949,7 +3034,7 @@ void Universe::GetEmpireKnownObjectsToSerialize(EmpireObjectMap& empire_latest_k if (encoding_empire == ALL_EMPIRES) { // copy all ObjectMaps' contents - for (const EmpireObjectMap::value_type& entry : m_empire_latest_known_objects) { + for (const auto& entry : m_empire_latest_known_objects) { int empire_id = entry.first; const ObjectMap& map = entry.second; //the maps in m_empire_latest_known_objects are already processed for visibility, so can be copied fully @@ -2959,7 +3044,9 @@ void Universe::GetEmpireKnownObjectsToSerialize(EmpireObjectMap& empire_latest_k } } -void Universe::GetEmpireObjectVisibilityMap(EmpireObjectVisibilityMap& empire_object_visibility, int encoding_empire) const { +void Universe::GetEmpireObjectVisibilityMap(EmpireObjectVisibilityMap& empire_object_visibility, + int encoding_empire) const +{ if (encoding_empire == ALL_EMPIRES) { empire_object_visibility = m_empire_object_visibility; return; @@ -2969,15 +3056,16 @@ void Universe::GetEmpireObjectVisibilityMap(EmpireObjectVisibilityMap& empire_ob // than no visibility of. TODO: include what requested empire knows about // other empires' visibilites of objects empire_object_visibility.clear(); - for (ObjectMap::const_iterator<> it = m_objects.const_begin(); it != m_objects.const_end(); ++it) { - int object_id = it->ID(); - Visibility vis = GetObjectVisibilityByEmpire(object_id, encoding_empire); + for (const auto& object : m_objects.all()) { + Visibility vis = GetObjectVisibilityByEmpire(object->ID(), encoding_empire); if (vis > VIS_NO_VISIBILITY) - empire_object_visibility[encoding_empire][object_id] = vis; + empire_object_visibility[encoding_empire][object->ID()] = vis; } } -void Universe::GetEmpireObjectVisibilityTurnMap(EmpireObjectVisibilityTurnMap& empire_object_visibility_turns, int encoding_empire) const { +void Universe::GetEmpireObjectVisibilityTurnMap(EmpireObjectVisibilityTurnMap& empire_object_visibility_turns, + int encoding_empire) const +{ if (encoding_empire == ALL_EMPIRES) { empire_object_visibility_turns = m_empire_object_visibility_turns; return; @@ -2985,12 +3073,14 @@ void Universe::GetEmpireObjectVisibilityTurnMap(EmpireObjectVisibilityTurnMap& e // include just requested empire's visibility turn information empire_object_visibility_turns.clear(); - EmpireObjectVisibilityTurnMap::const_iterator it = m_empire_object_visibility_turns.find(encoding_empire); + auto it = m_empire_object_visibility_turns.find(encoding_empire); if (it != m_empire_object_visibility_turns.end()) empire_object_visibility_turns[encoding_empire] = it->second; } -void Universe::GetEmpireKnownDestroyedObjects(ObjectKnowledgeMap& empire_known_destroyed_object_ids, int encoding_empire) const { +void Universe::GetEmpireKnownDestroyedObjects(ObjectKnowledgeMap& empire_known_destroyed_object_ids, + int encoding_empire) const +{ if (&empire_known_destroyed_object_ids == &m_empire_known_destroyed_object_ids) return; @@ -3002,12 +3092,14 @@ void Universe::GetEmpireKnownDestroyedObjects(ObjectKnowledgeMap& empire_known_d empire_known_destroyed_object_ids.clear(); // copy info about what encoding empire knows - ObjectKnowledgeMap::const_iterator it = m_empire_known_destroyed_object_ids.find(encoding_empire); + auto it = m_empire_known_destroyed_object_ids.find(encoding_empire); if (it != m_empire_known_destroyed_object_ids.end()) empire_known_destroyed_object_ids[encoding_empire] = it->second; } -void Universe::GetEmpireStaleKnowledgeObjects(ObjectKnowledgeMap& empire_stale_knowledge_object_ids, int encoding_empire) const { +void Universe::GetEmpireStaleKnowledgeObjects(ObjectKnowledgeMap& empire_stale_knowledge_object_ids, + int encoding_empire) const +{ if (&empire_stale_knowledge_object_ids == &m_empire_stale_knowledge_object_ids) return; @@ -3019,30 +3111,23 @@ void Universe::GetEmpireStaleKnowledgeObjects(ObjectKnowledgeMap& empire_stale_k empire_stale_knowledge_object_ids.clear(); // copy stale data for this empire - ObjectKnowledgeMap::const_iterator it = m_empire_stale_knowledge_object_ids.find(encoding_empire); + auto it = m_empire_stale_knowledge_object_ids.find(encoding_empire); if (it != m_empire_stale_knowledge_object_ids.end()) empire_stale_knowledge_object_ids[encoding_empire] = it->second; } -void Universe::ResetUniverse() { - m_objects.Clear(); // wipe out anything present in the object map - - m_empire_known_destroyed_object_ids.clear(); - m_empire_known_ship_design_ids.clear(); - m_empire_latest_known_objects.clear(); - m_effect_accounting_map.clear(); - m_empire_object_visibility.clear(); - m_empire_object_visibility_turns.clear(); - m_empire_object_visible_specials.clear(); - m_effect_accounting_map.clear(); - m_effect_discrepancy_map.clear(); - m_marked_destroyed.clear(); - m_destroyed_object_ids.clear(); - m_ship_designs.clear(); - m_stat_records.clear(); - m_universe_width = 1000.0; +std::map CheckSumContent() { + std::map checksums; - ResetAllIDAllocation(); + // add entries for various content managers... + checksums["BuildingTypeManager"] = GetBuildingTypeManager().GetCheckSum(); + checksums["Encyclopedia"] = GetEncyclopedia().GetCheckSum(); + checksums["FieldTypeManager"] = GetFieldTypeManager().GetCheckSum(); + checksums["ShipHullManager"] = GetShipHullManager().GetCheckSum(); + checksums["ShipPartManager"] = GetShipPartManager().GetCheckSum(); + checksums["PredefinedShipDesignManager"] = GetPredefinedShipDesignManager().GetCheckSum(); + checksums["SpeciesManager"] = GetSpeciesManager().GetCheckSum(); + checksums["TechManager"] = GetTechManager().GetCheckSum(); - GetSpeciesManager().ClearSpeciesHomeworlds(); + return checksums; } diff --git a/universe/Universe.h b/universe/Universe.h index 5d11f4748e1..7039dac5b99 100644 --- a/universe/Universe.h +++ b/universe/Universe.h @@ -5,17 +5,12 @@ #include "EnumsFwd.h" #include "ObjectMap.h" #include "UniverseObject.h" +#include "../util/Pending.h" #include -// Fix for issue #1513 (boost ticket #12978) -// Starting with v1.64, boost::numeric::ublas was not updated for changes to -// boost::serialization, which moved array_wrapper to separate header -#if BOOST_VERSION >= 106400 - #include -#endif -#include #include #include +#include #include #include @@ -23,6 +18,7 @@ #include #include #include +#include #include "../util/Export.h" @@ -34,21 +30,29 @@ class ShipDesign; class System; class Pathfinder; class IDAllocator; +struct UnlockableItem; +class FleetPlan; +class MonsterFleetPlan; + namespace Condition { - struct ConditionBase; + struct Condition; typedef std::vector> ObjectSet; } namespace Effect { struct AccountingInfo; - struct TargetsAndCause; - struct SourcedEffectsGroup; + struct TargetsAndCause; // struct TargetsAndCause { TargetSet target_set; EffectCause effect_cause; }; + struct SourcedEffectsGroup; // struct SourcedEffectsGroup { int source_object_id; const EffectsGroup* effects_group; }; class EffectsGroup; typedef std::vector> TargetSet; - typedef std::map>> AccountingMap; - typedef std::vector> TargetsCauses; - typedef std::map> DiscrepancyMap; + typedef std::unordered_map>> AccountingMap; + typedef std::vector> SourcesEffectsTargetsAndCausesVec; +} + +namespace ValueRef { + template + struct ValueRef; } #if defined(_MSC_VER) @@ -59,7 +63,6 @@ namespace boost { # endif #endif - /** The Universe class contains the majority of FreeOrion gamestate: All the * UniverseObjects in a game, and (of less importance) all ShipDesigns in a * game. (Other gamestate is contained in the Empire class.) @@ -80,6 +83,18 @@ class FO_COMMON_API Universe { typedef std::map> ObjectKnowledgeMap; ///< IDs of Empires which know information about an object (or deleted object); keyed by object id + typedef const ValueRef::ValueRef* VisValRef; + typedef std::vector> SrcVisValRefVec; + typedef std::map ObjSrcVisValRefVecMap; + typedef std::map EmpireObjectVisValueRefMap; + + /** Discrepancy between meter's value at start of turn, and the value that + * this client calculate that the meter should have with the knowledge + * available -> the unknown factor affecting the meter. This is used + * when generating effect accounting, in the case where the expected + * and actual meter values don't match. */ + typedef std::unordered_map> DiscrepancyMap; + public: typedef std::map ObjectVisibilityMap; ///< map from object id to Visibility level for a particular empire typedef std::map EmpireObjectVisibilityMap; ///< map from empire id to ObjectVisibilityMap for that empire @@ -97,7 +112,6 @@ class FO_COMMON_API Universe { /** \name Structors */ //@{ Universe(); - virtual ~Universe(); //@} @@ -120,6 +134,7 @@ class FO_COMMON_API Universe { /** Returns IDs of objects that have been destroyed. */ const std::set& DestroyedObjectIds() const; + int HighestDestroyedObjectID() const; /** Returns IDs of objects that the Empire with id \a empire_id knows have * been destroyed. Each empire's latest known objects data contains the @@ -133,10 +148,11 @@ class FO_COMMON_API Universe { * these objects suggests that they should be visible, but they are not. */ const std::set& EmpireStaleKnowledgeObjectIDs(int empire_id) const; - const ShipDesign* GetShipDesign(int ship_design_id) const; ///< returns the ship design with id \a ship_design id, or 0 if non exists - void RenameShipDesign(int design_id, const std::string& name = "", const std::string& description = ""); - ship_design_iterator beginShipDesigns() const {return m_ship_designs.begin();} ///< returns the begin iterator for ship designs - ship_design_iterator endShipDesigns() const {return m_ship_designs.end();} ///< returns the end iterator for ship designs + const ShipDesign* GetShipDesign(int ship_design_id) const; ///< returns the ship design with id \a ship_design id, or 0 if non exists + void RenameShipDesign(int design_id, const std::string& name = "", + const std::string& description = ""); + ship_design_iterator beginShipDesigns() const {return m_ship_designs.begin();} + ship_design_iterator endShipDesigns() const {return m_ship_designs.end();} const ShipDesign* GetGenericShipDesign(const std::string& name) const; @@ -148,30 +164,29 @@ class FO_COMMON_API Universe { /** Returns the Visibility level of empire with id \a empire_id of * UniverseObject with id \a object_id as determined by calling * UpdateEmpireObjectVisibilities. */ - Visibility GetObjectVisibilityByEmpire(int object_id, int empire_id) const; + Visibility GetObjectVisibilityByEmpire(int object_id, int empire_id) const; /** Returns the map from Visibility level to turn number on which the empire * with id \a empire_id last had the various Visibility levels of the * UniverseObject with id \a object_id . The returned map may be empty or * not have entries for all visibility levels, if the empire has not seen * the object at that visibility level yet. */ - const VisibilityTurnMap& - GetObjectVisibilityTurnMapByEmpire(int object_id, int empire_id) const; + const VisibilityTurnMap& GetObjectVisibilityTurnMapByEmpire(int object_id, int empire_id) const; /** Returns the set of specials attached to the object with id \a object_id * that the empire with id \a empire_id can see this turn. */ - std::set GetObjectVisibleSpecialsByEmpire(int object_id, int empire_id) const; + std::set GetObjectVisibleSpecialsByEmpire(int object_id, int empire_id) const; /** Return the Pathfinder */ - std::shared_ptr GetPathfinder() const {return m_pathfinder;} + std::shared_ptr GetPathfinder() const {return m_pathfinder;} /** Returns map, indexed by object id, to map, indexed by MeterType, * to vector of EffectAccountInfo for the meter, in order effects * were applied to the meter. */ - const Effect::AccountingMap& GetEffectAccountingMap() const {return m_effect_accounting_map;} + const Effect::AccountingMap& GetEffectAccountingMap() const {return m_effect_accounting_map;} const std::map>>& - GetStatRecords() const { return m_stat_records; } + GetStatRecords() const { return m_stat_records; } mutable UniverseObjectDeleteSignalType UniverseObjectDeleteSignal; ///< the state changed signal object for this UniverseObject //@} @@ -180,133 +195,130 @@ class FO_COMMON_API Universe { /** Inserts \a ship_design into the universe. Return true on success. The ship design's id will be the newly assigned id. \note Universe gains ownership of \a ship_design once inserted. */ - bool InsertShipDesign(ShipDesign* ship_design); + bool InsertShipDesign(ShipDesign* ship_design); /** Inserts \a ship_design into the universe with given \a id; returns true * on success, or false on failure. An empire id of none indicates that * the server is allocating an id on behalf of itself. This can be removed * when no longer supporting legacy id allocation in pending Orders. \note * Universe gains ownership of \a ship_design once inserted. */ - bool InsertShipDesignID(ShipDesign* ship_design, boost::optional empire_id, int id); - - // Resets the universe object to prepare for generation of a new universe - void ResetUniverse(); + bool InsertShipDesignID(ShipDesign* ship_design, boost::optional empire_id, int id); /** Reset object and ship design id allocation for a new game. */ void ResetAllIDAllocation(const std::vector& empire_ids = std::vector()); /** Clears main ObjectMap, empires' latest known objects map, and * ShipDesign map. */ - void Clear(); + void Clear(); + + /** Resets meters */ + void ResetAllObjectMeters(bool target_max_unpaired = true, bool active = true); /** Determines all effectsgroups' target sets, then resets meters and * executes all effects on all objects. Then clamps meter values so * target and max meters are within a reasonable range and any current * meters with associated max meters are limited by their max. */ - void ApplyAllEffectsAndUpdateMeters(bool do_accounting = true); + void ApplyAllEffectsAndUpdateMeters(bool do_accounting = true); /** Determines all effectsgroups' target sets, then resets meters and * executes only SetMeter effects on all objects whose ids are listed in * \a object_ids. Then clamps meter values so target and max meters are * within a reasonable range and any current meters with associated max * meters are limited by their max. */ - void ApplyMeterEffectsAndUpdateMeters(const std::vector& object_ids, bool do_accounting = true); + void ApplyMeterEffectsAndUpdateMeters(const std::vector& object_ids, bool do_accounting = true); /** Calls above ApplyMeterEffectsAndUpdateMeters() function on all objects.*/ - void ApplyMeterEffectsAndUpdateMeters(bool do_accounting = true); - /** Like ApplyMeterEffectsAndUpdateMeters(), but only target, max and unpaired meters are updated.*/ - void ApplyMeterEffectsAndUpdateTargetMaxUnpairedMeters(bool do_accounting = true); + void ApplyMeterEffectsAndUpdateMeters(bool do_accounting = true); /** Executes effects that modify objects' appearance in the human client. */ - void ApplyAppearanceEffects(const std::vector& object_ids); + void ApplyAppearanceEffects(const std::vector& object_ids); /** Executes effects that modify objects' apperance for all objects. */ - void ApplyAppearanceEffects(); + void ApplyAppearanceEffects(); /** Executes effects that modify objects' apperance for all objects. */ - void ApplyGenerateSitRepEffects(); + void ApplyGenerateSitRepEffects(); /** For all objects and meters, determines discrepancies between actual meter * maxes and what the known universe should produce, and and stores in * m_effect_discrepancy_map. */ - void InitMeterEstimatesAndDiscrepancies(); + void InitMeterEstimatesAndDiscrepancies(); /** Based on (known subset of, if in a client) universe and any orders * given so far this turn, updates estimated meter maxes for next turn * for the objects with ids indicated in \a objects_vec. */ - void UpdateMeterEstimates(const std::vector& objects_vec); + void UpdateMeterEstimates(const std::vector& objects_vec); /** Updates indicated object's meters, and if applicable, the * meters of objects contained within the indicated object. * If \a object_id is INVALID_OBJECT_ID, then all * objects' meters are updated. */ - void UpdateMeterEstimates(int object_id, bool update_contained_objects = false); + void UpdateMeterEstimates(int object_id, bool update_contained_objects = false); /** Updates all meters for all (known) objects */ - void UpdateMeterEstimates(); + void UpdateMeterEstimates(); + void UpdateMeterEstimates(bool do_accounting); /** Sets all objects' meters' initial values to their current values. */ - void BackPropagateObjectMeters(); - - /** Sets indicated objects' meters' initial values to their current values. */ - void BackPropagateObjectMeters(const std::vector& object_ids); + void BackPropagateObjectMeters(); /** Determines which empires can see which objects at what visibility * level, based on */ - void UpdateEmpireObjectVisibilities(); + void UpdateEmpireObjectVisibilities(); /** Sets a special record of visibility that overrides the standard * empire-object visibility after the latter is processed. */ - void SetEffectDerivedVisibility(int empire_id, int object_id, Visibility vis); + void SetEffectDerivedVisibility(int empire_id, int object_id, int source_id, + const ValueRef::ValueRef* vis); /** Applies empire-object visibilities set by effects. */ - void ApplyEffectDerivedVisibilities(); + void ApplyEffectDerivedVisibilities(); /** If an \p empire_id can't currently see \p object_id, then remove * \p object_id' object from the object map and the set of known objects. */ - void ForgetKnownObject(int empire_id, int object_id); + void ForgetKnownObject(int empire_id, int object_id); /** Sets visibility for indicated \a empire_id of object with \a object_id * a vis */ - void SetEmpireObjectVisibility(int empire_id, int object_id, Visibility vis); + void SetEmpireObjectVisibility(int empire_id, int object_id, Visibility vis); /** Sets visibility for indicated \a empire_id for the indicated \a special */ - void SetEmpireSpecialVisibility(int empire_id, int object_id, - const std::string& special_name, bool visible = true); + void SetEmpireSpecialVisibility(int empire_id, int object_id, + const std::string& special_name, bool visible = true); /** Stores latest known information about each object for each empire and * updates the record of the last turn on which each empire has visibility * of object that can be seen on the current turn with the level of * visibility that the empire has this turn. */ - void UpdateEmpireLatestKnownObjectsAndVisibilityTurns(); + void UpdateEmpireLatestKnownObjectsAndVisibilityTurns(); /** Checks latest known information about each object for each empire and, * in cases when the latest known state (stealth and location) suggests * that the empire should be able to see the object, but the object can't * be seen by the empire, updates the latest known state to note this. */ - void UpdateEmpireStaleObjectKnowledge(); + void UpdateEmpireStaleObjectKnowledge(); /** Fills pathfinding data structure and determines least jumps distances * between systems for the empire with id \a for_empire_id or uses the * main / true / visible objects if \a for_empire_id is ALL_EMPIRES*/ - void InitializeSystemGraph(int for_empire_id = ALL_EMPIRES); + void InitializeSystemGraph(int for_empire_id = ALL_EMPIRES); /** Regenerates per-empire system view graphs by filtering the complete * system graph based on empire visibility. Does not regenerate the base * graph to account for actual system-starlane connectivity changes. */ - void UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id = ALL_EMPIRES); + void UpdateEmpireVisibilityFilteredSystemGraphs(int for_empire_id = ALL_EMPIRES); /** Adds the object ID \a object_id to the set of object ids for the empire * with id \a empire_id that the empire knows have been destroyed. */ - void SetEmpireKnowledgeOfDestroyedObject(int object_id, int empire_id); + void SetEmpireKnowledgeOfDestroyedObject(int object_id, int empire_id); /** Adds the ship design ID \a ship_design_id to the set of ship design ids * known by the empire with id \a empire_id */ - void SetEmpireKnowledgeOfShipDesign(int ship_design_id, int empire_id); + void SetEmpireKnowledgeOfShipDesign(int ship_design_id, int empire_id); /** Record in statistics that \a object_id was destroyed by species/empire * associated with \a source_object_id */ - void CountDestructionInStats(int object_id, int source_object_id); + void CountDestructionInStats(int object_id, int source_object_id); /** Removes the object with ID number \a object_id from the universe's map * of existing objects, and adds the object's id to the set of destroyed @@ -316,49 +328,49 @@ class FO_COMMON_API Universe { * or limited versions of objects remain in empires latest known objects * ObjectMap, regardless of whether the empire knows the object is * destroyed. */ - void Destroy(int object_id, bool update_destroyed_object_knowers = true); + void Destroy(int object_id, bool update_destroyed_object_knowers = true); /** Destroys object with ID \a object_id, and destroys any associted * objects, such as contained buildings of planets, contained anything of * systems, or fleets if their last ship has id \a object_id and the fleet * is thus empty. Returns the ids of all destroyed objects. */ - std::set RecursiveDestroy(int object_id); + std::set RecursiveDestroy(int object_id); /** Used by the Destroy effect to mark an object for destruction later * during turn processing. (objects can't be destroyed immediately as * other effects might depend on their existence) */ - void EffectDestroy(int object_id, int source_object_id = INVALID_OBJECT_ID); + void EffectDestroy(int object_id, int source_object_id = INVALID_OBJECT_ID); /** Permanently deletes object with ID number \a object_id. no * information about this object is retained in the Universe. Can be * performed on objects whether or not the have been destroyed. Returns * true if such an object was found, false otherwise. */ - bool Delete(int object_id); + bool Delete(int object_id); /** Permanently deletes the ship design with ID number \a design_id. No * information about this design is retained in the Universe. */ - bool DeleteShipDesign(int design_id); + bool DeleteShipDesign(int design_id); /** Sets whether to inhibit UniverseObjectSignals. Inhibits if \a inhibit * is true, and (re)enables UniverseObjectSignals if \a inhibit is false. */ - void InhibitUniverseObjectSignals(bool inhibit = true); + void InhibitUniverseObjectSignals(bool inhibit = true); - void UpdateStatRecords(); + void UpdateStatRecords(); //@} /** Returns true if UniverseOjbectSignals are inhibited, false otherwise. */ - const bool& UniverseObjectSignalsInhibited(); + const bool& UniverseObjectSignalsInhibited(); /** HACK! This must be set to the encoding empire's id when serializing a * Universe, so that only the relevant parts of the Universe are * serialized. The use of this global variable is done just so I don't * have to rewrite any custom boost::serialization classes that implement * empire-dependent visibility. */ - int& EncodingEmpire(); + int& EncodingEmpire(); - double UniverseWidth() const; - void SetUniverseWidth(double width) { m_universe_width = width; } - bool AllObjectsVisible() const { return m_all_objects_visible; } + double UniverseWidth() const; + void SetUniverseWidth(double width) { m_universe_width = width; } + bool AllObjectsVisible() const { return m_all_objects_visible; } /** \name Generators */ //@{ /** InsertNew constructs and inserts a UniverseObject into the object map with a new @@ -390,6 +402,32 @@ class FO_COMMON_API Universe { } //@} + //! Set items unlocked before turn 1 from \p future. + void SetInitiallyUnlockedItems(Pending::Pending>&& future); + //! Items unlocked before turn 1. + const std::vector& InitiallyUnlockedItems() const; + + //! Set buildings unlocked before turn 1 from \p future. + void SetInitiallyUnlockedBuildings(Pending::Pending>&& future); + //! Buildings unlocked before turn 1. + const std::vector& InitiallyUnlockedBuildings() const; + + /** Set fleets unlocked before turn 1 from \p future.*/ + void SetInitiallyUnlockedFleetPlans(Pending::Pending>>&& future); + /** Fleets unlocked before turn 1.*/ + const std::vector InitiallyUnlockedFleetPlans() const; + + /** Set items unlocked before turn 1 from \p future..*/ + void SetMonsterFleetPlans(Pending::Pending>>&& future); + /** Items unlocked before turn 1.*/ + const std::vector MonsterFleetPlans() const; + + /** Set the empire stats from \p future. */ + using EmpireStatsMap = std::map>>; + void SetEmpireStats(Pending::Pending future); +private: + const EmpireStatsMap& EmpireStats() const; +public: /** ObfuscateIDGenerator applies randomization to the IDAllocator to prevent clients from inferring too much information about other client's id generation activities. */ void ObfuscateIDGenerator(); @@ -397,15 +435,15 @@ class FO_COMMON_API Universe { private: /* Pathfinder setup for the viewing empire */ - std::shared_ptr const m_pathfinder; + std::shared_ptr m_pathfinder; /** Generates an object ID for a future object. Usually used by the server * to service new ID requests. */ - int GenerateObjectID(); + int GenerateObjectID(); /** Generates design ID for a new (ship) design. Usually used by the * server to service new ID requests. */ - int GenerateDesignID(); + int GenerateDesignID(); /** Verify that an object ID \p id could be generated by \p empire_id. */ bool VerifyUnusedObjectID(const int empire_id, const int id); @@ -413,7 +451,7 @@ class FO_COMMON_API Universe { /** Inserts object \p obj into the universe with the given \p id. */ template std::shared_ptr InsertID(int id, Args&&... args) { - auto obj = std::shared_ptr(new T(std::forward(args)...)); + auto obj = std::make_shared(std::forward(args)...); auto uobj = std::dynamic_pointer_cast(obj); if (!uobj) return std::shared_ptr(); @@ -425,34 +463,39 @@ class FO_COMMON_API Universe { /** Inserts object \p obj into the universe with the given \p id. */ void InsertIDCore(std::shared_ptr obj, int id); - /** Clears \a targets_causes, and then populates with all + /** Clears \a source_effects_targets_causes, and then populates with all * EffectsGroups and their targets in the known universe. */ - void GetEffectsAndTargets(Effect::TargetsCauses& targets_causes); + void GetEffectsAndTargets(std::map& source_effects_targets_causes, + bool only_meter_effects = false) const; - /** Removes entries in \a targets_causes about effects groups acting + /** Removes entries in \a source_effects_targets_causes about effects groups acting * on objects in \a target_objects, and then repopulates for EffectsGroups - * that act on at least one of the objects in \a target_objects. If + * that act on at least one of the objects in \a target_objects. If * \a target_objects is empty then default target candidates will be used. */ - void GetEffectsAndTargets(Effect::TargetsCauses& targets_causes, - const std::vector& target_objects); + void GetEffectsAndTargets(std::map& source_effects_targets_causes, + const std::vector& target_objects, + bool only_meter_effects = false) const; + + void ResetObjectMeters(const std::vector>& objects, + bool target_max_unpaired = true, bool active = true); /** Executes all effects. For use on server when processing turns. * If \a only_meter_effects is true, then only SetMeter effects are * executed. This is useful on server or clients to update meter * values after the rest of effects (including non-meter effects) have * been executed. */ - void ExecuteEffects(const Effect::TargetsCauses& targets_causes, - bool update_effect_accounting, - bool only_meter_effects = false, - bool only_appearance_effects = false, - bool include_empire_meter_effects = false, - bool only_generate_sitrep_effects = false); + void ExecuteEffects(std::map& source_effects_targets_causes, + bool update_effect_accounting, + bool only_meter_effects = false, + bool only_appearance_effects = false, + bool include_empire_meter_effects = false, + bool only_generate_sitrep_effects = false); /** Does actual updating of meter estimates after the public function have * processed objects_vec or whatever they were passed and cleared the - * relevant effect accounting for those objects and meters. If an empty + * relevant effect accounting for those objects and meters. If an empty * vector is passed, it will instead update all existing objects. */ - void UpdateMeterEstimatesImpl(const std::vector& objects_vec); + void UpdateMeterEstimatesImpl(const std::vector& objects_vec, bool do_accounting); ObjectMap m_objects; ///< map from object id to UniverseObjects in the universe. for the server: all of them, up to date and true information about object is stored; for clients, only limited information based on what the client knows about is sent. EmpireObjectMap m_empire_latest_known_objects; ///< map from empire id to (map from object id to latest known information about each object by that empire) @@ -462,7 +505,7 @@ class FO_COMMON_API Universe { EmpireObjectVisibilityMap m_empire_object_visibility; ///< map from empire id to (map from object id to visibility of that object for that empire) EmpireObjectVisibilityTurnMap m_empire_object_visibility_turns; ///< map from empire id to (map from object id to (map from Visibility rating to turn number on which the empire last saw the object at the indicated Visibility rating or higher) - EmpireObjectVisibilityMap m_effect_specified_empire_object_visibilities; + EmpireObjectVisValueRefMap m_effect_specified_empire_object_visibilities; EmpireObjectSpecialsMap m_empire_object_visible_specials; ///< map from empire id to (map from object id to (set of names of specials that empire can see are on that object) ) @@ -473,7 +516,12 @@ class FO_COMMON_API Universe { std::map> m_empire_known_ship_design_ids; ///< ship designs known to each empire Effect::AccountingMap m_effect_accounting_map; ///< map from target object id, to map from target meter, to orderered list of structs with details of an effect and what it does to the meter - Effect::DiscrepancyMap m_effect_discrepancy_map; ///< map from target object id, to map from target meter, to discrepancy between meter's actual initial value, and the initial value that this meter should have as far as the client can tell: the unknown factor affecting the meter + + /// map from target object id, to map from target meter, to discrepancy + /// between meter's actual initial value, and the initial value that this + /// meter should have as far as the client can tell: the unknown factor + /// affecting the meter. + DiscrepancyMap m_effect_discrepancy_map; std::map> m_marked_destroyed; ///< used while applying effects to cache objects that have been destroyed. this allows to-be-destroyed objects to remain undestroyed until all effects have been processed, which ensures that to-be-destroyed objects still exist when other effects need to access them as a source object. key is destroyed object, and value set are the ids of objects that caused the destruction (may be multiples destroying a single target on a given turn) @@ -485,37 +533,57 @@ class FO_COMMON_API Universe { std::map>> m_stat_records; ///< storage for statistics calculated for empires. Indexed by stat name (string), contains a map indexed by empire id, contains a map from turn number (int) to stat value (double). + //! @name Parsed items + //! Various unlocked items are kept as a Pending::Pending while being parsed and + //! then transfered. They are mutable to allow processing in const accessors. + //! @{ + mutable boost::optional>> m_pending_items = boost::none; + mutable boost::optional>> m_pending_buildings = boost::none; + mutable boost::optional>>> m_pending_fleet_plans = boost::none; + mutable boost::optional>>> m_pending_monster_fleet_plans = boost::none; + mutable boost::optional> m_pending_empire_stats = boost::none; + + mutable std::vector m_unlocked_items; + mutable std::vector m_unlocked_buildings; + mutable std::vector> m_unlocked_fleet_plans; + mutable std::vector> m_monster_fleet_plans; + mutable EmpireStatsMap m_empire_stats; + //! @} + /** Fills \a designs_to_serialize with ShipDesigns known to the empire with * the ID \a encoding empire. If encoding_empire is ALL_EMPIRES, then all * designs are included. */ - void GetShipDesignsToSerialize(ShipDesignMap& designs_to_serialize, int encoding_empire) const; + void GetShipDesignsToSerialize(ShipDesignMap& designs_to_serialize, int encoding_empire) const; /** Fills \a objects with copies of UniverseObjects that should be sent * to the empire with id \a encoding_empires */ - void GetObjectsToSerialize(ObjectMap& objects, int encoding_empire) const; + void GetObjectsToSerialize(ObjectMap& objects, int encoding_empire) const; /** Fills \a destroyed_object_ids with ids of objects known to be destroyed * by the empire with ID \a encoding empire. If encoding_empire is * ALL_EMPIRES, then all destroyed objects are included. */ - void GetDestroyedObjectsToSerialize(std::set& destroyed_object_ids, int encoding_empire) const; + void GetDestroyedObjectsToSerialize(std::set& destroyed_object_ids, int encoding_empire) const; /** Fills \a empire_latest_known_objects map with the latest known data * about UniverseObjects for the empire with id \a encoding_empire. If * the encoding empire is ALL_EMPIRES then all stored empire object * knowledge is included. */ - void GetEmpireKnownObjectsToSerialize(EmpireObjectMap& empire_latest_known_objects, int encoding_empire) const; + void GetEmpireKnownObjectsToSerialize(EmpireObjectMap& empire_latest_known_objects, int encoding_empire) const; + + /***/ + void GetEmpireObjectVisibilityMap(EmpireObjectVisibilityMap& empire_object_visibility, int encoding_empire) const; /***/ - void GetEmpireObjectVisibilityMap(EmpireObjectVisibilityMap& empire_object_visibility, int encoding_empire) const; + void GetEmpireObjectVisibilityTurnMap(EmpireObjectVisibilityTurnMap& empire_object_visibility_turns, int encoding_empire) const; /***/ - void GetEmpireObjectVisibilityTurnMap(EmpireObjectVisibilityTurnMap& empire_object_visibility_turns, int encoding_empire) const; + //void GetEffectSpecifiedVisibilities(EmpireObjectVisibilityMap& effect_specified_empire_object_visibilities, int encoding_empire) const; /***/ - void GetEmpireKnownDestroyedObjects(ObjectKnowledgeMap& empire_known_destroyed_object_ids, int encoding_empire) const; + void GetEmpireKnownDestroyedObjects(ObjectKnowledgeMap& empire_known_destroyed_object_ids, int encoding_empire) const; /***/ - void GetEmpireStaleKnowledgeObjects(ObjectKnowledgeMap& empire_stale_knowledge_object_ids, int encoding_empire) const; + void GetEmpireStaleKnowledgeObjects(ObjectKnowledgeMap& empire_stale_knowledge_object_ids, int encoding_empire) const; /** Manages allocating and verifying new object ids.*/ std::unique_ptr const m_object_id_allocator; @@ -524,9 +592,13 @@ class FO_COMMON_API Universe { std::unique_ptr const m_design_id_allocator; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; +/** Compute a checksum for each of the universe's content managers. Each value will be of the form + ("BuildingManager", ) */ +FO_COMMON_API std::map CheckSumContent(); + #endif // _Universe_h_ diff --git a/universe/UniverseGenerator.cpp b/universe/UniverseGenerator.cpp deleted file mode 100644 index 79ef7d5b58f..00000000000 --- a/universe/UniverseGenerator.cpp +++ /dev/null @@ -1,868 +0,0 @@ -#include "UniverseGenerator.h" - -#include "../util/Directories.h" -#include "../util/Random.h" -#include "../util/i18n.h" -#include "../util/Logger.h" -#include "../parse/Parse.h" -#include "../Empire/Empire.h" -#include "../Empire/EmpireManager.h" - -#include "Planet.h" -#include "System.h" -#include "Species.h" -#include "ValueRef.h" -#include "Enums.h" - -////////////////////////////////////////// -// Universe Setup Functions // -////////////////////////////////////////// - -namespace Delauney { - /** simple 2D point. would have used array of systems, but System - * class has limits on the range of positions that would interfere - * with the triangulation algorithm (need a single large covering - * triangle that overlaps all actual points being triangulated) */ - class DTPoint { - public: - DTPoint() : - x(0.0), - y(0.0) - {} - DTPoint(double x_, double y_) : - x(x_), - y(y_) - {} - double x; - double y; - }; - - /* simple class for an integer that has an associated "sorting value", - * so the integer can be stored in a list sorted by something other than - * the value of the integer */ - class SortValInt { - public: - SortValInt(int num_, double sort_val_) : - num(num_), - sortVal(sort_val_) - {} - int num; - double sortVal; - }; - - - /** list of three interger array indices, and some additional info about - * the triangle that the corresponding points make up, such as the - * circumcentre and radius, and a function to find if another point is in - * the circumcircle */ - class DTTriangle { - public: - DTTriangle(); - DTTriangle(int vert1, int vert2, int vert3, std::vector &points); - - bool PointInCircumCircle(Delauney::DTPoint &p); ///< determines whether a specified point is within the circumcircle of the triangle - const std::vector& Verts() {return verts;} - - private: - std::vector verts; ///< indices of vertices of triangle - Delauney::DTPoint centre; ///< location of circumcentre of triangle - double radius2; ///< radius of circumcircle squared - }; - - DTTriangle::DTTriangle(int vert1, int vert2, int vert3, std::vector& points) { - double a, Sx, Sy, b; - double x1, x2, x3, y1, y2, y3; - - if (vert1 == vert2 || vert1 == vert3 || vert2 == vert3) - throw std::runtime_error("Attempted to create Triangle with two of the same vertex indices."); - - verts = std::vector(3); - - // record indices of vertices of triangle - verts[0] = vert1; - verts[1] = vert2; - verts[2] = vert3; - - // extract position info for vertices - x1 = points[vert1].x; - x2 = points[vert2].x; - x3 = points[vert3].x; - y1 = points[vert1].y; - y2 = points[vert2].y; - y3 = points[vert3].y; - - // calculate circumcircle and circumcentre of triangle - a = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2); - - Sx = 0.5 * ((x1 * x1 + y1 * y1) * (y2 - y3) + - (x2 * x2 + y2 * y2) * (y3 - y1) + - (x3 * x3 + y3 * y3) * (y1 - y2)); - - Sy = -0.5* ((x1 * x1 + y1 * y1) * (x2 - x3) + - (x2 * x2 + y2 * y2) * (x3 - x1) + - (x3 * x3 + y3 * y3) * (x1 - x2)); - - b = ((x1 * x1 + y1 * y1) * (x2 * y3 - x3 * y2) + - (x2 * x2 + y2 * y2) * (x3 * y1 - x1 * y3) + - (x3 * x3 + y3 * y3) * (x1 * y2 - x2 * y1)); - - // make sure nothing funky's going on... - if (std::abs(a) < 0.01) - throw std::runtime_error("Attempted to find circumcircle for a triangle with vertices in a line."); - - // finish! - centre.x = Sx / a; - centre.y = Sy / a; - radius2 = (Sx*Sx + Sy*Sy)/(a*a) + b/a; - }; - - DTTriangle::DTTriangle() : - verts(3, 0), - centre(0.0, 0.0), - radius2(0.0) - {}; - - bool DTTriangle::PointInCircumCircle(Delauney::DTPoint &p) { - double vectX, vectY; - - vectX = p.x - centre.x; - vectY = p.y - centre.y; - - if (vectX*vectX + vectY*vectY < radius2) - return true; - return false; - }; - - - - /** runs a Delauney Triangulation routine on a set of 2D points extracted - * from an array of systems returns the list of triangles produced */ - std::list* DelauneyTriangulate(std::vector> &systems) { - - int n, c, theSize, num, num2; // loop counters, storage for retreived size of a vector, temp storage - std::list::iterator itCur, itEnd; - std::list::iterator itCur2, itEnd2; - // vector of x and y positions of stars - std::vector points; - // pointer to main list of triangles algorithm works with. - std::list *triangle_list; - // list of indices in vector of points extracted from removed triangles that need to be retriangulated - std::list pointNumList; - double vx, vy, mag; // vector components, magnitude - - // ensure a useful list of systems was passed... - if (systems.empty()) - throw std::runtime_error("Attempted to run Delauney Triangulation on empty array of systems"); - - // extract systems positions, and store in vector. Can't use actual systems data since - // systems have position limitations which would interfere with algorithm - for (std::shared_ptr system : systems) { - points.push_back(Delauney::DTPoint(system->X(), system->Y())); - } - - // add points for covering triangle. the point positions should be big enough to form a triangle - // that encloses all the systems of the galaxy (or at least one whose circumcircle covers all points) - points.push_back(Delauney::DTPoint(-1.0, -1.0)); - points.push_back(Delauney::DTPoint(2.0 * (GetUniverse().UniverseWidth() + 1.0), -1.0)); - points.push_back(Delauney::DTPoint(-1.0, 2.0 * (GetUniverse().UniverseWidth() + 1.0))); - - // initialize triangle_list. algorithm adds and removes triangles from this list, and the resulting - // list is returned (so should be deleted externally) - triangle_list = new std::list; - - // add last three points into the first triangle, the "covering triangle" - theSize = static_cast(points.size()); - triangle_list->push_front(Delauney::DTTriangle(theSize-1, theSize-2, theSize-3, points)); - - // loop through "real" points (from systems, not the last three added to make the covering triangle) - for (n = 0; n < theSize - 3; n++) { - pointNumList.clear(); - - // check each triangle in list, to see if the new point lies in its circumcircle. if so, delete - // the triangle and add its vertices to a list - itCur = triangle_list->begin(); - itEnd = triangle_list->end(); - while (itCur != itEnd) { - // get current triangle - Delauney::DTTriangle& tri = *itCur; - - // check if point to be added to triangulation is within the circumcircle for the current triangle - if (tri.PointInCircumCircle(points[n])) { - // if so, insert the triangle's vertices' indices into the list. add in sorted position - // based on angle of direction to current point n being inserted. don't add if doing - // so would duplicate an index already in the list - for (c = 0; c < 3; c++) { - num = (tri.Verts())[c]; // store "current point" - - // get sorting value to order points clockwise circumferentially around point n - // vector from point n to current point - vx = points[num].x - points[n].x; - vy = points[num].y - points[n].y; - mag = std::sqrt(vx*vx + vy*vy); - // normalize - vx /= mag; - vy /= mag; - // dot product with (0, 1) is vy, magnitude of cross product is vx - // this gives a range of "sortValue" from -2 to 2, around the circle - if (vx >= 0) mag = vy + 1; else mag = -vy - 1; - // sorting value in "mag" - - // iterate through list, finding insert spot and verifying uniqueness (or add if list is empty) - itCur2 = pointNumList.begin(); - itEnd2 = pointNumList.end(); - if (itCur2 == itEnd2) { - // list is empty - pointNumList.push_back(Delauney::SortValInt(num, mag)); - } else { - while (itCur2 != itEnd2) { - if (itCur2->num == num) - break; - if (itCur2->sortVal > mag) { - pointNumList.insert(itCur2, Delauney::SortValInt(num, mag)); - break; - } - ++itCur2; - } - if (itCur2 == itEnd2) { - // point wasn't added, so should go at end - pointNumList.push_back(Delauney::SortValInt(num, mag)); - } - } - } // end for c - - // remove current triangle from list of triangles - itCur = triangle_list->erase(itCur); - } else { - // point not in circumcircle for this triangle - // to go next triangle in list - ++itCur; - } - } // end while - - // go through list of points, making new triangles out of them - itCur2 = pointNumList.begin(); - itEnd2 = pointNumList.end(); - assert(itCur2 != itEnd2); - - // add triangle for last and first points and n - triangle_list->push_front(Delauney::DTTriangle(n, (pointNumList.front()).num, (pointNumList.back()).num, points)); - - num = itCur2->num; - ++itCur2; - while (itCur2 != itEnd2) { - num2 = num; - num = itCur2->num; - - triangle_list->push_front(Delauney::DTTriangle(n, num2, num, points)); - - ++itCur2; - } // end while - - } // end for - return triangle_list; - } // end function -} - -//////////////////////////////////////// -// FleetPlan // -//////////////////////////////////////// -const std::string& FleetPlan::Name() const { - if (m_name_in_stringtable) - return UserString(m_name); - else - return m_name; -} - -namespace { - /** Used by GenerateStarlanes. Determines if two systems are connected by - * maxLaneJumps or less edges on graph. */ - bool ConnectedWithin(int system1, int system2, int maxLaneJumps, std::vector>& laneSetArray) { - // list of indices of systems that are accessible from previously visited systems. - // when a new system is found to be accessible, it is added to the back of the - // list. the list is iterated through from front to back to find systems - // to examine - std::list accessibleSystemsList; - std::list::iterator sysListIter, sysListEnd; - - // map using star index number as the key, and also storing the number of starlane - // jumps away from system1 a given system is. this is used to determine if a - // system has already been added to the accessibleSystemsList without needing - // to iterate through the list. it also provides some indication of the - // current depth of the search, which allows the serch to terminate after searching - // to the depth of maxLaneJumps without finding system2 - // (considered using a vector for this, but felt that for large galaxies, the - // size of the vector and the time to intialize would be too much) - std::map accessibleSystemsMap; - - // system currently being investigated, destination of a starlane origination at curSys - int curSys, curLaneDest; - // "depth" level in tree of system currently being investigated - int curDepth; - - // iterators to set of starlanes, in graph, for the current system - std::set::iterator curSysLanesSetIter, curSysLanesSetEnd; - - // check for simple cases for quick termination - if (system1 == system2) return true; // system is always connected to itself - if (0 == maxLaneJumps) return false; // no system is connected to any other system by less than 1 jump - if (0 == (laneSetArray[system1]).size()) return false; // no lanes out of start system - if (0 == (laneSetArray[system2]).size()) return false; // no lanes into destination system - if (system1 >= static_cast(laneSetArray.size()) || system2 >= static_cast(laneSetArray.size())) return false; // out of range - if (system1 < 0 || system2 < 0) return false; // out of range - - // add starting system to list and set of accessible systems - accessibleSystemsList.push_back(system1); - accessibleSystemsMap.insert(std::pair(system1, 0)); - - // loop through visited systems - sysListIter = accessibleSystemsList.begin(); - sysListEnd = accessibleSystemsList.end(); - while (sysListIter != sysListEnd) { - curSys = *sysListIter; - - // check that iteration hasn't reached maxLaneJumps levels deep, which would - // mean that system2 isn't within maxLaneJumps starlane jumps of system1 - curDepth = (*accessibleSystemsMap.find(curSys)).second; - - if (curDepth >= maxLaneJumps) return false; - - // get set of starlanes for this system - curSysLanesSetIter = (laneSetArray[curSys]).begin(); - curSysLanesSetEnd = (laneSetArray[curSys]).end(); - - // add starlanes accessible from this system to list and set of accessible starlanes - // (and check for the goal starlane) - while (curSysLanesSetIter != curSysLanesSetEnd) { - curLaneDest = *curSysLanesSetIter; - - // check if curLaneDest has been added to the map of accessible systems - if (0 == accessibleSystemsMap.count(curLaneDest)) { - - // check for goal - if (curLaneDest == system2) return true; - - // add curLaneDest to accessible systems list and map - accessibleSystemsList.push_back(curLaneDest); - accessibleSystemsMap.insert(std::pair(curLaneDest, curDepth + 1)); - } - - ++curSysLanesSetIter; - } - - ++sysListIter; - } - return false; // default - } - - /** Removes lanes from passed graph that are angularly too close to - * each other. */ - void CullAngularlyTooCloseLanes(double maxLaneUVectDotProd, std::vector>& laneSetArray, - std::vector> &systems) - { - // start and end systems of a new lane being considered, and end points of lanes that already exist with that - // start at the start or destination of the new lane - int curSys, dest1, dest2; - - // geometry stuff... points componenets, vector componenets dot product & magnitudes of vectors - double startX, startY, vectX1, vectX2, vectY1, vectY2, dotProd, mag1, mag2; - // 2 component vector and vect + magnitude typedefs - - typedef std::pair VectTypeQQ; - typedef std::pair VectAndMagTypeQQ; - typedef std::pair MapInsertableTypeQQ; - - std::map laneVectsMap; // componenets of vectors of lanes of current system, indexed by destination system number - std::map::iterator laneVectsMapIter; - - VectTypeQQ tempVect; - VectAndMagTypeQQ tempVectAndMag; - - // iterators to go through sets of lanes in array - std::set::iterator laneSetIter1, laneSetIter2, laneSetEnd; - - std::set> lanesToRemoveSet; // start and end stars of lanes to be removed in final step... - std::set>::iterator lanesToRemoveIter, lanesToRemoveEnd; - std::pair lane1, lane2; - - int curNumLanes; - - int num_systems = systems.size(); - // make sure data is consistent - if (static_cast(laneSetArray.size()) != num_systems) { - ErrorLogger() << "CullAngularlyTooCloseLanes got different size vectors of lane sets and systems. Doing nothing."; - return; - } - - if (num_systems < 3) return; // nothing worth doing for less than three systems - - //DebugLogger() << "Culling Too Close Angularly Lanes"; - - // loop through systems - for (curSys = 0; curSys < num_systems; curSys++) { - // get position of current system (for use in calculated vectors) - startX = systems[curSys]->X(); - startY = systems[curSys]->Y(); - - // get number of starlanes current system has - curNumLanes = laneSetArray[curSys].size(); - - // can't have pairs of lanes with less than two lanes... - if (curNumLanes > 1) { - - // remove any old lane Vector Data - laneVectsMap.clear(); - - // get unit vectors for all lanes of this system - laneSetIter1 = laneSetArray[curSys].begin(); - laneSetEnd = laneSetArray[curSys].end(); - while (laneSetIter1 != laneSetEnd) { - // get destination for this lane - dest1 = *laneSetIter1; - // get vector to this lane destination - vectX1 = systems[dest1]->X() - startX; - vectY1 = systems[dest1]->Y() - startY; - // normalize - mag1 = std::sqrt(vectX1 * vectX1 + vectY1 * vectY1); - vectX1 /= mag1; - vectY1 /= mag1; - - // store lane in map of lane vectors - tempVect = VectTypeQQ(vectX1, vectY1); - tempVectAndMag = VectAndMagTypeQQ(tempVect, mag1); - laneVectsMap.insert( MapInsertableTypeQQ(dest1, tempVectAndMag) ); - - ++laneSetIter1; - } - - // iterate through lanes of curSys - laneSetIter1 = laneSetArray[curSys].begin(); - ++laneSetIter1; // start at second, since iterators are used in pairs, and starting both at the first wouldn't be a valid pair - while (laneSetIter1 != laneSetEnd) { - // get destination of current starlane - dest1 = *laneSetIter1; - - if (curSys < dest1) - lane1 = std::pair(curSys, dest1); - else - lane1 = std::pair(dest1, curSys); - - // check if this lane has already been added to the set of lanes to remove - if (0 == lanesToRemoveSet.count(lane1)) { - - // extract data on starlane vector... - laneVectsMapIter = laneVectsMap.find(dest1); - assert(laneVectsMapIter != laneVectsMap.end()); - tempVectAndMag = laneVectsMapIter->second; - tempVect = tempVectAndMag.first; - vectX1 = tempVect.first; - vectY1 = tempVect.second; - mag1 = tempVectAndMag.second; - - // iterate through other lanes of curSys, in order to get all possible pairs of lanes - laneSetIter2 = laneSetArray[curSys].begin(); - while (laneSetIter2 != laneSetIter1) { - dest2 = *laneSetIter2; - - if (curSys < dest2) - lane2 = std::pair(curSys, dest2); - else - lane2 = std::pair(dest2, curSys); - - // check if this lane has already been added to the set of lanes to remove - if (0 == lanesToRemoveSet.count(lane2)) { - - // extract data on starlane vector... - laneVectsMapIter = laneVectsMap.find(dest2); - assert(laneVectsMapIter != laneVectsMap.end()); - tempVectAndMag = laneVectsMapIter->second; - tempVect = tempVectAndMag.first; - vectX2 = tempVect.first; - vectY2 = tempVect.second; - mag2 = tempVectAndMag.second; - - // find dot product - dotProd = vectX1 * vectX2 + vectY1 * vectY2; - - // if dotProd is big enough, then lanes are too close angularly - // thus one needs to be removed. - if (dotProd > maxLaneUVectDotProd) { - - // preferentially remove the longer lane - if (mag1 > mag2) { - lanesToRemoveSet.insert(lane1); - break; // don't need to check any more lanes against lane1, since lane1 has been removed - } else { - lanesToRemoveSet.insert(lane2); - } - } - } - - ++laneSetIter2; - } - } - - ++laneSetIter1; - } - } - } - - // iterate through set of lanes to remove, and remove them in turn... - lanesToRemoveIter = lanesToRemoveSet.begin(); - lanesToRemoveEnd = lanesToRemoveSet.end(); - while (lanesToRemoveIter != lanesToRemoveEnd) { - lane1 = *lanesToRemoveIter; - - laneSetArray[lane1.first].erase(lane1.second); - laneSetArray[lane1.second].erase(lane1.first); - - // check that removing lane hasn't disconnected systems - if (!ConnectedWithin(lane1.first, lane1.second, num_systems, laneSetArray)) { - // they aren't connected... reconnect them - laneSetArray[lane1.first].insert(lane1.second); - laneSetArray[lane1.second].insert(lane1.first); - } - - ++lanesToRemoveIter; - } - } - - /** Removes lanes from passed graph that are angularly too close to - * each other. */ - void CullTooLongLanes(double maxLaneLength, std::vector>& laneSetArray, - std::vector> &systems) - { - // start and end systems of a new lane being considered, and end points of lanes that already exist with that start - // at the start or destination of the new lane - int curSys, dest; - - // geometry stuff... points components, vector componenets - double startX, startY, vectX, vectY; - - // iterators to go through sets of lanes in array - std::set::iterator laneSetIter, laneSetEnd; - - // map, indexed by lane length, of start and end stars of lanes to be removed - std::multimap, std::greater> lanesToRemoveMap; - std::multimap, std::greater>::iterator lanesToRemoveIter, lanesToRemoveEnd; - std::pair lane; - typedef std::pair> MapInsertableTypeQQ; - - int num_systems = systems.size(); - // make sure data is consistent - if (static_cast(laneSetArray.size()) != num_systems) { - return; - } - - if (num_systems < 2) return; // nothing worth doing for less than two systems (no lanes!) - - // get squared max lane lenth, so as to eliminate the need to take square roots of lane lenths... - double maxLaneLength2 = maxLaneLength*maxLaneLength; - - // loop through systems - for (curSys = 0; curSys < num_systems; curSys++) { - // get position of current system (for use in calculating vector) - startX = systems[curSys]->X(); - startY = systems[curSys]->Y(); - - // iterate through all lanes in system, checking lengths and marking to be removed if necessary - laneSetIter = laneSetArray[curSys].begin(); - laneSetEnd = laneSetArray[curSys].end(); - while (laneSetIter != laneSetEnd) { - // get destination for this lane - dest = *laneSetIter; - // convert start and end into ordered pair to represent lane - if (curSys < dest) - lane = std::pair(curSys, dest); - else - lane = std::pair(dest, curSys); - - // get vector to this lane destination - vectX = systems[dest]->X() - startX; - vectY = systems[dest]->Y() - startY; - - // compare magnitude of vector to max allowed - double laneLength2 = vectX*vectX + vectY*vectY; - if (laneLength2 > maxLaneLength2) { - // lane is too long! mark it to be removed - lanesToRemoveMap.insert( MapInsertableTypeQQ(laneLength2, lane) ); - } - - ++laneSetIter; - } - } - - // Iterate through set of lanes to remove, and remove them in turn. Since lanes were inserted in the map indexed by - // their length, iteration starting with begin starts with the longest lane first, then moves through the lanes as - // they get shorter, ensuring that the longest lanes are removed first. - lanesToRemoveIter = lanesToRemoveMap.begin(); - lanesToRemoveEnd = lanesToRemoveMap.end(); - while (lanesToRemoveIter != lanesToRemoveEnd) { - lane = lanesToRemoveIter->second; - - // ensure the lane still exists - if (laneSetArray[lane.first].count(lane.second) > 0 && - laneSetArray[lane.second].count(lane.first) > 0) { - - // remove lane - laneSetArray[lane.first].erase(lane.second); - laneSetArray[lane.second].erase(lane.first); - - // if removing lane has disconnected systems, reconnect them - if (!ConnectedWithin(lane.first, lane.second, num_systems, laneSetArray)) { - laneSetArray[lane.first].insert(lane.second); - laneSetArray[lane.second].insert(lane.first); - } - } - ++lanesToRemoveIter; - } - } -} - -void GenerateStarlanes(int max_jumps_between_systems, int max_starlane_length) { - int num_systems, s1, s2, s3; // numbers of systems, indices in vec_sys - int n; // loop counter - - std::vector triVerts; // indices of stars that form vertices of a triangle - - // array of set to store final, included starlanes for each star - std::vector> laneSetArray; - - // array of set to store possible starlanes for each star, as extracted form triangulation - std::vector> potential_lane_set_array; - - // iterators for traversing lists of starlanes - std::set::iterator laneSetIter, laneSetEnd, laneSetIter2, laneSetEnd2; - - // get systems - std::vector> sys_vec = Objects().FindObjects(); - num_systems = sys_vec.size(); // (actually = number of systems + 1) - - // pass systems to Delauney Triangulation routine, getting array of triangles back - std::list* triangle_list = Delauney::DelauneyTriangulate(sys_vec); - if (!triangle_list ||triangle_list->empty()) { - ErrorLogger() << "Got no list or blank list of triangles from Triangulation."; - return; - } - - Delauney::DTTriangle tri; - // initialize arrays... - potential_lane_set_array.resize(num_systems); - laneSetArray.resize(num_systems); - - // extract triangles from list, add edges to sets of potential starlanes for each star (in array) - while (!triangle_list->empty()) { - tri = triangle_list->front(); - - triVerts = tri.Verts(); - s1 = triVerts[0]; - s2 = triVerts[1]; - s3 = triVerts[2]; - - // add starlanes to list of potential starlanes for each star, making sure each pair involves - // only stars that actually exist. triangle generation uses three extra points which don't - // represent actual systems and which need to be weeded out here. - if ((s1 >= 0) && (s2 >= 0) && (s3 >= 0)) { - if ((s1 < num_systems) && (s2 < num_systems)) { - potential_lane_set_array[s1].insert(s2); - potential_lane_set_array[s2].insert(s1); - } - if ((s1 < num_systems) && (s3 < num_systems)) { - potential_lane_set_array[s1].insert(s3); - potential_lane_set_array[s3].insert(s1); - } - if ((s2 < num_systems) && (s3 < num_systems)) { - potential_lane_set_array[s2].insert(s3); - potential_lane_set_array[s3].insert(s2); - } - } - - triangle_list->pop_front(); - } - - // cleanup - delete triangle_list; - - //DebugLogger() << "Extracted Potential Starlanes from Triangulation"; - - CullTooLongLanes(max_starlane_length, potential_lane_set_array, sys_vec); - - CullAngularlyTooCloseLanes(0.98, potential_lane_set_array, sys_vec); - - //DebugLogger() << "Culled Agularly Too Close Lanes"; - - laneSetArray = potential_lane_set_array; - - // attempt removing lanes, but don't do so if it would make the systems - // the lane connects too far apart - for (n = 0; n < num_systems; ++n) { - laneSetIter = potential_lane_set_array[n].begin(); - - while (laneSetIter != potential_lane_set_array[n].end()) { - s1 = *laneSetIter; - - // try removing lane - laneSetArray[n].erase(s1); - laneSetArray[s1].erase(n); - - if (!ConnectedWithin(n, s1, max_jumps_between_systems, laneSetArray)) { - // lane removal was a bad idea. restore it - laneSetArray[n].insert(s1); - laneSetArray[s1].insert(n); - } - - ++laneSetIter; - } // end while - } - - // add the starlane to the stars - for (n = 0; n < num_systems; ++n) { - for (int system_idx : laneSetArray[n]) - sys_vec[n]->AddStarlane(sys_vec[system_idx]->ID()); // System::AddStarlane() expects a system ID - } - - DebugLogger() << "Initializing System Graph"; - GetUniverse().InitializeSystemGraph(); -} - -void SetActiveMetersToTargetMaxCurrentValues(ObjectMap& object_map) { - std::map meters; - meters[METER_POPULATION] = METER_TARGET_POPULATION; - meters[METER_INDUSTRY] = METER_TARGET_INDUSTRY; - meters[METER_RESEARCH] = METER_TARGET_RESEARCH; - meters[METER_TRADE] = METER_TARGET_TRADE; - meters[METER_CONSTRUCTION] = METER_TARGET_CONSTRUCTION; - meters[METER_HAPPINESS] = METER_TARGET_HAPPINESS; - meters[METER_FUEL] = METER_MAX_FUEL; - meters[METER_SHIELD] = METER_MAX_SHIELD; - meters[METER_STRUCTURE] = METER_MAX_STRUCTURE; - meters[METER_DEFENSE] = METER_MAX_DEFENSE; - meters[METER_TROOPS] = METER_MAX_TROOPS; - meters[METER_SUPPLY] = METER_MAX_SUPPLY; - - // check for each pair of meter types. if both exist, set active - // meter current value equal to target meter current value. - for (std::shared_ptr object : object_map) { - for (std::map::value_type& entry : meters) - if (Meter* meter = object->GetMeter(entry.first)) - if (Meter* targetmax_meter = object->GetMeter(entry.second)) - meter->SetCurrent(targetmax_meter->Current()); - } -} - -void SetNativePopulationValues(ObjectMap& object_map) { - for (std::shared_ptr object : object_map) { - Meter* meter = object->GetMeter(METER_POPULATION); - Meter* targetmax_meter = object->GetMeter(METER_TARGET_POPULATION); - // only applies to unowned planets - if (meter && targetmax_meter && object->Unowned()) { - double r = RandZeroToOne(); - double factor = (0.1 < r) ? r : 0.1; - meter->SetCurrent(targetmax_meter->Current() * factor); - } - } -} - -bool SetEmpireHomeworld(Empire* empire, int planet_id, std::string species_name) { - // get home planet and system, check if they exist - std::shared_ptr home_planet = GetPlanet(planet_id); - std::shared_ptr home_system; - if (home_planet) - home_system = GetSystem(home_planet->SystemID()); - if (!home_planet || !home_system) { - ErrorLogger() << "SetEmpireHomeworld: couldn't get homeworld or system for empire" << empire->EmpireID(); - return false; - } - - DebugLogger() << "SetEmpireHomeworld: setting system " << home_system->ID() - << " (planet " << home_planet->ID() << ") to be home system for empire " << empire->EmpireID(); - - // get species, check if it exists - Species* species = GetSpeciesManager().GetSpecies(species_name); - if (!species) { - ErrorLogger() << "SetEmpireHomeworld: couldn't get species \"" - << species_name << "\" to set with homeworld id " << home_planet->ID(); - return false; - } - - // set homeword's planet type to the preferred type for this species - const std::map& spte = species->PlanetEnvironments(); - if (!spte.empty()) { - // invert map from planet type to environments to map from - // environments to type, sorted by environment - std::map sept; - for (const std::map::value_type& entry : spte) - sept[entry.second] = entry.first; - // assuming enum values are ordered in increasing goodness... - PlanetType preferred_planet_type = sept.rbegin()->second; - - // both the current as well as the original type need to be set to the preferred type - home_planet->SetType(preferred_planet_type); - home_planet->SetOriginalType(preferred_planet_type); - // set planet size according to planet type - if (preferred_planet_type == PT_ASTEROIDS) - home_planet->SetSize(SZ_ASTEROIDS); - else if (preferred_planet_type == PT_GASGIANT) - home_planet->SetSize(SZ_GASGIANT); - else - home_planet->SetSize(SZ_MEDIUM); - } - - home_planet->Colonize(empire->EmpireID(), species_name, Meter::LARGE_VALUE); - species->AddHomeworld(home_planet->ID()); - empire->SetCapitalID(home_planet->ID()); - empire->AddExploredSystem(home_planet->SystemID()); - - return true; -} - -void InitEmpires(const std::map& player_setup_data) -{ - DebugLogger() << "Initializing " << player_setup_data.size() << " empires"; - - // copy empire colour table, so that individual colours can be removed after they're used - std::vector colors = EmpireColors(); - - // create empire objects and do some basic initilization for each player - for (const std::map::value_type& entry : player_setup_data) - { - int player_id = entry.first; - if (player_id == Networking::INVALID_PLAYER_ID) - ErrorLogger() << "InitEmpires player id (" << player_id << ") is invalid"; - - // use player ID for empire ID so that the calling code can get the - // correct empire for each player ID in player_setup_data - int empire_id = player_id; - std::string player_name = entry.second.m_player_name; - GG::Clr empire_colour = entry.second.m_empire_color; - - // validate or generate empire colour - // ensure no other empire gets auto-assigned this colour automatically - std::vector::iterator color_it = std::find(colors.begin(), colors.end(), empire_colour); - if (color_it != colors.end()) - colors.erase(color_it); - - // if no colour already set, do so automatically - if (empire_colour == GG::Clr(0, 0, 0, 0)) { - if (!colors.empty()) { - // take next colour from list - empire_colour = colors[0]; - colors.erase(colors.begin()); - } else { - // as a last resort, make up a colour - empire_colour = GG::FloatClr(static_cast(RandZeroToOne()), static_cast(RandZeroToOne()), - static_cast(RandZeroToOne()), 1.0f); - } - } - - // set generic default empire name - std::string empire_name = UserString("EMPIRE") + std::to_string(empire_id); - - DebugLogger() << "Universe::InitEmpires creating new empire" << " with ID: " << empire_id - << " for player: " << player_name << " (with player id: " << player_id << ")"; - - // create new Empire object through empire manager - Empires().CreateEmpire(empire_id, empire_name, player_name, empire_colour); - } - - Empires().ResetDiplomacy(); -} diff --git a/universe/UniverseGenerator.h b/universe/UniverseGenerator.h deleted file mode 100644 index d0db5ef4b46..00000000000 --- a/universe/UniverseGenerator.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef _UniverseGenerator_h_ -#define _UniverseGenerator_h_ - -#include "Condition.h" -#include "Universe.h" - -#include "../util/MultiplayerCommon.h" - - -struct PlayerSetupData; - -/** A combination of names of ShipDesign that can be put together to make a - * fleet of ships, and a name for such a fleet, loaded from - * default/scripting/starting_unlocks/fleets.inf - * ShipDesign names refer to designs listed in default/scripting/ship_designs. - * Useful for saving or specifying prearranged combinations of prearranged - * ShipDesigns to automatically put together, such as during universe creation.*/ -class FleetPlan { -public: - FleetPlan(const std::string& fleet_name, const std::vector& ship_design_names, - bool lookup_name_userstring = false) : - m_name(fleet_name), - m_ship_designs(ship_design_names), - m_name_in_stringtable(lookup_name_userstring) - {} - FleetPlan() : - m_name(""), - m_ship_designs(), - m_name_in_stringtable(false) - {} - virtual ~FleetPlan() {}; - const std::string& Name() const; - const std::vector& ShipDesigns() const { return m_ship_designs; } -protected: - std::string m_name; - std::vector m_ship_designs; - bool m_name_in_stringtable; -}; - -/** The combination of a FleetPlan and spawning instructions for start of game - * monsters. */ -class FO_COMMON_API MonsterFleetPlan : public FleetPlan { -public: - MonsterFleetPlan(const std::string& fleet_name, const std::vector& ship_design_names, - double spawn_rate = 1.0, int spawn_limit = 9999, - const Condition::ConditionBase* location = nullptr, - bool lookup_name_userstring = false) : - FleetPlan(fleet_name, ship_design_names, lookup_name_userstring), - m_spawn_rate(spawn_rate), - m_spawn_limit(spawn_limit), - m_location(location) - {} - MonsterFleetPlan() : - FleetPlan() - {} - virtual ~MonsterFleetPlan() - { delete m_location; } - double SpawnRate() const { return m_spawn_rate; } - int SpawnLimit() const { return m_spawn_limit; } - const Condition::ConditionBase* Location() const { return m_location; } -protected: - double m_spawn_rate = 1.0; - int m_spawn_limit = 9999; - const Condition::ConditionBase* m_location = nullptr; -}; - -/** Set active meter current values equal to target/max meter current - * values. Useful when creating new object after applying effects. */ -void SetActiveMetersToTargetMaxCurrentValues(ObjectMap& object_map); - -/** Set the population of unowned planets to a random fraction of - * their target values. */ -void SetNativePopulationValues(ObjectMap& object_map); - -/** Creates starlanes and adds them systems already generated. */ -void GenerateStarlanes(int max_jumps_between_systems, int max_starlane_length); - -/** Sets empire homeworld - * This includes setting ownership, capital, species, - * preferred environment (planet type) for the species */ -bool SetEmpireHomeworld(Empire* empire, int planet_id, std::string species_name); - -/** Creates Empires objects for each entry in \a player_setup_data with - * empire id equal to the specified player ids (so that the calling code - * can know which empire belongs to which player). */ -void InitEmpires(const std::map& player_setup_data); - -#endif // _UniverseGenerator_h_ diff --git a/universe/UniverseObject.cpp b/universe/UniverseObject.cpp index 316da918edd..6e28e34e02d 100644 --- a/universe/UniverseObject.cpp +++ b/universe/UniverseObject.cpp @@ -2,6 +2,7 @@ #include "../util/i18n.h" #include "../util/Logger.h" +#include "../util/AppInterface.h" #include "../Empire/EmpireManager.h" #include "Meter.h" #include "System.h" @@ -14,7 +15,6 @@ #include #include - // static(s) const int INVALID_OBJECT_ID = -1; const int TEMPORARY_OBJECT_ID = -2; @@ -24,14 +24,16 @@ const int UniverseObject::INVALID_OBJECT_AGE = -(1 << 30) - 1; // using b const int UniverseObject::SINCE_BEFORE_TIME_AGE = (1 << 30) + 1; UniverseObject::UniverseObject() : - StateChangedSignal(blocking_combiner>(GetUniverse().UniverseObjectSignalsInhibited())), + StateChangedSignal(blocking_combiner>( + GetUniverse().UniverseObjectSignalsInhibited())), m_x(INVALID_POSITION), m_y(INVALID_POSITION), m_created_on_turn(CurrentTurn() ) {} UniverseObject::UniverseObject(const std::string name, double x, double y) : - StateChangedSignal(blocking_combiner>(GetUniverse().UniverseObjectSignalsInhibited())), + StateChangedSignal(blocking_combiner>( + GetUniverse().UniverseObjectSignalsInhibited())), m_name(name), m_x(x), m_y(y), @@ -41,8 +43,8 @@ UniverseObject::UniverseObject(const std::string name, double x, double y) : UniverseObject::~UniverseObject() {} -void UniverseObject::Copy(std::shared_ptr copied_object, Visibility vis, - const std::set& visible_specials) +void UniverseObject::Copy(std::shared_ptr copied_object, + Visibility vis, const std::set& visible_specials) { if (copied_object.get() == this) return; @@ -51,20 +53,19 @@ void UniverseObject::Copy(std::shared_ptr copied_object, V return; } - std::map censored_meters = copied_object->CensoredMeters(vis); - for (const std::map::value_type& entry : copied_object->m_meters) - { + auto censored_meters = copied_object->CensoredMeters(vis); + for (const auto& entry : copied_object->m_meters) { MeterType type = entry.first; // get existing meter in this object, or create a default one - std::map::iterator m_meter_it = m_meters.find(type); + auto m_meter_it = m_meters.find(type); bool meter_already_known = (m_meter_it != m_meters.end()); if (!meter_already_known) m_meters[type]; // default initialize to (0, 0). Alternative: = Meter(Meter::INVALID_VALUE, Meter::INVALID_VALUE);*/ Meter& this_meter = m_meters[type]; // if there is an update to meter from censored meters, update this object's copy - std::map::const_iterator censored_it = censored_meters.find(type); + auto censored_it = censored_meters.find(type); if (censored_it != censored_meters.end()) { const Meter& copied_object_meter = censored_it->second; @@ -72,7 +73,7 @@ void UniverseObject::Copy(std::shared_ptr copied_object, V // have no previous info, so just use whatever is given this_meter = copied_object_meter; - } else if (meter_already_known) { + } else { // don't want to override legit meter history with sentinel values used for insufficiently visible objects if (copied_object_meter.Initial() != Meter::LARGE_VALUE || copied_object_meter.Current() != Meter::LARGE_VALUE) @@ -91,9 +92,9 @@ void UniverseObject::Copy(std::shared_ptr copied_object, V this->m_y = copied_object->m_y; this->m_specials.clear(); - for (const std::map>::value_type& entry_special : copied_object->m_specials) { - if (visible_specials.find(entry_special.first) != visible_specials.end()) - { this->m_specials[entry_special.first] = entry_special.second; } + for (const auto& entry_special : copied_object->m_specials) { + if (visible_specials.count(entry_special.first)) + this->m_specials[entry_special.first] = entry_special.second; } if (vis >= VIS_PARTIAL_VISIBILITY) { @@ -143,17 +144,17 @@ const std::map>& UniverseObject::Specials() c { return m_specials; } bool UniverseObject::HasSpecial(const std::string& name) const -{ return m_specials.find(name) != m_specials.end(); } +{ return m_specials.count(name); } int UniverseObject::SpecialAddedOnTurn(const std::string& name) const { - std::map>::const_iterator it = m_specials.find(name); + auto it = m_specials.find(name); if (it == m_specials.end()) return INVALID_GAME_TURN; return it->second.first; } float UniverseObject::SpecialCapacity(const std::string& name) const { - std::map>::const_iterator it = m_specials.find(name); + auto it = m_specials.find(name); if (it == m_specials.end()) return 0.0f; return it->second.second; @@ -168,12 +169,12 @@ bool UniverseObject::HasTag(const std::string& name) const UniverseObjectType UniverseObject::ObjectType() const { return INVALID_UNIVERSE_OBJECT_TYPE; } -std::string UniverseObject::Dump() const { - std::shared_ptr system = GetSystem(this->SystemID()); +std::string UniverseObject::Dump(unsigned short ntabs) const { + auto system = Objects().get(this->SystemID()); std::stringstream os; - os << boost::lexical_cast(this->ObjectType()) << " " + os << this->ObjectType() << " " << this->ID() << ": " << this->Name(); if (system) { @@ -185,19 +186,19 @@ std::string UniverseObject::Dump() const { } else { os << " at: (" << this->X() << ", " << this->Y() << ")"; int near_id = GetPathfinder()->NearestSystemTo(this->X(), this->Y()); - std::shared_ptr system = GetSystem(near_id); - if (system) { - const std::string& sys_name = system->Name(); + auto near_system = Objects().get(near_id); + if (near_system) { + const std::string& sys_name = near_system->Name(); if (sys_name.empty()) - os << " nearest (System " << system->ID() << ")"; + os << " nearest (System " << near_system->ID() << ")"; else - os << " nearest " << system->Name(); + os << " nearest " << near_system->Name(); } } if (Unowned()) { os << " owner: (Unowned) "; } else { - std::string empire_name = Empires().GetEmpireName(m_owner_empire_id); + const std::string& empire_name = Empires().GetEmpireName(m_owner_empire_id); if (!empire_name.empty()) os << " owner: " << empire_name; else @@ -205,10 +206,10 @@ std::string UniverseObject::Dump() const { } os << " created on turn: " << m_created_on_turn << " specials: "; - for (const std::map>::value_type& entry : m_specials) + for (const auto& entry : m_specials) os << "(" << entry.first << ", " << entry.second.first << ", " << entry.second.second << ") "; os << " Meters: "; - for (const std::map::value_type& entry : m_meters) + for (const auto& entry : m_meters) os << entry.first << ": " << entry.second.Dump() << " "; return os.str(); @@ -241,14 +242,14 @@ bool UniverseObject::ContainedBy(int object_id) const { return false; } const Meter* UniverseObject::GetMeter(MeterType type) const { - std::map::const_iterator it = m_meters.find(type); + auto it = m_meters.find(type); if (it != m_meters.end()) return &(it->second); return nullptr; } float UniverseObject::CurrentMeterValue(MeterType type) const { - std::map::const_iterator it = m_meters.find(type); + auto it = m_meters.find(type); if (it == m_meters.end()) throw std::invalid_argument("UniverseObject::CurrentMeterValue was passed a MeterType that this UniverseObject does not have: " + boost::lexical_cast(type)); @@ -256,16 +257,13 @@ float UniverseObject::CurrentMeterValue(MeterType type) const { } float UniverseObject::InitialMeterValue(MeterType type) const { - std::map::const_iterator it = m_meters.find(type); + auto it = m_meters.find(type); if (it == m_meters.end()) throw std::invalid_argument("UniverseObject::InitialMeterValue was passed a MeterType that this UniverseObject does not have: " + boost::lexical_cast(type)); return it->second.Initial(); } -float UniverseObject::NextTurnCurrentMeterValue(MeterType type) const -{ return UniverseObject::CurrentMeterValue(type); } - void UniverseObject::AddMeter(MeterType meter_type) { if (INVALID_METER_TYPE == meter_type) ErrorLogger() << "UniverseObject::AddMeter asked to add invalid meter type!"; @@ -279,6 +277,9 @@ bool UniverseObject::Unowned() const bool UniverseObject::OwnedBy(int empire) const { return empire != ALL_EMPIRES && empire == Owner(); } +bool UniverseObject::HostileToEmpire(int empire_id) const +{ return false; } + Visibility UniverseObject::GetVisibility(int empire_id) const { return GetUniverse().GetObjectVisibilityByEmpire(this->ID(), empire_id); } @@ -302,7 +303,7 @@ void UniverseObject::Move(double x, double y) { MoveTo(m_x + x, m_y + y); } void UniverseObject::MoveTo(int object_id) -{ MoveTo(GetUniverseObject(object_id)); } +{ MoveTo(Objects().get(object_id)); } void UniverseObject::MoveTo(std::shared_ptr object) { if (!object) { @@ -313,13 +314,6 @@ void UniverseObject::MoveTo(std::shared_ptr object) { } void UniverseObject::MoveTo(double x, double y) { - if ((x < 0.0 || GetUniverse().UniverseWidth() < x || y < 0.0 || GetUniverse().UniverseWidth() < y) - && (x != INVALID_POSITION || y != INVALID_POSITION)) - { - DebugLogger() << "UniverseObject::MoveTo : Placing object \"" << m_name << "\" (" - << m_id << ") outside the map area at (" << x << ", " << y << ")."; - } - if (m_x == x && m_y == y) return; @@ -330,16 +324,15 @@ void UniverseObject::MoveTo(double x, double y) { } Meter* UniverseObject::GetMeter(MeterType type) { - std::map::iterator it = m_meters.find(type); + auto it = m_meters.find(type); if (it != m_meters.end()) return &(it->second); return nullptr; } void UniverseObject::BackPropagateMeters() { - for (MeterType i = MeterType(0); i != NUM_METER_TYPES; i = MeterType(i + 1)) - if (Meter* meter = this->GetMeter(i)) - meter->BackPropagate(); + for (auto& m : m_meters) + m.second.BackPropagate(); } void UniverseObject::SetOwner(int id) { @@ -364,7 +357,7 @@ void UniverseObject::AddSpecial(const std::string& name, float capacity) { m_specials[name] = std::make_pair(CurrentTurn(), capacity); } void UniverseObject::SetSpecialCapacity(const std::string& name, float capacity) { - if (m_specials.find(name) != m_specials.end()) + if (m_specials.count(name)) m_specials[name].second = capacity; else AddSpecial(name, capacity); @@ -377,30 +370,31 @@ std::map UniverseObject::CensoredMeters(Visibility vis) const std::map retval; if (vis >= VIS_PARTIAL_VISIBILITY) { retval = m_meters; - } else if (vis == VIS_BASIC_VISIBILITY && m_meters.find(METER_STEALTH) != m_meters.end()) { - retval[METER_STEALTH] = Meter(Meter::LARGE_VALUE, Meter::LARGE_VALUE); - } + } else if (vis == VIS_BASIC_VISIBILITY && m_meters.count(METER_STEALTH)) + retval.emplace(std::make_pair(METER_STEALTH, Meter{Meter::LARGE_VALUE, Meter::LARGE_VALUE})); return retval; } void UniverseObject::ResetTargetMaxUnpairedMeters() { - if (Meter* meter = GetMeter(METER_STEALTH)) - meter->ResetCurrent(); + auto it = m_meters.find(METER_STEALTH); + if (it != m_meters.end()) + it->second.ResetCurrent(); } void UniverseObject::ResetPairedActiveMeters() { // iterate over paired active meters (those that have an associated max or // target meter. if another paired meter type is added to Enums.h, it // should be added here as well. - for (MeterType meter_type = MeterType(METER_POPULATION); - meter_type <= MeterType(METER_TROOPS); - meter_type = MeterType(meter_type + 1)) - { - if (Meter* meter = GetMeter(meter_type)) - meter->SetCurrent(meter->Initial()); + for (auto& m : m_meters) { + if (m.first > METER_TROOPS) + break; + if (m.first >= METER_POPULATION) + m.second.SetCurrent(m.second.Initial()); } } -void UniverseObject::ClampMeters() -{ GetMeter(METER_STEALTH)->ClampCurrentToRange(); } - +void UniverseObject::ClampMeters() { + auto it = m_meters.find(METER_STEALTH); + if (it != m_meters.end()) + it->second.ClampCurrentToRange(); +} diff --git a/universe/UniverseObject.h b/universe/UniverseObject.h index 0bbb497ad64..0eabddd5363 100644 --- a/universe/UniverseObject.h +++ b/universe/UniverseObject.h @@ -63,6 +63,8 @@ class FO_COMMON_API UniverseObject : virtual public std::enable_shared_from_this virtual int Owner() const; ///< returns the ID of the empire that owns this object, or ALL_EMPIRES if there is no owner bool Unowned() const; ///< returns true iff there are no owners of this object bool OwnedBy(int empire) const; ///< returns true iff the empire with id \a empire owns this object; unowned objects always return false; + /** Object owner is at war with empire @p empire_id */ + virtual bool HostileToEmpire(int empire_id) const; virtual int SystemID() const; ///< returns the ID number of the system in which this object can be found, or INVALID_OBJECT_ID if the object is not within any system @@ -79,8 +81,9 @@ class FO_COMMON_API UniverseObject : virtual public std::enable_shared_from_this virtual UniverseObjectType ObjectType() const; - /** Outputs textual description of object to logger. */ - virtual std::string Dump() const; + /** Return human readable string description of object offset \p ntabs from + margin. */ + virtual std::string Dump(unsigned short ntabs = 0) const; /** Returns id of the object that directly contains this object, if any, or INVALID_OBJECT_ID if this object is not contained by any other. */ @@ -102,11 +105,7 @@ class FO_COMMON_API UniverseObject : virtual public std::enable_shared_from_this const std::map& Meters() const { return m_meters; } ///< returns this UniverseObject's meters const Meter* GetMeter(MeterType type) const; ///< returns the requested Meter, or 0 if no such Meter of that type is found in this object float CurrentMeterValue(MeterType type) const; ///< returns current value of the specified meter \a type - float InitialMeterValue(MeterType type) const; ///< returns this turn's initial value for the speicified meter \a type - - /** Returns an estimate of the next turn's current value of the specified - meter \a type. */ - virtual float NextTurnCurrentMeterValue(MeterType type) const; + float InitialMeterValue(MeterType type) const; ///< returns this turn's initial value for the specified meter \a type Visibility GetVisibility(int empire_id) const; ///< returns the visibility status of this universe object relative to the input empire. @@ -126,70 +125,70 @@ class FO_COMMON_API UniverseObject : virtual public std::enable_shared_from_this /** copies data from \a copied_object to this object, limited to only copy * data about the copied object that is known to the empire with id * \a empire_id (or all data if empire_id is ALL_EMPIRES) */ - virtual void Copy(std::shared_ptr copied_object, int empire_id) = 0; + virtual void Copy(std::shared_ptr copied_object, int empire_id) = 0; - void SetID(int id); ///< sets the ID number of the object to \a id - void Rename(const std::string& name); ///< renames this object to \a name + void SetID(int id); ///< sets the ID number of the object to \a id + void Rename(const std::string& name); ///< renames this object to \a name /** moves this object by relative displacements x and y. */ - void Move(double x, double y); + void Move(double x, double y); /** calls MoveTo(std::shared_ptr) with the object pointed to by \a object_id. */ - void MoveTo(int object_id); + void MoveTo(int object_id); /** moves this object to exact map coordinates of specified \a object. */ - void MoveTo(std::shared_ptr object); + void MoveTo(std::shared_ptr object); /** moves this object to map coordinates (x, y). */ - void MoveTo(double x, double y); + void MoveTo(double x, double y); std::map& - Meters() { return m_meters; } ///< returns this UniverseObject's meters - Meter* GetMeter(MeterType type); ///< returns the requested Meter, or 0 if no such Meter of that type is found in this object + Meters() { return m_meters; } ///< returns this UniverseObject's meters + Meter* GetMeter(MeterType type); ///< returns the requested Meter, or 0 if no such Meter of that type is found in this object /** Sets all this UniverseObject's meters' initial values equal to their current values. */ - virtual void BackPropagateMeters(); + virtual void BackPropagateMeters(); /** Sets the empire that owns this object. */ - virtual void SetOwner(int id); + virtual void SetOwner(int id); - void SetSystem(int sys); ///< assigns this object to a System. does not actually move object in universe - virtual void AddSpecial(const std::string& name, float capacity = 0.0f); ///< adds the Special \a name to this object, if it is not already present - virtual void RemoveSpecial(const std::string& name); ///< removes the Special \a name from this object, if it is already present - void SetSpecialCapacity(const std::string& name, float capacity); + void SetSystem(int sys); ///< assigns this object to a System. does not actually move object in universe + virtual void AddSpecial(const std::string& name, float capacity = 0.0f); ///< adds the Special \a name to this object, if it is not already present + virtual void RemoveSpecial(const std::string& name); ///< removes the Special \a name from this object, if it is already present + void SetSpecialCapacity(const std::string& name, float capacity); /** Performs the movement that this object is responsible for this object's * actions during the movement phase of a turn. */ - virtual void MovementPhase() + virtual void MovementPhase() {}; /** Sets current value of max, target and unpaired meters in in this * UniverseObject to Meter::DEFAULT_VALUE. This should be done before any * Effects that alter these meter(s) act on the object. */ - virtual void ResetTargetMaxUnpairedMeters(); + virtual void ResetTargetMaxUnpairedMeters(); /** Sets current value of active paired meters (the non-max non-target * meters that have a max or target meter associated with them) back to * the initial value the meter had at the start of this turn. */ - virtual void ResetPairedActiveMeters(); + virtual void ResetPairedActiveMeters(); /** calls Clamp(min, max) on meters each meter in this UniverseObject, to * ensure that meter current values aren't outside the valid range for * each meter. */ - virtual void ClampMeters(); + virtual void ClampMeters(); /** performs the movement that this object is responsible for this object's actions during the pop growth/production/research phase of a turn. */ - virtual void PopGrowthProductionResearchPhase() + virtual void PopGrowthProductionResearchPhase() {}; //@} - static const double INVALID_POSITION; ///< the position in x and y at which default-constructed objects are placed - static const int INVALID_OBJECT_AGE; ///< the age returned by UniverseObject::AgeInTurns() if the current turn is INVALID_GAME_TURN, or if the turn on which an object was created is INVALID_GAME_TURN - static const int SINCE_BEFORE_TIME_AGE; ///< the age returned by UniverseObject::AgeInTurns() if an object was created on turn BEFORE_FIRST_TURN + static const double INVALID_POSITION; ///< the position in x and y at which default-constructed objects are placed + static const int INVALID_OBJECT_AGE; ///< the age returned by UniverseObject::AgeInTurns() if the current turn is INVALID_GAME_TURN, or if the turn on which an object was created is INVALID_GAME_TURN + static const int SINCE_BEFORE_TIME_AGE; ///< the age returned by UniverseObject::AgeInTurns() if an object was created on turn BEFORE_FIRST_TURN protected: friend class Universe; @@ -199,7 +198,7 @@ class FO_COMMON_API UniverseObject : virtual public std::enable_shared_from_this UniverseObject(); UniverseObject(const std::string name, double x, double y); - template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); + template friend void boost::python::detail::value_destroyer::execute(T const volatile* p); public: virtual ~UniverseObject(); @@ -235,8 +234,13 @@ class FO_COMMON_API UniverseObject : virtual public std::enable_shared_from_this int m_created_on_turn = INVALID_GAME_TURN; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; +/** A function that returns the correct amount of spacing for an indentation of + * \p ntabs during a dump. */ +inline std::string DumpIndent(unsigned short ntabs = 1) +{ return std::string(ntabs * 4 /* conversion to size_t is safe */, ' '); } + #endif // _UniverseObject_h_ diff --git a/universe/UnlockableItem.cpp b/universe/UnlockableItem.cpp new file mode 100644 index 00000000000..9ab26b9e8a5 --- /dev/null +++ b/universe/UnlockableItem.cpp @@ -0,0 +1,41 @@ +#include "UnlockableItem.h" + +#include "../util/Logger.h" +#include "../util/CheckSums.h" + + +UnlockableItem::UnlockableItem() : + type(INVALID_UNLOCKABLE_ITEM_TYPE), + name() +{} + +std::string UnlockableItem::Dump(unsigned short ntabs) const { + std::string retval = "Item type = "; + switch (type) { + case UIT_BUILDING: retval += "Building"; break; + case UIT_SHIP_PART: retval += "ShipPart"; break; + case UIT_SHIP_HULL: retval += "ShipHull"; break; + case UIT_SHIP_DESIGN: retval += "ShipDesign"; break; + case UIT_TECH: retval += "Tech" ; break; + default: retval += "?" ; break; + } + retval += " name = \"" + name + "\"\n"; + return retval; +} + +bool operator==(const UnlockableItem& lhs, const UnlockableItem& rhs) { + return lhs.type == rhs.type && + lhs.name == rhs.name; +} + +bool operator!=(const UnlockableItem& lhs, const UnlockableItem& rhs) +{ return !(lhs == rhs); } + + +namespace CheckSums { + void CheckSumCombine(unsigned int& sum, const UnlockableItem& item) { + TraceLogger() << "CheckSumCombine(Slot): " << typeid(item).name(); + CheckSumCombine(sum, item.type); + CheckSumCombine(sum, item.name); + } +} diff --git a/universe/UnlockableItem.h b/universe/UnlockableItem.h new file mode 100644 index 00000000000..777375e0ec8 --- /dev/null +++ b/universe/UnlockableItem.h @@ -0,0 +1,61 @@ +#ifndef _UnlockableItem_h_ +#define _UnlockableItem_h_ + + +#include +#include "../util/Export.h" + + +/** types of items that can be unlocked for empires */ +GG_ENUM(UnlockableItemType, + INVALID_UNLOCKABLE_ITEM_TYPE = -1, + UIT_BUILDING, ///< a kind of Building + UIT_SHIP_PART, ///< a kind of ship part (which are placed into hulls to make designs) + UIT_SHIP_HULL, ///< a ship hull (into which parts are placed) + UIT_SHIP_DESIGN, ///< a complete ship design + UIT_TECH, ///< a technology + NUM_UNLOCKABLE_ITEM_TYPES ///< keep last, the number of types of unlockable item +) + + +//! Specifies single item of game content that may be unlocked for an empire. +//! The @a type field stores the type of item that is being unlocked, such as +//! building or ship component, and the @a name field contains the name of the +//! actual item (e.g. (UIT_BUILDING, "Superfarm") or +//! (UIT_SHIP_PART, "Death Ray")). +struct FO_COMMON_API UnlockableItem { + UnlockableItem(); + UnlockableItem(UnlockableItemType type_, const std::string& name_) : + type(type_), + name(name_) + {} + + //! Returns a data file format representation of this object + auto Dump(unsigned short ntabs = 0) const -> std::string; + + //! Returns a number, calculated from the contained data, which should be + //! different for different contained data, and must be the same for + //! the same contained data, and must be the same on different platforms + //! and executions of the program and the function. Useful to verify that + //! the parsed content is consistent without sending it all between + //! clients and server. + auto GetCheckSum() const -> unsigned int; + + //! The kind of item this is + UnlockableItemType type; + + //! the exact item this is + std::string name; +}; + + +FO_COMMON_API bool operator==(const UnlockableItem& lhs, const UnlockableItem& rhs); + +bool operator!=(const UnlockableItem& lhs, const UnlockableItem& rhs); + + +namespace CheckSums { + FO_COMMON_API void CheckSumCombine(unsigned int& sum, const UnlockableItem& item); +} + +#endif // _UnlockableItem_h_ diff --git a/universe/ValueRef.h b/universe/ValueRef.h index 9e1069d80c0..0ecfffca23c 100644 --- a/universe/ValueRef.h +++ b/universe/ValueRef.h @@ -1,113 +1,24 @@ #ifndef _ValueRef_h_ #define _ValueRef_h_ -#include "Condition.h" +#include "ScriptingContext.h" #include "../util/Export.h" -#include "../util/i18n.h" -#include "../util/Random.h" -#include "../util/CheckSums.h" -#include -#include -#include -#include - -#include -#include -#include - -namespace CheckSums { - template - void CheckSumCombine(unsigned int& sum, const typename ValueRef::ValueRefBase& c) - { - TraceLogger() << "CheckSumCombine(ValueRef::ValueRefBase): " << typeid(c).name(); - sum += c.GetCheckSum(); - sum %= CHECKSUM_MODULUS; - } -} - -class UniverseObject; - -struct ScriptingContext { - /** Empty context. Useful for evaluating ValueRef::Constant that don't - * depend on their context. */ - ScriptingContext() - {} - - /** Context with only a source object. Useful for evaluating effectsgroup - * scope and activation conditions that have no external candidates or - * effect target to propagate. */ - explicit ScriptingContext(std::shared_ptr source_) : - source(source_) - {} - - ScriptingContext(std::shared_ptr source_, std::shared_ptr target_) : - source(source_), - effect_target(target_) - {} - - ScriptingContext(std::shared_ptr source_, std::shared_ptr target_, - const boost::any& current_value_) : - source(source_), - effect_target(target_), - current_value(current_value_) - {} - - /** For evaluating ValueRef in an Effect::Execute function. Keeps input - * context, but specifies the current value. */ - ScriptingContext(const ScriptingContext& parent_context, - const boost::any& current_value_) : - source(parent_context.source), - effect_target(parent_context.effect_target), - condition_root_candidate(parent_context.condition_root_candidate), - condition_local_candidate(parent_context.condition_local_candidate), - current_value(current_value_) - {} - - /** For recusrive evaluation of Conditions. Keeps source and effect_target - * from input context, but sets local candidate with input object, and if - * there is no root candidate in the parent context, then the input object - * becomes the root candidate. */ - ScriptingContext(const ScriptingContext& parent_context, - std::shared_ptr condition_local_candidate) : - source( parent_context.source), - effect_target( parent_context.effect_target), - condition_root_candidate( parent_context.condition_root_candidate ? - parent_context.condition_root_candidate : - condition_local_candidate), // if parent context doesn't already have a root candidate, the new local candidate is the root - condition_local_candidate( condition_local_candidate), // new local candidate - current_value( parent_context.current_value) - {} - - ScriptingContext(std::shared_ptr source_, std::shared_ptr target_, - const boost::any& current_value_, - std::shared_ptr condition_root_candidate_, - std::shared_ptr condition_local_candidate_) : - source(source_), - condition_root_candidate(condition_root_candidate_), - condition_local_candidate(condition_local_candidate_), - current_value(current_value_) - {} - - std::shared_ptr source; - std::shared_ptr effect_target; - std::shared_ptr condition_root_candidate; - std::shared_ptr condition_local_candidate; - const boost::any current_value; -}; +#include namespace ValueRef { + /** The base class for all ValueRef classes. This class provides the public * interface for a ValueRef expression tree. */ -template -struct FO_COMMON_API ValueRefBase +template +struct FO_COMMON_API ValueRef { - virtual ~ValueRefBase() + virtual ~ValueRef() {} - virtual bool operator==(const ValueRefBase& rhs) const; + virtual bool operator==(const ValueRef& rhs) const; - bool operator!=(const ValueRefBase& rhs) const + bool operator!=(const ValueRef& rhs) const { return !(*this == rhs); } /** Evaluates the expression tree with an empty context. Useful for @@ -142,7 +53,7 @@ struct FO_COMMON_API ValueRefBase virtual std::string Description() const = 0; /** Returns a text description of this type of special. */ - virtual std::string Dump() const = 0; + virtual std::string Dump(unsigned short ntabs = 0) const = 0; virtual void SetTopLevelContent(const std::string& content_name) {} @@ -152,2211 +63,26 @@ struct FO_COMMON_API ValueRefBase private: friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** the constant value leaf ValueRef class. */ -template -struct FO_COMMON_API Constant : public ValueRefBase -{ - explicit Constant(T value); - - bool operator==(const ValueRefBase& rhs) const override; - - T Eval(const ScriptingContext& context) const override; - - bool RootCandidateInvariant() const override - { return true; } - - bool LocalCandidateInvariant() const override - { return true; } - - bool TargetInvariant() const override - { return true; } - - bool SourceInvariant() const override - { return true; } - - bool ConstantExpr() const override - { return true; } - - std::string Description() const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - T Value() const; - - unsigned int GetCheckSum() const override; - -private: - T m_value; - std::string m_top_level_content; // in the special case that T is std::string and m_value is "CurrentContent", return this instead - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** The variable value ValueRef class. The value returned by this node is - * taken from the gamestate, most often from the Source or Target objects. */ -template -struct FO_COMMON_API Variable : public ValueRefBase -{ - explicit Variable(ReferenceType ref_type, const std::string& property_name = ""); - Variable(ReferenceType ref_type, const std::vector& property_name); - - bool operator==(const ValueRefBase& rhs) const override; - - T Eval(const ScriptingContext& context) const override; - - bool RootCandidateInvariant() const override; - - bool LocalCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description() const override; - - std::string Dump() const override; - - ReferenceType GetReferenceType() const; - - const std::vector& PropertyName() const; - - unsigned int GetCheckSum() const override; - -protected: - mutable ReferenceType m_ref_type; - std::vector m_property_name; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** The variable statistic class. The value returned by this node is - * computed from the general gamestate; the value of the indicated - * \a property_name is computed for each object that matches - * \a sampling_condition and the statistic indicated by \a stat_type is - * calculated from them and returned. */ -template -struct FO_COMMON_API Statistic : public Variable -{ - Statistic(ValueRefBase* value_ref, - StatisticType stat_type, - Condition::ConditionBase* sampling_condition); - - ~Statistic(); - - bool operator==(const ValueRefBase& rhs) const override; - - T Eval(const ScriptingContext& context) const override; - - bool RootCandidateInvariant() const override; - - bool LocalCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description() const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - StatisticType GetStatisticType() const - { return m_stat_type; } - - const Condition::ConditionBase* GetSamplingCondition() const - { return m_sampling_condition; } - - const ValueRefBase* GetValueRef() const - { return m_value_ref; } - - unsigned int GetCheckSum() const override; - -protected: - /** Gets the set of objects in the Universe that match the sampling condition. */ - void GetConditionMatches(const ScriptingContext& context, - Condition::ObjectSet& condition_targets, - Condition::ConditionBase* condition) const; - - /** Evaluates the property for the specified objects. */ - void GetObjectPropertyValues(const ScriptingContext& context, - const Condition::ObjectSet& objects, - std::map, T>& object_property_values) const; - - /** Computes the statistic from the specified set of property values. */ - T ReduceData(const std::map, T>& object_property_values) const; - -private: - StatisticType m_stat_type; - Condition::ConditionBase* m_sampling_condition; - ValueRefBase* m_value_ref; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** The complex variable ValueRef class. The value returned by this node - * is taken from the gamestate. */ -template -struct FO_COMMON_API ComplexVariable : public Variable -{ - explicit ComplexVariable(const std::string& variable_name, - ValueRefBase* int_ref1 = nullptr, - ValueRefBase* int_ref2 = nullptr, - ValueRefBase* int_ref3 = nullptr, - ValueRefBase* string_ref1 = nullptr, - ValueRefBase* string_ref2 = nullptr); - - ~ComplexVariable(); - - bool operator==(const ValueRefBase& rhs) const override; - - T Eval(const ScriptingContext& context) const override; - - bool RootCandidateInvariant() const override; - - bool LocalCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description() const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - const ValueRefBase* IntRef1() const; - - const ValueRefBase* IntRef2() const; - - const ValueRefBase* IntRef3() const; - - const ValueRefBase* StringRef1() const; - - const ValueRefBase* StringRef2() const; - - unsigned int GetCheckSum() const override; - -protected: - ValueRefBase* m_int_ref1; - ValueRefBase* m_int_ref2; - ValueRefBase* m_int_ref3; - ValueRefBase*m_string_ref1; - ValueRefBase*m_string_ref2; - -private: - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** The variable static_cast class. The value returned by this node is taken - * from the ctor \a value_ref parameter's FromType value, static_cast to - * ToType. */ -template -struct FO_COMMON_API StaticCast : public Variable -{ - StaticCast(Variable* value_ref); - - StaticCast(ValueRefBase* value_ref); - - ~StaticCast(); - - bool operator==(const ValueRefBase& rhs) const override; - - ToType Eval(const ScriptingContext& context) const override; - - bool RootCandidateInvariant() const override; - - bool LocalCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description() const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - const ValueRefBase* GetValueRef() const - { return m_value_ref; } - - unsigned int GetCheckSum() const override; - -private: - ValueRefBase* m_value_ref; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** The variable lexical_cast to string class. The value returned by this node - * is taken from the ctor \a value_ref parameter's FromType value, - * lexical_cast to std::string */ -template -struct FO_COMMON_API StringCast : public Variable -{ - StringCast(Variable* value_ref); - StringCast(ValueRefBase* value_ref); - ~StringCast(); - - bool operator==(const ValueRefBase& rhs) const override; - - std::string Eval(const ScriptingContext& context) const override; - - bool RootCandidateInvariant() const override; - - bool LocalCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description() const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - const ValueRefBase* GetValueRef() const - { return m_value_ref; } - - unsigned int GetCheckSum() const override; - -private: - ValueRefBase* m_value_ref; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Looks up a string ValueRef or vector of string ValueRefs, and returns - * and returns the UserString equivalent(s). */ -template -struct FO_COMMON_API UserStringLookup : public Variable { - explicit UserStringLookup(Variable* value_ref); - explicit UserStringLookup(ValueRefBase* value_ref); - ~UserStringLookup(); - - bool operator==(const ValueRefBase& rhs) const override; - - std::string Eval(const ScriptingContext& context) const override; - - bool RootCandidateInvariant() const override; - - bool LocalCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description() const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - const ValueRefBase* GetValueRef() const - { return m_value_ref; } - - unsigned int GetCheckSum() const override; - -private: - ValueRefBase* m_value_ref; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); -}; - -/** Returns the in-game name of the object / empire / etc. with a specified id. */ -struct FO_COMMON_API NameLookup : public Variable { - enum LookupType { - INVALID_LOOKUP = -1, - OBJECT_NAME, - EMPIRE_NAME, - SHIP_DESIGN_NAME - }; - - NameLookup(ValueRefBase* value_ref, LookupType lookup_type); - ~NameLookup(); - - bool operator==(const ValueRefBase& rhs) const override; - - std::string Eval(const ScriptingContext& context) const override; - - bool RootCandidateInvariant() const override; - - bool LocalCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - std::string Description() const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - const ValueRefBase* GetValueRef() const - { return m_value_ref; } - - LookupType GetLookupType() const - { return m_lookup_type; } - - unsigned int GetCheckSum() const override; - -private: - ValueRefBase* m_value_ref; - LookupType m_lookup_type; - - friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -/** An arithmetic operation node ValueRef class. One of addition, subtraction, - * mutiplication, division, or unary negation is performed on the child(ren) - * of this node, and the result is returned. */ -template -struct FO_COMMON_API Operation : public ValueRefBase -{ - /** Binary operation ctor. */ - Operation(OpType op_type, ValueRefBase* operand1, - ValueRefBase* operand2); - - /** Unary operation ctor. */ - Operation(OpType op_type, ValueRefBase* operand); - - /* N-ary operation ctor. */ - Operation(OpType op_type, const std::vector*>& operands); - - ~Operation(); - - bool operator==(const ValueRefBase& rhs) const override; - - T Eval(const ScriptingContext& context) const override; - - bool RootCandidateInvariant() const override; - - bool LocalCandidateInvariant() const override; - - bool TargetInvariant() const override; - - bool SourceInvariant() const override; - - bool SimpleIncrement() const override; - - bool ConstantExpr() const override - { return m_constant_expr; } - - std::string Description() const override; - - std::string Dump() const override; - - void SetTopLevelContent(const std::string& content_name) override; - - OpType GetOpType() const; - - /** 1st operand (or 0 if none exists). */ - const ValueRefBase* LHS() const; - - /** 2nd operand (or 0 if only one exists) */ - const ValueRefBase* RHS() const; - - /** all operands */ - const std::vector*>& Operands() const; - - unsigned int GetCheckSum() const override; - -private: - void DetermineIfConstantExpr(); - void CacheConstValue(); - T EvalImpl(const ScriptingContext& context) const; - - OpType m_op_type; - std::vector*> m_operands; - bool m_constant_expr = false; - T m_cached_const_value; - - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version); +enum StatisticType : int { + INVALID_STATISTIC_TYPE = -1, + COUNT, // returns the number of objects matching the condition + UNIQUE_COUNT, // returns the number of distinct property values of objects matching the condition. eg. if 3 objects have the property value "small", and two have "big", then this value is 2, as there are 2 unique property values. + IF, // returns T(1) if anything matches the condition, or T(0) otherwise + SUM, // returns the sum of the property values of all objects matching the condition + MEAN, // returns the mean of the property values of all objects matching the condition + RMS, // returns the sqrt of the mean of the squares of the property values of all objects matching the condition + MODE, // returns the most common property value of objects matching the condition. supported for non-numeric types such as enums. + MAX, // returns the maximum value of the property amongst objects matching the condition + MIN, // returns the minimum value of the property amongst objects matching the condition + SPREAD, // returns the (positive) difference between the maximum and minimum values of the property amongst objects matching the condition + STDEV, // returns the standard deviation of the property values of all objects matching the condition + PRODUCT // returns the product of the property values of all objects matching the condition }; -FO_COMMON_API MeterType NameToMeter(const std::string& name); -FO_COMMON_API std::string MeterToName(MeterType meter); -FO_COMMON_API std::string ReconstructName(const std::vector& property_name, - ReferenceType ref_type); - -// Template Implementations -/////////////////////////////////////////////////////////// -// ValueRefBase // -/////////////////////////////////////////////////////////// -template -bool ValueRefBase::operator==(const ValueRefBase& rhs) const -{ - if (&rhs == this) - return true; - if (typeid(rhs) != typeid(*this)) - return false; - return true; -} - -template -template -void ValueRefBase::serialize(Archive& ar, const unsigned int version) -{} - -/////////////////////////////////////////////////////////// -// Constant // -/////////////////////////////////////////////////////////// -template -Constant::Constant(T value) : - m_value(value) -{} - -template -bool Constant::operator==(const ValueRefBase& rhs) const -{ - if (&rhs == this) - return true; - if (typeid(rhs) != typeid(*this)) - return false; - const Constant& rhs_ = static_cast&>(rhs); - - return m_value == rhs_.m_value && m_top_level_content == rhs_.m_top_level_content; -} - -template -T Constant::Value() const -{ return m_value; } - -template -T Constant::Eval(const ScriptingContext& context) const -{ return m_value; } - -template -std::string Constant::Description() const -{ return UserString(boost::lexical_cast(m_value)); } - -template -void Constant::SetTopLevelContent(const std::string& content_name) -{ m_top_level_content = content_name; } - -template -unsigned int Constant::GetCheckSum() const -{ - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "ValueRef::Constant"); - CheckSums::CheckSumCombine(retval, m_value); - TraceLogger() << "GetCheckSum(Constant): " << typeid(*this).name() << " value: " << m_value << " retval: " << retval; - return retval; -} - -template <> -FO_COMMON_API std::string Constant::Description() const; - -template <> -FO_COMMON_API std::string Constant::Description() const; - -template <> -FO_COMMON_API std::string Constant::Description() const; - -template <> -FO_COMMON_API std::string Constant::Dump() const; - -template <> -FO_COMMON_API std::string Constant::Dump() const; - -template <> -FO_COMMON_API std::string Constant::Dump() const; - -template <> -FO_COMMON_API std::string Constant::Dump() const; - -template <> -FO_COMMON_API std::string Constant::Dump() const; - -template <> -FO_COMMON_API std::string Constant::Dump() const; - -template <> -FO_COMMON_API std::string Constant::Dump() const; - -template <> -FO_COMMON_API std::string Constant::Dump() const; - -template <> -FO_COMMON_API std::string Constant::Eval(const ScriptingContext& context) const; - -template -template -void Constant::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRefBase) - & BOOST_SERIALIZATION_NVP(m_value) - & BOOST_SERIALIZATION_NVP(m_top_level_content); -} - - -/////////////////////////////////////////////////////////// -// Variable // -/////////////////////////////////////////////////////////// -template -Variable::Variable(ReferenceType ref_type, const std::vector& property_name) : - m_ref_type(ref_type), - m_property_name(property_name.begin(), property_name.end()) -{} - -template -Variable::Variable(ReferenceType ref_type, const std::string& property_name) : - m_ref_type(ref_type), - m_property_name() -{ - m_property_name.push_back(property_name); -} - -template -bool Variable::operator==(const ValueRefBase& rhs) const -{ - if (&rhs == this) - return true; - if (typeid(rhs) != typeid(*this)) - return false; - const Variable& rhs_ = static_cast&>(rhs); - return (m_ref_type == rhs_.m_ref_type) && (m_property_name == rhs_.m_property_name); } -template -ReferenceType Variable::GetReferenceType() const -{ return m_ref_type; } - -template -const std::vector& Variable::PropertyName() const -{ return m_property_name; } - -template -bool Variable::RootCandidateInvariant() const -{ return m_ref_type != CONDITION_ROOT_CANDIDATE_REFERENCE; } - -template -bool Variable::LocalCandidateInvariant() const -{ return m_ref_type != CONDITION_LOCAL_CANDIDATE_REFERENCE; } - -template -bool Variable::TargetInvariant() const -{ return m_ref_type != EFFECT_TARGET_REFERENCE && m_ref_type != EFFECT_TARGET_VALUE_REFERENCE; } - -template -bool Variable::SourceInvariant() const -{ return m_ref_type != SOURCE_REFERENCE; } - -FO_COMMON_API std::string FormatedDescriptionPropertyNames(ReferenceType ref_type, - const std::vector& property_names); - -template -std::string Variable::Description() const -{ return FormatedDescriptionPropertyNames(m_ref_type, m_property_name); } - -template -std::string Variable::Dump() const -{ return ReconstructName(m_property_name, m_ref_type); } - -template -unsigned int Variable::GetCheckSum() const -{ - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "ValueRef::Variable"); - CheckSums::CheckSumCombine(retval, m_property_name); - CheckSums::CheckSumCombine(retval, m_ref_type); - TraceLogger() << "GetCheckSum(Variable): " << typeid(*this).name() << " retval: " << retval; - return retval; -} - -template <> -FO_COMMON_API PlanetSize Variable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API PlanetType Variable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API PlanetEnvironment Variable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API UniverseObjectType Variable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API StarType Variable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API double Variable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API int Variable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API std::string Variable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API std::vector Variable>::Eval(const ScriptingContext& context) const; - -template -template -void Variable::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRefBase) - & BOOST_SERIALIZATION_NVP(m_ref_type) - & BOOST_SERIALIZATION_NVP(m_property_name); -} - -/////////////////////////////////////////////////////////// -// Statistic // -/////////////////////////////////////////////////////////// -template -Statistic::Statistic(ValueRefBase* value_ref, StatisticType stat_type, - Condition::ConditionBase* sampling_condition) : - Variable(NON_OBJECT_REFERENCE, ""), - m_stat_type(stat_type), - m_sampling_condition(sampling_condition), - m_value_ref(value_ref) -{} - -template -Statistic::~Statistic() -{ - delete m_sampling_condition; - delete m_value_ref; -} - -template -bool Statistic::operator==(const ValueRefBase& rhs) const -{ - if (&rhs == this) - return true; - if (typeid(rhs) != typeid(*this)) - return false; - const Statistic& rhs_ = static_cast&>(rhs); - - if (m_stat_type != rhs_.m_stat_type) - return false; - if (this->m_value_ref != rhs_.m_value_ref) - return false; - - if (m_sampling_condition == rhs_.m_sampling_condition) { - // check next member - } else if (!m_sampling_condition || !rhs_.m_sampling_condition) { - return false; - } else { - if (*m_sampling_condition != *(rhs_.m_sampling_condition)) - return false; - } - - return true; -} - -template -void Statistic::GetConditionMatches(const ScriptingContext& context, - Condition::ObjectSet& condition_targets, - Condition::ConditionBase* condition) const -{ - condition_targets.clear(); - if (!condition) - return; - condition->Eval(context, condition_targets); -} - -template -void Statistic::GetObjectPropertyValues(const ScriptingContext& context, - const Condition::ObjectSet& objects, - std::map, T>& object_property_values) const -{ - object_property_values.clear(); - - if (m_value_ref) { - // evaluate ValueRef with each condition match as the LocalCandidate - // TODO: Can / should this be paralleized? - for (std::shared_ptr object : objects) { - T property_value = m_value_ref->Eval(ScriptingContext(context, object)); - object_property_values[object] = property_value; - } - } -} - -template -bool Statistic::RootCandidateInvariant() const -{ - return Variable::RootCandidateInvariant() && - m_sampling_condition->RootCandidateInvariant() && - (!m_value_ref || m_value_ref->RootCandidateInvariant()); -} - -template -bool Statistic::LocalCandidateInvariant() const -{ - // don't need to check if sampling condition is LocalCandidateInvariant, as - // all conditions aren't, but that refers to their own local candidate. no - // condition is explicitly dependent on the parent context's local candidate. - return Variable::LocalCandidateInvariant() && - (!m_value_ref || m_value_ref->LocalCandidateInvariant()); -} - -template -bool Statistic::TargetInvariant() const -{ - return Variable::TargetInvariant() && - m_sampling_condition->TargetInvariant() && - (!m_value_ref || m_value_ref->TargetInvariant()); -} - -template -bool Statistic::SourceInvariant() const -{ - return Variable::SourceInvariant() && - m_sampling_condition->SourceInvariant() && - (!m_value_ref || m_value_ref->SourceInvariant()); -} - -template -std::string Statistic::Description() const { - std::string retval = UserString("DESC_STATISTIC") + ": [(" + UserString("DESC_STAT_TYPE") + ": " + boost::lexical_cast(m_stat_type) + ")"; - switch (m_stat_type) { - case COUNT: retval += "(COUNT)"; break; - case UNIQUE_COUNT: retval += "(UNIQUE_COUNT)"; break; - case IF: retval += "(IF ANY)"; break; - case SUM: retval += "(SUM)"; break; - case MEAN: retval += "(MEAN)"; break; - case RMS: retval += "(RMS)"; break; - case MODE: retval += "(MODE)"; break; - case MAX: retval += "(MAX)"; break; - case MIN: retval += "(MIN)"; break; - case SPREAD: retval += "(SPREAD)"; break; - case STDEV: retval += "(STDEV)"; break; - case PRODUCT: retval += "(PRODUCT)"; break; - default: retval += "()"; break; - } - if (m_value_ref) { - retval += "(" + m_value_ref->Description() + ")"; - } else if (!Variable::Description().empty()){ - retval += "(" + Variable::Description() + ")"; - } - retval += "(" + UserString("DESC_SAMPLING_CONDITION") + ": " + m_sampling_condition->Description() + ")]"; - return retval; -} - -template -std::string Statistic::Dump() const -{ return Description(); } - -template -void Statistic::SetTopLevelContent(const std::string& content_name) -{ - if (m_sampling_condition) - m_sampling_condition->SetTopLevelContent(content_name); - if (m_value_ref) - m_value_ref->SetTopLevelContent(content_name); -} - -template -T Statistic::Eval(const ScriptingContext& context) const -{ - // the only statistic that can be computed on non-number property types - // and that is itself of a non-number type is the most common value - if (m_stat_type != MODE) - throw std::runtime_error("ValueRef evaluated with an invalid StatisticType for the return type."); - - Condition::ObjectSet condition_matches; - GetConditionMatches(context, condition_matches, m_sampling_condition); - - if (condition_matches.empty()) - return T(-1); // should be INVALID_T of enum types - - // evaluate property for each condition-matched object - std::map, T> object_property_values; - GetObjectPropertyValues(context, condition_matches, object_property_values); - - // count number of each result, tracking which has the most occurances - std::map histogram; - typename std::map::const_iterator most_common_property_value_it = histogram.begin(); - unsigned int max_seen(0); - - for (const typename std::map, T>::value_type& entry : object_property_values) { - const T& property_value = entry.second; - - typename std::map::iterator hist_it = histogram.find(property_value); - if (hist_it == histogram.end()) - hist_it = histogram.insert({property_value, 0}).first; - unsigned int& num_seen = hist_it->second; - - num_seen++; - - if (num_seen > max_seen) { - most_common_property_value_it = hist_it; - max_seen = num_seen; - } - } - - // return result (property value) that occured most frequently - return most_common_property_value_it->first; -} - -template -unsigned int Statistic::GetCheckSum() const -{ - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "ValueRef::Statistic"); - CheckSums::CheckSumCombine(retval, m_stat_type); - CheckSums::CheckSumCombine(retval, m_sampling_condition); - CheckSums::CheckSumCombine(retval, m_value_ref); - TraceLogger() << "GetCheckSum(Statisic): " << typeid(*this).name() << " retval: " << retval; - return retval; -} - -template <> -FO_COMMON_API double Statistic::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API int Statistic::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API std::string Statistic::Eval(const ScriptingContext& context) const; - -template -T Statistic::ReduceData(const std::map, T>& object_property_values) const -{ - if (object_property_values.empty()) - return T(0); - - switch (m_stat_type) { - case COUNT: { - return T(object_property_values.size()); - break; - } - case UNIQUE_COUNT: { - std::set observed_values; - for (const typename std::map, T>::value_type& entry : object_property_values) { - observed_values.insert(entry.second); - } - return T(observed_values.size()); - break; - } - case IF: { - if (object_property_values.empty()) - return T(0); - return T(1); - break; - } - case SUM: { - T accumulator(0); - for (const typename std::map, T>::value_type& entry : object_property_values) { - accumulator += entry.second; - } - return accumulator; - break; - } - - case MEAN: { - T accumulator(0); - for (const typename std::map, T>::value_type& entry : object_property_values) { - accumulator += entry.second; - } - return accumulator / static_cast(object_property_values.size()); - break; - } - - case RMS: { - T accumulator(0); - for (const typename std::map, T>::value_type& entry : object_property_values) { - accumulator += (entry.second * entry.second); - } - accumulator /= static_cast(object_property_values.size()); - - double retval = std::sqrt(static_cast(accumulator)); - return static_cast(retval); - break; - } - - case MODE: { - // count number of each result, tracking which has the most occurances - std::map histogram; - typename std::map::const_iterator most_common_property_value_it = histogram.begin(); - unsigned int max_seen(0); - - for (const typename std::map, T>::value_type& entry : object_property_values) { - const T& property_value = entry.second; - - typename std::map::iterator hist_it = histogram.find(property_value); - if (hist_it == histogram.end()) - hist_it = histogram.insert({property_value, 0}).first; - unsigned int& num_seen = hist_it->second; - - num_seen++; - - if (num_seen > max_seen) { - most_common_property_value_it = hist_it; - max_seen = num_seen; - } - } - - // return result (property value) that occured most frequently - return most_common_property_value_it->first; - break; - } - - case MAX: { - typename std::map, T>::const_iterator max_it = object_property_values.begin(); - - for (typename std::map, T>::const_iterator it = object_property_values.begin(); - it != object_property_values.end(); ++it) - { - const T& property_value = it->second; - if (property_value > max_it->second) - max_it = it; - } - - // return maximal observed propery value - return max_it->second; - break; - } - - case MIN: { - typename std::map, T>::const_iterator min_it = object_property_values.begin(); - - for (typename std::map, T>::const_iterator it = object_property_values.begin(); - it != object_property_values.end(); ++it) - { - const T& property_value = it->second; - if (property_value < min_it->second) - min_it = it; - } - - // return minimal observed propery value - return min_it->second; - break; - } - - case SPREAD: { - typename std::map, T>::const_iterator max_it = object_property_values.begin(); - typename std::map, T>::const_iterator min_it = object_property_values.begin(); - - for (typename std::map, T>::const_iterator it = object_property_values.begin(); - it != object_property_values.end(); ++it) - { - const T& property_value = it->second; - if (property_value > max_it->second) - max_it = it; - if (property_value < min_it->second) - min_it = it; - } - - // return difference between maximal and minimal observed propery values - return max_it->second - min_it->second; - break; - } - - case STDEV: { - if (object_property_values.size() < 2) - return T(0); - - // find sample mean - T accumulator(0); - for (const typename std::map, T>::value_type& entry : object_property_values) { - accumulator += entry.second; - } - const T MEAN(accumulator / static_cast(object_property_values.size())); - - // find average of squared deviations from sample mean - accumulator = T(0); - for (const typename std::map, T>::value_type& entry : object_property_values) { - accumulator += (entry.second - MEAN) * (entry.second - MEAN); - } - const T MEAN_DEV2(accumulator / static_cast(static_cast(object_property_values.size()) - 1)); - double retval = std::sqrt(static_cast(MEAN_DEV2)); - return static_cast(retval); - break; - } - - case PRODUCT: { - T accumulator(1); - for (const typename std::map, T>::value_type& entry : object_property_values) { - accumulator *= entry.second; - } - return accumulator; - break; - } - - default: - throw std::runtime_error("ValueRef evaluated with an unknown or invalid StatisticType."); - break; - } -} - -template -template -void Statistic::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Variable) - & BOOST_SERIALIZATION_NVP(m_stat_type) - & BOOST_SERIALIZATION_NVP(m_sampling_condition) - & BOOST_SERIALIZATION_NVP(m_value_ref); -} - -/////////////////////////////////////////////////////////// -// ComplexVariable // -/////////////////////////////////////////////////////////// -template -ComplexVariable::ComplexVariable(const std::string& variable_name, - ValueRefBase* int_ref1, - ValueRefBase* int_ref2, - ValueRefBase* int_ref3, - ValueRefBase* string_ref1, - ValueRefBase* string_ref2) : - Variable(NON_OBJECT_REFERENCE, std::vector(1, variable_name)), - m_int_ref1(int_ref1), - m_int_ref2(int_ref2), - m_int_ref3(int_ref3), - m_string_ref1(string_ref1), - m_string_ref2(string_ref2) -{} - -template -ComplexVariable::~ComplexVariable() -{ - delete m_int_ref1; - delete m_int_ref2; - delete m_int_ref3; - delete m_string_ref1; - delete m_string_ref2; -} - -template -bool ComplexVariable::operator==(const ValueRefBase& rhs) const -{ - if (&rhs == this) - return true; - if (typeid(rhs) != typeid(*this)) - return false; - const ComplexVariable& rhs_ = static_cast&>(rhs); - - if (this->m_property_name != rhs_.m_property_name) - return false; - - if (m_int_ref1 == rhs_.m_int_ref1) { - // check next member - } else if (!m_int_ref1 || !rhs_.m_int_ref1) { - return false; - } else { - if (*m_int_ref1 != *(rhs_.m_int_ref1)) - return false; - } - - if (m_int_ref2 == rhs_.m_int_ref2) { - // check next member - } else if (!m_int_ref2 || !rhs_.m_int_ref2) { - return false; - } else { - if (*m_int_ref2 != *(rhs_.m_int_ref2)) - return false; - } - - if (m_int_ref3 == rhs_.m_int_ref3) { - // check next member - } else if (!m_int_ref3 || !rhs_.m_int_ref3) { - return false; - } else { - if (*m_int_ref3 != *(rhs_.m_int_ref3)) - return false; - } - - if (m_string_ref1 == rhs_.m_string_ref1) { - // check next member - } else if (!m_string_ref1 || !rhs_.m_string_ref1) { - return false; - } else { - if (*m_string_ref1 != *(rhs_.m_string_ref1)) - return false; - } - - if (m_string_ref2 == rhs_.m_string_ref2) { - // check next member - } else if (!m_string_ref2 || !rhs_.m_string_ref2) { - return false; - } else { - if (*m_string_ref2 != *(rhs_.m_string_ref2)) - return false; - } - - return true; -} - -template -const ValueRefBase* ComplexVariable::IntRef1() const -{ return m_int_ref1; } - -template -const ValueRefBase* ComplexVariable::IntRef2() const -{ return m_int_ref2; } - -template -const ValueRefBase* ComplexVariable::IntRef3() const -{ return m_int_ref3; } - -template -const ValueRefBase* ComplexVariable::StringRef1() const -{ return m_string_ref1; } - -template -const ValueRefBase* ComplexVariable::StringRef2() const -{ return m_string_ref2; } - -template -bool ComplexVariable::RootCandidateInvariant() const -{ - return Variable::RootCandidateInvariant() - && (!m_int_ref1 || m_int_ref1->RootCandidateInvariant()) - && (!m_int_ref2 || m_int_ref2->RootCandidateInvariant()) - && (!m_int_ref3 || m_int_ref3->RootCandidateInvariant()) - && (!m_string_ref1 || m_string_ref1->RootCandidateInvariant()) - && (!m_string_ref2 || m_string_ref2->RootCandidateInvariant()); -} - -template -bool ComplexVariable::LocalCandidateInvariant() const -{ - return (!m_int_ref1 || m_int_ref1->LocalCandidateInvariant()) - && (!m_int_ref2 || m_int_ref2->LocalCandidateInvariant()) - && (!m_int_ref3 || m_int_ref3->LocalCandidateInvariant()) - && (!m_string_ref1 || m_string_ref1->LocalCandidateInvariant()) - && (!m_string_ref2 || m_string_ref2->LocalCandidateInvariant()); -} - -template -bool ComplexVariable::TargetInvariant() const -{ - return (!m_int_ref1 || m_int_ref1->TargetInvariant()) - && (!m_int_ref2 || m_int_ref2->TargetInvariant()) - && (!m_int_ref3 || m_int_ref3->TargetInvariant()) - && (!m_string_ref1 || m_string_ref1->TargetInvariant()) - && (!m_string_ref2 || m_string_ref2->TargetInvariant()); -} - -template -bool ComplexVariable::SourceInvariant() const -{ - return (!m_int_ref1 || m_int_ref1->SourceInvariant()) - && (!m_int_ref2 || m_int_ref2->SourceInvariant()) - && (!m_int_ref3 || m_int_ref3->SourceInvariant()) - && (!m_string_ref1 || m_string_ref1->SourceInvariant()) - && (!m_string_ref2 || m_string_ref2->SourceInvariant()); -} - -template -std::string ComplexVariable::Description() const { - std::string variable_name; - if (!this->m_property_name.empty()) - variable_name = this->m_property_name.back(); - std::string retval = UserString("DESC_COMPLEX") + ": [(" + UserString("DESC_VARIABLE_NAME") + ": " + variable_name + ") ("; - if (variable_name == "PartCapacity") { - //retval += m_string_ref1->Description(); - } else if (variable_name == "JumpsBetween") { - if (m_int_ref1) - retval += m_int_ref1->Description() + ", "; - if (m_int_ref2) - retval += m_int_ref2->Description() + ", "; - } else if (false) { - if (m_int_ref1) - retval += m_int_ref1->Description() + ", "; - if (m_int_ref2) - retval += m_int_ref2->Description() + ", "; - if (m_int_ref2) - retval += m_int_ref2->Description() + ", "; - if (m_string_ref1) - retval += m_string_ref1->Description() + ", "; - if (m_string_ref2) - retval += m_string_ref2->Description(); - } - retval += ")]"; - return retval; -} - -template -std::string ComplexVariable::Dump() const -{ return "ComplexVariable"; } - -template -void ComplexVariable::SetTopLevelContent(const std::string& content_name) -{ - if (m_int_ref1) - m_int_ref1->SetTopLevelContent(content_name); - if (m_int_ref2) - m_int_ref2->SetTopLevelContent(content_name); - if (m_int_ref3) - m_int_ref3->SetTopLevelContent(content_name); - if (m_string_ref1) - m_string_ref1->SetTopLevelContent(content_name); - if (m_string_ref2) - m_string_ref2->SetTopLevelContent(content_name); -} - -template -unsigned int ComplexVariable::GetCheckSum() const -{ - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "ValueRef::ComplexVariable"); - CheckSums::CheckSumCombine(retval, m_int_ref1); - CheckSums::CheckSumCombine(retval, m_int_ref2); - CheckSums::CheckSumCombine(retval, m_int_ref3); - CheckSums::CheckSumCombine(retval, m_string_ref1); - CheckSums::CheckSumCombine(retval, m_string_ref2); - TraceLogger() << "GetCheckSum(ComplexVariable): " << typeid(*this).name() << " retval: " << retval; - return retval; -} - -template <> -FO_COMMON_API PlanetSize ComplexVariable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API PlanetType ComplexVariable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API PlanetEnvironment ComplexVariable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API UniverseObjectType ComplexVariable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API StarType ComplexVariable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API double ComplexVariable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API int ComplexVariable::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API std::string ComplexVariable::Eval(const ScriptingContext& context) const; - -template -template -void ComplexVariable::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Variable) - & BOOST_SERIALIZATION_NVP(m_int_ref1) - & BOOST_SERIALIZATION_NVP(m_int_ref2) - & BOOST_SERIALIZATION_NVP(m_int_ref3) - & BOOST_SERIALIZATION_NVP(m_string_ref1) - & BOOST_SERIALIZATION_NVP(m_string_ref2); -} - -/////////////////////////////////////////////////////////// -// StaticCast // -/////////////////////////////////////////////////////////// -template -StaticCast::StaticCast(Variable* value_ref) : - Variable(value_ref->GetReferenceType(), value_ref->PropertyName()), - m_value_ref(value_ref) -{} - -template -StaticCast::StaticCast(ValueRefBase* value_ref) : - Variable(NON_OBJECT_REFERENCE), - m_value_ref(value_ref) -{} - -template -StaticCast::~StaticCast() -{ delete m_value_ref; } - -template -bool StaticCast::operator==(const ValueRefBase& rhs) const -{ - if (&rhs == this) - return true; - if (typeid(rhs) != typeid(*this)) - return false; - const StaticCast& rhs_ = - static_cast&>(rhs); - - if (m_value_ref == rhs_.m_value_ref) { - // check next member - } else if (!m_value_ref || !rhs_.m_value_ref) { - return false; - } else { - if (*m_value_ref != *(rhs_.m_value_ref)) - return false; - } - - return true; -} - -template -ToType StaticCast::Eval(const ScriptingContext& context) const -{ return static_cast(m_value_ref->Eval(context)); } - -template -bool StaticCast::RootCandidateInvariant() const -{ return m_value_ref->RootCandidateInvariant(); } - -template -bool StaticCast::LocalCandidateInvariant() const -{ return m_value_ref->LocalCandidateInvariant(); } - -template -bool StaticCast::TargetInvariant() const -{ return m_value_ref->TargetInvariant(); } - -template -bool StaticCast::SourceInvariant() const -{ return m_value_ref->SourceInvariant(); } - -template -std::string StaticCast::Description() const -{ return m_value_ref->Description(); } - -template -std::string StaticCast::Dump() const -{ return m_value_ref->Dump(); } - -template -void StaticCast::SetTopLevelContent(const std::string& content_name) -{ - if (m_value_ref) - m_value_ref->SetTopLevelContent(content_name); -} - -template -unsigned int StaticCast::GetCheckSum() const -{ - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "ValueRef::StaticCast"); - CheckSums::CheckSumCombine(retval, m_value_ref); - TraceLogger() << "GetCheckSum(StaticCast): " << typeid(*this).name() << " retval: " << retval; - return retval; -} - -template -template -void StaticCast::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRefBase) - & BOOST_SERIALIZATION_NVP(m_value_ref); -} - -/////////////////////////////////////////////////////////// -// StringCast // -/////////////////////////////////////////////////////////// -template -StringCast::StringCast(Variable* value_ref) : - Variable(value_ref->GetReferenceType(), value_ref->PropertyName()), - m_value_ref(value_ref) -{} - -template -StringCast::StringCast(ValueRefBase* value_ref) : - Variable(NON_OBJECT_REFERENCE), - m_value_ref(value_ref) -{} - -template -StringCast::~StringCast() -{ delete m_value_ref; } - -template -bool StringCast::operator==(const ValueRefBase& rhs) const -{ - if (&rhs == this) - return true; - if (typeid(rhs) != typeid(*this)) - return false; - const StringCast& rhs_ = - static_cast&>(rhs); - - if (m_value_ref == rhs_.m_value_ref) { - // check next member - } else if (!m_value_ref || !rhs_.m_value_ref) { - return false; - } else { - if (*m_value_ref != *(rhs_.m_value_ref)) - return false; - } - - return true; -} - -template -std::string StringCast::Eval(const ScriptingContext& context) const -{ - if (!m_value_ref) - return ""; - std::string retval; - try { - retval = boost::lexical_cast(m_value_ref->Eval(context)); - } catch (...) { - } - return retval; -} - -template -unsigned int StringCast::GetCheckSum() const -{ - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "ValueRef::StringCast"); - CheckSums::CheckSumCombine(retval, m_value_ref); - TraceLogger() << "GetCheckSum(StringCast): " << typeid(*this).name() << " retval: " << retval; - return retval; -} - -template <> -FO_COMMON_API std::string StringCast::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API std::string StringCast::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API std::string StringCast>::Eval(const ScriptingContext& context) const; - -template -bool StringCast::RootCandidateInvariant() const -{ return m_value_ref->RootCandidateInvariant(); } - -template -bool StringCast::LocalCandidateInvariant() const -{ return m_value_ref->LocalCandidateInvariant(); } - -template -bool StringCast::TargetInvariant() const -{ return m_value_ref->TargetInvariant(); } - -template -bool StringCast::SourceInvariant() const -{ return m_value_ref->SourceInvariant(); } - -template -std::string StringCast::Description() const -{ return m_value_ref->Description(); } - -template -std::string StringCast::Dump() const -{ return m_value_ref->Dump(); } - -template -void StringCast::SetTopLevelContent(const std::string& content_name) { - if (m_value_ref) - m_value_ref->SetTopLevelContent(content_name); -} - -template -template -void StringCast::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRefBase) - & BOOST_SERIALIZATION_NVP(m_value_ref); -} - -/////////////////////////////////////////////////////////// -// UserStringLookup // -/////////////////////////////////////////////////////////// -template -UserStringLookup::UserStringLookup(Variable* value_ref) : - Variable(value_ref->GetReferenceType(), value_ref->PropertyName()), - m_value_ref(value_ref) -{} - -template -UserStringLookup::UserStringLookup(ValueRefBase* value_ref) : - Variable(NON_OBJECT_REFERENCE), - m_value_ref(value_ref) -{} - -template -UserStringLookup::~UserStringLookup() -{ - delete m_value_ref; -} - -template -bool UserStringLookup::operator==(const ValueRefBase& rhs) const { - if (&rhs == this) - return true; - if (typeid(rhs) != typeid(*this)) - return false; - const UserStringLookup& rhs_ = - static_cast(rhs); - - if (m_value_ref == rhs_.m_value_ref) { - // check next member - } - else if (!m_value_ref || !rhs_.m_value_ref) { - return false; - } - else { - if (*m_value_ref != *(rhs_.m_value_ref)) - return false; - } - - return true; -} - -template -std::string UserStringLookup::Eval(const ScriptingContext& context) const { - if (!m_value_ref) - return ""; - std::string ref_val = boost::lexical_cast(m_value_ref->Eval(context)); - if (ref_val.empty() || !UserStringExists(ref_val)) - return ""; - return UserString(ref_val); -} - -template <> -FO_COMMON_API std::string UserStringLookup::Eval(const ScriptingContext& context) const; - -template <> -FO_COMMON_API std::string UserStringLookup>::Eval(const ScriptingContext& context) const; - -template -bool UserStringLookup::RootCandidateInvariant() const -{ - return m_value_ref->RootCandidateInvariant(); -} - -template -bool UserStringLookup::LocalCandidateInvariant() const -{ - return !m_value_ref || m_value_ref->LocalCandidateInvariant(); -} - -template -bool UserStringLookup::TargetInvariant() const -{ - return !m_value_ref || m_value_ref->TargetInvariant(); -} - -template -bool UserStringLookup::SourceInvariant() const -{ - return !m_value_ref || m_value_ref->SourceInvariant(); -} - -template -std::string UserStringLookup::Description() const -{ - return m_value_ref->Description(); -} - -template -std::string UserStringLookup::Dump() const -{ - return m_value_ref->Dump(); -} - -template -void UserStringLookup::SetTopLevelContent(const std::string& content_name) { - if (m_value_ref) - m_value_ref->SetTopLevelContent(content_name); -} - -template -unsigned int UserStringLookup::GetCheckSum() const -{ - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "ValueRef::UserStringLookup"); - CheckSums::CheckSumCombine(retval, m_value_ref); - TraceLogger() << "GetCheckSum(UserStringLookup): " << typeid(*this).name() << " retval: " << retval; - return retval; -} - -template -template -void UserStringLookup::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRefBase) - & BOOST_SERIALIZATION_NVP(m_value_ref); -} - -/////////////////////////////////////////////////////////// -// NameLookup // -/////////////////////////////////////////////////////////// -template -void NameLookup::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRefBase) - & BOOST_SERIALIZATION_NVP(m_value_ref) - & BOOST_SERIALIZATION_NVP(m_lookup_type); -} - -/////////////////////////////////////////////////////////// -// Operation // -/////////////////////////////////////////////////////////// -template -Operation::Operation(OpType op_type, ValueRefBase* operand1, ValueRefBase* operand2) : - m_op_type(op_type), - m_operands(), - m_cached_const_value(T()) // overritten below if operation is constant, not used if operation is not constant -{ - if (operand1) - m_operands.push_back(operand1); - if (operand2) - m_operands.push_back(operand2); - DetermineIfConstantExpr(); - CacheConstValue(); -} - -template -Operation::Operation(OpType op_type, ValueRefBase* operand) : - m_op_type(op_type), - m_operands(), - m_cached_const_value(T()) // overritten below if operation is constant, not used if operation is not constant -{ - if (operand) - m_operands.push_back(operand); - DetermineIfConstantExpr(); - CacheConstValue(); -} - -template -Operation::Operation(OpType op_type, const std::vector*>& operands) : - m_op_type(op_type), - m_operands(operands), - m_cached_const_value(T()) // overritten below if operation is constant, not used if operation is not constant -{ - DetermineIfConstantExpr(); - CacheConstValue(); -} - -template -void Operation::DetermineIfConstantExpr() -{ - if (m_op_type == RANDOM_UNIFORM || m_op_type == RANDOM_PICK) { - m_constant_expr = false; - return; - } - - m_constant_expr = true; // may be overridden... - - for (ValueRefBase* operand : m_operands) { - if (operand && !operand->ConstantExpr()) { - m_constant_expr = false; - return; - } - } -} - -template -void Operation::CacheConstValue() -{ - if (!m_constant_expr) - return; - - m_cached_const_value = this->EvalImpl(ScriptingContext()); -} - -template -Operation::~Operation() -{ - for (ValueRefBase* operand : m_operands) { - delete operand; - } - m_operands.clear(); -} - -template -bool Operation::operator==(const ValueRefBase& rhs) const -{ - if (&rhs == this) - return true; - if (typeid(rhs) != typeid(*this)) - return false; - const Operation& rhs_ = static_cast&>(rhs); - - if (m_operands == rhs_.m_operands) - return true; - - if (m_operands.size() != rhs_.m_operands.size()) - return false; - - for (unsigned int i = 0; i < m_operands.size(); ++i) { - if (m_operands[i] != rhs_.m_operands[i]) - return false; - if (m_operands[i] && *(m_operands[i]) != *(rhs_.m_operands[i])) - return false; - } - - // should be redundant... - if (m_constant_expr != rhs_.m_constant_expr) - return false; - - return true; -} - -template -OpType Operation::GetOpType() const -{ return m_op_type; } - -template -const ValueRefBase* Operation::LHS() const -{ - if (m_operands.empty()) - return nullptr; - return m_operands[0]; -} - -template -const ValueRefBase* Operation::RHS() const -{ - if (m_operands.size() < 2) - return nullptr; - return m_operands[1]; -} - -template -const std::vector*>& Operation::Operands() const -{ return m_operands; } - -template -T Operation::Eval(const ScriptingContext& context) const -{ - if (m_constant_expr) - return m_cached_const_value; - return this->EvalImpl(context); -} - -template -T Operation::EvalImpl(const ScriptingContext& context) const -{ - switch (m_op_type) { - if (m_operands.empty()) - return T(-1); // should be INVALID_T of enum types - - case MAXIMUM: - case MINIMUM: { - // evaluate all operands, return smallest - std::set vals; - for (ValueRefBase* vr : m_operands) { - if (vr) - vals.insert(vr->Eval(context)); - } - if (m_op_type == MINIMUM) - return vals.empty() ? T(-1) : *vals.begin(); - else - return vals.empty() ? T(-1) : *vals.rbegin(); - break; - } - - case RANDOM_PICK: { - // select one operand, evaluate it, return result - if (m_operands.empty()) - return T(-1); // should be INVALID_T of enum types - unsigned int idx = RandSmallInt(0, m_operands.size() - 1); - ValueRefBase* vr = *std::next(m_operands.begin(), idx); - if (!vr) - return T(-1); // should be INVALID_T of enum types - return vr->Eval(context); - break; - } - - case COMPARE_EQUAL: - case COMPARE_GREATER_THAN: - case COMPARE_GREATER_THAN_OR_EQUAL: - case COMPARE_LESS_THAN: - case COMPARE_LESS_THAN_OR_EQUAL: - case COMPARE_NOT_EQUAL: { - const T&& lhs_val = LHS()->Eval(context); - const T&& rhs_val = RHS()->Eval(context); - bool test_result = false; - switch(m_op_type) { - case COMPARE_EQUAL: test_result = lhs_val == rhs_val; break; - case COMPARE_GREATER_THAN: test_result = lhs_val > rhs_val; break; - case COMPARE_GREATER_THAN_OR_EQUAL: test_result = lhs_val >= rhs_val; break; - case COMPARE_LESS_THAN: test_result = lhs_val < rhs_val; break; - case COMPARE_LESS_THAN_OR_EQUAL: test_result = lhs_val <= rhs_val; break; - case COMPARE_NOT_EQUAL: test_result = lhs_val != rhs_val; break; - default: break; // ??? do nothing, default to false - } - if (m_operands.size() < 3) { - return T(1); - } else if (m_operands.size() < 4) { - if (test_result) - return m_operands[2]->Eval(context); - else - return T(0); - } else { - if (test_result) - return m_operands[2]->Eval(context); - else - return m_operands[3]->Eval(context); - } - break; - } - - default: - break; - } - - throw std::runtime_error("ValueRef::Operation::EvalImpl evaluated with an unknown or invalid OpType."); -} - -template -unsigned int Operation::GetCheckSum() const -{ - unsigned int retval{0}; - - CheckSums::CheckSumCombine(retval, "ValueRef::Operation"); - CheckSums::CheckSumCombine(retval, m_op_type); - CheckSums::CheckSumCombine(retval, m_operands); - CheckSums::CheckSumCombine(retval, m_constant_expr); - CheckSums::CheckSumCombine(retval, m_cached_const_value); - TraceLogger() << "GetCheckSum(Operation): " << typeid(*this).name() << " retval: " << retval; - return retval; -} - -template <> -FO_COMMON_API std::string Operation::EvalImpl(const ScriptingContext& context) const; - -template <> -FO_COMMON_API double Operation::EvalImpl(const ScriptingContext& context) const; - -template <> -FO_COMMON_API int Operation::EvalImpl(const ScriptingContext& context) const; - -template -bool Operation::RootCandidateInvariant() const -{ - if (m_op_type == RANDOM_UNIFORM || m_op_type == RANDOM_PICK) - return false; - for (ValueRefBase* operand : m_operands) { - if (operand && !operand->RootCandidateInvariant()) - return false; - } - return true; -} - -template -bool Operation::LocalCandidateInvariant() const -{ - if (m_op_type == RANDOM_UNIFORM || m_op_type == RANDOM_PICK) - return false; - for (ValueRefBase* operand : m_operands) { - if (operand && !operand->LocalCandidateInvariant()) - return false; - } - return true; -} - -template -bool Operation::TargetInvariant() const -{ - if (m_op_type == RANDOM_UNIFORM || m_op_type == RANDOM_PICK) - return false; - for (ValueRefBase* operand : m_operands) { - if (operand && !operand->TargetInvariant()) - return false; - } - return true; -} - -template -bool Operation::SourceInvariant() const -{ - if (m_op_type == RANDOM_UNIFORM || m_op_type == RANDOM_PICK) - return false; - for (ValueRefBase* operand : m_operands) { - if (operand && !operand->SourceInvariant()) - return false; - } - return true; -} - -template -bool Operation::SimpleIncrement() const -{ - if (m_op_type != PLUS && m_op_type != MINUS) - return false; - if (m_operands.size() < 2 || !m_operands[0] || !m_operands[1]) - return false; - if (!(m_operands[1]->ConstantExpr())) - return false; - const Variable* lhs = dynamic_cast*>(m_operands[0]); - if (!lhs) - return false; - return lhs->GetReferenceType() == EFFECT_TARGET_VALUE_REFERENCE; -} - -template -std::string Operation::Description() const -{ - if (m_op_type == NEGATE) { - if (const Operation* rhs = dynamic_cast*>(LHS())) { - OpType op_type = rhs->GetOpType(); - if (op_type == PLUS || op_type == MINUS || - op_type == TIMES || op_type == DIVIDE || - op_type == NEGATE || op_type == EXPONENTIATE) - return "-(" + LHS()->Description() + ")"; - } else { - return "-" + LHS()->Description(); - } - } - - if (m_op_type == ABS) - return "abs(" + LHS()->Description() + ")"; - if (m_op_type == LOGARITHM) - return "log(" + LHS()->Description() + ")"; - if (m_op_type == SINE) - return "sin(" + LHS()->Description() + ")"; - if (m_op_type == COSINE) - return "cos(" + LHS()->Description() + ")"; - - if (m_op_type == MINIMUM) { - std::string retval = "min("; - for (typename std::vector*>::const_iterator it = m_operands.begin(); - it != m_operands.end(); ++it) - { - if (it != m_operands.begin()) - retval += ", "; - retval += (*it)->Description(); - } - retval += ")"; - return retval; - } - if (m_op_type == MAXIMUM) { - std::string retval = "max("; - for (typename std::vector*>::const_iterator it = m_operands.begin(); - it != m_operands.end(); ++it) - { - if (it != m_operands.begin()) - retval += ", "; - retval += (*it)->Description(); - } - retval += ")"; - return retval; - } - - if (m_op_type == RANDOM_UNIFORM) - return "RandomNumber(" + LHS()->Description() + ", " + RHS()->Description() + ")"; - - if (m_op_type == RANDOM_PICK) { - std::string retval = "OneOf("; - for (typename std::vector*>::const_iterator it = m_operands.begin(); - it != m_operands.end(); ++it) - { - if (it != m_operands.begin()) - retval += ", "; - retval += (*it)->Description(); - } - retval += ")"; - return retval; - } - - bool parenthesize_lhs = false; - bool parenthesize_rhs = false; - if (const Operation* lhs = dynamic_cast*>(LHS())) { - OpType op_type = lhs->GetOpType(); - if ( - (m_op_type == EXPONENTIATE && - (op_type == EXPONENTIATE || op_type == TIMES || op_type == DIVIDE || - op_type == PLUS || op_type == MINUS || op_type == NEGATE) - ) || - (((m_op_type == TIMES || m_op_type == DIVIDE) && - (op_type == PLUS || op_type == MINUS)) || op_type == NEGATE) - ) - parenthesize_lhs = true; - } - if (const Operation* rhs = dynamic_cast*>(RHS())) { - OpType op_type = rhs->GetOpType(); - if ( - (m_op_type == EXPONENTIATE && - (op_type == EXPONENTIATE || op_type == TIMES || op_type == DIVIDE || - op_type == PLUS || op_type == MINUS || op_type == NEGATE) - ) || - (((m_op_type == TIMES || m_op_type == DIVIDE) && - (op_type == PLUS || op_type == MINUS)) || op_type == NEGATE) - ) - parenthesize_rhs = true; - } - - std::string retval; - if (parenthesize_lhs) - retval += '(' + LHS()->Description() + ')'; - else - retval += LHS()->Description(); - - switch (m_op_type) { - case PLUS: retval += " + "; break; - case MINUS: retval += " - "; break; - case TIMES: retval += " * "; break; - case DIVIDE: retval += " / "; break; - case EXPONENTIATE: retval += " ^ "; break; - default: retval += " ? "; break; - } - - if (parenthesize_rhs) - retval += '(' + RHS()->Description() + ')'; - else - retval += RHS()->Description(); - - return retval; -} - -template -std::string Operation::Dump() const -{ - if (m_op_type == NEGATE) { - if (const Operation* rhs = dynamic_cast*>(LHS())) { - OpType op_type = rhs->GetOpType(); - if (op_type == PLUS || op_type == MINUS || - op_type == TIMES || op_type == DIVIDE || - op_type == NEGATE || op_type == EXPONENTIATE) - return "-(" + LHS()->Dump() + ")"; - } else { - return "-" + LHS()->Dump(); - } - } - - if (m_op_type == ABS) - return "abs(" + LHS()->Dump() + ")"; - if (m_op_type == LOGARITHM) - return "log(" + LHS()->Dump() + ")"; - if (m_op_type == SINE) - return "sin(" + LHS()->Dump() + ")"; - if (m_op_type == COSINE) - return "cos(" + LHS()->Dump() + ")"; - - if (m_op_type == MINIMUM) { - std::string retval = "min("; - for (typename std::vector*>::const_iterator it = m_operands.begin(); - it != m_operands.end(); ++it) - { - if (it != m_operands.begin()) - retval += ", "; - retval += (*it)->Dump(); - } - retval += ")"; - return retval; - } - if (m_op_type == MAXIMUM) { - std::string retval = "max("; - for (typename std::vector*>::const_iterator it = m_operands.begin(); - it != m_operands.end(); ++it) - { - if (it != m_operands.begin()) - retval += ", "; - retval += (*it)->Dump(); - } - retval += ")"; - return retval; - } - - if (m_op_type == RANDOM_UNIFORM) - return "random(" + LHS()->Dump() + ", " + LHS()->Dump() + ")"; - - if (m_op_type == RANDOM_PICK) { - std::string retval = "randompick("; - for (typename std::vector*>::const_iterator it = m_operands.begin(); - it != m_operands.end(); ++it) - { - if (it != m_operands.begin()) - retval += ", "; - retval += (*it)->Dump(); - } - retval += ")"; - return retval; - } - - - bool parenthesize_lhs = false; - bool parenthesize_rhs = false; - if (const Operation* lhs = dynamic_cast*>(LHS())) { - OpType op_type = lhs->GetOpType(); - if ( - (m_op_type == EXPONENTIATE && - (op_type == EXPONENTIATE || op_type == TIMES || op_type == DIVIDE || - op_type == PLUS || op_type == MINUS || op_type == NEGATE) - ) || - (((m_op_type == TIMES || m_op_type == DIVIDE) && - (op_type == PLUS || op_type == MINUS)) || op_type == NEGATE) - ) - parenthesize_lhs = true; - } - if (const Operation* rhs = dynamic_cast*>(RHS())) { - OpType op_type = rhs->GetOpType(); - if ( - (m_op_type == EXPONENTIATE && - (op_type == EXPONENTIATE || op_type == TIMES || op_type == DIVIDE || - op_type == PLUS || op_type == MINUS || op_type == NEGATE) - ) || - (((m_op_type == TIMES || m_op_type == DIVIDE) && - (op_type == PLUS || op_type == MINUS)) || op_type == NEGATE) - ) - parenthesize_rhs = true; - } - - std::string retval; - if (parenthesize_lhs) - retval += '(' + LHS()->Dump() + ')'; - else - retval += LHS()->Dump(); - - switch (m_op_type) { - case PLUS: retval += " + "; break; - case MINUS: retval += " - "; break; - case TIMES: retval += " * "; break; - case DIVIDE: retval += " / "; break; - case EXPONENTIATE: retval += " ^ "; break; - default: retval += " ? "; break; - } - - if (parenthesize_rhs) - retval += '(' + RHS()->Dump() + ')'; - else - retval += RHS()->Dump(); - - return retval; -} - -template -void Operation::SetTopLevelContent(const std::string& content_name) { - for (ValueRefBase* operand : m_operands) { - if (operand) - operand->SetTopLevelContent(content_name); - } -} - -template -template -void Operation::serialize(Archive& ar, const unsigned int version) -{ - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRefBase) - & BOOST_SERIALIZATION_NVP(m_op_type) - & BOOST_SERIALIZATION_NVP(m_operands) - & BOOST_SERIALIZATION_NVP(m_constant_expr) - & BOOST_SERIALIZATION_NVP(m_cached_const_value); -} - -} // namespace ValueRef - #endif // _ValueRef_h_ diff --git a/universe/ValueRefFwd.h b/universe/ValueRefFwd.h deleted file mode 100644 index 079b1e4179f..00000000000 --- a/universe/ValueRefFwd.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef _ValueRefFwd_h_ -#define _ValueRefFwd_h_ - -/** This namespace contains ValueRefBase and its subclasses. The ValueRefBase - * subclasses represent expression trees that may be evaluated at various - * times, and which refer to both constant and variable values. */ -namespace ValueRef { - enum ReferenceType { - INVALID_REFERENCE_TYPE = -1, - NON_OBJECT_REFERENCE, // ValueRef::Variable is not evalulated on any specific object - SOURCE_REFERENCE, // ValueRef::Variable is evaluated on the source object - EFFECT_TARGET_REFERENCE, // ValueRef::Variable is evaluated on the target object of an effect while it is being executed - EFFECT_TARGET_VALUE_REFERENCE, // ValueRef::Variable is evaluated on the target object value of an effect while it is being executed - CONDITION_LOCAL_CANDIDATE_REFERENCE,// ValueRef::Variable is evaluated on an object that is a candidate to be matched by a condition. In a subcondition, this will reference the local candidate, and not the candidate of an enclosing condition. - CONDITION_ROOT_CANDIDATE_REFERENCE // ValueRef::Variable is evaluated on an object that is a candidate to be matched by a condition. In a subcondition, this will still reference the root candidate, and not the candidate of the local condition. - }; - template struct ValueRefBase; - template struct Constant; - template struct Variable; - template struct Statistic; - template struct ComplexVariable; - enum StatisticType { - INVALID_STATISTIC_TYPE = -1, - COUNT, // returns the number of objects matching the condition - UNIQUE_COUNT, // returns the number of distinct property values of objects matching the condition - IF, // returns T(1) if anything matches the condition, or T(0) otherwise - SUM, // returns the sum of the property values of all objects matching the condition - MEAN, // returns the mean of the property values of all objects matching the condition - RMS, // returns the sqrt of the mean of the squares of the property values of all objects matching the condition - MODE, // returns the most common property value of objects matching the condition. supported for non-numeric types such as enums. - MAX, // returns the maximum value of the property amongst objects matching the condition - MIN, // returns the minimum value of the property amongst objects matching the condition - SPREAD, // returns the (positive) difference between the maximum and minimum values of the property amongst objects matching the condition - STDEV, // returns the standard deviation of the property values of all objects matching the condition - PRODUCT // returns the product of the property values of all objects matching the condition - }; - template struct StaticCast; - template struct StringCast; - template struct UserStringLookup; - struct NameLookup; - template struct Operation; - enum OpType { - PLUS, - MINUS, - TIMES, - DIVIDE, - NEGATE, - EXPONENTIATE, - ABS, - LOGARITHM, - SINE, - COSINE, - MINIMUM, - MAXIMUM, - RANDOM_UNIFORM, - RANDOM_PICK, - SUBSTITUTION, - COMPARE_EQUAL, - COMPARE_GREATER_THAN, - COMPARE_GREATER_THAN_OR_EQUAL, - COMPARE_LESS_THAN, - COMPARE_LESS_THAN_OR_EQUAL, - COMPARE_NOT_EQUAL - }; -} - -#endif // _ValueRefFwd_h_ diff --git a/universe/ValueRef.cpp b/universe/ValueRefs.cpp similarity index 64% rename from universe/ValueRef.cpp rename to universe/ValueRefs.cpp index 5db66bdad8b..5513ca5bcd8 100644 --- a/universe/ValueRef.cpp +++ b/universe/ValueRefs.cpp @@ -1,17 +1,20 @@ -#include "ValueRef.h" +#include "ValueRefs.h" #include "Building.h" #include "Fleet.h" +#include "ShipDesign.h" +#include "ShipPart.h" +#include "ShipHull.h" #include "Ship.h" #include "Planet.h" #include "Predicates.h" #include "Species.h" #include "System.h" #include "Field.h" +#include "Fighter.h" #include "Pathfinder.h" #include "Universe.h" #include "UniverseObject.h" -#include "Condition.h" #include "Enums.h" #include "Tech.h" #include "../Empire/EmpireManager.h" @@ -20,6 +23,7 @@ #include "../util/Random.h" #include "../util/Logger.h" #include "../util/MultiplayerCommon.h" +#include "../util/GameRules.h" #include #include @@ -35,10 +39,11 @@ bool UserStringExists(const std::string& str); FO_COMMON_API extern const int INVALID_DESIGN_ID; namespace { - std::shared_ptr FollowReference(std::vector::const_iterator first, - std::vector::const_iterator last, - ValueRef::ReferenceType ref_type, - const ScriptingContext& context) + std::shared_ptr FollowReference( + std::vector::const_iterator first, + std::vector::const_iterator last, + ValueRef::ReferenceType ref_type, + const ScriptingContext& context) { //DebugLogger() << "FollowReference: source: " << (context.source ? context.source->Name() : "0") // << " target: " << (context.effect_target ? context.effect_target->Name() : "0") @@ -46,7 +51,7 @@ namespace { // << " root c: " << (context.condition_root_candidate ? context.condition_root_candidate->Name() : "0"); std::shared_ptr obj; - switch(ref_type) { + switch (ref_type) { case ValueRef::NON_OBJECT_REFERENCE: return context.condition_local_candidate; break; case ValueRef::SOURCE_REFERENCE: obj = context.source; break; case ValueRef::EFFECT_TARGET_REFERENCE: obj = context.effect_target; break; @@ -57,7 +62,7 @@ namespace { if (!obj) { std::string type_string; - switch(ref_type) { + switch (ref_type) { case ValueRef::SOURCE_REFERENCE: type_string = "Source"; break; case ValueRef::EFFECT_TARGET_REFERENCE: type_string = "Target"; break; case ValueRef::CONDITION_ROOT_CANDIDATE_REFERENCE: type_string = "RootCandidate"; break; @@ -71,20 +76,20 @@ namespace { while (first != last) { std::string property_name = *first; if (property_name == "Planet") { - if (std::shared_ptr b = std::dynamic_pointer_cast(obj)) { - obj = GetPlanet(b->PlanetID()); + if (auto b = std::dynamic_pointer_cast(obj)) { + obj = context.ContextObjects().get(b->PlanetID()); } else { ErrorLogger() << "FollowReference : object not a building, so can't get its planet."; obj = nullptr; } } else if (property_name == "System") { if (obj) - obj = GetSystem(obj->SystemID()); + obj = context.ContextObjects().get(obj->SystemID()); if (!obj) ErrorLogger() << "FollowReference : Unable to get system for object"; } else if (property_name == "Fleet") { - if (std::shared_ptr s = std::dynamic_pointer_cast(obj)) { - obj = GetFleet(s->FleetID()); + if (auto s = std::dynamic_pointer_cast(obj)) { + obj = context.ContextObjects().get(s->FleetID()); } else { ErrorLogger() << "FollowReference : object not a ship, so can't get its fleet"; obj = nullptr; @@ -95,14 +100,16 @@ namespace { return obj; } - // Generates a debug trace that can be included in error logs, augmenting the ReconstructName() info with - // additional info identifying the object references that were successfully followed. - std::string TraceReference(const std::vector& property_name, ValueRef::ReferenceType ref_type, + // Generates a debug trace that can be included in error logs, augmenting + // the ReconstructName() info with additional info identifying the object + // references that were successfully followed. + std::string TraceReference(const std::vector& property_name, + ValueRef::ReferenceType ref_type, const ScriptingContext& context) { std::shared_ptr obj, initial_obj; - std::string retval = ReconstructName(property_name, ref_type) + " : "; - switch(ref_type) { + std::string retval = ReconstructName(property_name, ref_type, false) + " : "; + switch (ref_type) { case ValueRef::NON_OBJECT_REFERENCE: retval += " | Non Object Reference |"; return retval; @@ -132,26 +139,26 @@ namespace { } retval += " | "; - std::vector::const_iterator first = property_name.begin(); - std::vector::const_iterator last = property_name.end(); + auto first = property_name.begin(); + auto last = property_name.end(); while (first != last) { - std::string property_name = *first; - retval += " " + property_name + " "; - if (property_name == "Planet") { - if (std::shared_ptr b = std::dynamic_pointer_cast(obj)) { + std::string property_name_part = *first; + retval += " " + property_name_part + " "; + if (property_name_part == "Planet") { + if (auto b = std::dynamic_pointer_cast(obj)) { retval += "(" + std::to_string(b->PlanetID()) + "): "; - obj = GetPlanet(b->PlanetID()); + obj = context.ContextObjects().get(b->PlanetID()); } else obj = nullptr; - } else if (property_name == "System") { + } else if (property_name_part == "System") { if (obj) { retval += "(" + std::to_string(obj->SystemID()) + "): "; - obj = GetSystem(obj->SystemID()); + obj = context.ContextObjects().get(obj->SystemID()); } - } else if (property_name == "Fleet") { - if (std::shared_ptr s = std::dynamic_pointer_cast(obj)) { + } else if (property_name_part == "Fleet") { + if (auto s = std::dynamic_pointer_cast(obj)) { retval += "(" + std::to_string(s->FleetID()) + "): "; - obj = GetFleet(s->FleetID()); + obj = context.ContextObjects().get(s->FleetID()); } else obj = nullptr; } @@ -190,48 +197,46 @@ namespace { mutable UniverseObjectType m_type; }; -} -namespace { const std::map& GetMeterNameMap() { - static std::map meter_name_map; - if (meter_name_map.empty()) { - // todo: maybe need some thread guards here? - meter_name_map["Population"] = METER_POPULATION; - meter_name_map["TargetPopulation"] = METER_TARGET_POPULATION; - meter_name_map["Industry"] = METER_INDUSTRY; - meter_name_map["TargetIndustry"] = METER_TARGET_INDUSTRY; - meter_name_map["Research"] = METER_RESEARCH; - meter_name_map["TargetResearch"] = METER_TARGET_RESEARCH; - meter_name_map["Trade"] = METER_TRADE; - meter_name_map["TargetTrade"] = METER_TARGET_TRADE; - meter_name_map["Construction"] = METER_CONSTRUCTION; - meter_name_map["TargetConstruction"] = METER_TARGET_CONSTRUCTION; - meter_name_map["Happiness"] = METER_HAPPINESS; - meter_name_map["TargetHappiness"] = METER_TARGET_HAPPINESS; - meter_name_map["MaxFuel"] = METER_MAX_FUEL; - meter_name_map["Fuel"] = METER_FUEL; - meter_name_map["MaxStructure"] = METER_MAX_STRUCTURE; - meter_name_map["Structure"] = METER_STRUCTURE; - meter_name_map["MaxShield"] = METER_MAX_SHIELD; - meter_name_map["Shield"] = METER_SHIELD; - meter_name_map["MaxDefense"] = METER_MAX_DEFENSE; - meter_name_map["Defense"] = METER_DEFENSE; - meter_name_map["MaxTroops"] = METER_MAX_TROOPS; - meter_name_map["Troops"] = METER_TROOPS; - meter_name_map["RebelTroops"] = METER_REBEL_TROOPS; - meter_name_map["Supply"] = METER_SUPPLY; - meter_name_map["MaxSupply"] = METER_MAX_SUPPLY; - meter_name_map["Stealth"] = METER_STEALTH; - meter_name_map["Detection"] = METER_DETECTION; - meter_name_map["Speed"] = METER_SPEED; - meter_name_map["Damage"] = METER_CAPACITY; - meter_name_map["Capacity"] = METER_CAPACITY; - meter_name_map["MaxCapacity"] = METER_MAX_CAPACITY; - meter_name_map["SecondaryStat"] = METER_SECONDARY_STAT; - meter_name_map["MaxSecondaryStat"] = METER_MAX_SECONDARY_STAT; - meter_name_map["Size"] = METER_SIZE; - } + static const std::map meter_name_map{ + {"Population", METER_POPULATION}, + {"TargetPopulation", METER_TARGET_POPULATION}, + {"Industry", METER_INDUSTRY}, + {"TargetIndustry", METER_TARGET_INDUSTRY}, + {"Research", METER_RESEARCH}, + {"TargetResearch", METER_TARGET_RESEARCH}, + {"Trade", METER_TRADE}, + {"TargetTrade", METER_TARGET_TRADE}, + {"Construction", METER_CONSTRUCTION}, + {"TargetConstruction", METER_TARGET_CONSTRUCTION}, + {"Happiness", METER_HAPPINESS}, + {"TargetHappiness", METER_TARGET_HAPPINESS}, + {"MaxFuel", METER_MAX_FUEL}, + {"Fuel", METER_FUEL}, + {"MaxStructure", METER_MAX_STRUCTURE}, + {"Structure", METER_STRUCTURE}, + {"MaxShield", METER_MAX_SHIELD}, + {"Shield", METER_SHIELD}, + {"MaxDefense", METER_MAX_DEFENSE}, + {"Defense", METER_DEFENSE}, + {"MaxTroops", METER_MAX_TROOPS}, + {"Troops", METER_TROOPS}, + {"RebelTroops", METER_REBEL_TROOPS}, + {"Supply", METER_SUPPLY}, + {"MaxSupply", METER_MAX_SUPPLY}, + {"Stockpile", METER_STOCKPILE}, + {"MaxStockpile", METER_MAX_STOCKPILE}, + {"Stealth", METER_STEALTH}, + {"Detection", METER_DETECTION}, + {"Speed", METER_SPEED}, + {"Damage", METER_CAPACITY}, + {"Capacity", METER_CAPACITY}, + {"MaxCapacity", METER_MAX_CAPACITY}, + {"SecondaryStat", METER_SECONDARY_STAT}, + {"MaxSecondaryStat", METER_MAX_SECONDARY_STAT}, + {"Size", METER_SIZE} + }; return meter_name_map; } @@ -242,14 +247,14 @@ namespace { namespace ValueRef { MeterType NameToMeter(const std::string& name) { MeterType retval = INVALID_METER_TYPE; - std::map::const_iterator it = GetMeterNameMap().find(name); + auto it = GetMeterNameMap().find(name); if (it != GetMeterNameMap().end()) retval = it->second; return retval; } std::string MeterToName(MeterType meter) { - for (const std::map::value_type& entry : GetMeterNameMap()) { + for (const auto& entry : GetMeterNameMap()) { if (entry.second == meter) return entry.first; } @@ -257,9 +262,14 @@ std::string MeterToName(MeterType meter) { } std::string ReconstructName(const std::vector& property_name, - ReferenceType ref_type) + ReferenceType ref_type, + bool return_immediate_value) { std::string retval; + + if (return_immediate_value) + retval += "Value("; + switch (ref_type) { case SOURCE_REFERENCE: retval = "Source"; break; case EFFECT_TARGET_REFERENCE: retval = "Target"; break; @@ -274,12 +284,144 @@ std::string ReconstructName(const std::vector& property_name, for (const std::string& property_name_part : property_name) { if (!retval.empty()) retval += '.'; - retval += property_name_part.c_str(); + retval += property_name_part; } } + + if (return_immediate_value) + retval += ")"; + + return retval; +} + +std::string FormatedDescriptionPropertyNames(ReferenceType ref_type, + const std::vector& property_names, + bool return_immediate_value) +{ + int num_references = property_names.size(); + if (ref_type == NON_OBJECT_REFERENCE) + num_references--; + for (const std::string& property_name_part : property_names) + if (property_name_part.empty()) + num_references--; + num_references = std::max(0, num_references); + std::string format_string; + switch (num_references) { + case 0: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE0"); break; + case 1: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE1"); break; + case 2: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE2"); break; + case 3: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE3"); break; + case 4: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE4"); break; + case 5: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE5"); break; + case 6: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE6"); break; + default:format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLEMANY"); break; + } + + boost::format formatter = FlexibleFormat(format_string); + + switch (ref_type) { + case SOURCE_REFERENCE: formatter % UserString("DESC_VAR_SOURCE"); break; + case EFFECT_TARGET_REFERENCE: formatter % UserString("DESC_VAR_TARGET"); break; + case EFFECT_TARGET_VALUE_REFERENCE: formatter % UserString("DESC_VAR_VALUE"); break; + case CONDITION_LOCAL_CANDIDATE_REFERENCE: formatter % UserString("DESC_VAR_LOCAL_CANDIDATE"); break; + case CONDITION_ROOT_CANDIDATE_REFERENCE: formatter % UserString("DESC_VAR_ROOT_CANDIDATE"); break; + case NON_OBJECT_REFERENCE: break; + default: formatter % "???"; break; + } + + for (const std::string& property_name_part : property_names) { + if (property_name_part.empty()) // apparently is empty for a EFFECT_TARGET_VALUE_REFERENCE + continue; + std::string stringtable_key("DESC_VAR_" + boost::to_upper_copy(property_name_part)); + formatter % UserString(stringtable_key); + } + + std::string retval = boost::io::str(formatter); + //std::cerr << "ret: " << retval << std::endl; + return retval; +} + +std::string ComplexVariableDescription(const std::vector& property_names, + const ValueRef* int_ref1, + const ValueRef* int_ref2, + const ValueRef* int_ref3, + const ValueRef* string_ref1, + const ValueRef* string_ref2) +{ + if (property_names.empty()) { + ErrorLogger() << "ComplexVariableDescription passed empty property names?!"; + return ""; + } + + std::string stringtable_key("DESC_VAR_" + boost::to_upper_copy(property_names.back())); + if (!UserStringExists(stringtable_key)) + return ""; + + boost::format formatter = FlexibleFormat(UserString(stringtable_key)); + + if (int_ref1) + formatter % int_ref1->Description(); + if (int_ref2) + formatter % int_ref2->Description(); + if (int_ref3) + formatter % int_ref3->Description(); + if (string_ref1) + formatter % string_ref1->Description(); + if (string_ref2) + formatter % string_ref2->Description(); + + return boost::io::str(formatter); +} + +std::string ComplexVariableDump(const std::vector& property_names, + const ValueRef* int_ref1, + const ValueRef* int_ref2, + const ValueRef* int_ref3, + const ValueRef* string_ref1, + const ValueRef* string_ref2) +{ + std::string retval; + if (property_names.empty()) { + ErrorLogger() << "ComplexVariableDump passed empty property names?!"; + return "ComplexVariable"; + } else { + retval += property_names.back(); + } + + // TODO: Depending on the property name, the parameter names will vary. + // Need to handle each case correctly, in order for the Dumped + // text to be parsable as the correct ComplexVariable. + + if (int_ref1) + retval += " int1 = " + int_ref1->Dump(); + if (int_ref2) + retval += " int2 = " + int_ref2->Dump(); + if (int_ref3) + retval += " int3 = " + int_ref3->Dump(); + if (string_ref1) + retval += " string1 = " + string_ref1->Dump(); + if (string_ref2) + retval += " string2 = " + string_ref2->Dump(); + return retval; } +std::string StatisticDescription(StatisticType stat_type, + const std::string& value_desc, + const std::string& condition_desc) +{ + std::string stringtable_key("DESC_VAR_" + boost::to_upper_copy( + boost::lexical_cast(stat_type))); + + if (UserStringExists(stringtable_key)) { + boost::format formatter = FlexibleFormat(stringtable_key); + formatter % value_desc % condition_desc; + return boost::io::str(formatter); + } + + return UserString("DESC_VAR_STATISITIC"); +} + /////////////////////////////////////////////////////////// // Constant // /////////////////////////////////////////////////////////// @@ -304,7 +446,7 @@ std::string Constant::Description() const } template <> -std::string Constant::Dump() const +std::string Constant::Dump(unsigned short ntabs) const { switch (m_value) { case SZ_TINY: return "Tiny"; @@ -319,7 +461,7 @@ std::string Constant::Dump() const } template <> -std::string Constant::Dump() const +std::string Constant::Dump(unsigned short ntabs) const { switch (m_value) { case PT_SWAMP: return "Swamp"; @@ -338,7 +480,7 @@ std::string Constant::Dump() const } template <> -std::string Constant::Dump() const +std::string Constant::Dump(unsigned short ntabs) const { switch (m_value) { case PE_UNINHABITABLE: return "Uninhabitable"; @@ -351,7 +493,7 @@ std::string Constant::Dump() const } template <> -std::string Constant::Dump() const +std::string Constant::Dump(unsigned short ntabs) const { switch (m_value) { case OBJ_BUILDING: return "Building"; @@ -367,7 +509,7 @@ std::string Constant::Dump() const } template <> -std::string Constant::Dump() const +std::string Constant::Dump(unsigned short ntabs) const { switch (m_value) { case STAR_BLUE: return "Blue"; @@ -383,15 +525,27 @@ std::string Constant::Dump() const } template <> -std::string Constant::Dump() const -{ return Description(); } +std::string Constant::Dump(unsigned short ntabs) const +{ + switch (m_value) { + case VIS_NO_VISIBILITY: return "Invisible"; + case VIS_BASIC_VISIBILITY: return "Basic"; + case VIS_PARTIAL_VISIBILITY:return "Partial"; + case VIS_FULL_VISIBILITY: return "Full"; + default: return "Unknown"; + } +} + +template <> +std::string Constant::Dump(unsigned short ntabs) const +{ return std::to_string(m_value); } template <> -std::string Constant::Dump() const -{ return Description(); } +std::string Constant::Dump(unsigned short ntabs) const +{ return std::to_string(m_value); } template <> -std::string Constant::Dump() const +std::string Constant::Dump(unsigned short ntabs) const { return "\"" + Description() + "\""; } template <> @@ -405,53 +559,7 @@ std::string Constant::Eval(const ScriptingContext& context) const /////////////////////////////////////////////////////////// // Variable // /////////////////////////////////////////////////////////// -std::string FormatedDescriptionPropertyNames(ReferenceType ref_type, - const std::vector& property_names) -{ - int num_references = property_names.size(); - if (ref_type == NON_OBJECT_REFERENCE) - num_references--; - for (const std::string& property_name_part : property_names) - if (property_name_part.empty()) - num_references--; - num_references = std::max(0, num_references); - std::string format_string; - switch (num_references) { - case 0: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE0"); break; - case 1: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE1"); break; - case 2: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE2"); break; - case 3: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE3"); break; - case 4: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE4"); break; - case 5: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE5"); break; - case 6: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLE6"); break; - default: format_string = UserString("DESC_VALUE_REF_MULTIPART_VARIABLEMANY"); break; - } - - boost::format formatter = FlexibleFormat(format_string); - - switch (ref_type) { - case SOURCE_REFERENCE: formatter % UserString("DESC_VAR_SOURCE"); break; - case EFFECT_TARGET_REFERENCE: formatter % UserString("DESC_VAR_TARGET"); break; - case EFFECT_TARGET_VALUE_REFERENCE: formatter % UserString("DESC_VAR_VALUE"); break; - case CONDITION_LOCAL_CANDIDATE_REFERENCE: formatter % UserString("DESC_VAR_LOCAL_CANDIDATE"); break; - case CONDITION_ROOT_CANDIDATE_REFERENCE: formatter % UserString("DESC_VAR_ROOT_CANDIDATE"); break; - case NON_OBJECT_REFERENCE: break; - default: formatter % "???"; break; - } - - for (const std::string& property_name_part : property_names) { - if (property_name_part.empty()) // apparently is empty for a EFFECT_TARGET_VALUE_REFERENCE - continue; - std::string stringtable_key("DESC_VAR_" + boost::to_upper_copy(property_name_part)); - formatter % UserString(stringtable_key); - } - - std::string retval = boost::io::str(formatter); - //std::cerr << "ret: " << retval << std::endl; - return retval; -} - -#define IF_CURRENT_VALUE(T) \ +#define IF_CURRENT_VALUE(T) \ if (m_ref_type == EFFECT_TARGET_VALUE_REFERENCE) { \ if (context.current_value.empty()) \ throw std::runtime_error( \ @@ -466,34 +574,48 @@ if (m_ref_type == EFFECT_TARGET_VALUE_REFERENCE) { \ } \ } +#define LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(T) \ +ErrorLogger() << "Variable<" #T ">::Eval unrecognized object " \ + "property: " \ + << TraceReference(m_property_name, m_ref_type, context); \ +if (context.source) \ + ErrorLogger() << "source: " << context.source->ObjectType() << " " \ + << context.source->ID() << " ( " \ + << context.source->Name() << " ) "; \ +else \ + ErrorLogger() << "source (none)"; + template <> PlanetSize Variable::Eval(const ScriptingContext& context) const { - const std::string& property_name = m_property_name.back(); + const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back(); IF_CURRENT_VALUE(PlanetSize) - std::shared_ptr object = FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); + auto object = FollowReference(m_property_name.begin(), m_property_name.end(), + m_ref_type, context); if (!object) { ErrorLogger() << "Variable::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context); return INVALID_PLANET_SIZE; } - if (std::shared_ptr p = std::dynamic_pointer_cast(object)) { - if (property_name == "PlanetSize") + if (property_name == "PlanetSize") { + if (auto p = std::dynamic_pointer_cast(object)) return p->Size(); - else if (property_name == "NextLargerPlanetSize") + return INVALID_PLANET_SIZE; + + } else if (property_name == "NextLargerPlanetSize") { + if (auto p = std::dynamic_pointer_cast(object)) return p->NextLargerPlanetSize(); - else if (property_name == "NextSmallerPlanetSize") + return INVALID_PLANET_SIZE; + + } else if (property_name == "NextSmallerPlanetSize") { + if (auto p = std::dynamic_pointer_cast(object)) return p->NextSmallerPlanetSize(); + return INVALID_PLANET_SIZE; } - ErrorLogger() << "Variable::Eval unrecognized object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(PlanetSize) return INVALID_PLANET_SIZE; } @@ -501,37 +623,49 @@ PlanetSize Variable::Eval(const ScriptingContext& context) const template <> PlanetType Variable::Eval(const ScriptingContext& context) const { - const std::string& property_name = m_property_name.back(); + const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back(); IF_CURRENT_VALUE(PlanetType) - std::shared_ptr object = FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); + auto object = FollowReference(m_property_name.begin(), m_property_name.end(), + m_ref_type, context); if (!object) { ErrorLogger() << "Variable::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context); return INVALID_PLANET_TYPE; } - if (std::shared_ptr p = std::dynamic_pointer_cast(object)) { - if (property_name == "PlanetType") + if (property_name == "PlanetType") { + if (auto p = std::dynamic_pointer_cast(object)) return p->Type(); - else if (property_name == "OriginalType") + return INVALID_PLANET_TYPE; + + } else if (property_name == "OriginalType") { + if (auto p = std::dynamic_pointer_cast(object)) return p->OriginalType(); - else if (property_name == "NextCloserToOriginalPlanetType") + return INVALID_PLANET_TYPE; + + } else if (property_name == "NextCloserToOriginalPlanetType") { + if (auto p = std::dynamic_pointer_cast(object)) return p->NextCloserToOriginalPlanetType(); - else if (property_name == "NextBetterPlanetType") + return INVALID_PLANET_TYPE; + + } else if (property_name == "NextBetterPlanetType") { + if (auto p = std::dynamic_pointer_cast(object)) return p->NextBetterPlanetTypeForSpecies(); - else if (property_name == "ClockwiseNextPlanetType") + return INVALID_PLANET_TYPE; + + } else if (property_name == "ClockwiseNextPlanetType") { + if (auto p = std::dynamic_pointer_cast(object)) return p->ClockwiseNextPlanetType(); - else if (property_name == "CounterClockwiseNextPlanetType") + return INVALID_PLANET_TYPE; + + } else if (property_name == "CounterClockwiseNextPlanetType") { + if (auto p = std::dynamic_pointer_cast(object)) return p->CounterClockwiseNextPlanetType(); + return INVALID_PLANET_TYPE; } - ErrorLogger() << "Variable::Eval unrecognized object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(PlanetType) return INVALID_PLANET_TYPE; } @@ -539,26 +673,23 @@ PlanetType Variable::Eval(const ScriptingContext& context) const template <> PlanetEnvironment Variable::Eval(const ScriptingContext& context) const { - const std::string& property_name = m_property_name.back(); + const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back(); IF_CURRENT_VALUE(PlanetEnvironment) if (property_name == "PlanetEnvironment") { - std::shared_ptr object = FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); + auto object = FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); if (!object) { ErrorLogger() << "Variable::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context); return INVALID_PLANET_ENVIRONMENT; } - if (std::shared_ptr p = std::dynamic_pointer_cast(object)) + if (auto p = std::dynamic_pointer_cast(object)) return p->EnvironmentForSpecies(); + + return INVALID_PLANET_ENVIRONMENT; } - ErrorLogger() << "Variable::Eval unrecognized object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(PlanetEnvironment) return INVALID_PLANET_ENVIRONMENT; } @@ -566,12 +697,12 @@ PlanetEnvironment Variable::Eval(const ScriptingContext& cont template <> UniverseObjectType Variable::Eval(const ScriptingContext& context) const { - const std::string& property_name = m_property_name.back(); + const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back(); IF_CURRENT_VALUE(UniverseObjectType) if (property_name == "ObjectType") { - std::shared_ptr object = FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); + auto object = FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); if (!object) { ErrorLogger() << "Variable::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context); return INVALID_UNIVERSE_OBJECT_TYPE; @@ -583,14 +714,11 @@ UniverseObjectType Variable::Eval(const ScriptingContext& co return OBJ_POP_CENTER; else if (std::dynamic_pointer_cast(object)) return OBJ_PROD_CENTER; + + return INVALID_UNIVERSE_OBJECT_TYPE; } - ErrorLogger() << "Variable::Eval unrecognized object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(UniverseObjectType) return INVALID_UNIVERSE_OBJECT_TYPE; } @@ -598,39 +726,56 @@ UniverseObjectType Variable::Eval(const ScriptingContext& co template <> StarType Variable::Eval(const ScriptingContext& context) const { - const std::string& property_name = m_property_name.back(); + const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back(); IF_CURRENT_VALUE(StarType) - std::shared_ptr object = FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); + auto object = FollowReference(m_property_name.begin(), m_property_name.end(), + m_ref_type, context); if (!object) { ErrorLogger() << "Variable::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context); return INVALID_STAR_TYPE; } - if (std::shared_ptr s = std::dynamic_pointer_cast(object)) { - if (property_name == "StarType") + if (property_name == "StarType") { + if (auto s = std::dynamic_pointer_cast(object)) return s->GetStarType(); - else if (property_name == "NextOlderStarType") + return INVALID_STAR_TYPE; + + } else if (property_name == "NextOlderStarType") { + if (auto s = std::dynamic_pointer_cast(object)) return s->NextOlderStarType(); - else if (property_name == "NextYoungerStarType") + return INVALID_STAR_TYPE; + + } else if (property_name == "NextYoungerStarType") { + if (auto s = std::dynamic_pointer_cast(object)) return s->NextYoungerStarType(); + return INVALID_STAR_TYPE; } - ErrorLogger() << "Variable::Eval unrecognized object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << std::to_string(context.source->ID()) << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(StarType) return INVALID_STAR_TYPE; } +template <> +Visibility Variable::Eval(const ScriptingContext& context) const +{ + IF_CURRENT_VALUE(Visibility) + + // As of this writing, there are no properties of objects that directly + // return a Visibility, as it will normally need to be queried for a + // particular empire + + ErrorLogger() << "Variable::Eval unrecognized object property: " << TraceReference(m_property_name, m_ref_type, context); + + return INVALID_VISIBILITY; +} + template <> double Variable::Eval(const ScriptingContext& context) const { - const std::string& property_name = m_property_name.back(); + const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back(); IF_CURRENT_VALUE(float) @@ -644,39 +789,29 @@ double Variable::Eval(const ScriptingContext& context) const } // add more non-object reference double functions here - ErrorLogger() << "Variable::Eval unrecognized non-object property: " - << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(float) return 0.0; } - std::shared_ptr object = - FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); + auto object = FollowReference(m_property_name.begin(), m_property_name.end(), + m_ref_type, context); if (!object) { - ErrorLogger() << "Variable::Eval unable to follow reference: " - << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(float) return 0.0; } MeterType meter_type = NameToMeter(property_name); if (object && meter_type != INVALID_METER_TYPE) { - if (object->GetMeter(meter_type)) - return object->InitialMeterValue(meter_type); - - } else if (property_name == "TradeStockpile") { - if (const Empire* empire = GetEmpire(object->Owner())) - return empire->ResourceStockpile(RE_TRADE); + if (object->GetMeter(meter_type)) { + if (m_return_immediate_value) + return object->CurrentMeterValue(meter_type); + else + return object->InitialMeterValue(meter_type); + } + return 0.0; } else if (property_name == "X") { return object->X(); @@ -685,48 +820,51 @@ double Variable::Eval(const ScriptingContext& context) const return object->Y(); } else if (property_name == "SizeAsDouble") { - if (std::shared_ptr planet = std::dynamic_pointer_cast(object)) - return planet->SizeAsInt(); + if (auto planet = std::dynamic_pointer_cast(object)) + return planet->Size(); + return 0.0; + + } else if (property_name == "HabitableSize") { + if (auto planet = std::dynamic_pointer_cast(object)) + return planet->HabitableSize(); + return 0.0; } else if (property_name == "DistanceFromOriginalType") { - if (std::shared_ptr planet = std::dynamic_pointer_cast(object)) + if (auto planet = std::dynamic_pointer_cast(object)) return planet->DistanceFromOriginalType(); + return 0.0; - } else if (property_name == "NextTurnPopGrowth") { - if (std::shared_ptr pop = std::dynamic_pointer_cast(object)) - return pop->NextTurnPopGrowth(); + } else if (property_name == "CombatBout") { + return context.combat_info.bout; } else if (property_name == "CurrentTurn") { return CurrentTurn(); } else if (property_name == "Attack") { - if (std::shared_ptr fleet = std::dynamic_pointer_cast(object)) + if (auto fleet = std::dynamic_pointer_cast(object)) return fleet->Damage(); - if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) + if (auto ship = std::dynamic_pointer_cast(object)) return ship->TotalWeaponsDamage(); + if (auto fighter = std::dynamic_pointer_cast(object)) + return fighter->Damage(); + return 0.0; } else if (property_name == "PropagatedSupplyRange") { - const std::map& ranges = GetSupplyManager().PropagatedSupplyRanges(); - std::map::const_iterator range_it = ranges.find(object->SystemID()); + const auto& ranges = GetSupplyManager().PropagatedSupplyRanges(); + auto range_it = ranges.find(object->SystemID()); if (range_it == ranges.end()) return 0.0; return range_it->second; } else if (property_name == "PropagatedSupplyDistance") { - const std::map& ranges = GetSupplyManager().PropagatedSupplyDistances(); - std::map::const_iterator range_it = ranges.find(object->SystemID()); + const auto& ranges = GetSupplyManager().PropagatedSupplyDistances(); + auto range_it = ranges.find(object->SystemID()); if (range_it == ranges.end()) return 0.0; return range_it->second; } - ErrorLogger() << "Variable::Eval unrecognized object property: " - << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(float) return 0.0; } @@ -734,11 +872,13 @@ double Variable::Eval(const ScriptingContext& context) const template <> int Variable::Eval(const ScriptingContext& context) const { - const std::string& property_name = m_property_name.back(); + const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back(); IF_CURRENT_VALUE(int) if (m_ref_type == NON_OBJECT_REFERENCE) { + if (property_name == "CombatBout") + return context.combat_info.bout; if (property_name == "CurrentTurn") return CurrentTurn(); if (property_name == "GalaxySize") @@ -760,198 +900,231 @@ int Variable::Eval(const ScriptingContext& context) const if (property_name == "GalaxyMaxAIAggression") return static_cast(GetGalaxySetupData().GetAggression()); + // non-object values passed by abuse of context.current_value + if (property_name == "UsedInDesignID") { + // check if an int was passed as the current_value, as would be + // done when evaluating a ValueRef for the cost or production + // time of a part or hull in a ship design. this should be the id + // of the design. + try { + return boost::any_cast(context.current_value); + } catch (...) { + ErrorLogger() << "Variable::Eval could get ship design id for property: " << TraceReference(m_property_name, m_ref_type, context); + } + return 0; + } + // add more non-object reference int functions here - ErrorLogger() << "Variable::Eval unrecognized non-object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(int) return 0; } - std::shared_ptr object = - FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); + auto object = FollowReference(m_property_name.begin(), m_property_name.end(), + m_ref_type, context); if (!object) { - ErrorLogger() << "Variable::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(int) return 0; } if (property_name == "Owner") { return object->Owner(); - } else if (property_name == "SupplyingEmpire") { + } + else if (property_name == "SupplyingEmpire") { return GetSupplyManager().EmpireThatCanSupplyAt(object->SystemID()); - } else if (property_name == "ID") { + } + else if (property_name == "ID") { return object->ID(); - } else if (property_name == "CreationTurn") { + } + else if (property_name == "CreationTurn") { return object->CreationTurn(); - } else if (property_name == "Age") { + } + else if (property_name == "Age") { return object->AgeInTurns(); - } else if (property_name == "TurnsSinceFocusChange") { - if (std::shared_ptr planet = std::dynamic_pointer_cast(object)) + } + else if (property_name == "TurnsSinceFocusChange") { + if (auto planet = std::dynamic_pointer_cast(object)) return planet->TurnsSinceFocusChange(); - else - return 0; + return 0; - } else if (property_name == "ProducedByEmpireID") { - if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) + } + else if (property_name == "ProducedByEmpireID") { + if (auto ship = std::dynamic_pointer_cast(object)) return ship->ProducedByEmpireID(); - else if (std::shared_ptr building = std::dynamic_pointer_cast(object)) + else if (auto building = std::dynamic_pointer_cast(object)) return building->ProducedByEmpireID(); - else - return ALL_EMPIRES; + return ALL_EMPIRES; - } else if (property_name == "ArrivedOnTurn") { - if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) + } + else if (property_name == "ArrivedOnTurn") { + if (auto ship = std::dynamic_pointer_cast(object)) return ship->ArrivedOnTurn(); - else - return INVALID_GAME_TURN; + return INVALID_GAME_TURN; - } else if (property_name == "DesignID") { - if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) + } + else if (property_name == "DesignID") { + if (auto ship = std::dynamic_pointer_cast(object)) return ship->DesignID(); - else - return INVALID_DESIGN_ID; + return INVALID_DESIGN_ID; - } else if (property_name == "SpeciesID") { - if (std::shared_ptr planet = std::dynamic_pointer_cast(object)) + } + else if (property_name == "SpeciesID") { + if (auto planet = std::dynamic_pointer_cast(object)) return GetSpeciesManager().GetSpeciesID(planet->SpeciesName()); - else if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) + else if (auto ship = std::dynamic_pointer_cast(object)) return GetSpeciesManager().GetSpeciesID(ship->SpeciesName()); - else - return -1; + return -1; - } else if (property_name == "FleetID") { - if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) + } + else if (property_name == "FleetID") { + if (auto ship = std::dynamic_pointer_cast(object)) return ship->FleetID(); - else if (std::shared_ptr fleet = std::dynamic_pointer_cast(object)) + else if (auto fleet = std::dynamic_pointer_cast(object)) return fleet->ID(); - else - return INVALID_OBJECT_ID; + return INVALID_OBJECT_ID; - } else if (property_name == "PlanetID") { - if (std::shared_ptr building = std::dynamic_pointer_cast(object)) + } + else if (property_name == "PlanetID") { + if (auto building = std::dynamic_pointer_cast(object)) return building->PlanetID(); - else if (std::shared_ptr planet = std::dynamic_pointer_cast(object)) + else if (auto planet = std::dynamic_pointer_cast(object)) return planet->ID(); - else - return INVALID_OBJECT_ID; + return INVALID_OBJECT_ID; - } else if (property_name == "SystemID") { + } + else if (property_name == "SystemID") { return object->SystemID(); - } else if (property_name == "FinalDestinationID") { - if (std::shared_ptr fleet = std::dynamic_pointer_cast(object)) + } + else if (property_name == "FinalDestinationID") { + if (auto fleet = std::dynamic_pointer_cast(object)) return fleet->FinalDestinationID(); - else - return INVALID_OBJECT_ID; + return INVALID_OBJECT_ID; - } else if (property_name == "NextSystemID") { - if (std::shared_ptr fleet = std::dynamic_pointer_cast(object)) + } + else if (property_name == "NextSystemID") { + if (auto fleet = std::dynamic_pointer_cast(object)) return fleet->NextSystemID(); - else - return INVALID_OBJECT_ID; + return INVALID_OBJECT_ID; - } else if (property_name == "PreviousSystemID") { - if (std::shared_ptr fleet = std::dynamic_pointer_cast(object)) + } + else if (property_name == "PreviousSystemID") { + if (auto fleet = std::dynamic_pointer_cast(object)) return fleet->PreviousSystemID(); - else - return INVALID_OBJECT_ID; + return INVALID_OBJECT_ID; + + } + else if (property_name == "ArrivalStarlaneID") { + if (auto fleet = std::dynamic_pointer_cast(object)) + return fleet->ArrivalStarlane(); + return INVALID_OBJECT_ID; - } else if (property_name == "NearestSystemID") { + } + else if (property_name == "NearestSystemID") { if (object->SystemID() != INVALID_OBJECT_ID) return object->SystemID(); - return GetPathfinder()->NearestSystemTo(object->X(), object->Y()); - } else if (property_name == "NumShips") { - if (std::shared_ptr fleet = std::dynamic_pointer_cast(object)) + } + else if (property_name == "NumShips") { + if (auto fleet = std::dynamic_pointer_cast(object)) return fleet->NumShips(); - else - return 0; + return 0; - } else if (property_name == "NumStarlanes") { - if (std::shared_ptr system = std::dynamic_pointer_cast(object)) + } + else if (property_name == "NumStarlanes") { + if (auto system = std::dynamic_pointer_cast(object)) return system->NumStarlanes(); - else - return 0; + return 0; - } else if (property_name == "LastTurnBattleHere") { - if (std::shared_ptr system = std::dynamic_pointer_cast(object)) - return system->LastTurnBattleHere(); - else if (std::shared_ptr system = GetSystem(object->SystemID())) + } + else if (property_name == "LastTurnBattleHere") { + if (auto const_system = std::dynamic_pointer_cast(object)) + return const_system->LastTurnBattleHere(); + else if (auto system = context.ContextObjects().get(object->SystemID())) return system->LastTurnBattleHere(); - else - return INVALID_GAME_TURN; + return INVALID_GAME_TURN; - } else if (property_name == "LastTurnActiveInBattle") { - if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) + } + else if (property_name == "LastTurnActiveInBattle") { + if (auto ship = std::dynamic_pointer_cast(object)) return ship->LastTurnActiveInCombat(); - else - return INVALID_GAME_TURN; + return INVALID_GAME_TURN; + + } + else if (property_name == "LastTurnAttackedByShip") { + if (auto planet = std::dynamic_pointer_cast(object)) + return planet->LastTurnAttackedByShip(); + return INVALID_GAME_TURN; + + } + else if (property_name == "LastTurnColonized") { + if (auto planet = std::dynamic_pointer_cast(object)) + return planet->LastTurnColonized(); + return INVALID_GAME_TURN; - } else if (property_name == "Orbit") { - if (std::shared_ptr system = GetSystem(object->SystemID())) + } + else if (property_name == "LastTurnConquered") { + if (auto planet = std::dynamic_pointer_cast(object)) + return planet->LastTurnConquered(); + return INVALID_GAME_TURN; + + } + else if (property_name == "LastTurnResupplied") { + if (auto ship = std::dynamic_pointer_cast(object)) + return ship->LastResuppliedOnTurn(); + return INVALID_GAME_TURN; + + } + else if (property_name == "Orbit") { + if (auto system = context.ContextObjects().get(object->SystemID())) return system->OrbitOfPlanet(object->ID()); return -1; - } else if (property_name == "ETA") { - if (std::shared_ptr fleet = std::dynamic_pointer_cast(object)) + } + else if (property_name == "ETA") { + if (auto fleet = std::dynamic_pointer_cast(object)) return fleet->ETA().first; - else - return 0; + return 0; - } else if (property_name == "NumSpecials") { + } + else if (property_name == "NumSpecials") { return object->Specials().size(); + + } + else if (property_name == "LaunchedFrom") { + if (auto fighter = std::dynamic_pointer_cast(object)) + return fighter->LaunchedFrom(); + return INVALID_OBJECT_ID; } - ErrorLogger() << "Variable::Eval unrecognized object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(int) return 0; } template <> -std::vector Variable>::Eval(const ScriptingContext& context) const +std::vector Variable>::Eval( + const ScriptingContext& context) const { - const std::string& property_name = m_property_name.back(); + const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back(); IF_CURRENT_VALUE(std::vector) - if (m_ref_type == NON_OBJECT_REFERENCE) { - // add more non-object reference int functions here - ErrorLogger() << "std::vector::Eval unrecognized non-object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + // add more non-object reference string vector functions here + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::vector) return {}; } - std::shared_ptr object = - FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); + auto object = FollowReference(m_property_name.begin(), m_property_name.end(), + m_ref_type, context); if (!object) { - ErrorLogger() << "Variable::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::vector) return {}; } @@ -969,21 +1142,18 @@ std::vector Variable>::Eval(const Scriptin return retval; } else if (property_name == "AvailableFoci") { - if (std::shared_ptr planet = std::dynamic_pointer_cast(object)) + if (auto planet = std::dynamic_pointer_cast(object)) return planet->AvailableFoci(); + return {}; } else if (property_name == "Parts") { - if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) + if (auto ship = std::dynamic_pointer_cast(object)) if (const ShipDesign* design = ship->Design()) return design->Parts(); + return {}; } - ErrorLogger() << "std::vector::Eval unrecognized object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::vector) return {}; } @@ -991,7 +1161,7 @@ std::vector Variable>::Eval(const Scriptin template <> std::string Variable::Eval(const ScriptingContext& context) const { - const std::string& property_name = m_property_name.back(); + const std::string& property_name = m_property_name.empty() ? "" : m_property_name.back(); IF_CURRENT_VALUE(std::string) @@ -999,25 +1169,16 @@ std::string Variable::Eval(const ScriptingContext& context) const if (property_name == "GalaxySeed") return GetGalaxySetupData().GetSeed(); - ErrorLogger() << "Variable::Eval unrecognized non-object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + // add more non-object reference string functions here + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::string) return ""; } - std::shared_ptr object = - FollowReference(m_property_name.begin(), m_property_name.end(), m_ref_type, context); + auto object = FollowReference(m_property_name.begin(), m_property_name.end(), + m_ref_type, context); if (!object) { - ErrorLogger() << "Variable::Eval unable to follow reference: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::string) return ""; } @@ -1035,36 +1196,47 @@ std::string Variable::Eval(const ScriptingContext& context) const return boost::lexical_cast(object->ObjectType()); } else if (property_name == "Species") { - if (std::shared_ptr planet = std::dynamic_pointer_cast(object)) + if (auto planet = std::dynamic_pointer_cast(object)) return planet->SpeciesName(); - else if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) + else if (auto ship = std::dynamic_pointer_cast(object)) return ship->SpeciesName(); + else if (auto fighter = std::dynamic_pointer_cast(object)) + return fighter->SpeciesName(); + return ""; } else if (property_name == "Hull") { - if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) + if (auto ship = std::dynamic_pointer_cast(object)) if (const ShipDesign* design = ship->Design()) return design->Hull(); + return ""; + + } else if (property_name == "FieldType") { + if (auto field = std::dynamic_pointer_cast(object)) + return field->FieldTypeName(); + return ""; } else if (property_name == "BuildingType") { - if (std::shared_ptr building = std::dynamic_pointer_cast(object)) + if (auto building = std::dynamic_pointer_cast(object)) return building->BuildingTypeName(); + return ""; } else if (property_name == "Focus") { - if (std::shared_ptr planet = std::dynamic_pointer_cast(object)) + if (auto planet = std::dynamic_pointer_cast(object)) return planet->Focus(); + return ""; } else if (property_name == "PreferredFocus") { const Species* species = nullptr; - if (std::shared_ptr planet = std::dynamic_pointer_cast(object)) { + if (auto planet = std::dynamic_pointer_cast(object)) { species = GetSpecies(planet->SpeciesName()); - } else if (std::shared_ptr ship = std::dynamic_pointer_cast(object)) { + } else if (auto ship = std::dynamic_pointer_cast(object)) { species = GetSpecies(ship->SpeciesName()); } if (species) return species->PreferredFocus(); return ""; - } else if (property_name == "OwnerMostExpensiveEnqueuedTech") { + } else if (property_name == "OwnerLeastExpensiveEnqueuedTech") { const Empire* empire = GetEmpire(object->Owner()); if (!empire) return ""; @@ -1095,12 +1267,7 @@ std::string Variable::Eval(const ScriptingContext& context) const return empire->TopPriorityEnqueuedTech(); } - ErrorLogger() << "Variable::Eval unrecognized object property: " << TraceReference(m_property_name, m_ref_type, context); - if (context.source) - ErrorLogger() << "source: " << context.source->ObjectType() << " " - << context.source->ID() << " ( " << context.source->Name() << " ) "; - else - ErrorLogger() << "source (none)"; + LOG_UNKNOWN_VARIABLE_PROPERTY_TRACE(std::string) return ""; } @@ -1114,7 +1281,7 @@ template <> double Statistic::Eval(const ScriptingContext& context) const { Condition::ObjectSet condition_matches; - GetConditionMatches(context, condition_matches, m_sampling_condition); + GetConditionMatches(context, condition_matches, m_sampling_condition.get()); // these two statistic types don't depend on the object property values, // so can be evaluated without getting those values. @@ -1134,7 +1301,7 @@ template <> int Statistic::Eval(const ScriptingContext& context) const { Condition::ObjectSet condition_matches; - GetConditionMatches(context, condition_matches, m_sampling_condition); + GetConditionMatches(context, condition_matches, m_sampling_condition.get()); // these two statistic types don't depend on the object property values, // so can be evaluated without getting those values. @@ -1153,32 +1320,41 @@ int Statistic::Eval(const ScriptingContext& context) const template <> std::string Statistic::Eval(const ScriptingContext& context) const { - // the only statistic that can be computed on non-number property types - // and that is itself of a non-number type is the most common value - if (m_stat_type != MODE) - throw std::runtime_error("ValueRef evaluated with an invalid StatisticType for the return type (string)."); - Condition::ObjectSet condition_matches; - GetConditionMatches(context, condition_matches, m_sampling_condition); + GetConditionMatches(context, condition_matches, m_sampling_condition.get()); if (condition_matches.empty()) return ""; + // special case for IF statistic... return a non-empty string for true + if (m_stat_type == IF) + return " "; // not an empty string + + // todo: consider allowing MAX and MIN using string sorting? + + // the only other statistic that can be computed on non-number property + // types and that is itself of a non-number type is the most common value + if (m_stat_type != MODE) { + ErrorLogger() << "Statistic::Eval has invalid statistic type: " + << m_stat_type; + return ""; + } + // evaluate property for each condition-matched object std::map, std::string> object_property_values; GetObjectPropertyValues(context, condition_matches, object_property_values); // count number of each result, tracking which has the most occurances std::map histogram; - std::map::const_iterator most_common_property_value_it = histogram.begin(); + auto most_common_property_value_it = histogram.begin(); unsigned int max_seen(0); - for (const std::map, std::string>::value_type& entry : object_property_values) { + for (const auto& entry : object_property_values) { const std::string& property_value = entry.second; - std::map::iterator hist_it = histogram.find(property_value); + auto hist_it = histogram.find(property_value); if (hist_it == histogram.end()) - hist_it = histogram.insert(std::make_pair(property_value, 0)).first; + hist_it = histogram.insert({property_value, 0}).first; unsigned int& num_seen = hist_it->second; num_seen++; @@ -1202,11 +1378,29 @@ PlanetSize ComplexVariable::Eval(const ScriptingContext& context) co template <> PlanetType ComplexVariable::Eval(const ScriptingContext& context) const -{ return INVALID_PLANET_TYPE; } +{ return INVALID_PLANET_TYPE; } // TODO: Species favourite planet type? template <> PlanetEnvironment ComplexVariable::Eval(const ScriptingContext& context) const -{ return INVALID_PLANET_ENVIRONMENT; } +{ + const std::string& variable_name = m_property_name.back(); + + if (variable_name == "PlanetEnvironmentForSpecies") { + int planet_id = INVALID_OBJECT_ID; + if (m_int_ref1) + planet_id = m_int_ref1->Eval(context); + const auto planet = context.ContextObjects().get(planet_id); + if (!planet) + return INVALID_PLANET_ENVIRONMENT; + + std::string species_name; + if (m_string_ref1) + species_name = m_string_ref1->Eval(context); + return planet->EnvironmentForSpecies(species_name); + } + + return INVALID_PLANET_ENVIRONMENT; +} template <> UniverseObjectType ComplexVariable::Eval(const ScriptingContext& context) const @@ -1216,6 +1410,32 @@ template <> StarType ComplexVariable::Eval(const ScriptingContext& context) const { return INVALID_STAR_TYPE; } +template <> +Visibility ComplexVariable::Eval(const ScriptingContext& context) const +{ + const std::string& variable_name = m_property_name.back(); + + if (variable_name == "EmpireObjectVisiblity") { + int empire_id = ALL_EMPIRES; + if (m_int_ref1) { + empire_id = m_int_ref1->Eval(context); + if (empire_id == ALL_EMPIRES) + return VIS_NO_VISIBILITY; + } + + int object_id = INVALID_OBJECT_ID; + if (m_int_ref2) { + object_id = m_int_ref2->Eval(context); + if (object_id == INVALID_OBJECT_ID) + return VIS_NO_VISIBILITY; + } + + return GetUniverse().GetObjectVisibilityByEmpire(object_id, empire_id); + } + + return INVALID_VISIBILITY; +} + namespace { static std::map EMPTY_STRING_INT_MAP; static std::map EMPTY_INT_INT_MAP; @@ -1251,7 +1471,9 @@ namespace { if (parsed_map_name == "SpeciesShipsScrapped") return empire->SpeciesShipsScrapped(); if (parsed_map_name == "ShipPartsOwned") - return empire->ShipPartTypesOwned(); + return empire->ShipPartsOwned(); + if (parsed_map_name == "TurnTechResearched") + return empire->ResearchedTechs(); return EMPTY_STRING_INT_MAP; } @@ -1269,6 +1491,8 @@ namespace { return empire->ShipDesignsLost(); if (parsed_map_name == "ShipDesignsOwned") return empire->ShipDesignsOwned(); + if (parsed_map_name == "ShipDesignsInProduction") + return empire->ShipDesignsInProduction(); if (parsed_map_name == "ShipDesignsProduced") return empire->ShipDesignsProduced(); if (parsed_map_name == "ShipDesignsScrapped") @@ -1314,17 +1538,17 @@ namespace { // single empire if (empire_id != ALL_EMPIRES) { - const std::map& map = GetEmpireStringIntMap(empire_id, parsed_property_name); - std::map::const_iterator it = map.find(map_key); + const auto& map = GetEmpireStringIntMap(empire_id, parsed_property_name); + auto it = map.find(map_key); if (it == map.end()) return 0; return it->second; } // all empires summed - for (std::map::value_type& entry : Empires()) { - const std::map& map = GetEmpireStringIntMap(entry.first, parsed_property_name); - std::map::const_iterator map_it = map.find(map_key); + for (auto& entry : Empires()) { + const auto& map = GetEmpireStringIntMap(entry.first, parsed_property_name); + auto map_it = map.find(map_key); if (map_it != map.end()) sum += map_it->second; } @@ -1338,14 +1562,14 @@ namespace { // single empire if (empire_id != ALL_EMPIRES) { // sum of all key entries for this empire - for (const std::map::value_type& entry : GetEmpireStringIntMap(empire_id, parsed_property_name)) + for (const auto& entry : GetEmpireStringIntMap(empire_id, parsed_property_name)) sum += entry.second; return sum; } // all empires summed - for (const std::map::value_type& empire_entry : Empires()) { - for (const std::map::value_type& property_entry : GetEmpireStringIntMap(empire_entry.first, parsed_property_name)) + for (const auto& empire_entry : Empires()) { + for (const auto& property_entry : GetEmpireStringIntMap(empire_entry.first, parsed_property_name)) sum += property_entry.second; } return sum; @@ -1359,17 +1583,17 @@ namespace { // single empire if (empire_id != ALL_EMPIRES) { - const std::map& map = GetEmpireIntIntMap(empire_id, parsed_property_name); - std::map::const_iterator it = map.find(map_key); + const auto& map = GetEmpireIntIntMap(empire_id, parsed_property_name); + auto it = map.find(map_key); if (it == map.end()) return 0; return it->second; } // all empires summed - for (std::map::value_type& empire_entry : Empires()) { - const std::map& map = GetEmpireIntIntMap(empire_entry.first, parsed_property_name); - std::map::const_iterator map_it = map.find(map_key); + for (const auto& empire_entry : Empires()) { + const auto& map = GetEmpireIntIntMap(empire_entry.first, parsed_property_name); + auto map_it = map.find(map_key); if (map_it != map.end()) sum += map_it->second; } @@ -1383,17 +1607,17 @@ namespace { // single empire if (empire_id != ALL_EMPIRES) { - const std::map& map = GetEmpireIntFloatMap(empire_id, parsed_property_name); - std::map::const_iterator it = map.find(map_key); + const auto& map = GetEmpireIntFloatMap(empire_id, parsed_property_name); + auto it = map.find(map_key); if (it == map.end()) return 0.0f; return it->second; } // all empires summed - for (std::map::value_type& empire_entry : Empires()) { - const std::map& map = GetEmpireIntFloatMap(empire_entry.first, parsed_property_name); - std::map::const_iterator map_it = map.find(map_key); + for (const auto& empire_entry : Empires()) { + const auto& map = GetEmpireIntFloatMap(empire_entry.first, parsed_property_name); + auto map_it = map.find(map_key); if (map_it != map.end()) sum += map_it->second; } @@ -1407,14 +1631,14 @@ namespace { // single empire if (empire_id != ALL_EMPIRES) { // sum of all key entries for this empire - for (const std::map::value_type& property_entry : GetEmpireIntIntMap(empire_id, parsed_property_name)) + for (const auto& property_entry : GetEmpireIntIntMap(empire_id, parsed_property_name)) sum += property_entry.second; return sum; } // all empires summed - for (const std::map::value_type& empire_entry : Empires()) { - for (const std::map::value_type& property_entry : GetEmpireIntIntMap(empire_entry.first, parsed_property_name)) + for (const auto& empire_entry : Empires()) { + for (const auto& property_entry : GetEmpireIntIntMap(empire_entry.first, parsed_property_name)) sum += property_entry.second; } return sum; @@ -1426,14 +1650,14 @@ namespace { // single empire if (empire_id != ALL_EMPIRES) { // sum of all key entries for this empire - for (const std::map::value_type& property_entry : GetEmpireIntFloatMap(empire_id, parsed_property_name)) + for (const auto& property_entry : GetEmpireIntFloatMap(empire_id, parsed_property_name)) sum += property_entry.second; return sum; } // all empires summed - for (const std::map::value_type& empire_entry : Empires()) { - for (const std::map::value_type& property_entry : GetEmpireIntFloatMap(empire_entry.first, parsed_property_name)) + for (const auto& empire_entry : Empires()) { + for (const auto& property_entry : GetEmpireIntFloatMap(empire_entry.first, parsed_property_name)) sum += property_entry.second; } return sum; @@ -1464,7 +1688,7 @@ namespace { } // all empires summed - for (const std::map::value_type& empire_entry : Empires()) + for (const auto& empire_entry : Empires()) sum += GetIntEmpirePropertyNoKeyImpl(empire_entry.first, parsed_property_name); return sum; } @@ -1479,17 +1703,16 @@ int ComplexVariable::Eval(const ScriptingContext& context) const if (variable_name == "BuildingTypesOwned" || variable_name == "BuildingTypesProduced" || variable_name == "BuildingTypesScrapped" || - variable_name == "SpeciesShipsDestroyed" || variable_name == "SpeciesColoniesOwned" || variable_name == "SpeciesPlanetsBombed" || variable_name == "SpeciesPlanetsDepoped" || - variable_name == "SpeciesPlanetsOwned" || variable_name == "SpeciesPlanetsInvaded" || variable_name == "SpeciesShipsDestroyed" || variable_name == "SpeciesShipsLost" || variable_name == "SpeciesShipsOwned" || variable_name == "SpeciesShipsProduced" || - variable_name == "SpeciesShipsScrapped") + variable_name == "SpeciesShipsScrapped" || + variable_name == "TurnTechResearched") { int empire_id = ALL_EMPIRES; if (m_int_ref1) { @@ -1502,12 +1725,24 @@ int ComplexVariable::Eval(const ScriptingContext& context) const key_string = m_string_ref1->Eval(context); if (key_string.empty()) return 0; - } - // if a string specified, get just that entry (for single empire, or - // summed for all empires) - if (m_string_ref1) - return GetIntEmpirePropertySingleKey(empire_id, variable_name, key_string); + // if a string specified, get just that entry (for single empire, or + // summed for all empires) + if (variable_name != "TurnTechResearched") { + return GetIntEmpirePropertySingleKey(empire_id, variable_name, + key_string); + } else { + // special case for techs: make unresearched-tech's research-turn a big number + if (const auto* empire = GetEmpire(empire_id)) { + if (empire->TechResearched(key_string)) + return GetIntEmpirePropertySingleKey(empire_id, variable_name, + key_string); + return IMPOSSIBLY_LARGE_TURN; + } + return GetIntEmpirePropertySingleKey(empire_id, variable_name, + key_string); + } + } // if no string specified, get sum of all entries (for single empire // or summed for all empires) @@ -1517,6 +1752,7 @@ int ComplexVariable::Eval(const ScriptingContext& context) const // empire properties indexed by integers if (variable_name == "EmpireShipsDestroyed" || variable_name == "ShipDesignsDestroyed" || + variable_name == "ShipDesignsInProduction" || variable_name == "ShipDesignsLost" || variable_name == "ShipDesignsOwned" || variable_name == "ShipDesignsProduced" || @@ -1583,16 +1819,16 @@ int ComplexVariable::Eval(const ScriptingContext& context) const // single empire if (empire_id != ALL_EMPIRES) { Empire* empire = GetEmpire(empire_id); - for (const std::map::value_type& property_entry : empire->ShipPartClassOwned()) + for (const auto& property_entry : empire->ShipPartClassOwned()) if (part_class == NUM_SHIP_PART_CLASSES || property_entry.first == part_class) sum += property_entry.second; return sum; } // all empires summed - for (const std::map::value_type& empire_entry : Empires()) { + for (const auto& empire_entry : Empires()) { Empire* empire = GetEmpire(empire_entry.first); - for (const std::map::value_type& property_entry : empire->ShipPartClassOwned()) + for (const auto& property_entry : empire->ShipPartClassOwned()) if (part_class == NUM_SHIP_PART_CLASSES || property_entry.first == part_class) sum += property_entry.second; } @@ -1625,21 +1861,28 @@ int ComplexVariable::Eval(const ScriptingContext& context) const if (!GetGameRules().RuleExists(rule_name)) return 0; try { - // may throw if no such int-valued rule exists - return GetGameRules().Get(rule_name); - } catch (...) { - // may throw if no such bool (or int) -valued rule exists - if (GetGameRules().Get(rule_name)) - return 1; - else - return 0; - try { - } catch (...) { + // can cast boolean, int, or double-valued rules to int + switch (GetGameRules().GetType(rule_name)) { + case GameRules::Type::TOGGLE: { + return GetGameRules().Get(rule_name); + break; + } + case GameRules::Type::INT: { + return GetGameRules().Get(rule_name); + break; + } + case GameRules::Type::DOUBLE: { + return static_cast(GetGameRules().Get(rule_name)); + break; } + default: + break; + } + } catch (...) { } return 0; - - } else if (variable_name == "PartsInShipDesign") { + } + else if (variable_name == "PartsInShipDesign") { int design_id = INVALID_DESIGN_ID; if (m_int_ref1) { design_id = m_int_ref1->Eval(context); @@ -1649,25 +1892,26 @@ int ComplexVariable::Eval(const ScriptingContext& context) const return 0; } - std::string part_type_name; + std::string ship_part_name; if (m_string_ref1) { - part_type_name = m_string_ref1->Eval(context); + ship_part_name = m_string_ref1->Eval(context); } const ShipDesign* design = GetShipDesign(design_id); if (!design) return 0; + if (ship_part_name.empty()) + return design->PartCount(); + int count = 0; for (const std::string& part : design->Parts()) { - if (part_type_name.empty() && !part.empty()) + if (ship_part_name == part) count++; - else if (!part_type_name.empty() && part_type_name == part) - count ++; } return count; - - } else if (variable_name == "PartOfClassInShipDesign") { + } + else if (variable_name == "PartOfClassInShipDesign") { int design_id = INVALID_DESIGN_ID; if (m_int_ref1) { design_id = m_int_ref1->Eval(context); @@ -1698,15 +1942,15 @@ int ComplexVariable::Eval(const ScriptingContext& context) const for (const std::string& part_name : design->Parts()) { if (part_name.empty()) continue; - const PartType* part = GetPartType(part_name); + const ShipPart* part = GetShipPart(part_name); if (!part) continue; if (part->Class() == part_class) count++; } return count; - - } else if (variable_name == "JumpsBetween") { + } + else if (variable_name == "JumpsBetween") { int object1_id = INVALID_OBJECT_ID; if (m_int_ref1) object1_id = m_int_ref1->Eval(context); @@ -1719,8 +1963,8 @@ int ComplexVariable::Eval(const ScriptingContext& context) const if (retval == INT_MAX) return -1; return retval; - - } else if (variable_name == "JumpsBetweenByEmpireSupplyConnections") { + } + else if (variable_name == "JumpsBetweenByEmpireSupplyConnections") { int object1_id = INVALID_OBJECT_ID; if (m_int_ref1) object1_id = m_int_ref1->Eval(context); @@ -1735,26 +1979,24 @@ int ComplexVariable::Eval(const ScriptingContext& context) const //if (m_int_ref3) // empire_id = m_int_ref3->Eval(context); - int retval = GetPathfinder()->JumpDistanceBetweenObjects(object1_id, object2_id/*, empire_id*/); if (retval == INT_MAX) return -1; return retval; - - } else if (variable_name == "SlotsInHull") { - const HullType* hull_type = nullptr; + } + else if (variable_name == "SlotsInHull") { + const ShipHull* ship_hull = nullptr; if (m_string_ref1) { std::string hull_name = m_string_ref1->Eval(context); - hull_type = GetHullType(hull_name); - if (!hull_type) + ship_hull = GetShipHull(hull_name); + if (!ship_hull) return 0; } else { return 0; } - - return hull_type->Slots().size(); - - } else if (variable_name == "SlotsInShipDesign") { + return ship_hull->Slots().size(); + } + else if (variable_name == "SlotsInShipDesign") { int design_id = INVALID_DESIGN_ID; if (m_int_ref1) { design_id = m_int_ref1->Eval(context); @@ -1768,10 +2010,28 @@ int ComplexVariable::Eval(const ScriptingContext& context) const if (!design) return 0; - const HullType* hull_type = GetHullType(design->Hull()); - if (!hull_type) + const ShipHull* ship_hull = GetShipHull(design->Hull()); + if (!ship_hull) + return 0; + return ship_hull->Slots().size(); + } + else if (variable_name == "SpecialAddedOnTurn") { + int object_id = INVALID_OBJECT_ID; + if (m_int_ref1) + object_id = m_int_ref1->Eval(context); + if (object_id == INVALID_OBJECT_ID) + return 0; + auto object = context.ContextObjects().get(object_id); + if (!object) + return 0; + + std::string special_name; + if (m_string_ref1) + special_name = m_string_ref1->Eval(context); + if (special_name.empty()) return 0; - return hull_type->Slots().size(); + + return object->SpecialAddedOnTurn(special_name); } return 0; @@ -1794,7 +2054,7 @@ double ComplexVariable::Eval(const ScriptingContext& context) const return 0.0; } - // if a key integer specified, get just that entry (for single empire or sum of all empires) + // if a key integer is specified, get just that entry (for single empire or sum of all empires) if (m_int_ref2) { int key_int = m_int_ref2->Eval(context); return GetFloatEmpirePropertySingleKey(empire_id, variable_name, key_int); @@ -1814,82 +2074,117 @@ double ComplexVariable::Eval(const ScriptingContext& context) const if (!GetGameRules().RuleExists(rule_name)) return 0.0; try { - // may throw if no such int-valued rule exists - return GetGameRules().Get(rule_name); + // can cast boolean, int, or double-valued rules to double + switch (GetGameRules().GetType(rule_name)) { + case GameRules::Type::TOGGLE: { + return GetGameRules().Get(rule_name); + break; + } + case GameRules::Type::INT: { + return GetGameRules().Get(rule_name); + break; + } + case GameRules::Type::DOUBLE: { + return GetGameRules().Get(rule_name); + break; + } + default: + break; + } } catch (...) { } return 0.0; - - } else if (variable_name == "HullFuel") { - std::string hull_type_name; + } + else if (variable_name == "HullFuel") { + std::string ship_hull_name; if (m_string_ref1) - hull_type_name = m_string_ref1->Eval(context); + ship_hull_name = m_string_ref1->Eval(context); - const HullType* hull_type = GetHullType(hull_type_name); - if (!hull_type) + const ShipHull* ship_hull = GetShipHull(ship_hull_name); + if (!ship_hull) return 0.0; - return hull_type->Fuel(); + return ship_hull->Fuel(); } else if (variable_name == "HullStealth") { - std::string hull_type_name; + std::string ship_hull_name; if (m_string_ref1) - hull_type_name = m_string_ref1->Eval(context); + ship_hull_name = m_string_ref1->Eval(context); - const HullType* hull_type = GetHullType(hull_type_name); - if (!hull_type) + const ShipHull* ship_hull = GetShipHull(ship_hull_name); + if (!ship_hull) return 0.0; - return hull_type->Stealth(); + return ship_hull->Stealth(); } else if (variable_name == "HullStructure") { - std::string hull_type_name; + std::string ship_hull_name; if (m_string_ref1) - hull_type_name = m_string_ref1->Eval(context); + ship_hull_name = m_string_ref1->Eval(context); - const HullType* hull_type = GetHullType(hull_type_name); - if (!hull_type) + const ShipHull* ship_hull = GetShipHull(ship_hull_name); + if (!ship_hull) return 0.0f; - return hull_type->Structure(); + return ship_hull->Structure(); } else if (variable_name == "HullSpeed") { - std::string hull_type_name; + std::string ship_hull_name; if (m_string_ref1) - hull_type_name = m_string_ref1->Eval(context); + ship_hull_name = m_string_ref1->Eval(context); - const HullType* hull_type = GetHullType(hull_type_name); - if (!hull_type) + const ShipHull* ship_hull = GetShipHull(ship_hull_name); + if (!ship_hull) return 0.0; - return hull_type->Speed(); + return ship_hull->Speed(); } else if (variable_name == "PartCapacity") { - std::string part_type_name; + std::string ship_part_name; if (m_string_ref1) - part_type_name = m_string_ref1->Eval(context); + ship_part_name = m_string_ref1->Eval(context); - const PartType* part_type = GetPartType(part_type_name); - if (!part_type) + const ShipPart* ship_part = GetShipPart(ship_part_name); + if (!ship_part) return 0.0; - return part_type->Capacity(); + return ship_part->Capacity(); } else if (variable_name == "PartSecondaryStat") { - std::string part_type_name; + std::string ship_part_name; if (m_string_ref1) - part_type_name = m_string_ref1->Eval(context); + ship_part_name = m_string_ref1->Eval(context); + + const ShipPart* ship_part = GetShipPart(ship_part_name); + if (!ship_part) + return 0.0; + + return ship_part->SecondaryStat(); + + } + else if (variable_name == "ShipDesignCost") { + int design_id = INVALID_DESIGN_ID; + if (m_int_ref1) + design_id = m_int_ref1->Eval(context); - const PartType* part_type = GetPartType(part_type_name); - if (!part_type) + const ShipDesign* design = GetShipDesign(design_id); + if (!design) return 0.0; - return part_type->SecondaryStat(); + int empire_id = ALL_EMPIRES; + if (m_int_ref2) + empire_id = m_int_ref2->Eval(context); + + int location_id = INVALID_OBJECT_ID; + if (m_int_ref3) + location_id = m_int_ref3->Eval(context); + + return design->ProductionCost(empire_id, location_id); } else if (variable_name == "EmpireMeterValue") { @@ -1912,14 +2207,14 @@ double ComplexVariable::Eval(const ScriptingContext& context) const int object1_id = INVALID_OBJECT_ID; if (m_int_ref1) object1_id = m_int_ref1->Eval(context); - std::shared_ptr obj1 = GetUniverseObject(object1_id); + auto obj1 = context.ContextObjects().get(object1_id); if (!obj1) return 0.0; int object2_id = INVALID_OBJECT_ID; if (m_int_ref2) object2_id = m_int_ref2->Eval(context); - std::shared_ptr obj2 = GetUniverseObject(object2_id); + auto obj2 = context.ContextObjects().get(object2_id); if (!obj2) return 0.0; @@ -1963,18 +2258,64 @@ double ComplexVariable::Eval(const ScriptingContext& context) const return GetSpeciesManager().SpeciesSpeciesOpinion(opinionated_species_name, rated_species_name); } + else if (variable_name == "SpecialCapacity") { + int object_id = INVALID_OBJECT_ID; + if (m_int_ref1) + object_id = m_int_ref1->Eval(context); + auto object = context.ContextObjects().get(object_id); + if (!object) + return 0.0; + + std::string special_name; + if (m_string_ref1) + special_name = m_string_ref1->Eval(context); + if (special_name.empty()) + return 0.0; + + return object->SpecialCapacity(special_name); + } + else if (variable_name == "ShipPartMeter") { + int object_id = INVALID_OBJECT_ID; + if (m_int_ref1) + object_id = m_int_ref1->Eval(context); + auto object = context.ContextObjects().get(object_id); + if (!object) + return 0.0; + auto ship = std::dynamic_pointer_cast(object); + if (!ship) + return 0.0; + + std::string part_name; + if (m_string_ref1) + part_name = m_string_ref1->Eval(context); + if (part_name.empty()) + return 0.0; + + std::string meter_name; + if (m_string_ref2) + meter_name = m_string_ref2->Eval(context); + if (meter_name.empty()) + return 0.0; + + MeterType meter_type = NameToMeter(meter_name); + if (meter_type != INVALID_METER_TYPE) { + if (m_return_immediate_value) + return ship->CurrentPartMeterValue(meter_type, part_name); + else + return ship->InitialPartMeterValue(meter_type, part_name); + } + } return 0.0; } - namespace { std::vector TechsResearchedByEmpire(int empire_id) { std::vector retval; const Empire* empire = GetEmpire(empire_id); if (!empire) return retval; - for (const Tech* tech : GetTechManager()) { + for (const auto& tech : GetTechManager()) { if (empire->TechResearched(tech->Name())) retval.push_back(tech->Name()); } @@ -1986,7 +2327,7 @@ namespace { const Empire* empire = GetEmpire(empire_id); if (!empire) return retval; - for (const Tech* tech : GetTechManager()) { + for (const auto& tech : GetTechManager()) { if (empire->ResearchableTech(tech->Name())) retval.push_back(tech->Name()); } @@ -2247,7 +2588,7 @@ std::string ComplexVariable::Eval(const ScriptingContext& context) // search queue to find which transferrable tech is at the top of the list const ResearchQueue& queue = empire2->GetResearchQueue(); for (const std::string& tech : sendable_techs) { - ResearchQueue::const_iterator queue_it = queue.find(tech); + auto queue_it = queue.find(tech); if (queue_it == queue.end()) continue; int queue_pos = std::distance(queue.begin(), queue_it); @@ -2346,8 +2687,27 @@ std::string ComplexVariable::Eval(const ScriptingContext& context) if (!GetGameRules().RuleExists(rule_name)) return ""; try { - // may throw if no such int-valued rule exists - return GetGameRules().Get(rule_name); + // can cast boolean, int, double, or string-valued rules to strings + switch (GetGameRules().GetType(rule_name)) { + case GameRules::Type::TOGGLE: { + return std::to_string(GetGameRules().Get(rule_name)); + break; + } + case GameRules::Type::INT: { + return std::to_string(GetGameRules().Get(rule_name)); + break; + } + case GameRules::Type::DOUBLE: { + return DoubleToString(GetGameRules().Get(rule_name), 3, false); + break; + } + case GameRules::Type::STRING: { + return GetGameRules().Get(rule_name); + break; + } + default: + break; + } } catch (...) { } return ""; @@ -2358,6 +2718,120 @@ std::string ComplexVariable::Eval(const ScriptingContext& context) #undef IF_CURRENT_VALUE +template <> +std::string ComplexVariable::Dump(unsigned short ntabs) const +{ + const std::string& variable_name = m_property_name.back(); + std::string retval = variable_name; + + if (variable_name == "EmpireObjectVisiblity") { + if (m_int_ref1) + retval += " empire = " + m_int_ref1->Dump(ntabs); + if (m_int_ref2) + retval += " object = " + m_int_ref2->Dump(ntabs); + } + + return retval; +} + +template <> +std::string ComplexVariable::Dump(unsigned short ntabs) const +{ + const std::string& variable_name = m_property_name.back(); + std::string retval = variable_name; + + // empire properties indexed by integers + if (variable_name == "PropagatedSystemSupplyRange" || + variable_name == "SystemSupplyRange" || + variable_name == "PropagatedSystemSupplyDistance") + { + if (m_int_ref1) + retval += " empire = " + m_int_ref1->Dump(ntabs); + if (m_int_ref2) + retval += " system = " + m_int_ref2->Dump(ntabs); + + } + else if (variable_name == "GameRule" || + variable_name == "HullFuel" || + variable_name == "HullStealth" || + variable_name == "HullStructure" || + variable_name == "HullSpeed" || + variable_name == "PartCapacity" || + variable_name == "PartSecondaryStat") + { + if (m_string_ref1) + retval += " name = " + m_string_ref1->Dump(ntabs); + + } + else if (variable_name == "EmpireMeterValue") { + if (m_int_ref1) + retval += " empire = " + m_int_ref1->Dump(ntabs); + if (m_string_ref1) + retval += " meter = " + m_string_ref1->Dump(ntabs); + + } + else if (variable_name == "ShipPartMeter") { + // ShipPartMeter part = "SR_WEAPON_1_1" meter = Capacity object = Source.ID + if (m_string_ref1) + retval += " part = " + m_string_ref1->Dump(ntabs); + if (m_string_ref2) + retval += " meter = " + m_string_ref2->Dump(ntabs); // wrapped in quotes " but shouldn't be to be consistent with parser + if (m_int_ref1) + retval += " object = " + m_int_ref1->Dump(ntabs); + + } + else if (variable_name == "DirectDistanceBetween" || + variable_name == "ShortestPath") + { + if (m_int_ref1) + retval += " object = " + m_int_ref1->Dump(ntabs); + if (m_int_ref2) + retval += " object = " + m_int_ref2->Dump(ntabs); + + } + else if (variable_name == "SpeciesEmpireOpinion") { + if (m_int_ref1) + retval += " empire = " + m_int_ref1->Dump(ntabs); + if (m_string_ref1) + retval += " species = " + m_string_ref1->Dump(ntabs); + + } + else if (variable_name == "SpeciesSpeciesOpinion") { + if (m_string_ref1) + retval += " species = " + m_string_ref1->Dump(ntabs); + if (m_string_ref2) + retval += " species = " + m_string_ref2->Dump(ntabs); + + } + else if (variable_name == "SpecialCapacity") { + if (m_string_ref1) + retval += " name = " + m_string_ref1->Dump(ntabs); + if (m_int_ref1) + retval += " object = " + m_int_ref1->Dump(ntabs); + + } + + return retval; +} + +template <> +std::string ComplexVariable::Dump(unsigned short ntabs) const +{ + const std::string& variable_name = m_property_name.back(); + std::string retval = variable_name; + + return retval; +} + +template <> +std::string ComplexVariable::Dump(unsigned short ntabs) const +{ + const std::string& variable_name = m_property_name.back(); + std::string retval = variable_name; + + return retval; +} + /////////////////////////////////////////////////////////// // StringCast // /////////////////////////////////////////////////////////// @@ -2369,7 +2843,7 @@ std::string StringCast::Eval(const ScriptingContext& context) const double temp = m_value_ref->Eval(context); // special case for a few sub-value-refs to help with UI representation - if (Variable* int_var = dynamic_cast*>(m_value_ref)) { + if (Variable* int_var = dynamic_cast*>(m_value_ref.get())) { if (int_var->PropertyName().back() == "X" || int_var->PropertyName().back() == "Y") { if (temp == UniverseObject::INVALID_POSITION) return UserString("INVALID_POSITION"); @@ -2391,7 +2865,7 @@ std::string StringCast::Eval(const ScriptingContext& context) const int temp = m_value_ref->Eval(context); // special case for a few sub-value-refs to help with UI representation - if (Variable* int_var = dynamic_cast*>(m_value_ref)) { + if (Variable* int_var = dynamic_cast*>(m_value_ref.get())) { if (int_var->PropertyName().back() == "ETA") { if (temp == Fleet::ETA_UNKNOWN) { return UserString("FW_FLEET_ETA_UNKNOWN"); @@ -2452,16 +2926,13 @@ std::string UserStringLookup>::Eval(const ScriptingCont ///////////////////////////////////////////////////// // NameLookup // ///////////////////////////////////////////////////// -NameLookup::NameLookup(ValueRefBase* value_ref, LookupType lookup_type) : +NameLookup::NameLookup(std::unique_ptr>&& value_ref, LookupType lookup_type) : Variable(NON_OBJECT_REFERENCE), - m_value_ref(value_ref), + m_value_ref(std::move(value_ref)), m_lookup_type(lookup_type) {} -NameLookup::~NameLookup() -{ delete m_value_ref; } - -bool NameLookup::operator==(const ValueRefBase& rhs) const { +bool NameLookup::operator==(const ValueRef& rhs) const { if (&rhs == this) return true; if (typeid(rhs) != typeid(*this)) @@ -2493,7 +2964,7 @@ std::string NameLookup::Eval(const ScriptingContext& context) const { switch (m_lookup_type) { case OBJECT_NAME: { - std::shared_ptr obj = GetUniverseObject(m_value_ref->Eval(context)); + auto obj = context.ContextObjects().get(m_value_ref->Eval(context)); return obj ? obj->Name() : ""; break; } @@ -2527,8 +2998,8 @@ bool NameLookup::SourceInvariant() const std::string NameLookup::Description() const { return m_value_ref->Description(); } -std::string NameLookup::Dump() const -{ return m_value_ref->Dump(); } +std::string NameLookup::Dump(unsigned short ntabs) const +{ return m_value_ref->Dump(ntabs); } void NameLookup::SetTopLevelContent(const std::string& content_name) { if (m_value_ref) @@ -2554,10 +3025,20 @@ std::string Operation::EvalImpl(const ScriptingContext& context) co if (m_op_type == PLUS) { return LHS()->Eval(context) + RHS()->Eval(context); + } else if (m_op_type == TIMES) { + // useful for writing a "Statistic If" expression with strings. Number- + // valued types return 0 or 1 for nothing or something matching the sampling + // condition. For strings, an empty string indicates no matches, and non-empty + // string indicates matches, which is treated like a multiplicative identity + // operation, so just returns the RHS of the expression. + if (LHS()->Eval(context).empty()) + return ""; + return RHS()->Eval(context); + } else if (m_op_type == MINIMUM || m_op_type == MAXIMUM) { // evaluate all operands, return sorted first/last std::set vals; - for (ValueRefBase* vr : m_operands) { + for (auto& vr : m_operands) { if (vr) vals.insert(vr->Eval(context)); } @@ -2571,7 +3052,7 @@ std::string Operation::EvalImpl(const ScriptingContext& context) co if (m_operands.empty()) return ""; unsigned int idx = RandSmallInt(0, m_operands.size() - 1); - ValueRefBase* vr = *std::next(m_operands.begin(), idx); + auto& vr = *std::next(m_operands.begin(), idx); if (!vr) return ""; return vr->Eval(context); @@ -2580,14 +3061,14 @@ std::string Operation::EvalImpl(const ScriptingContext& context) co // insert string into other string in place of %1% or similar placeholder if (m_operands.empty()) return ""; - ValueRefBase* template_op = *(m_operands.begin()); + auto& template_op = *(m_operands.begin()); if (!template_op) return ""; std::string template_str = template_op->Eval(context); boost::format formatter = FlexibleFormat(template_str); - for (ValueRefBase* op : m_operands) { + for (auto& op : m_operands) { if (!op) { formatter % ""; continue; @@ -2629,7 +3110,7 @@ std::string Operation::EvalImpl(const ScriptingContext& context) co } template <> -double Operation::EvalImpl(const ScriptingContext& context) const +double Operation::EvalImpl(const ScriptingContext& context) const { switch (m_op_type) { case PLUS: @@ -2638,8 +3119,13 @@ double Operation::EvalImpl(const ScriptingContext& context) const case MINUS: return LHS()->Eval(context) - RHS()->Eval(context); break; - case TIMES: - return LHS()->Eval(context) * RHS()->Eval(context); break; + case TIMES: { + double op1 = LHS()->Eval(context); + if (op1 == 0.0) + return 0.0; + return op1 * RHS()->Eval(context); + break; + } case DIVIDE: { double op2 = RHS()->Eval(context); @@ -2650,18 +3136,24 @@ double Operation::EvalImpl(const ScriptingContext& context) const } case NEGATE: - return -(LHS()->Eval(context)); break; + return -(LHS()->Eval(context)); break; case EXPONENTIATE: { - return std::pow(LHS()->Eval(context), - RHS()->Eval(context)); + double op2 = RHS()->Eval(context); + if (op2 == 0.0) + return 1.0; + try { + double op1 = LHS()->Eval(context); + return std::pow(op1, op2); + } catch (...) { + ErrorLogger() << "Error evaluating exponentiation ValueRef::Operation"; + return 0.0; + } break; } - case ABS: { - return std::abs(LHS()->Eval(context)); - break; - } + case ABS: + return std::abs(LHS()->Eval(context)); break; case LOGARITHM: { double op1 = LHS()->Eval(context); @@ -2672,15 +3164,15 @@ double Operation::EvalImpl(const ScriptingContext& context) const } case SINE: - return std::sin(LHS()->Eval(context)); break; + return std::sin(LHS()->Eval(context)); break; case COSINE: - return std::cos(LHS()->Eval(context)); break; + return std::cos(LHS()->Eval(context)); break; case MINIMUM: case MAXIMUM: { std::set vals; - for (ValueRefBase* vr : m_operands) { + for (auto& vr : m_operands) { if (vr) vals.insert(vr->Eval(context)); } @@ -2705,7 +3197,7 @@ double Operation::EvalImpl(const ScriptingContext& context) const if (m_operands.empty()) return 0.0; unsigned int idx = RandSmallInt(0, m_operands.size() - 1); - ValueRefBase* vr = *std::next(m_operands.begin(), idx); + auto& vr = *std::next(m_operands.begin(), idx); if (!vr) return 0.0; return vr->Eval(context); @@ -2744,6 +3236,16 @@ double Operation::EvalImpl(const ScriptingContext& context) const return m_operands[3]->Eval(context); } } + + case ROUND_NEAREST: + return std::round(LHS()->Eval(context)); break; + case ROUND_UP: + return std::ceil(LHS()->Eval(context)); break; + case ROUND_DOWN: + return std::floor(LHS()->Eval(context)); break; + + default: + break; } throw std::runtime_error("double ValueRef evaluated with an unknown or invalid OpType."); @@ -2751,17 +3253,22 @@ double Operation::EvalImpl(const ScriptingContext& context) const } template <> -int Operation::EvalImpl(const ScriptingContext& context) const +int Operation::EvalImpl(const ScriptingContext& context) const { switch (m_op_type) { case PLUS: - return LHS()->Eval(context) + RHS()->Eval(context); break; + return LHS()->Eval(context) + RHS()->Eval(context); break; case MINUS: - return LHS()->Eval(context) - RHS()->Eval(context); break; + return LHS()->Eval(context) - RHS()->Eval(context); break; - case TIMES: - return LHS()->Eval(context) * RHS()->Eval(context); break; + case TIMES: { + double op1 = LHS()->Eval(context); + if (op1 == 0) + return 0; + return op1 * RHS()->Eval(context); + break; + } case DIVIDE: { int op2 = RHS()->Eval(context); @@ -2772,12 +3279,19 @@ int Operation::EvalImpl(const ScriptingContext& context) const } case NEGATE: - return -LHS()->Eval(context); break; + return -LHS()->Eval(context); break; case EXPONENTIATE: { - double op1 = LHS()->Eval(context); double op2 = RHS()->Eval(context); - return static_cast(std::pow(op1, op2)); + if (op2 == 0) + return 1; + try { + double op1 = LHS()->Eval(context); + return static_cast(std::pow(op1, op2)); + } catch (...) { + ErrorLogger() << "Error evaluating exponentiation ValueRef::Operation"; + return 0; + } break; } @@ -2809,7 +3323,7 @@ int Operation::EvalImpl(const ScriptingContext& context) const case MINIMUM: case MAXIMUM: { std::set vals; - for (ValueRefBase* vr : m_operands) { + for (auto& vr : m_operands) { if (vr) vals.insert(vr->Eval(context)); } @@ -2834,13 +3348,21 @@ int Operation::EvalImpl(const ScriptingContext& context) const if (m_operands.empty()) return 0; unsigned int idx = RandSmallInt(0, m_operands.size() - 1); - ValueRefBase* vr = *std::next(m_operands.begin(), idx); + auto& vr = *std::next(m_operands.begin(), idx); if (!vr) return 0; return vr->Eval(context); break; } + case ROUND_NEAREST: + case ROUND_UP: + case ROUND_DOWN: { + // integers don't need to be rounded... + return LHS()->Eval(context); + break; + } + case COMPARE_EQUAL: case COMPARE_GREATER_THAN: case COMPARE_GREATER_THAN_OR_EQUAL: @@ -2873,6 +3395,8 @@ int Operation::EvalImpl(const ScriptingContext& context) const return m_operands[3]->Eval(context); } } + + default: break; } throw std::runtime_error("double ValueRef evaluated with an unknown or invalid OpType."); diff --git a/universe/ValueRefs.h b/universe/ValueRefs.h new file mode 100644 index 00000000000..5cb45f274aa --- /dev/null +++ b/universe/ValueRefs.h @@ -0,0 +1,2267 @@ +#ifndef _ValueRefs_h_ +#define _ValueRefs_h_ + +#include "ScriptingContext.h" +#include "ValueRef.h" +#include "Condition.h" +#include "Universe.h" +#include "../util/Export.h" +#include "../util/i18n.h" +#include "../util/Random.h" +#include "../util/CheckSums.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace CheckSums { + template + void CheckSumCombine(unsigned int& sum, const typename ValueRef::ValueRef& c) + { + TraceLogger() << "CheckSumCombine(ValueRef::ValueRef): " << typeid(c).name(); + sum += c.GetCheckSum(); + sum %= CHECKSUM_MODULUS; + } +} + +class UniverseObject; + +namespace ValueRef { +/** the constant value leaf ValueRef class. */ +template +struct FO_COMMON_API Constant final : public ValueRef +{ + explicit Constant(T value); + + bool operator==(const ValueRef& rhs) const override; + T Eval(const ScriptingContext& context) const override; + + bool RootCandidateInvariant() const override + { return true; } + + bool LocalCandidateInvariant() const override + { return true; } + + bool TargetInvariant() const override + { return true; } + + bool SourceInvariant() const override + { return true; } + + bool ConstantExpr() const override + { return true; } + + std::string Description() const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + T Value() const; + unsigned int GetCheckSum() const override; + +private: + T m_value; + std::string m_top_level_content; // in the special case that T is std::string and m_value is "CurrentContent", return this instead + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +enum ReferenceType : int { + INVALID_REFERENCE_TYPE = -1, + NON_OBJECT_REFERENCE, // ValueRef::Variable is not evalulated on any specific object + SOURCE_REFERENCE, // ValueRef::Variable is evaluated on the source object + EFFECT_TARGET_REFERENCE, // ValueRef::Variable is evaluated on the target object of an effect while it is being executed + EFFECT_TARGET_VALUE_REFERENCE, // ValueRef::Variable is evaluated on the target object value of an effect while it is being executed + CONDITION_LOCAL_CANDIDATE_REFERENCE,// ValueRef::Variable is evaluated on an object that is a candidate to be matched by a condition. In a subcondition, this will reference the local candidate, and not the candidate of an enclosing condition. + CONDITION_ROOT_CANDIDATE_REFERENCE // ValueRef::Variable is evaluated on an object that is a candidate to be matched by a condition. In a subcondition, this will still reference the root candidate, and not the candidate of the local condition. +}; + +/** The variable value ValueRef class. The value returned by this node is + * taken from the gamestate, most often from the Source or Target objects. */ +template +struct FO_COMMON_API Variable : public ValueRef +{ + explicit Variable(ReferenceType ref_type, const std::string& property_name = "", + bool return_immediate_value = false); + Variable(ReferenceType ref_type, const std::vector& property_name, + bool return_immediate_value = false); + Variable(ReferenceType ref_type, + const boost::optional& container_name, + const std::string& property_name, + bool return_immediate_value = false); + + bool operator==(const ValueRef& rhs) const override; + T Eval(const ScriptingContext& context) const override; + bool RootCandidateInvariant() const override; + bool LocalCandidateInvariant() const override; + bool TargetInvariant() const override; + bool SourceInvariant() const override; + std::string Description() const override; + std::string Dump(unsigned short ntabs = 0) const override; + ReferenceType GetReferenceType() const; + const std::vector& PropertyName() const; + bool ReturnImmediateValue() const; + unsigned int GetCheckSum() const override; + +protected: + ReferenceType m_ref_type = INVALID_REFERENCE_TYPE; + std::vector m_property_name; + bool m_return_immediate_value = false; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** The variable statistic class. The value returned by this node is + * computed from the general gamestate; the value of the indicated + * \a property_name is computed for each object that matches + * \a sampling_condition and the statistic indicated by \a stat_type is + * calculated from them and returned. */ +template +struct FO_COMMON_API Statistic final : public Variable +{ + Statistic(std::unique_ptr>&& value_ref, + StatisticType stat_type, + std::unique_ptr&& sampling_condition); + + bool operator==(const ValueRef& rhs) const override; + T Eval(const ScriptingContext& context) const override; + bool RootCandidateInvariant() const override; + bool LocalCandidateInvariant() const override; + bool TargetInvariant() const override; + bool SourceInvariant() const override; + std::string Description() const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + + StatisticType GetStatisticType() const + { return m_stat_type; } + + const Condition::Condition* GetSamplingCondition() const + { return m_sampling_condition.get(); } + + const ValueRef* GetValueRef() const + { return m_value_ref.get(); } + + unsigned int GetCheckSum() const override; + +protected: + /** Gets the set of objects in the Universe that match the sampling condition. */ + void GetConditionMatches(const ScriptingContext& context, + Condition::ObjectSet& condition_targets, + Condition::Condition* condition) const; + + /** Evaluates the property for the specified objects. */ + void GetObjectPropertyValues(const ScriptingContext& context, + const Condition::ObjectSet& objects, + std::map, T>& object_property_values) const; + + /** Computes the statistic from the specified set of property values. */ + T ReduceData(const std::map, T>& object_property_values) const; + +private: + StatisticType m_stat_type; + std::unique_ptr m_sampling_condition; + std::unique_ptr> m_value_ref; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** The complex variable ValueRef class. The value returned by this node + * is taken from the gamestate. */ +template +struct FO_COMMON_API ComplexVariable final : public Variable +{ + explicit ComplexVariable(const std::string& variable_name, + std::unique_ptr>&& int_ref1 = nullptr, + std::unique_ptr>&& int_ref2 = nullptr, + std::unique_ptr>&& int_ref3 = nullptr, + std::unique_ptr>&& string_ref1 = nullptr, + std::unique_ptr>&& string_ref2 = nullptr, + bool return_immediate_value = false); + + bool operator==(const ValueRef& rhs) const override; + T Eval(const ScriptingContext& context) const override; + bool RootCandidateInvariant() const override; + bool LocalCandidateInvariant() const override; + bool TargetInvariant() const override; + bool SourceInvariant() const override; + std::string Description() const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + const ValueRef* IntRef1() const; + const ValueRef* IntRef2() const; + const ValueRef* IntRef3() const; + const ValueRef* StringRef1() const; + const ValueRef* StringRef2() const; + unsigned int GetCheckSum() const override; + +protected: + std::unique_ptr> m_int_ref1; + std::unique_ptr> m_int_ref2; + std::unique_ptr> m_int_ref3; + std::unique_ptr> m_string_ref1; + std::unique_ptr> m_string_ref2; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** The variable static_cast class. The value returned by this node is taken + * from the ctor \a value_ref parameter's FromType value, static_cast to + * ToType. */ +template +struct FO_COMMON_API StaticCast final : public Variable +{ + template + StaticCast(T&& value_ref, + typename std::enable_if>>::value>::type* = nullptr); + + template + StaticCast(T&& value_ref, + typename std::enable_if< + std::is_convertible>>::value + && !std::is_convertible>>::value>::type* = nullptr); + + bool operator==(const ValueRef& rhs) const override; + ToType Eval(const ScriptingContext& context) const override; + bool RootCandidateInvariant() const override; + bool LocalCandidateInvariant() const override; + bool TargetInvariant() const override; + bool SourceInvariant() const override; + std::string Description() const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + + const ValueRef* GetValueRef() const + { return m_value_ref.get(); } + + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_value_ref; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** The variable lexical_cast to string class. The value returned by this node + * is taken from the ctor \a value_ref parameter's FromType value, + * lexical_cast to std::string */ +template +struct FO_COMMON_API StringCast final : public Variable +{ + StringCast(std::unique_ptr>&& value_ref); + + bool operator==(const ValueRef& rhs) const override; + std::string Eval(const ScriptingContext& context) const override; + bool RootCandidateInvariant() const override; + bool LocalCandidateInvariant() const override; + bool TargetInvariant() const override; + bool SourceInvariant() const override; + std::string Description() const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + + const ValueRef* GetValueRef() const + { return m_value_ref; } + + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_value_ref; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Looks up a string ValueRef or vector of string ValueRefs, and returns + * and returns the UserString equivalent(s). */ +template +struct FO_COMMON_API UserStringLookup final : public Variable { + explicit UserStringLookup(std::unique_ptr>&& value_ref); + + bool operator==(const ValueRef& rhs) const override; + std::string Eval(const ScriptingContext& context) const override; + bool RootCandidateInvariant() const override; + bool LocalCandidateInvariant() const override; + bool TargetInvariant() const override; + bool SourceInvariant() const override; + std::string Description() const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + + const ValueRef* GetValueRef() const + { return m_value_ref; } + + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_value_ref; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Returns the in-game name of the object / empire / etc. with a specified id. */ +struct FO_COMMON_API NameLookup final : public Variable { + enum LookupType : int { + INVALID_LOOKUP = -1, + OBJECT_NAME, + EMPIRE_NAME, + SHIP_DESIGN_NAME + }; + + NameLookup(std::unique_ptr>&& value_ref, LookupType lookup_type); + + bool operator==(const ValueRef& rhs) const override; + std::string Eval(const ScriptingContext& context) const override; + bool RootCandidateInvariant() const override; + bool LocalCandidateInvariant() const override; + bool TargetInvariant() const override; + bool SourceInvariant() const override; + std::string Description() const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + + const ValueRef* GetValueRef() const + { return m_value_ref.get(); } + + LookupType GetLookupType() const + { return m_lookup_type; } + + unsigned int GetCheckSum() const override; + +private: + std::unique_ptr> m_value_ref; + LookupType m_lookup_type; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +enum OpType : int { + PLUS, + MINUS, + TIMES, + DIVIDE, + NEGATE, + EXPONENTIATE, + ABS, + LOGARITHM, + SINE, + COSINE, + MINIMUM, + MAXIMUM, + RANDOM_UNIFORM, + RANDOM_PICK, + SUBSTITUTION, + COMPARE_EQUAL, + COMPARE_GREATER_THAN, + COMPARE_GREATER_THAN_OR_EQUAL, + COMPARE_LESS_THAN, + COMPARE_LESS_THAN_OR_EQUAL, + COMPARE_NOT_EQUAL, + ROUND_NEAREST, + ROUND_UP, + ROUND_DOWN +}; + +/** An arithmetic operation node ValueRef class. Unary or binary operations such + * as addition, mutiplication, negation, exponentiation, rounding, + * value substitution, value comparisons, or random value selection or + * random number generation are performed on the child(ren) of this node, and + * the result is returned. */ +template +struct FO_COMMON_API Operation final : public ValueRef +{ + /** Binary operation ctor. */ + Operation(OpType op_type, std::unique_ptr>&& operand1, + std::unique_ptr>&& operand2); + + /** Unary operation ctor. */ + Operation(OpType op_type, std::unique_ptr>&& operand); + + /* N-ary operation ctor. */ + Operation(OpType op_type, std::vector>>&& operands); + + bool operator==(const ValueRef& rhs) const override; + T Eval(const ScriptingContext& context) const override; + bool RootCandidateInvariant() const override; + bool LocalCandidateInvariant() const override; + bool TargetInvariant() const override; + bool SourceInvariant() const override; + bool SimpleIncrement() const override; + bool ConstantExpr() const override { return m_constant_expr; } + std::string Description() const override; + std::string Dump(unsigned short ntabs = 0) const override; + void SetTopLevelContent(const std::string& content_name) override; + OpType GetOpType() const; + + /** 1st operand (or 0 if none exists). */ + const ValueRef* LHS() const; + + /** 2nd operand (or 0 if only one exists) */ + const ValueRef* RHS() const; + + /** all operands */ + const std::vector*> Operands() const; + + unsigned int GetCheckSum() const override; + +private: + void DetermineIfConstantExpr(); + void CacheConstValue(); + T EvalImpl(const ScriptingContext& context) const; + + OpType m_op_type = TIMES; + std::vector>> m_operands; + bool m_constant_expr = false; + T m_cached_const_value = T(); + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +FO_COMMON_API MeterType NameToMeter(const std::string& name); +FO_COMMON_API std::string MeterToName(MeterType meter); +FO_COMMON_API std::string ReconstructName(const std::vector& property_name, + ReferenceType ref_type, + bool return_immediate_value = false); + +FO_COMMON_API std::string FormatedDescriptionPropertyNames( + ReferenceType ref_type, const std::vector& property_names, + bool return_immediate_value = false); + +FO_COMMON_API std::string ComplexVariableDescription( + const std::vector& property_names, + const ValueRef* int_ref1, + const ValueRef* int_ref2, + const ValueRef* int_ref3, + const ValueRef* string_ref1, + const ValueRef* string_ref2); + +FO_COMMON_API std::string ComplexVariableDump( + const std::vector& property_names, + const ValueRef* int_ref1, + const ValueRef* int_ref2, + const ValueRef* int_ref3, + const ValueRef* string_ref1, + const ValueRef* string_ref2); + +FO_COMMON_API std::string StatisticDescription(StatisticType stat_type, + const std::string& value_desc, + const std::string& condition_desc); + +// Template Implementations +/////////////////////////////////////////////////////////// +// ValueRef // +/////////////////////////////////////////////////////////// +template +bool ValueRef::operator==(const ValueRef& rhs) const +{ + if (&rhs == this) + return true; + if (typeid(rhs) != typeid(*this)) + return false; + return true; +} + +template +template +void ValueRef::serialize(Archive& ar, const unsigned int version) +{} + +/////////////////////////////////////////////////////////// +// Constant // +/////////////////////////////////////////////////////////// +template +Constant::Constant(T value) : + m_value(value) +{} + +template +bool Constant::operator==(const ValueRef& rhs) const +{ + if (&rhs == this) + return true; + if (typeid(rhs) != typeid(*this)) + return false; + const Constant& rhs_ = static_cast&>(rhs); + + return m_value == rhs_.m_value && m_top_level_content == rhs_.m_top_level_content; +} + +template +T Constant::Value() const +{ return m_value; } + +template +T Constant::Eval(const ScriptingContext& context) const +{ return m_value; } + +template +std::string Constant::Description() const +{ return UserString(boost::lexical_cast(m_value)); } + +template +void Constant::SetTopLevelContent(const std::string& content_name) +{ m_top_level_content = content_name; } + +template +unsigned int Constant::GetCheckSum() const +{ + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "ValueRef::Constant"); + CheckSums::CheckSumCombine(retval, m_value); + TraceLogger() << "GetCheckSum(Constant): " << typeid(*this).name() << " value: " << m_value << " retval: " << retval; + return retval; +} + +template <> +FO_COMMON_API std::string Constant::Description() const; + +template <> +FO_COMMON_API std::string Constant::Description() const; + +template <> +FO_COMMON_API std::string Constant::Description() const; + +template <> +FO_COMMON_API std::string Constant::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string Constant::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string Constant::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string Constant::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string Constant::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string Constant::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string Constant::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string Constant::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string Constant::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string Constant::Eval(const ScriptingContext& context) const; + +template +template +void Constant::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRef) + & BOOST_SERIALIZATION_NVP(m_value) + & BOOST_SERIALIZATION_NVP(m_top_level_content); +} + +/////////////////////////////////////////////////////////// +// Variable // +/////////////////////////////////////////////////////////// +template +Variable::Variable(ReferenceType ref_type, const std::vector& property_name, + bool return_immediate_value) : + m_ref_type(ref_type), + m_property_name(property_name.begin(), property_name.end()), + m_return_immediate_value(return_immediate_value) +{} + +template +Variable::Variable(ReferenceType ref_type, const std::string& property_name, + bool return_immediate_value) : + m_ref_type(ref_type), + m_property_name(), + m_return_immediate_value(return_immediate_value) +{ + m_property_name.push_back(property_name); +} + +template +Variable::Variable(ReferenceType ref_type, + const boost::optional& container_name, + const std::string& property_name, + bool return_immediate_value) : + m_ref_type(ref_type), + m_property_name(), + m_return_immediate_value(return_immediate_value) +{ + if (container_name) + m_property_name.push_back(*container_name); + + m_property_name.push_back(property_name); +} + +template +bool Variable::operator==(const ValueRef& rhs) const +{ + if (&rhs == this) + return true; + if (typeid(rhs) != typeid(*this)) + return false; + const Variable& rhs_ = static_cast&>(rhs); + return (m_ref_type == rhs_.m_ref_type) && + (m_property_name == rhs_.m_property_name) && + (m_return_immediate_value == rhs_.m_return_immediate_value); +} + +template +ReferenceType Variable::GetReferenceType() const +{ return m_ref_type; } + +template +const std::vector& Variable::PropertyName() const +{ return m_property_name; } + +template +bool Variable::ReturnImmediateValue() const +{ return m_return_immediate_value; } + +template +bool Variable::RootCandidateInvariant() const +{ return m_ref_type != CONDITION_ROOT_CANDIDATE_REFERENCE; } + +template +bool Variable::LocalCandidateInvariant() const +{ return m_ref_type != CONDITION_LOCAL_CANDIDATE_REFERENCE; } + +template +bool Variable::TargetInvariant() const +{ return m_ref_type != EFFECT_TARGET_REFERENCE && m_ref_type != EFFECT_TARGET_VALUE_REFERENCE; } + +template +bool Variable::SourceInvariant() const +{ return m_ref_type != SOURCE_REFERENCE; } + +template +std::string Variable::Description() const +{ return FormatedDescriptionPropertyNames(m_ref_type, m_property_name, m_return_immediate_value); } + +template +std::string Variable::Dump(unsigned short ntabs) const +{ return ReconstructName(m_property_name, m_ref_type, m_return_immediate_value); } + +template +unsigned int Variable::GetCheckSum() const +{ + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "ValueRef::Variable"); + CheckSums::CheckSumCombine(retval, m_property_name); + CheckSums::CheckSumCombine(retval, m_ref_type); + CheckSums::CheckSumCombine(retval, m_return_immediate_value); + TraceLogger() << "GetCheckSum(Variable): " << typeid(*this).name() << " retval: " << retval; + return retval; +} + +template <> +FO_COMMON_API PlanetSize Variable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API PlanetType Variable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API PlanetEnvironment Variable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API UniverseObjectType Variable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API StarType Variable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API Visibility Variable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API double Variable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API int Variable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API std::string Variable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API std::vector Variable>::Eval(const ScriptingContext& context) const; + +template +template +void Variable::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRef) + & BOOST_SERIALIZATION_NVP(m_ref_type) + & BOOST_SERIALIZATION_NVP(m_property_name) + & BOOST_SERIALIZATION_NVP(m_return_immediate_value); +} + +/////////////////////////////////////////////////////////// +// Statistic // +/////////////////////////////////////////////////////////// +template +Statistic::Statistic(std::unique_ptr>&& value_ref, StatisticType stat_type, + std::unique_ptr&& sampling_condition) : + Variable(NON_OBJECT_REFERENCE, ""), + m_stat_type(stat_type), + m_sampling_condition(std::move(sampling_condition)), + m_value_ref(std::move(value_ref)) +{} + +template +bool Statistic::operator==(const ValueRef& rhs) const +{ + if (&rhs == this) + return true; + if (typeid(rhs) != typeid(*this)) + return false; + const Statistic& rhs_ = static_cast&>(rhs); + + if (m_stat_type != rhs_.m_stat_type) + return false; + if (this->m_value_ref != rhs_.m_value_ref) + return false; + + if (m_sampling_condition == rhs_.m_sampling_condition) { + // check next member + } else if (!m_sampling_condition || !rhs_.m_sampling_condition) { + return false; + } else { + if (*m_sampling_condition != *(rhs_.m_sampling_condition)) + return false; + } + + return true; +} + +template +void Statistic::GetConditionMatches(const ScriptingContext& context, + Condition::ObjectSet& condition_targets, + Condition::Condition* condition) const +{ + condition_targets.clear(); + if (!condition) + return; + condition->Eval(context, condition_targets); +} + +template +void Statistic::GetObjectPropertyValues(const ScriptingContext& context, + const Condition::ObjectSet& objects, + std::map, T>& object_property_values) const +{ + object_property_values.clear(); + + if (m_value_ref) { + // evaluate ValueRef with each condition match as the LocalCandidate + // TODO: Can / should this be paralleized? + for (auto& object : objects) { + T property_value = m_value_ref->Eval(ScriptingContext(context, object)); + object_property_values[object] = property_value; + } + } +} + +template +bool Statistic::RootCandidateInvariant() const +{ + return Variable::RootCandidateInvariant() && + m_sampling_condition->RootCandidateInvariant() && + (!m_value_ref || m_value_ref->RootCandidateInvariant()); +} + +template +bool Statistic::LocalCandidateInvariant() const +{ + // don't need to check if sampling condition is LocalCandidateInvariant, as + // all conditions aren't, but that refers to their own local candidate. no + // condition is explicitly dependent on the parent context's local candidate. + return Variable::LocalCandidateInvariant() && + (!m_value_ref || m_value_ref->LocalCandidateInvariant()); +} + +template +bool Statistic::TargetInvariant() const +{ + return Variable::TargetInvariant() && + m_sampling_condition->TargetInvariant() && + (!m_value_ref || m_value_ref->TargetInvariant()); +} + +template +bool Statistic::SourceInvariant() const +{ + return Variable::SourceInvariant() && + m_sampling_condition->SourceInvariant() && + (!m_value_ref || m_value_ref->SourceInvariant()); +} + +template +std::string Statistic::Description() const +{ + if (m_value_ref) + return StatisticDescription(m_stat_type, m_value_ref->Description(), + m_sampling_condition ? m_sampling_condition->Description() : ""); + + auto temp = Variable::Description(); + if (!temp.empty()) + return StatisticDescription(m_stat_type, temp, m_sampling_condition ? m_sampling_condition->Description() : ""); + + return StatisticDescription(m_stat_type, "", m_sampling_condition ? m_sampling_condition->Description() : ""); +} + +template +std::string Statistic::Dump(unsigned short ntabs) const +{ + std::string retval = "Statistic "; + + switch (m_stat_type) { + case COUNT: retval += "Count"; break; + case UNIQUE_COUNT: retval += "CountUnique"; break; + case IF: retval += "If"; break; + case SUM: retval += "Sum"; break; + case MEAN: retval += "Mean"; break; + case RMS: retval += "RMS"; break; + case MODE: retval += "Mode"; break; + case MAX: retval += "Max"; break; + case MIN: retval += "Min"; break; + case SPREAD: retval += "Spread"; break; + case STDEV: retval += "StDev"; break; + case PRODUCT: retval += "Product"; break; + default: retval += "???"; break; + } + if (m_value_ref) + retval += " value = " + m_value_ref->Dump(); + if (m_sampling_condition) + retval += " condition = " + m_sampling_condition->Dump(); + return retval; +} + +template +void Statistic::SetTopLevelContent(const std::string& content_name) +{ + if (m_sampling_condition) + m_sampling_condition->SetTopLevelContent(content_name); + if (m_value_ref) + m_value_ref->SetTopLevelContent(content_name); +} + +template +T Statistic::Eval(const ScriptingContext& context) const +{ + Condition::ObjectSet condition_matches; + GetConditionMatches(context, condition_matches, m_sampling_condition.get()); + + // special case for IF statistic... return a T(1) for true. + if (m_stat_type == IF) { + if (condition_matches.empty()) + return T(0); + else + return T(1); + } + + // todo: consider allowing MAX and MIN using string sorting? + + // the only other statistic that can be computed on non-number property + // types and that is itself of a non-number type is the most common value + if (m_stat_type != MODE) { + ErrorLogger() << "Statistic::Eval has invalid statistic type: " + << m_stat_type; + return T(-1); + } + + // evaluate property for each condition-matched object + std::map, T> object_property_values; + GetObjectPropertyValues(context, condition_matches, object_property_values); + + // count number of each result, tracking which has the most occurances + std::map histogram; + auto most_common_property_value_it = histogram.begin(); + unsigned int max_seen(0); + + for (const auto& entry : object_property_values) { + const T& property_value = entry.second; + + auto hist_it = histogram.find(property_value); + if (hist_it == histogram.end()) + hist_it = histogram.insert({property_value, 0}).first; + unsigned int& num_seen = hist_it->second; + + num_seen++; + + if (num_seen > max_seen) { + most_common_property_value_it = hist_it; + max_seen = num_seen; + } + } + + // return result (property value) that occured most frequently + return most_common_property_value_it->first; +} + +template +unsigned int Statistic::GetCheckSum() const +{ + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "ValueRef::Statistic"); + CheckSums::CheckSumCombine(retval, m_stat_type); + CheckSums::CheckSumCombine(retval, m_sampling_condition); + CheckSums::CheckSumCombine(retval, m_value_ref); + TraceLogger() << "GetCheckSum(Statisic): " << typeid(*this).name() << " retval: " << retval; + return retval; +} + +template <> +FO_COMMON_API double Statistic::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API int Statistic::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API std::string Statistic::Eval(const ScriptingContext& context) const; + +template +T Statistic::ReduceData(const std::map, T>& object_property_values) const +{ + if (object_property_values.empty()) + return T(0); + + switch (m_stat_type) { + case COUNT: { + return T(object_property_values.size()); + break; + } + case UNIQUE_COUNT: { + std::set observed_values; + for (const auto& entry : object_property_values) { + observed_values.insert(entry.second); + } + return T(observed_values.size()); + break; + } + case IF: { + if (object_property_values.empty()) + return T(0); + return T(1); + break; + } + case SUM: { + T accumulator(0); + for (const auto& entry : object_property_values) { + accumulator += entry.second; + } + return accumulator; + break; + } + + case MEAN: { + T accumulator(0); + for (const auto& entry : object_property_values) { + accumulator += entry.second; + } + return accumulator / static_cast(object_property_values.size()); + break; + } + + case RMS: { + T accumulator(0); + for (const auto& entry : object_property_values) { + accumulator += (entry.second * entry.second); + } + accumulator /= static_cast(object_property_values.size()); + + double retval = std::sqrt(static_cast(accumulator)); + return static_cast(retval); + break; + } + + case MODE: { + // count number of each result, tracking which has the most occurances + std::map histogram; + auto most_common_property_value_it = histogram.begin(); + unsigned int max_seen(0); + + for (const auto& entry : object_property_values) { + const T& property_value = entry.second; + + auto hist_it = histogram.find(property_value); + if (hist_it == histogram.end()) + hist_it = histogram.insert({property_value, 0}).first; + unsigned int& num_seen = hist_it->second; + + num_seen++; + + if (num_seen > max_seen) { + most_common_property_value_it = hist_it; + max_seen = num_seen; + } + } + + // return result (property value) that occured most frequently + return most_common_property_value_it->first; + break; + } + + case MAX: { + auto max_it = object_property_values.begin(); + + for (auto it = object_property_values.begin(); + it != object_property_values.end(); ++it) + { + const T& property_value = it->second; + if (property_value > max_it->second) + max_it = it; + } + + // return maximal observed propery value + return max_it->second; + break; + } + + case MIN: { + auto min_it = object_property_values.begin(); + + for (auto it = object_property_values.begin(); + it != object_property_values.end(); ++it) + { + const T& property_value = it->second; + if (property_value < min_it->second) + min_it = it; + } + + // return minimal observed propery value + return min_it->second; + break; + } + + case SPREAD: { + auto max_it = object_property_values.begin(); + auto min_it = object_property_values.begin(); + + for (auto it = object_property_values.begin(); + it != object_property_values.end(); ++it) + { + const T& property_value = it->second; + if (property_value > max_it->second) + max_it = it; + if (property_value < min_it->second) + min_it = it; + } + + // return difference between maximal and minimal observed propery values + return max_it->second - min_it->second; + break; + } + + case STDEV: { + if (object_property_values.size() < 2) + return T(0); + + // find sample mean + T accumulator(0); + for (const auto& entry : object_property_values) { + accumulator += entry.second; + } + const T MEAN(accumulator / static_cast(object_property_values.size())); + + // find average of squared deviations from sample mean + accumulator = T(0); + for (const auto& entry : object_property_values) { + accumulator += (entry.second - MEAN) * (entry.second - MEAN); + } + const T MEAN_DEV2(accumulator / static_cast(static_cast(object_property_values.size()) - 1)); + double retval = std::sqrt(static_cast(MEAN_DEV2)); + return static_cast(retval); + break; + } + + case PRODUCT: { + T accumulator(1); + for (const auto& entry : object_property_values) { + accumulator *= entry.second; + } + return accumulator; + break; + } + + default: + throw std::runtime_error("ValueRef evaluated with an unknown or invalid StatisticType."); + break; + } +} + +template +template +void Statistic::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Variable) + & BOOST_SERIALIZATION_NVP(m_stat_type) + & BOOST_SERIALIZATION_NVP(m_sampling_condition) + & BOOST_SERIALIZATION_NVP(m_value_ref); +} + +/////////////////////////////////////////////////////////// +// ComplexVariable // +/////////////////////////////////////////////////////////// +template +ComplexVariable::ComplexVariable(const std::string& variable_name, + std::unique_ptr>&& int_ref1, + std::unique_ptr>&& int_ref2, + std::unique_ptr>&& int_ref3, + std::unique_ptr>&& string_ref1, + std::unique_ptr>&& string_ref2, + bool return_immediate_value) : + Variable(NON_OBJECT_REFERENCE, std::vector(1, variable_name), return_immediate_value), + m_int_ref1(std::move(int_ref1)), + m_int_ref2(std::move(int_ref2)), + m_int_ref3(std::move(int_ref3)), + m_string_ref1(std::move(string_ref1)), + m_string_ref2(std::move(string_ref2)) +{} + +template +bool ComplexVariable::operator==(const ValueRef& rhs) const +{ + if (&rhs == this) + return true; + if (typeid(rhs) != typeid(*this)) + return false; + const ComplexVariable& rhs_ = static_cast&>(rhs); + + if (this->m_property_name != rhs_.m_property_name) + return false; + if (this->m_return_immediate_value != rhs_.m_return_immediate_value) + return false; + + if (m_int_ref1 == rhs_.m_int_ref1) { + // check next member + } else if (!m_int_ref1 || !rhs_.m_int_ref1) { + return false; + } else { + if (*m_int_ref1 != *(rhs_.m_int_ref1)) + return false; + } + + if (m_int_ref2 == rhs_.m_int_ref2) { + // check next member + } else if (!m_int_ref2 || !rhs_.m_int_ref2) { + return false; + } else { + if (*m_int_ref2 != *(rhs_.m_int_ref2)) + return false; + } + + if (m_int_ref3 == rhs_.m_int_ref3) { + // check next member + } else if (!m_int_ref3 || !rhs_.m_int_ref3) { + return false; + } else { + if (*m_int_ref3 != *(rhs_.m_int_ref3)) + return false; + } + + if (m_string_ref1 == rhs_.m_string_ref1) { + // check next member + } else if (!m_string_ref1 || !rhs_.m_string_ref1) { + return false; + } else { + if (*m_string_ref1 != *(rhs_.m_string_ref1)) + return false; + } + + if (m_string_ref2 == rhs_.m_string_ref2) { + // check next member + } else if (!m_string_ref2 || !rhs_.m_string_ref2) { + return false; + } else { + if (*m_string_ref2 != *(rhs_.m_string_ref2)) + return false; + } + + return true; +} + +template +const ValueRef* ComplexVariable::IntRef1() const +{ return m_int_ref1.get(); } + +template +const ValueRef* ComplexVariable::IntRef2() const +{ return m_int_ref2.get(); } + +template +const ValueRef* ComplexVariable::IntRef3() const +{ return m_int_ref3.get(); } + +template +const ValueRef* ComplexVariable::StringRef1() const +{ return m_string_ref1.get(); } + +template +const ValueRef* ComplexVariable::StringRef2() const +{ return m_string_ref2.get(); } + +template +bool ComplexVariable::RootCandidateInvariant() const +{ + return Variable::RootCandidateInvariant() + && (!m_int_ref1 || m_int_ref1->RootCandidateInvariant()) + && (!m_int_ref2 || m_int_ref2->RootCandidateInvariant()) + && (!m_int_ref3 || m_int_ref3->RootCandidateInvariant()) + && (!m_string_ref1 || m_string_ref1->RootCandidateInvariant()) + && (!m_string_ref2 || m_string_ref2->RootCandidateInvariant()); +} + +template +bool ComplexVariable::LocalCandidateInvariant() const +{ + return (!m_int_ref1 || m_int_ref1->LocalCandidateInvariant()) + && (!m_int_ref2 || m_int_ref2->LocalCandidateInvariant()) + && (!m_int_ref3 || m_int_ref3->LocalCandidateInvariant()) + && (!m_string_ref1 || m_string_ref1->LocalCandidateInvariant()) + && (!m_string_ref2 || m_string_ref2->LocalCandidateInvariant()); +} + +template +bool ComplexVariable::TargetInvariant() const +{ + return (!m_int_ref1 || m_int_ref1->TargetInvariant()) + && (!m_int_ref2 || m_int_ref2->TargetInvariant()) + && (!m_int_ref3 || m_int_ref3->TargetInvariant()) + && (!m_string_ref1 || m_string_ref1->TargetInvariant()) + && (!m_string_ref2 || m_string_ref2->TargetInvariant()); +} + +template +bool ComplexVariable::SourceInvariant() const +{ + return (!m_int_ref1 || m_int_ref1->SourceInvariant()) + && (!m_int_ref2 || m_int_ref2->SourceInvariant()) + && (!m_int_ref3 || m_int_ref3->SourceInvariant()) + && (!m_string_ref1 || m_string_ref1->SourceInvariant()) + && (!m_string_ref2 || m_string_ref2->SourceInvariant()); +} + +template +std::string ComplexVariable::Description() const +{ + std::string retval = ComplexVariableDescription( + this->m_property_name, + m_int_ref1 ? m_int_ref1.get() : nullptr, + m_int_ref2 ? m_int_ref2.get() : nullptr, + m_int_ref3 ? m_int_ref3.get() : nullptr, + m_string_ref1 ? m_string_ref1.get() : nullptr, + m_string_ref2 ? m_string_ref2.get() : nullptr); + if (retval.empty()) + return Dump(); + return retval; +} + +template +std::string ComplexVariable::Dump(unsigned short ntabs) const +{ + return ComplexVariableDump(this->m_property_name, + m_int_ref1 ? m_int_ref1.get() : nullptr, + m_int_ref2 ? m_int_ref2.get() : nullptr, + m_int_ref3 ? m_int_ref3.get() : nullptr, + m_string_ref1 ? m_string_ref1.get() : nullptr, + m_string_ref2 ? m_string_ref2.get() : nullptr); +} + +template +void ComplexVariable::SetTopLevelContent(const std::string& content_name) +{ + if (m_int_ref1) + m_int_ref1->SetTopLevelContent(content_name); + if (m_int_ref2) + m_int_ref2->SetTopLevelContent(content_name); + if (m_int_ref3) + m_int_ref3->SetTopLevelContent(content_name); + if (m_string_ref1) + m_string_ref1->SetTopLevelContent(content_name); + if (m_string_ref2) + m_string_ref2->SetTopLevelContent(content_name); +} + +template +unsigned int ComplexVariable::GetCheckSum() const +{ + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "ValueRef::ComplexVariable"); + CheckSums::CheckSumCombine(retval, m_int_ref1); + CheckSums::CheckSumCombine(retval, m_int_ref2); + CheckSums::CheckSumCombine(retval, m_int_ref3); + CheckSums::CheckSumCombine(retval, m_string_ref1); + CheckSums::CheckSumCombine(retval, m_string_ref2); + TraceLogger() << "GetCheckSum(ComplexVariable): " << typeid(*this).name() << " retval: " << retval; + return retval; +} + +template <> +FO_COMMON_API PlanetSize ComplexVariable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API PlanetType ComplexVariable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API PlanetEnvironment ComplexVariable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API UniverseObjectType ComplexVariable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API StarType ComplexVariable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API Visibility ComplexVariable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API double ComplexVariable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API int ComplexVariable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API std::string ComplexVariable::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API std::string ComplexVariable::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string ComplexVariable::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string ComplexVariable::Dump(unsigned short ntabs) const; + +template <> +FO_COMMON_API std::string ComplexVariable::Dump(unsigned short ntabs) const; + +template +template +void ComplexVariable::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Variable) + & BOOST_SERIALIZATION_NVP(m_int_ref1) + & BOOST_SERIALIZATION_NVP(m_int_ref2) + & BOOST_SERIALIZATION_NVP(m_int_ref3) + & BOOST_SERIALIZATION_NVP(m_string_ref1) + & BOOST_SERIALIZATION_NVP(m_string_ref2); +} + +/////////////////////////////////////////////////////////// +// StaticCast // +/////////////////////////////////////////////////////////// +template +template +StaticCast::StaticCast( + T&& value_ref, + typename std::enable_if>>::value>::type*) : + Variable(value_ref->GetReferenceType(), value_ref->PropertyName()), + m_value_ref(std::move(value_ref)) +{} + +template +template +StaticCast::StaticCast( + T&& value_ref, + typename std::enable_if< + std::is_convertible>>::value + && !std::is_convertible>>::value>::type*) : + Variable(NON_OBJECT_REFERENCE), + m_value_ref(std::move(value_ref)) +{} + +template +bool StaticCast::operator==(const ValueRef& rhs) const +{ + if (&rhs == this) + return true; + if (typeid(rhs) != typeid(*this)) + return false; + const StaticCast& rhs_ = + static_cast&>(rhs); + + if (m_value_ref == rhs_.m_value_ref) { + // check next member + } else if (!m_value_ref || !rhs_.m_value_ref) { + return false; + } else { + if (*m_value_ref != *(rhs_.m_value_ref)) + return false; + } + + return true; +} + +template +ToType StaticCast::Eval(const ScriptingContext& context) const +{ return static_cast(m_value_ref->Eval(context)); } + +template +bool StaticCast::RootCandidateInvariant() const +{ return m_value_ref->RootCandidateInvariant(); } + +template +bool StaticCast::LocalCandidateInvariant() const +{ return m_value_ref->LocalCandidateInvariant(); } + +template +bool StaticCast::TargetInvariant() const +{ return m_value_ref->TargetInvariant(); } + +template +bool StaticCast::SourceInvariant() const +{ return m_value_ref->SourceInvariant(); } + +template +std::string StaticCast::Description() const +{ return m_value_ref->Description(); } + +template +std::string StaticCast::Dump(unsigned short ntabs) const +{ return m_value_ref->Dump(ntabs); } + +template +void StaticCast::SetTopLevelContent(const std::string& content_name) +{ + if (m_value_ref) + m_value_ref->SetTopLevelContent(content_name); +} + +template +unsigned int StaticCast::GetCheckSum() const +{ + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "ValueRef::StaticCast"); + CheckSums::CheckSumCombine(retval, m_value_ref); + TraceLogger() << "GetCheckSum(StaticCast): " << typeid(*this).name() << " retval: " << retval; + return retval; +} + +template +template +void StaticCast::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRef) + & BOOST_SERIALIZATION_NVP(m_value_ref); +} + +/////////////////////////////////////////////////////////// +// StringCast // +/////////////////////////////////////////////////////////// +template +StringCast::StringCast(std::unique_ptr>&& value_ref) : + Variable(NON_OBJECT_REFERENCE), + m_value_ref(std::move(value_ref)) +{ + auto raw_ref_ptr = m_value_ref.get(); + // if looking up a the results of ValueRef::Variable::Eval, can copy that + // ValueRef's internals to expose the reference type and property name from + // this ValueRef + if (auto var_ref = dynamic_cast*>(raw_ref_ptr)) { + this->m_ref_type = var_ref->GetReferenceType(); + this->m_property_name = var_ref->PropertyName(); + } +} + +template +bool StringCast::operator==(const ValueRef& rhs) const +{ + if (&rhs == this) + return true; + if (typeid(rhs) != typeid(*this)) + return false; + const StringCast& rhs_ = + static_cast&>(rhs); + + if (m_value_ref == rhs_.m_value_ref) { + // check next member + } else if (!m_value_ref || !rhs_.m_value_ref) { + return false; + } else { + if (*m_value_ref != *(rhs_.m_value_ref)) + return false; + } + + return true; +} + +template +std::string StringCast::Eval(const ScriptingContext& context) const +{ + if (!m_value_ref) + return ""; + std::string retval; + try { + retval = boost::lexical_cast(m_value_ref->Eval(context)); + } catch (...) { + } + return retval; +} + +template +unsigned int StringCast::GetCheckSum() const +{ + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "ValueRef::StringCast"); + CheckSums::CheckSumCombine(retval, m_value_ref); + TraceLogger() << "GetCheckSum(StringCast): " << typeid(*this).name() << " retval: " << retval; + return retval; +} + +template <> +FO_COMMON_API std::string StringCast::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API std::string StringCast::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API std::string StringCast>::Eval(const ScriptingContext& context) const; + +template +bool StringCast::RootCandidateInvariant() const +{ return m_value_ref->RootCandidateInvariant(); } + +template +bool StringCast::LocalCandidateInvariant() const +{ return m_value_ref->LocalCandidateInvariant(); } + +template +bool StringCast::TargetInvariant() const +{ return m_value_ref->TargetInvariant(); } + +template +bool StringCast::SourceInvariant() const +{ return m_value_ref->SourceInvariant(); } + +template +std::string StringCast::Description() const +{ return m_value_ref->Description(); } + +template +std::string StringCast::Dump(unsigned short ntabs) const +{ return m_value_ref->Dump(ntabs); } + +template +void StringCast::SetTopLevelContent(const std::string& content_name) { + if (m_value_ref) + m_value_ref->SetTopLevelContent(content_name); +} + +template +template +void StringCast::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRef) + & BOOST_SERIALIZATION_NVP(m_value_ref); +} + +/////////////////////////////////////////////////////////// +// UserStringLookup // +/////////////////////////////////////////////////////////// +template +UserStringLookup::UserStringLookup(std::unique_ptr>&& value_ref) : + Variable(NON_OBJECT_REFERENCE), + m_value_ref(std::move(value_ref)) +{ + auto raw_ref_ptr = m_value_ref.get(); + // if looking up a the results of ValueRef::Variable::Eval, can copy that + // ValueRef's internals to expose the reference type and property name from + // this ValueRef + if (auto var_ref = dynamic_cast*>(raw_ref_ptr)) { + this->m_ref_type = var_ref->GetReferenceType(); + this->m_property_name = var_ref->PropertyName(); + } +} + +template +bool UserStringLookup::operator==(const ValueRef& rhs) const { + if (&rhs == this) + return true; + if (typeid(rhs) != typeid(*this)) + return false; + const UserStringLookup& rhs_ = static_cast(rhs); + + if (m_value_ref == rhs_.m_value_ref) { + // check next member + } + else if (!m_value_ref || !rhs_.m_value_ref) { + return false; + } + else { + if (*m_value_ref != *(rhs_.m_value_ref)) + return false; + } + + return true; +} + +template +std::string UserStringLookup::Eval(const ScriptingContext& context) const { + if (!m_value_ref) + return ""; + std::string ref_val = boost::lexical_cast(m_value_ref->Eval(context)); + if (ref_val.empty() || !UserStringExists(ref_val)) + return ""; + return UserString(ref_val); +} + +template <> +FO_COMMON_API std::string UserStringLookup::Eval(const ScriptingContext& context) const; + +template <> +FO_COMMON_API std::string UserStringLookup>::Eval(const ScriptingContext& context) const; + +template +bool UserStringLookup::RootCandidateInvariant() const +{ + return m_value_ref->RootCandidateInvariant(); +} + +template +bool UserStringLookup::LocalCandidateInvariant() const +{ + return !m_value_ref || m_value_ref->LocalCandidateInvariant(); +} + +template +bool UserStringLookup::TargetInvariant() const +{ + return !m_value_ref || m_value_ref->TargetInvariant(); +} + +template +bool UserStringLookup::SourceInvariant() const +{ + return !m_value_ref || m_value_ref->SourceInvariant(); +} + +template +std::string UserStringLookup::Description() const +{ + return m_value_ref->Description(); +} + +template +std::string UserStringLookup::Dump(unsigned short ntabs) const +{ + return m_value_ref->Dump(ntabs); +} + +template +void UserStringLookup::SetTopLevelContent(const std::string& content_name) { + if (m_value_ref) + m_value_ref->SetTopLevelContent(content_name); +} + +template +unsigned int UserStringLookup::GetCheckSum() const +{ + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "ValueRef::UserStringLookup"); + CheckSums::CheckSumCombine(retval, m_value_ref); + TraceLogger() << "GetCheckSum(UserStringLookup): " << typeid(*this).name() << " retval: " << retval; + return retval; +} + +template +template +void UserStringLookup::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRef) + & BOOST_SERIALIZATION_NVP(m_value_ref); +} + +/////////////////////////////////////////////////////////// +// NameLookup // +/////////////////////////////////////////////////////////// +template +void NameLookup::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRef) + & BOOST_SERIALIZATION_NVP(m_value_ref) + & BOOST_SERIALIZATION_NVP(m_lookup_type); +} + +/////////////////////////////////////////////////////////// +// Operation // +/////////////////////////////////////////////////////////// +template +Operation::Operation(OpType op_type, + std::unique_ptr>&& operand1, + std::unique_ptr>&& operand2) : + m_op_type(op_type) +{ + if (operand1) + m_operands.push_back(std::move(operand1)); + if (operand2) + m_operands.push_back(std::move(operand2)); + DetermineIfConstantExpr(); + CacheConstValue(); +} + +template +Operation::Operation(OpType op_type, std::unique_ptr>&& operand) : + m_op_type(op_type) +{ + if (operand) + m_operands.push_back(std::move(operand)); + DetermineIfConstantExpr(); + CacheConstValue(); +} + +template +Operation::Operation(OpType op_type, std::vector>>&& operands) : + m_op_type(op_type), + m_operands(std::move(operands)) +{ + DetermineIfConstantExpr(); + CacheConstValue(); +} + +template +void Operation::DetermineIfConstantExpr() +{ + if (m_op_type == RANDOM_UNIFORM || m_op_type == RANDOM_PICK) { + m_constant_expr = false; + return; + } + + m_constant_expr = true; // may be overridden... + + for (auto& operand : m_operands) { + if (operand && !operand->ConstantExpr()) { + m_constant_expr = false; + return; + } + } +} + +template +void Operation::CacheConstValue() +{ + if (!m_constant_expr) + return; + + m_cached_const_value = this->EvalImpl(ScriptingContext()); +} + +template +bool Operation::operator==(const ValueRef& rhs) const +{ + if (&rhs == this) + return true; + if (typeid(rhs) != typeid(*this)) + return false; + const Operation& rhs_ = static_cast&>(rhs); + + if (m_operands == rhs_.m_operands) + return true; + + if (m_operands.size() != rhs_.m_operands.size()) + return false; + + for (unsigned int i = 0; i < m_operands.size(); ++i) { + if (m_operands[i] != rhs_.m_operands[i]) + return false; + if (m_operands[i] && *(m_operands[i]) != *(rhs_.m_operands[i])) + return false; + } + + // should be redundant... + if (m_constant_expr != rhs_.m_constant_expr) + return false; + + return true; +} + +template +OpType Operation::GetOpType() const +{ return m_op_type; } + +template +const ValueRef* Operation::LHS() const +{ + if (m_operands.empty()) + return nullptr; + return m_operands[0].get(); +} + +template +const ValueRef* Operation::RHS() const +{ + if (m_operands.size() < 2) + return nullptr; + return m_operands[1].get(); +} + +template +const std::vector*> Operation::Operands() const +{ + std::vector*> retval(m_operands.size()); + std::transform(m_operands.begin(), m_operands.end(), retval.begin(), + [](const auto& xx){ return xx.get(); }); + return retval; +} + +template +T Operation::Eval(const ScriptingContext& context) const +{ + if (m_constant_expr) + return m_cached_const_value; + return this->EvalImpl(context); +} + +template +T Operation::EvalImpl(const ScriptingContext& context) const +{ + switch (m_op_type) { + case TIMES: { + // useful for writing a "Statistic If" expression with arbitrary types. + // If returns T(0) or T(1) for nothing or something matching the + // sampling condition. This can be checked here by returning T(0) if + // the LHS operand is T(0) and just returning RHS() otherwise. + if (!LHS()->Eval(context)) + return T(0); + return RHS()->Eval(context); + break; + } + + case MAXIMUM: + case MINIMUM: { + // evaluate all operands, return smallest or biggest + std::set vals; + for (auto& vr : m_operands) { + if (vr) + vals.insert(vr->Eval(context)); + } + if (m_op_type == MINIMUM) + return vals.empty() ? T(-1) : *vals.begin(); + else + return vals.empty() ? T(-1) : *vals.rbegin(); + break; + } + + case RANDOM_PICK: { + // select one operand, evaluate it, return result + if (m_operands.empty()) + return T(-1); // should be INVALID_T of enum types + unsigned int idx = RandSmallInt(0, m_operands.size() - 1); + auto& vr = *std::next(m_operands.begin(), idx); + if (!vr) + return T(-1); // should be INVALID_T of enum types + return vr->Eval(context); + break; + } + + case COMPARE_EQUAL: + case COMPARE_GREATER_THAN: + case COMPARE_GREATER_THAN_OR_EQUAL: + case COMPARE_LESS_THAN: + case COMPARE_LESS_THAN_OR_EQUAL: + case COMPARE_NOT_EQUAL: { + const T&& lhs_val = LHS()->Eval(context); + const T&& rhs_val = RHS()->Eval(context); + bool test_result = false; + switch (m_op_type) { + case COMPARE_EQUAL: test_result = lhs_val == rhs_val; break; + case COMPARE_GREATER_THAN: test_result = lhs_val > rhs_val; break; + case COMPARE_GREATER_THAN_OR_EQUAL: test_result = lhs_val >= rhs_val; break; + case COMPARE_LESS_THAN: test_result = lhs_val < rhs_val; break; + case COMPARE_LESS_THAN_OR_EQUAL: test_result = lhs_val <= rhs_val; break; + case COMPARE_NOT_EQUAL: test_result = lhs_val != rhs_val; break; + default: break; // ??? do nothing, default to false + } + if (m_operands.size() < 3) { + return T(1); + } else if (m_operands.size() < 4) { + if (test_result) + return m_operands[2]->Eval(context); + else + return T(0); + } else { + if (test_result) + return m_operands[2]->Eval(context); + else + return m_operands[3]->Eval(context); + } + break; + } + + default: + break; + } + + throw std::runtime_error("ValueRef::Operation::EvalImpl evaluated with an unknown or invalid OpType."); +} + +template +unsigned int Operation::GetCheckSum() const +{ + unsigned int retval{0}; + + CheckSums::CheckSumCombine(retval, "ValueRef::Operation"); + CheckSums::CheckSumCombine(retval, m_op_type); + CheckSums::CheckSumCombine(retval, m_operands); + CheckSums::CheckSumCombine(retval, m_constant_expr); + CheckSums::CheckSumCombine(retval, m_cached_const_value); + TraceLogger() << "GetCheckSum(Operation): " << typeid(*this).name() << " retval: " << retval; + return retval; +} + +template <> +FO_COMMON_API std::string Operation::EvalImpl(const ScriptingContext& context) const; + +template <> +FO_COMMON_API double Operation::EvalImpl(const ScriptingContext& context) const; + +template <> +FO_COMMON_API int Operation::EvalImpl(const ScriptingContext& context) const; + +template +bool Operation::RootCandidateInvariant() const +{ + if (m_op_type == RANDOM_UNIFORM || m_op_type == RANDOM_PICK) + return false; + for (auto& operand : m_operands) { + if (operand && !operand->RootCandidateInvariant()) + return false; + } + return true; +} + +template +bool Operation::LocalCandidateInvariant() const +{ + if (m_op_type == RANDOM_UNIFORM || m_op_type == RANDOM_PICK) + return false; + for (auto& operand : m_operands) { + if (operand && !operand->LocalCandidateInvariant()) + return false; + } + return true; +} + +template +bool Operation::TargetInvariant() const +{ + if (m_op_type == RANDOM_UNIFORM || m_op_type == RANDOM_PICK) + return false; + for (auto& operand : m_operands) { + if (operand && !operand->TargetInvariant()) + return false; + } + return true; +} + +template +bool Operation::SourceInvariant() const +{ + if (m_op_type == RANDOM_UNIFORM || m_op_type == RANDOM_PICK) + return false; + for (auto& operand : m_operands) { + if (operand && !operand->SourceInvariant()) + return false; + } + return true; +} + +template +bool Operation::SimpleIncrement() const +{ + if (m_op_type != PLUS && m_op_type != MINUS) + return false; + if (m_operands.size() < 2 || !m_operands[0] || !m_operands[1]) + return false; + if (!(m_operands[1]->ConstantExpr())) + return false; + const auto lhs = dynamic_cast*>(m_operands[0].get()); + if (!lhs) + return false; + return lhs->GetReferenceType() == EFFECT_TARGET_VALUE_REFERENCE; +} + +template +std::string Operation::Description() const +{ + if (m_op_type == NEGATE) { + if (auto rhs = dynamic_cast*>(LHS())) { + OpType op_type = rhs->GetOpType(); + if (op_type == PLUS || op_type == MINUS || + op_type == TIMES || op_type == DIVIDE || + op_type == NEGATE || op_type == EXPONENTIATE) + return "-(" + LHS()->Description() + ")"; + } else { + return "-" + LHS()->Description(); + } + } + + if (m_op_type == ABS) + return "abs(" + LHS()->Description() + ")"; + if (m_op_type == LOGARITHM) + return "log(" + LHS()->Description() + ")"; + if (m_op_type == SINE) + return "sin(" + LHS()->Description() + ")"; + if (m_op_type == COSINE) + return "cos(" + LHS()->Description() + ")"; + + if (m_op_type == MINIMUM) { + std::string retval = "min("; + for (auto it = m_operands.begin(); it != m_operands.end(); ++it) { + if (it != m_operands.begin()) + retval += ", "; + retval += (*it)->Description(); + } + retval += ")"; + return retval; + } + if (m_op_type == MAXIMUM) { + std::string retval = "max("; + for (auto it = m_operands.begin(); it != m_operands.end(); ++it) { + if (it != m_operands.begin()) + retval += ", "; + retval += (*it)->Description(); + } + retval += ")"; + return retval; + } + + if (m_op_type == RANDOM_UNIFORM) + return "RandomNumber(" + LHS()->Description() + ", " + RHS()->Description() + ")"; + + if (m_op_type == RANDOM_PICK) { + std::string retval = "OneOf("; + for (auto it = m_operands.begin(); it != m_operands.end(); ++it) { + if (it != m_operands.begin()) + retval += ", "; + retval += (*it)->Description(); + } + retval += ")"; + return retval; + } + + if (m_op_type == ROUND_NEAREST) + return "round(" + LHS()->Description() + ")"; + if (m_op_type == ROUND_UP) + return "ceil(" + LHS()->Description() + ")"; + if (m_op_type == ROUND_DOWN) + return "floor(" + LHS()->Description() + ")"; + + bool parenthesize_lhs = false; + bool parenthesize_rhs = false; + if (auto lhs = dynamic_cast*>(LHS())) { + OpType op_type = lhs->GetOpType(); + if ( + (m_op_type == EXPONENTIATE && + (op_type == EXPONENTIATE || op_type == TIMES || op_type == DIVIDE || + op_type == PLUS || op_type == MINUS || op_type == NEGATE) + ) || + (((m_op_type == TIMES || m_op_type == DIVIDE) && + (op_type == PLUS || op_type == MINUS)) || op_type == NEGATE) + ) + parenthesize_lhs = true; + } + if (auto rhs = dynamic_cast*>(RHS())) { + OpType op_type = rhs->GetOpType(); + if ( + (m_op_type == EXPONENTIATE && + (op_type == EXPONENTIATE || op_type == TIMES || op_type == DIVIDE || + op_type == PLUS || op_type == MINUS || op_type == NEGATE) + ) || + (((m_op_type == TIMES || m_op_type == DIVIDE) && + (op_type == PLUS || op_type == MINUS)) || op_type == NEGATE) + ) + parenthesize_rhs = true; + } + + std::string retval; + if (parenthesize_lhs) + retval += '(' + LHS()->Description() + ')'; + else + retval += LHS()->Description(); + + switch (m_op_type) { + case PLUS: retval += " + "; break; + case MINUS: retval += " - "; break; + case TIMES: retval += " * "; break; + case DIVIDE: retval += " / "; break; + case EXPONENTIATE: retval += " ^ "; break; + default: retval += " ? "; break; + } + + if (parenthesize_rhs) + retval += '(' + RHS()->Description() + ')'; + else + retval += RHS()->Description(); + + return retval; +} + +template +std::string Operation::Dump(unsigned short ntabs) const +{ + if (m_op_type == NEGATE) { + if (auto rhs = dynamic_cast*>(LHS())) { + OpType op_type = rhs->GetOpType(); + if (op_type == PLUS || op_type == MINUS || + op_type == TIMES || op_type == DIVIDE || + op_type == NEGATE || op_type == EXPONENTIATE) + return "-(" + LHS()->Dump(ntabs) + ")"; + } else { + return "-" + LHS()->Dump(ntabs); + } + } + + if (m_op_type == ABS) + return "abs(" + LHS()->Dump(ntabs) + ")"; + if (m_op_type == LOGARITHM) + return "log(" + LHS()->Dump(ntabs) + ")"; + if (m_op_type == SINE) + return "sin(" + LHS()->Dump(ntabs) + ")"; + if (m_op_type == COSINE) + return "cos(" + LHS()->Dump(ntabs) + ")"; + + if (m_op_type == MINIMUM) { + std::string retval = "min("; + for (auto it = m_operands.begin(); it != m_operands.end(); ++it) { + if (it != m_operands.begin()) + retval += ", "; + retval += (*it)->Dump(ntabs); + } + retval += ")"; + return retval; + } + if (m_op_type == MAXIMUM) { + std::string retval = "max("; + for (auto it = m_operands.begin(); it != m_operands.end(); ++it) { + if (it != m_operands.begin()) + retval += ", "; + retval += (*it)->Dump(ntabs); + } + retval += ")"; + return retval; + } + + if (m_op_type == RANDOM_UNIFORM) + return "random(" + LHS()->Dump(ntabs) + ", " + LHS()->Dump(ntabs) + ")"; + + if (m_op_type == RANDOM_PICK) { + std::string retval = "randompick("; + for (auto it = m_operands.begin(); it != m_operands.end(); ++it) { + if (it != m_operands.begin()) + retval += ", "; + retval += (*it)->Dump(ntabs); + } + retval += ")"; + return retval; + } + + if (m_op_type == ROUND_NEAREST) + return "round(" + LHS()->Dump(ntabs) + ")"; + if (m_op_type == ROUND_UP) + return "ceil(" + LHS()->Dump(ntabs) + ")"; + if (m_op_type == ROUND_DOWN) + return "floor(" + LHS()->Dump(ntabs) + ")"; + + bool parenthesize_lhs = false; + bool parenthesize_rhs = false; + if (auto lhs = dynamic_cast*>(LHS())) { + OpType op_type = lhs->GetOpType(); + if ( + (m_op_type == EXPONENTIATE && + (op_type == EXPONENTIATE || op_type == TIMES || op_type == DIVIDE || + op_type == PLUS || op_type == MINUS || op_type == NEGATE) + ) || + (((m_op_type == TIMES || m_op_type == DIVIDE) && + (op_type == PLUS || op_type == MINUS)) || op_type == NEGATE) + ) + parenthesize_lhs = true; + } + if (auto rhs = dynamic_cast*>(RHS())) { + OpType op_type = rhs->GetOpType(); + if ( + (m_op_type == EXPONENTIATE && + (op_type == EXPONENTIATE || op_type == TIMES || op_type == DIVIDE || + op_type == PLUS || op_type == MINUS || op_type == NEGATE) + ) || + (((m_op_type == TIMES || m_op_type == DIVIDE) && + (op_type == PLUS || op_type == MINUS)) || op_type == NEGATE) + ) + parenthesize_rhs = true; + } + + std::string retval; + if (parenthesize_lhs) + retval += '(' + LHS()->Dump(ntabs) + ')'; + else + retval += LHS()->Dump(ntabs); + + switch (m_op_type) { + case PLUS: retval += " + "; break; + case MINUS: retval += " - "; break; + case TIMES: retval += " * "; break; + case DIVIDE: retval += " / "; break; + case EXPONENTIATE: retval += " ^ "; break; + default: retval += " ? "; break; + } + + if (parenthesize_rhs) + retval += '(' + RHS()->Dump(ntabs) + ')'; + else + retval += RHS()->Dump(ntabs); + + return retval; +} + +template +void Operation::SetTopLevelContent(const std::string& content_name) { + for (auto& operand : m_operands) { + if (operand) + operand->SetTopLevelContent(content_name); + } +} + +template +template +void Operation::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ValueRef) + & BOOST_SERIALIZATION_NVP(m_op_type) + & BOOST_SERIALIZATION_NVP(m_operands) + & BOOST_SERIALIZATION_NVP(m_constant_expr) + & BOOST_SERIALIZATION_NVP(m_cached_const_value); +} + +} // namespace ValueRef + +#endif // _ValueRefs_h_ diff --git a/util/AppInterface.cpp b/util/AppInterface.cpp index a45b167fb0a..1bb71790415 100644 --- a/util/AppInterface.cpp +++ b/util/AppInterface.cpp @@ -1,5 +1,26 @@ #include "AppInterface.h" +#include "../parse/Parse.h" +#include "../Empire/EmpireManager.h" +#include "../universe/BuildingType.h" +#include "../universe/Encyclopedia.h" +#include "../universe/FieldType.h" +#include "../universe/Special.h" +#include "../universe/Species.h" +#include "../universe/ShipDesign.h" +#include "../universe/ShipPart.h" +#include "../universe/ShipHull.h" +#include "../universe/Tech.h" +#include "../util/Directories.h" +#include "../util/GameRules.h" +#include "../util/Pending.h" + +#include + +#include + +extern template TechManager::TechParseTuple parse::techs(const boost::filesystem::path& path); + const int INVALID_GAME_TURN = -(2 << 15) + 1; const int BEFORE_FIRST_TURN = -(2 << 14); const int IMPOSSIBLY_LARGE_TURN = 2 << 15; @@ -28,3 +49,73 @@ int IApp::MAX_AI_PLAYERS() { static const int max_number_AIs = 40; return max_number_AIs; } + +void IApp::StartBackgroundParsing() { + namespace fs = boost::filesystem; + + const auto& rdir = GetResourceDir(); + if (!fs::exists(rdir) || !fs::is_directory(rdir)) { + ErrorLogger() << "Background parse given non-existant resources directory!"; + return; + } + + if (fs::exists(rdir / "scripting/buildings")) + GetBuildingTypeManager().SetBuildingTypes(Pending::StartParsing(parse::buildings, rdir / "scripting/buildings")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/buildings").string(); + + if (fs::exists(rdir / "scripting/encyclopedia")) + GetEncyclopedia().SetArticles(Pending::StartParsing(parse::encyclopedia_articles, rdir / "scripting/encyclopedia")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/encyclopedia").string(); + + if (fs::exists(rdir / "scripting/fields")) + GetFieldTypeManager().SetFieldTypes(Pending::StartParsing(parse::fields, rdir / "scripting/fields")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/fields").string(); + + if (fs::exists(rdir / "scripting/specials")) + GetSpecialsManager().SetSpecialsTypes(Pending::StartParsing(parse::specials, rdir / "scripting/specials")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/specials").string(); + + if (fs::exists(rdir / "scripting/species")) + GetSpeciesManager().SetSpeciesTypes(Pending::StartParsing(parse::species, rdir / "scripting/species")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/species").string(); + + if (fs::exists(rdir / "scripting/ship_parts")) + GetShipPartManager().SetShipParts(Pending::StartParsing(parse::ship_parts, rdir / "scripting/ship_parts")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/ship_parts").string(); + + if (fs::exists(rdir / "scripting/ship_hulls")) + GetShipHullManager().SetShipHulls(Pending::StartParsing(parse::ship_hulls, rdir / "scripting/ship_hulls")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/ship_hulls").string(); + + if (fs::exists(rdir / "scripting/ship_designs")) + GetPredefinedShipDesignManager().SetShipDesignTypes(Pending::StartParsing(parse::ship_designs, rdir / "scripting/ship_designs")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/ship_designs").string(); + + if (fs::exists(rdir / "scripting/monster_designs")) + GetPredefinedShipDesignManager().SetMonsterDesignTypes(Pending::StartParsing(parse::ship_designs, rdir / "scripting/monster_designs")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/monster_designs").string(); + + if (fs::exists(rdir / "scripting/game_rules.focs.txt")) + GetGameRules().Add(Pending::StartParsing(parse::game_rules, rdir / "scripting/game_rules.focs.txt")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/game_rules.focs.txt").string(); + + if (fs::exists(rdir / "scripting/techs")) + GetTechManager().SetTechs(Pending::StartParsing(parse::techs, rdir / "scripting/techs")); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "scripting/techs").string(); + + if (fs::exists(rdir / "empire_colors.xml")) + InitEmpireColors(rdir / "empire_colors.xml"); + else + ErrorLogger() << "Background parse path doesn't exist: " << (rdir / "empire_colors.xml").string(); +} diff --git a/util/AppInterface.h b/util/AppInterface.h index 94dc1572fe4..2dc559b44d3 100644 --- a/util/AppInterface.h +++ b/util/AppInterface.h @@ -22,15 +22,33 @@ class Field; struct GalaxySetupData; class FO_COMMON_API IApp { +protected: + IApp(); + public: + IApp(const IApp&) = delete; + + IApp(IApp&&) = delete; + virtual ~IApp(); + const IApp& operator=(const IApp&) = delete; + + IApp& operator=(IApp&&) = delete; + /** Returns a IApp pointer to the singleton instance of the app. */ static IApp* GetApp(); - /** Returns applications copy of Universe. */ + //! Returns the ::Universe known to this application + //! + //! @return + //! A constant reference to the single ::Universe instance representing the + //! known universe of this application. virtual Universe& GetUniverse() = 0; + /** Start parsing universe object types on a separate thread. */ + virtual void StartBackgroundParsing(); + /** Returns the set of known Empires for this application. */ virtual EmpireManager& Empires() = 0; @@ -47,7 +65,9 @@ class FO_COMMON_API IApp { virtual std::string GetVisibleObjectName(std::shared_ptr object) = 0; - /** Returns the current game turn. */ + //! Returns the current game turn + //! + //! @return The number representing the current game turn. virtual int CurrentTurn() const = 0; static int MAX_AI_PLAYERS(); /// GetPathfinder() { return IApp::GetApp()->GetUniverse().GetPathfinder(); } /** Accessor for all (on server) or all known (on client) objects ObjectMap */ -inline ObjectMap& Objects() -{ return IApp::GetApp()->GetUniverse().Objects(); } +inline ObjectMap& Objects() { + static ObjectMap empty_objects; + auto app = IApp::GetApp(); + return app ? app->GetUniverse().Objects() : empty_objects; +} /** Accessor for known objects of specified empire. */ inline ObjectMap& EmpireKnownObjects(int empire_id) { return IApp::GetApp()->EmpireKnownObjects(empire_id); } -/** Accessor for individual objects. */ -inline std::shared_ptr GetUniverseObject(int object_id) -{ return IApp::GetApp()->GetUniverseObject(object_id); } - -inline std::shared_ptr GetEmpireKnownObject(int object_id, int empire_id) -{ return IApp::GetApp()->EmpireKnownObject(object_id, empire_id); } - -inline std::shared_ptr GetResourceCenter(int object_id) -{ return IApp::GetApp()->GetUniverse().Objects().Object(object_id); } - -inline std::shared_ptr GetEmpireKnownResourceCenter(int object_id, int empire_id) -{ return IApp::GetApp()->EmpireKnownObjects(empire_id).Object(object_id); } - -inline std::shared_ptr GetPopCenter(int object_id) -{ return IApp::GetApp()->GetUniverse().Objects().Object(object_id); } - -inline std::shared_ptr GetEmpireKnownPopCenter(int object_id, int empire_id) -{ return IApp::GetApp()->EmpireKnownObjects(empire_id).Object(object_id); } - -inline std::shared_ptr GetPlanet(int object_id) -{ return IApp::GetApp()->GetUniverse().Objects().Object(object_id); } - -inline std::shared_ptr GetEmpireKnownPlanet(int object_id, int empire_id) -{ return IApp::GetApp()->EmpireKnownObjects(empire_id).Object(object_id); } - -inline std::shared_ptr GetSystem(int object_id) -{ return IApp::GetApp()->GetUniverse().Objects().Object(object_id); } - -inline std::shared_ptr GetEmpireKnownSystem(int object_id, int empire_id) -{ return IApp::GetApp()->EmpireKnownObjects(empire_id).Object(object_id); } - -inline std::shared_ptr GetField(int object_id) -{ return IApp::GetApp()->GetUniverse().Objects().Object(object_id); } - -inline std::shared_ptr GetEmpireKnownField(int object_id, int empire_id) -{ return IApp::GetApp()->EmpireKnownObjects(empire_id).Object(object_id); } - -inline std::shared_ptr GetShip(int object_id) -{ return IApp::GetApp()->GetUniverse().Objects().Object(object_id); } - -inline std::shared_ptr GetEmpireKnownShip(int object_id, int empire_id) -{ return IApp::GetApp()->EmpireKnownObjects(empire_id).Object(object_id); } - -inline std::shared_ptr GetFleet(int object_id) -{ return IApp::GetApp()->GetUniverse().Objects().Object(object_id); } - -inline std::shared_ptr GetEmpireKnownFleet(int object_id, int empire_id) -{ return IApp::GetApp()->EmpireKnownObjects(empire_id).Object(object_id); } - -inline std::shared_ptr GetBuilding(int object_id) -{ return IApp::GetApp()->GetUniverse().Objects().Object(object_id); } - -inline std::shared_ptr GetEmpireKnownBuilding(int object_id, int empire_id) -{ return IApp::GetApp()->EmpireKnownObjects(empire_id).Object(object_id); } - /** Returns the object name of the universe object. This can be apperant object * name, if the application isn't supposed to see the real object name. */ inline std::string GetVisibleObjectName(std::shared_ptr object) diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 46b54f3d383..038d13483d5 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -1,6 +1,7 @@ target_sources(freeorioncommon PUBLIC ${CMAKE_CURRENT_LIST_DIR}/AppInterface.h + ${CMAKE_CURRENT_LIST_DIR}/base64_filter.h ${CMAKE_CURRENT_LIST_DIR}/blocking_combiner.h ${CMAKE_CURRENT_LIST_DIR}/CheckSums.h ${CMAKE_CURRENT_LIST_DIR}/Directories.h @@ -14,6 +15,7 @@ target_sources(freeorioncommon ${CMAKE_CURRENT_LIST_DIR}/OptionValidators.h ${CMAKE_CURRENT_LIST_DIR}/Order.h ${CMAKE_CURRENT_LIST_DIR}/OrderSet.h + ${CMAKE_CURRENT_LIST_DIR}/Pending.h ${CMAKE_CURRENT_LIST_DIR}/Process.h ${CMAKE_CURRENT_LIST_DIR}/Random.h ${CMAKE_CURRENT_LIST_DIR}/SaveGamePreviewUtils.h @@ -30,6 +32,7 @@ target_sources(freeorioncommon ${CMAKE_CURRENT_LIST_DIR}/CheckSums.cpp ${CMAKE_CURRENT_LIST_DIR}/Directories.cpp ${CMAKE_CURRENT_LIST_DIR}/EnumText.cpp + ${CMAKE_CURRENT_LIST_DIR}/GameRules.cpp ${CMAKE_CURRENT_LIST_DIR}/i18n.cpp ${CMAKE_CURRENT_LIST_DIR}/Logger.cpp ${CMAKE_CURRENT_LIST_DIR}/LoggerWithOptionsDB.cpp diff --git a/util/CheckSums.h b/util/CheckSums.h index bb97a6ca6a6..e2ad2940d22 100644 --- a/util/CheckSums.h +++ b/util/CheckSums.h @@ -19,14 +19,14 @@ namespace CheckSums { FO_COMMON_API void CheckSumCombine(unsigned int& sum, const std::string& c); // integeral types - template + template void CheckSumCombine(unsigned int& sum, T t, typename std::enable_if::value, T>::type* = nullptr) { sum += static_cast(std::abs(t)); sum %= CHECKSUM_MODULUS; } - template + template void CheckSumCombine(unsigned int& sum, T t, typename std::enable_if::value, T>::type* = nullptr) { @@ -35,7 +35,7 @@ namespace CheckSums { } // classes that have GetCheckSum methods - template + template void CheckSumCombine(unsigned int& sum, const C& c, decltype(std::declval().GetCheckSum())* = nullptr) { @@ -45,7 +45,7 @@ namespace CheckSums { } // enums - template + template void CheckSumCombine(unsigned int& sum, T t, typename std::enable_if::value, T>::type* = nullptr) { @@ -54,21 +54,21 @@ namespace CheckSums { } // pointer types - template + template void CheckSumCombine(unsigned int& sum, const T* p) { TraceLogger() << "CheckSumCombine(T*): " << typeid(p).name(); if (p) CheckSumCombine(sum, *p); } - template + template void CheckSumCombine(unsigned int& sum, const typename std::shared_ptr& p) { TraceLogger() << "CheckSumCombine(shared_ptr): " << typeid(p).name(); if (p) CheckSumCombine(sum, *p); } - template + template void CheckSumCombine(unsigned int& sum, const typename std::unique_ptr& p) { TraceLogger() << "CheckSumCombine(unique_ptr): " << typeid(p).name(); @@ -77,7 +77,7 @@ namespace CheckSums { } // pairs (including map value types) - template + template void CheckSumCombine(unsigned int& sum, const std::pair& p) { TraceLogger() << "CheckSumCombine(pair): " << typeid(p).name(); @@ -86,7 +86,7 @@ namespace CheckSums { } // iterable containers - template + template void CheckSumCombine(unsigned int& sum, const C& c, decltype(std::declval().begin())* = nullptr, decltype(std::declval().end())* = nullptr) diff --git a/util/DependencyVersions.cpp b/util/DependencyVersions.cpp index 18ba35e2b37..a3e08fe5400 100644 --- a/util/DependencyVersions.cpp +++ b/util/DependencyVersions.cpp @@ -8,7 +8,7 @@ #include #if defined(FREEORION_BUILD_SERVER) || defined(FREEORION_BUILD_AI) -# include +# include #endif #if defined(FREEORION_BUILD_HUMAN) @@ -79,7 +79,7 @@ std::map DependencyVersions() { void LogDependencyVersions() { InfoLogger() << "Dependency versions from headers:"; - for (const std::map::value_type& version : DependencyVersions()) { + for (const auto& version : DependencyVersions()) { if (!version.second.empty()) InfoLogger() << version.first << ": " << version.second; } diff --git a/util/Directories.cpp b/util/Directories.cpp index 734bd5775c1..3f4137662c8 100644 --- a/util/Directories.cpp +++ b/util/Directories.cpp @@ -1,8 +1,9 @@ #include "Directories.h" #include "OptionsDB.h" - +#include "i18n.h" #include +#include "../universe/Enums.h" #include #include @@ -33,7 +34,7 @@ namespace { globaldir: FreeOrion.app/Contents/Resources bindir: FreeOrion.app/Contents/Executables configpath: ~/Library/FreeOrion/config.xml - pythonhome: FreeOrion.app/Contents/Frameworks/Python.framework/Versions/Current + pythonhome: FreeOrion.app/Contents/Frameworks/Python.framework/Versions/{PythonMajor}.{PythonMinor} */ namespace { fs::path s_user_dir; @@ -72,12 +73,12 @@ void InitDirs(const std::string& argv0) { bundle_path = fs::path(bundle_dir); // search bundle_path for a directory named "FreeOrion.app", exiting if not found, else constructing a path to application bundle contents - fs::path::iterator appiter = std::find(bundle_path.begin(), bundle_path.end(), "FreeOrion.app"); + auto appiter = std::find(bundle_path.begin(), bundle_path.end(), "FreeOrion.app"); if (appiter == bundle_path.end()) { std::cerr << "Error: Application bundle must be named 'FreeOrion.app' and executables must not be called from outside of it." << std::endl; exit(-1); } else { - for (fs::path::iterator piter = bundle_path.begin(); piter != appiter; ++piter) { + for (auto piter = bundle_path.begin(); piter != appiter; ++piter) { app_path /= *piter; } app_path /= "FreeOrion.app/Contents"; @@ -87,7 +88,7 @@ void InitDirs(const std::string& argv0) { s_user_dir = fs::path(getenv("HOME")) / "Library" / "Application Support" / "FreeOrion"; s_bin_dir = app_path / "Executables"; s_config_path = s_user_dir / "config.xml"; - s_python_home = app_path / "Frameworks" / "Python.framework" / "Versions" / "Current"; + s_python_home = app_path / "Frameworks" / "Python.framework" / "Versions" / FREEORION_PYTHON_VERSION; fs::path p = s_user_dir; if (!exists(p)) @@ -97,6 +98,10 @@ void InitDirs(const std::string& argv0) { if (!exists(p)) fs::create_directories(p); + // Intentionally do not create the server save dir. + // The server save dir is publically accessible and should not be + // automatically created for the user. + g_initialized = true; } @@ -130,7 +135,7 @@ const fs::path GetPythonHome() { return s_python_home; } -#elif defined(FREEORION_LINUX) || defined(FREEORION_FREEBSD) +#elif defined(FREEORION_LINUX) || defined(FREEORION_FREEBSD) || defined(FREEORION_OPENBSD) #include "binreloc.h" #include #include @@ -174,7 +179,7 @@ namespace { << old_path << std::endl << std::endl << "Configuration were files copied to:" << std::endl << config_path << std::endl << std::endl << "Data Files were copied to:" << std::endl << data_path << std::endl << std::endl - << "If your save-dir option in persistent_config.xml was ~/.config, then you need to update it." + << "If your save.path option in persistent_config.xml was ~/.config, then you need to update it." << std::endl; try { @@ -203,7 +208,7 @@ namespace { } } - //Start update of save-dir in config file and complete it in CompleteXDGMigration() + //Start update of save.path in config file and complete it in CompleteXDGMigration() fs::path sentinel = GetUserDataDir() / "MIGRATION_TO_XDG_IN_PROGRESS"; if (!exists(sentinel)) { fs::ofstream touchfile(sentinel); @@ -260,6 +265,10 @@ void InitDirs(const std::string& argv0) { fs::create_directories(p); } + // Intentionally do not create the server save dir. + // The server save dir is publically accessible and should not be + // automatically created for the user. + InitBinDir(argv0); g_initialized = true; @@ -281,7 +290,7 @@ const fs::path GetUserDataDir() { const fs::path GetRootDataDir() { if (!g_initialized) InitDirs(""); - char* dir_name = br_find_data_dir("/usr/local/share"); + char* dir_name = br_find_data_dir(SHAREPATH); fs::path p(dir_name); std::free(dir_name); p /= "freeorion"; @@ -312,6 +321,14 @@ void InitBinDir(const std::string& argv0) { mib[3] = -1; size_t buf_size = sizeof(buf); sysctl(mib, 4, buf, &buf_size, 0, 0); +#elif defined(__OpenBSD__) + // OpenBSD does not have executable path's retrieval feature + std::string argpath(argv0); + boost::erase_all(argpath, "\""); + if (argpath[0] != '/') + problem = (nullptr == realpath(argpath.c_str(), buf)); + else + strncpy(buf, argpath.c_str(), sizeof(buf)); #else problem = (-1 == readlink("/proc/self/exe", buf, sizeof(buf) - 1)); #endif @@ -323,20 +340,20 @@ void InitBinDir(const std::string& argv0) { fs::path binary_file = fs::system_complete(fs::path(path_text)); bin_dir = binary_file.branch_path(); - // check that a "freeorion" file (hopefully the freeorion binary) exists in the found directory + // check that a "freeoriond" file (hopefully the freeorion server binary) exists in the found directory fs::path p(bin_dir); - p /= "freeorion"; + p /= "freeoriond"; if (!exists(p)) problem = true; } - } catch (fs::filesystem_error err) { + } catch (...) { problem = true; } if (problem) { // failed trying to parse the call path, so try hard-coded standard location... - char* dir_name = br_find_bin_dir("/usr/local/bin"); + char* dir_name = br_find_bin_dir(BINPATH); fs::path p(dir_name); std::free(dir_name); @@ -365,6 +382,10 @@ void InitDirs(const std::string& argv0) { if (!exists(p)) fs::create_directories(p); + // Intentionally do not create the server save dir. + // The server save dir is publically accessible and should not be + // automatically created for the user. + InitBinDir(argv0); g_initialized = true; @@ -395,13 +416,13 @@ void InitBinDir(const std::string& argv0) { try { fs::path binary_file = fs::system_complete(FilenameToPath(argv0)); bin_dir = binary_file.branch_path(); - } catch (fs::filesystem_error err) { + } catch (const fs::filesystem_error &) { bin_dir = fs::initial_path(); } } #else -# error Neither FREEORION_LINUX, FREEORION_FREEBSD nor FREEORION_WIN32 set +# error Neither FREEORION_LINUX, FREEORION_FREEBSD, FREEORION_OPENBSD nor FREEORION_WIN32 set #endif void CompleteXDGMigration() { @@ -409,24 +430,24 @@ void CompleteXDGMigration() { if (exists(sentinel)) { fs::remove(sentinel); // Update data dir in config file - const std::string options_save_dir = GetOptionsDB().Get("save-dir"); + const std::string options_save_dir = GetOptionsDB().Get("save.path"); const fs::path old_path = fs::path(getenv("HOME")) / ".freeorion"; if (fs::path(options_save_dir) == old_path) - GetOptionsDB().Set("save-dir", GetUserDataDir().string()); + GetOptionsDB().Set("save.path", GetUserDataDir().string()); } } const fs::path GetResourceDir() { // if resource dir option has been set, use specified location. otherwise, // use default location - std::string options_resource_dir = GetOptionsDB().Get("resource-dir"); + std::string options_resource_dir = GetOptionsDB().Get("resource.path"); fs::path dir = FilenameToPath(options_resource_dir); if (fs::exists(dir) && fs::is_directory(dir)) return dir; - dir = GetOptionsDB().GetDefault("resource-dir"); + dir = GetOptionsDB().GetDefault("resource.path"); if (!fs::is_directory(dir) || !fs::exists(dir)) - dir = FilenameToPath(GetOptionsDB().GetDefault("resource-dir")); + dir = FilenameToPath(GetOptionsDB().GetDefault("resource.path")); return dir; } @@ -444,9 +465,18 @@ const fs::path GetPersistentConfigPath() { const fs::path GetSaveDir() { // if save dir option has been set, use specified location. otherwise, // use default location - std::string options_save_dir = GetOptionsDB().Get("save-dir"); + std::string options_save_dir = GetOptionsDB().Get("save.path"); if (options_save_dir.empty()) - options_save_dir = GetOptionsDB().GetDefault("save-dir"); + options_save_dir = GetOptionsDB().GetDefault("save.path"); + return FilenameToPath(options_save_dir); +} + +const fs::path GetServerSaveDir() { + // if server save dir option has been set, use specified location. otherwise, + // use default location + std::string options_save_dir = GetOptionsDB().Get("save.server.path"); + if (options_save_dir.empty()) + options_save_dir = GetOptionsDB().GetDefault("save.server.path"); return FilenameToPath(options_save_dir); } @@ -454,10 +484,10 @@ fs::path RelativePath(const fs::path& from, const fs::path& to) { fs::path retval; fs::path from_abs = fs::absolute(from); fs::path to_abs = fs::absolute(to); - fs::path::iterator from_it = from_abs.begin(); - fs::path::iterator end_from_it = from_abs.end(); - fs::path::iterator to_it = to_abs.begin(); - fs::path::iterator end_to_it = to_abs.end(); + auto from_it = from_abs.begin(); + auto end_from_it = from_abs.end(); + auto to_it = to_abs.begin(); + auto end_to_it = to_abs.end(); while (from_it != end_from_it && to_it != end_to_it && *from_it == *to_it) { ++from_it; ++to_it; @@ -469,38 +499,59 @@ fs::path RelativePath(const fs::path& from, const fs::path& to) { return retval; } -std::string PathString(const fs::path& path) { -#ifndef FREEORION_WIN32 - return path.string(); -#else - fs::path::string_type native_string = path.native(); +#if defined(FREEORION_WIN32) + +std::string PathToString(const fs::path& path) { + fs::path::string_type native_string = path.generic_wstring(); std::string retval; utf8::utf16to8(native_string.begin(), native_string.end(), std::back_inserter(retval)); return retval; -#endif } -const fs::path FilenameToPath(const std::string& path_str) { -#if defined(FREEORION_WIN32) +fs::path FilenameToPath(const std::string& path_str) { // convert UTF-8 directory string to UTF-16 boost::filesystem::path::string_type directory_native; utf8::utf8to16(path_str.begin(), path_str.end(), std::back_inserter(directory_native)); - return fs::path(directory_native); +#if (BOOST_VERSION >= 106300) + return fs::path(directory_native).generic_path(); #else - return fs::path(path_str); + return fs::path(directory_native); #endif } +#else // defined(FREEORION_WIN32) + +std::string PathToString(const fs::path& path) +{ return path.string(); } + +fs::path FilenameToPath(const std::string& path_str) +{ return fs::path(path_str); } + +#endif // defined(FREEORION_WIN32) + std::string FilenameTimestamp() { boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%Y%m%d_%H%M%S"); std::stringstream date_stream; - date_stream.imbue(std::locale(date_stream.getloc(), facet)); + + date_stream.imbue(std::locale(date_stream.getloc(), facet));// alternate locales: GetLocale("en_US.UTF-8") or GetLocale("ja_JA.UTF-8") or date_stream.getloc() date_stream << boost::posix_time::microsec_clock::local_time(); - return date_stream.str(); + std::string retval = date_stream.str(); + TraceLogger() << "Filename initial timestamp: " << retval << " is valid utf8?: " << utf8::is_valid(retval.begin(), retval.end()); + + // replace spaces and colons with safer chars for filenames + std::replace(retval.begin(), retval.end(), ' ', '_'); + std::replace(retval.begin(), retval.end(), ':', '-'); + + // filter non-filename-safe characters that are valid single-byte UTF-8 characters, in case the default locale for this system has weird chars in the time-date format + auto do_remove = [](char c) -> bool { return !std::isalnum(c) && c <= 127 && c != '_' && c != '-'; }; + retval.erase(std::remove_if(retval.begin(), retval.end(), do_remove), retval.end()); + TraceLogger() << "Filename filtered timestamp: " << retval << " is valid utf8?: " << utf8::is_valid(retval.begin(), retval.end()); + + return retval; } /** \brief Return a vector of absolute paths to files in the given path - * + * * @param[in] path relative or absolute directory (searched recursively) * @return Any regular files in * @return if absolute directory: path @@ -510,7 +561,7 @@ std::vector ListDir(const fs::path& path) { std::vector retval; bool is_rel = path.is_relative(); if (!is_rel && (fs::is_empty(path) || !fs::is_directory(path))) { - DebugLogger() << "ListDir: File " << PathString(path) << " was not included as it is empty or not a directoy"; + DebugLogger() << "ListDir: File " << PathToString(path) << " was not included as it is empty or not a directoy"; } else { const fs::path& default_path = is_rel ? GetResourceDir() / path : path; @@ -520,7 +571,7 @@ std::vector ListDir(const fs::path& path) { if (fs::is_regular_file(dir_it->status())) { retval.push_back(dir_it->path()); } else if (!fs::is_directory(dir_it->status())) { - TraceLogger() << "Parse: Unknown file not included: " << PathString(dir_it->path()); + TraceLogger() << "Parse: Unknown file not included: " << PathToString(dir_it->path()); } } } @@ -534,3 +585,134 @@ std::vector ListDir(const fs::path& path) { bool IsValidUTF8(const std::string& in) { return utf8::is_valid(in.begin(), in.end()); } + +bool IsInDir(const fs::path& dir, const fs::path& test_dir) { + if (!fs::exists(dir) || !fs::is_directory(dir)) + return false; + + if (fs::exists(test_dir) && !fs::is_directory(test_dir)) + return false; + + // Resolve any symbolic links, dots or dot-dots + auto canon_dir = fs::canonical(dir); + // Don't resolve path if directory doesn't exist + // TODO: Change to fs::weakly_canonical after bump boost version above 1.60 + auto canon_path = test_dir; + if (fs::exists(test_dir)) + canon_path = fs::canonical(test_dir); + + // Paths shorter than dir are not in dir + auto dir_length = std::distance(canon_dir.begin(), canon_dir.end()); + auto path_length = std::distance(canon_path.begin(), canon_path.end()); + if (path_length < dir_length) + return false; + + // Check that the whole dir path matches the test path + // Extra portions of path are contained in dir + return std::equal(canon_dir.begin(), canon_dir.end(), canon_path.begin()); +} + +fs::path GetPath(PathType path_type) { + switch (path_type) { + case PATH_BINARY: + return GetBinDir(); + case PATH_RESOURCE: + return GetResourceDir(); + case PATH_DATA_ROOT: + return GetRootDataDir(); + case PATH_DATA_USER: + return GetUserDataDir(); + case PATH_CONFIG: + return GetUserConfigDir(); + case PATH_SAVE: + return GetSaveDir(); + case PATH_TEMP: + return fs::temp_directory_path(); + case PATH_PYTHON: +#if defined(FREEORION_MACOSX) || defined(FREEORION_WIN32) + return GetPythonHome(); +#endif + case PATH_INVALID: + default: + ErrorLogger() << "Invalid path type " << path_type; + return fs::temp_directory_path(); + } +} + +fs::path GetPath(const std::string& path_string) { + if (path_string.empty()) { + ErrorLogger() << "GetPath called with empty argument"; + return fs::temp_directory_path(); + } + + PathType path_type; + try { + path_type = boost::lexical_cast(path_string); + } catch (const boost::bad_lexical_cast& ec) { + // try partial match + std::string retval = path_string; + for (const auto& path_type_str : PathTypeStrings()) { + std::string path_type_string = PathToString(GetPath(path_type_str)); + boost::replace_all(retval, path_type_str, path_type_string); + } + if (path_string != retval) { + return FilenameToPath(retval); + } else { + ErrorLogger() << "Invalid cast for PathType from string " << path_string; + return fs::temp_directory_path(); + } + } + return GetPath(path_type); +} + +bool IsExistingFile(const fs::path& path) { + try { + auto stat = fs::status(path); + return fs::exists(stat) && fs::is_regular_file(stat); + } catch(boost::filesystem::filesystem_error& ec) { + ErrorLogger() << "Filesystem error during stat of " << PathToString(path) << " : " << ec.what(); + } + + return false; +} + +std::vector PathsInDir(const boost::filesystem::path& abs_dir_path, + std::function pred, + bool recursive_search) +{ + std::vector retval; + if (abs_dir_path.is_relative()) { + ErrorLogger() << "Passed relative path for fileysstem operation " << PathToString(abs_dir_path); + return retval; + } + + try { + auto dir_stat = fs::status(abs_dir_path); + if (!fs::exists(dir_stat) || !fs::is_directory(dir_stat)) { + ErrorLogger() << "Path is not an existing directory " << PathToString(abs_dir_path); + return retval; + } + + if (recursive_search) { + using dir_it_type = boost::filesystem::recursive_directory_iterator; + for (dir_it_type node_it(abs_dir_path); node_it != dir_it_type(); ++node_it) { + auto obj_path = node_it->path(); + if (pred(obj_path)) + retval.push_back(obj_path); + } + } else { + using dir_it_type = boost::filesystem::directory_iterator; + for (dir_it_type node_it(abs_dir_path); node_it != dir_it_type(); ++node_it) { + auto obj_path = node_it->path(); + if (pred(obj_path)) + retval.push_back(obj_path); + } + } + } catch(const fs::filesystem_error& ec) { + ErrorLogger() << "Filesystem error during directory traversal " << PathToString(abs_dir_path) + << " : " << ec.what(); + return {}; + } + + return retval; +} diff --git a/util/Directories.h b/util/Directories.h index 9bc207661ef..efd5c71233e 100644 --- a/util/Directories.h +++ b/util/Directories.h @@ -5,6 +5,7 @@ #include #include "Export.h" +#include "../universe/EnumsFwd.h" /** This function must be called before any Get*Dir function is called. It * stores the current working directory as well as creating local @@ -12,7 +13,7 @@ FO_COMMON_API void CompleteXDGMigration(); /** This function completes the migration of directories to the XDG - * specified location by updating the save-dir option to the new location + * specified location by updating the save.path option to the new location * after the option is loaded from XML files. It only updates the option * if it is set to the old default option. */ FO_COMMON_API void InitDirs(const std::string& argv0); @@ -40,7 +41,7 @@ FO_COMMON_API const boost::filesystem::path GetUserDataDir(); /** Converts UTF-8 string into a path, doing any required wide-character * conversions as determined by the operating system / filesystem. */ -FO_COMMON_API const boost::filesystem::path FilenameToPath(const std::string& path_str); +FO_COMMON_API boost::filesystem::path FilenameToPath(const std::string& path_str); /** Returns the directory that contains all game content files, such as string * table files, in-game tech, building, special, etc. definition files, and @@ -63,7 +64,7 @@ FO_COMMON_API const boost::filesystem::path GetBinDir(); #if defined(FREEORION_MACOSX) || defined(FREEORION_WIN32) /** This function returns the Python home directory from where it is embedded - * On OSX: within the application bundle + * On OSX: within the application bundle * On Windows: same directory where the binaries are located */ FO_COMMON_API const boost::filesystem::path GetPythonHome(); #endif @@ -78,8 +79,12 @@ FO_COMMON_API const boost::filesystem::path GetPersistentConfigPath(); * the directory "save" within the user directory. */ FO_COMMON_API const boost::filesystem::path GetSaveDir(); -/** Returns a canonical utf-8 string from the given filesystem path. */ -FO_COMMON_API std::string PathString(const boost::filesystem::path& path); +/** Returns the directory where server save files are located. This is typically + * the directory "save" within the user directory. */ +FO_COMMON_API const boost::filesystem::path GetServerSaveDir(); + +/** Returns a utf-8 string from the given filesystem path. */ +FO_COMMON_API std::string PathToString(const boost::filesystem::path& path); /** Returns current timestamp in a form that can be used in file names */ FO_COMMON_API std::string FilenameTimestamp(); @@ -93,5 +98,28 @@ FO_COMMON_API std::vector ListDir(const boost::filesyst /** Returns true iff the string \a in is valid UTF-8. */ FO_COMMON_API bool IsValidUTF8(const std::string& in); +/** Returns true iff the \p test_dir is in \p dir and \p dir + is existing directory. */ +FO_COMMON_API bool IsInDir(const boost::filesystem::path& dir, const boost::filesystem::path& test_dir); + +/** Returns path currently defined for @p path_type */ +FO_COMMON_API boost::filesystem::path GetPath(PathType path_type); + +/** Returns path for path type cast from @p path_string */ +FO_COMMON_API boost::filesystem::path GetPath(const std::string& path_string); + +/** Returns if path exists and is a regular file */ +FO_COMMON_API bool IsExistingFile(const boost::filesystem::path& path); + +/** All paths contained in a directory filtered by a functor + * + * @param[in] abs_dir_path Absolute path to directory + * @param[in] pred Predicate functor accepting a boost::filesystem::path constant reference + * @param[in] recursive_search If true, recurses into sub-directories + * + * @return vector List of filesytem objects found in @p abs_dir_path which satisfy @p pred */ +FO_COMMON_API std::vector PathsInDir(const boost::filesystem::path& abs_dir_path, + std::function pred, + bool recursive_search); #endif diff --git a/util/GameRules.cpp b/util/GameRules.cpp new file mode 100644 index 00000000000..d922bf1597b --- /dev/null +++ b/util/GameRules.cpp @@ -0,0 +1,191 @@ +#include "GameRules.h" + +namespace { + std::vector& GameRulesRegistry() { + static std::vector game_rules_registry; + return game_rules_registry; + } +} + + +///////////////////////////////////////////// +// Free Functions +///////////////////////////////////////////// +bool RegisterGameRules(GameRulesFn function) { + GameRulesRegistry().push_back(function); + return true; +} + +GameRules& GetGameRules() { + static GameRules game_rules; + if (!GameRulesRegistry().empty()) { + DebugLogger() << "Adding options rules"; + for (GameRulesFn fn : GameRulesRegistry()) + fn(game_rules); + GameRulesRegistry().clear(); + } + + return game_rules; +} + + +///////////////////////////////////////////////////// +// GameRules +///////////////////////////////////////////////////// +GameRules::Rule::Rule() : + OptionsDB::Option() +{} + +GameRules::Rule::Rule(Type type_, const std::string& name_, const boost::any& value_, + const boost::any& default_value_, const std::string& description_, + const ValidatorBase *validator_, bool engine_internal_, + const std::string& category_) : + OptionsDB::Option(static_cast(0), name_, value_, default_value_, + description_, validator_, engine_internal_, false, true, "setup.rules"), + type(type_), + category(category_) +{} + +GameRules::GameRules() +{} + +bool GameRules::Empty() const { + CheckPendingGameRules(); + return m_game_rules.empty(); +} + +std::unordered_map::const_iterator GameRules::begin() const { + CheckPendingGameRules(); + return m_game_rules.begin(); +} + +std::unordered_map::const_iterator GameRules::end() const { + CheckPendingGameRules(); + return m_game_rules.end(); +} + +bool GameRules::RuleExists(const std::string& name) const { + CheckPendingGameRules(); + return m_game_rules.count(name); +} + +bool GameRules::RuleExists(const std::string& name, Type type) const { + if (type == Type::INVALID) + return false; + CheckPendingGameRules(); + auto rule_it = m_game_rules.find(name); + if (rule_it == m_game_rules.end()) + return false; + return rule_it->second.type == type; +} + +GameRules::Type GameRules::GetType(const std::string& name) const { + CheckPendingGameRules(); + auto rule_it = m_game_rules.find(name); + if (rule_it == m_game_rules.end()) + return Type::INVALID; + return rule_it->second.type; +} + +bool GameRules::RuleIsInternal(const std::string& name) const { + CheckPendingGameRules(); + auto rule_it = m_game_rules.find(name); + if (rule_it == m_game_rules.end()) + return false; + return rule_it->second.IsInternal(); +} + +const std::string& GameRules::GetDescription(const std::string& rule_name) const { + CheckPendingGameRules(); + auto it = m_game_rules.find(rule_name); + if (it == m_game_rules.end()) + throw std::runtime_error(("GameRules::GetDescription(): No option called \"" + rule_name + "\" could be found.").c_str()); + return it->second.description; +} + +std::shared_ptr GameRules::GetValidator(const std::string& rule_name) const { + CheckPendingGameRules(); + auto it = m_game_rules.find(rule_name); + if (it == m_game_rules.end()) + throw std::runtime_error(("GameRules::GetValidator(): No option called \"" + rule_name + "\" could be found.").c_str()); + return it->second.validator; +} + +void GameRules::ClearExternalRules() { + CheckPendingGameRules(); + auto it = m_game_rules.begin(); + while (it != m_game_rules.end()) { + bool engine_internal = it->second.storable; // OptionsDB::Option member used to store if this option is engine-internal + if (!engine_internal) + it = m_game_rules.erase(it); + else + ++it; + } +} + +void GameRules::ResetToDefaults() { + CheckPendingGameRules(); + for (auto& it : m_game_rules) + it.second.SetToDefault(); +} + +std::map GameRules::GetRulesAsStrings() const { + CheckPendingGameRules(); + std::map retval; + for (const auto& rule : m_game_rules) + retval.insert({rule.first, rule.second.ValueToString()}); + return retval; +} + +void GameRules::Add(Pending::Pending&& future) +{ m_pending_rules = std::move(future); } + +void GameRules::SetFromStrings(const std::map& names_values) { + CheckPendingGameRules(); + DebugLogger() << "Setting Rules from Strings:"; + for (const auto& entry : names_values) + DebugLogger() << " " << entry.first << " : " << entry.second; + + ResetToDefaults(); + for (auto& entry : names_values) { + auto rule_it = m_game_rules.find(entry.first); + if (rule_it == m_game_rules.end()) { + InfoLogger() << "GameRules::serialize received unrecognized rule: " << entry.first; + continue; + } + try { + rule_it->second.SetFromString(entry.second); + } catch (const boost::bad_lexical_cast& e) { + ErrorLogger() << "Unable to set rule: " << entry.first << " to value: " << entry.second << " - couldn't cast string to allowed value for this option"; + } catch (...) { + ErrorLogger() << "Unable to set rule: " << entry.first << " to value: " << entry.second; + } + } + + DebugLogger() << "After Setting Rules:"; + for (const auto& entry : m_game_rules) + DebugLogger() << " " << entry.first << " : " << entry.second.ValueToString(); +} + +void GameRules::CheckPendingGameRules() const { + if (!m_pending_rules) + return; + + auto parsed = Pending::WaitForPending(m_pending_rules); + if (!parsed) + return; + + auto new_rules = std::move(*parsed); + for (const auto& rule : new_rules) { + const auto& name = rule.first; + if (m_game_rules.count(name)) { + ErrorLogger() << "GameRules::Add<>() : Rule " << name << " was added twice. Skipping ..."; + continue; + } + m_game_rules[name] = rule.second; + } + + DebugLogger() << "Registered and Parsed Game Rules:"; + for (const auto& entry : GetRulesAsStrings()) + DebugLogger() << " ... " << entry.first << " : " << entry.second; +} diff --git a/util/GameRules.h b/util/GameRules.h new file mode 100644 index 00000000000..1bde47ef082 --- /dev/null +++ b/util/GameRules.h @@ -0,0 +1,164 @@ +#ifndef _GameRules_h_ +#define _GameRules_h_ + +#include "OptionsDB.h" +#include "Pending.h" + +class GameRules; + +///////////////////////////////////////////// +// Free Functions +///////////////////////////////////////////// +using GameRulesFn = void (*) (GameRules&); ///< the function signature for functions that add Rules to the GameRules (void (GameRules&)) + +/** adds \a function to a vector of pointers to functions that add Rules to + * the GameRules. This function returns a boolean so that it can be used to + * declare a dummy static variable that causes \a function to be registered as + * a side effect (e.g. at file scope: + * "bool unused_bool = RegisterGameRules(&foo)"). */ +FO_COMMON_API bool RegisterGameRules(GameRulesFn function); + +/** returns the single instance of the GameRules class */ +FO_COMMON_API GameRules& GetGameRules(); + +/** Database of values that control how the game mechanics function. */ +class FO_COMMON_API GameRules { +public: + enum class Type : int { + INVALID = -1, + TOGGLE, + INT, + DOUBLE, + STRING + }; + + static inline constexpr Type RuleTypeForType(bool dummy) + { return Type::TOGGLE; } + static inline constexpr Type RuleTypeForType(int dummy) + { return Type::INT; } + static inline constexpr Type RuleTypeForType(double dummy) + { return Type::DOUBLE; } + static inline Type RuleTypeForType(std::string dummy) + { return Type::STRING; } + + struct FO_COMMON_API Rule : public OptionsDB::Option { + Rule(); + Rule(Type type_, const std::string& name_, const boost::any& value_, + const boost::any& default_value_, const std::string& description_, + const ValidatorBase *validator_, bool engine_internal_, + const std::string& category_ = ""); + bool IsInternal() const { return this->storable; } + + Type type = Type::INVALID; + std::string category = ""; + }; + + using GameRulesTypeMap = std::unordered_map; + + GameRules(); + + /** \name Accessors */ //@{ + bool Empty() const; + std::unordered_map::const_iterator begin() const; + std::unordered_map::const_iterator end() const; + + bool RuleExists(const std::string& name) const; + bool RuleExists(const std::string& name, Type type) const; + Type GetType(const std::string& name) const; + bool RuleIsInternal(const std::string& name) const; + + /** returns the description string for rule \a rule_name, or throws + * std::runtime_error if no such rule exists. */ + const std::string& GetDescription(const std::string& rule_name) const; + + /** returns the validator for rule \a rule_name, or throws + * std::runtime_error if no such rule exists. */ + std::shared_ptr GetValidator(const std::string& rule_name) const; + + /** returns all contained rules as map of name and value string. */ + std::map GetRulesAsStrings() const; + + template + T Get(const std::string& name) const + { + CheckPendingGameRules(); + auto it = m_game_rules.find(name); + if (it == m_game_rules.end()) + throw std::runtime_error("GameRules::Get<>() : Attempted to get nonexistent rule \"" + name + "\"."); + try { + return boost::any_cast(it->second.value); + } catch (const boost::bad_any_cast& e) { + ErrorLogger() << "bad any cast converting value of game rule named: " << name << ". Returning default value instead"; + try { + return boost::any_cast(it->second.default_value); + } catch (const boost::bad_any_cast&) { + ErrorLogger() << "bad any cast converting default value of game rule named: " << name << ". Returning data-type default value instead: " << T(); + return T(); + } + } + } + //@} + + /** \name Mutators */ //@{ + /** Adds a rule, optionally with a custom validator. + Adds option setup.rules.{RULE_NAME} to override default value and + option setup.rules.server-locked.{RULE_NAME} to block rule changes from players */ + template + void Add(const std::string& name, const std::string& description, + const std::string& category, T default_value, + bool engine_interal, const ValidatorBase& validator = Validator()) + { + CheckPendingGameRules(); + auto it = m_game_rules.find(name); + if (it != m_game_rules.end()) + throw std::runtime_error("GameRules::Add<>() : Rule " + name + " was added twice."); + if (!GetOptionsDB().OptionExists("setup.rules.server-locked." + name)) { + GetOptionsDB().Add("setup.rules.server-locked." + name, description, false); + } + if (!GetOptionsDB().OptionExists("setup.rules." + name)) { + GetOptionsDB().Add("setup.rules." + name, description, default_value, + validator); + } + T value = GetOptionsDB().Get("setup.rules." + name); + m_game_rules[name] = Rule(RuleTypeForType(T()), name, value, value, description, + validator.Clone(), engine_interal, category); + DebugLogger() << "Added game rule named " << name << " with default value " << value; + } + + /** Adds rules from the \p future. */ + void Add(Pending::Pending&& future); + + template + void Set(const std::string& name, const T& value) + { + CheckPendingGameRules(); + auto it = m_game_rules.find(name); + if (it == m_game_rules.end()) + throw std::runtime_error("GameRules::Set<>() : Attempted to set nonexistent rule \"" + name + "\"."); + it->second.SetFromValue(value); + } + + void SetFromStrings(const std::map& names_values); + + /** Removes game rules that were added without being specified as + engine internal. */ + void ClearExternalRules(); + + /** Resets all rules to default values. */ + void ResetToDefaults(); + //@} + +private: + /** Assigns any m_pending_rules to m_game_rules. */ + void CheckPendingGameRules() const; + + /** Future rules being parsed by parser. mutable so that it can + be assigned to m_game_rules when completed.*/ + mutable boost::optional> m_pending_rules = boost::none; + + mutable GameRulesTypeMap m_game_rules; + + friend FO_COMMON_API GameRules& GetGameRules(); +}; + +#endif // _GameRules_h_ diff --git a/util/Logger.cpp b/util/Logger.cpp index 8a72cccfd67..990bfbcbae0 100644 --- a/util/Logger.cpp +++ b/util/Logger.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -125,11 +126,6 @@ inline std::basic_istream& operator>>( return is; } -int g_indent = 0; - -std::string DumpIndent() -{ return std::string(g_indent * 4, ' '); } - namespace { std::string& LocalUnnamedLoggerIdentifier() { // Create default logger name as a static function variable to avoid static initialization fiasco @@ -146,19 +142,20 @@ namespace { return forced_threshold; } - using TextFileSinkBackend = sinks::text_file_backend; - using TextFileSinkFrontend = sinks::synchronous_sink; + using LoggerTextFileSinkFrontend = boost::log::sinks::synchronous_sink; - boost::shared_ptr& FileSinkBackend() { + using LoggerFileSinkFrontEndConfigurer = std::function; + + boost::shared_ptr& FileSinkBackend() { // Create the sink backend as a function local static variable to avoid the static // initilization fiasco. - static boost::shared_ptr m_sink_backend; + static boost::shared_ptr m_sink_backend; return m_sink_backend; } /** Create a new file sink front end for \p file_sink_backend for \p channel_name and configure it with \p configure_front_end. */ - void ConfigureToFileSinkFrontEndCore(const boost::shared_ptr& file_sink_backend, + void ConfigureToFileSinkFrontEndCore(const boost::shared_ptr& file_sink_backend, const std::string& channel_name, const LoggerFileSinkFrontEndConfigurer& configure_front_end); @@ -172,12 +169,12 @@ namespace { class LoggersToSinkFrontEnds { /// m_mutex serializes access from different threads std::mutex m_mutex = {}; - std::unordered_map> m_names_to_front_ends = {}; + std::unordered_map> m_names_to_front_ends = {}; std::unordered_map m_names_to_front_end_configurers = {}; public: void AddOrReplaceLoggerName(const std::string& channel_name, - boost::shared_ptr front_end = nullptr) + boost::shared_ptr front_end = nullptr) { std::lock_guard lock(m_mutex); @@ -215,7 +212,7 @@ namespace { } /** Configure front ends for any logger with stored configuration functions. */ - void ConfigureFrontEnds(const boost::shared_ptr& file_sink_backend) { + void ConfigureFrontEnds(const boost::shared_ptr& file_sink_backend) { for (const auto& name_and_conf: m_names_to_front_end_configurers) ConfigureToFileSinkFrontEndCore(file_sink_backend, name_and_conf.first, name_and_conf.second); } @@ -244,27 +241,12 @@ namespace { return loggers_names_to_front_ends; } - void ConfigureFileSinkFrontEnd(TextFileSinkFrontend& sink_frontend, const std::string& channel_name) { - // Create the format - sink_frontend.set_formatter( - expr::stream - << expr::format_date_time("TimeStamp", "%H:%M:%S.%f") - << " [" << log_severity << "] " - << DisplayName(channel_name) - << " : " << log_src_filename << ":" << log_src_linenum << " : " - << expr::message - ); - - // Set a filter to only format this channel - sink_frontend.set_filter(log_channel == channel_name); - } - - void ConfigureToFileSinkFrontEndCore(const boost::shared_ptr& file_sink_backend, + void ConfigureToFileSinkFrontEndCore(const boost::shared_ptr& file_sink_backend, const std::string& channel_name, const LoggerFileSinkFrontEndConfigurer& configure_front_end) { // Create a sink frontend for formatting. - auto sink_frontend = boost::make_shared(file_sink_backend); + auto sink_frontend = boost::make_shared(file_sink_backend); configure_front_end(*sink_frontend); @@ -294,6 +276,12 @@ const std::string& DefaultExecLoggerName() std::vector CreatedLoggersNames() { return GetLoggersToSinkFrontEnds().LoggersNames(); } +BOOST_LOG_ATTRIBUTE_KEYWORD(log_severity, "Severity", LogLevel); +BOOST_LOG_ATTRIBUTE_KEYWORD(log_channel, "Channel", std::string) +BOOST_LOG_ATTRIBUTE_KEYWORD(log_src_filename, "SrcFilename", std::string); +BOOST_LOG_ATTRIBUTE_KEYWORD(log_src_linenum, "SrcLinenum", int); +BOOST_LOG_ATTRIBUTE_KEYWORD(thread_id, "ThreadID", boost::log::attributes::current_thread_id::value_type); + namespace { /** LoggerThresholdSetter sets the threshold of a logger */ @@ -311,7 +299,6 @@ namespace { std::lock_guard lock(m_mutex); auto used_threshold = ForcedThreshold() ? *ForcedThreshold() : threshold; - logging::core::get()->reset_filter(); m_min_channel_severity[source] = used_threshold; logging::core::get()->set_filter(m_min_channel_severity); @@ -327,6 +314,22 @@ namespace { return logger_threshold_setter.SetThreshold(source, threshold); } + + void ConfigureFileSinkFrontEnd(LoggerTextFileSinkFrontend& sink_frontend, const std::string& channel_name) { + // Create the format + sink_frontend.set_formatter( + expr::stream + << expr::format_date_time("TimeStamp", "%H:%M:%S.%f") + << " {" << thread_id << "}" + << " [" << log_severity << "] " + << DisplayName(channel_name) + << " : " << log_src_filename << ":" << log_src_linenum << " : " + << expr::message + ); + + // Set a filter to only format this channel + sink_frontend.set_filter(log_channel == channel_name); + } } void SetLoggerThreshold(const std::string& source, LogLevel threshold) { @@ -348,7 +351,7 @@ void InitLoggingSystem(const std::string& log_file, const std::string& _unnamed_ // Create a sink backend that logs to a file auto& file_sink_backend = FileSinkBackend(); - file_sink_backend = boost::make_shared( + file_sink_backend = boost::make_shared( keywords::file_name = log_file.c_str(), keywords::auto_flush = true ); @@ -358,6 +361,7 @@ void InitLoggingSystem(const std::string& log_file, const std::string& _unnamed_ // Add global attributes to all records logging::core::get()->add_global_attribute("TimeStamp", attr::local_clock()); + logging::core::get()->add_global_attribute("ThreadID", attr::current_thread_id()); SetLoggerThresholdCore("", default_log_level_threshold); diff --git a/util/Logger.h b/util/Logger.h index 592dc7cabc0..4b3e3f10d14 100644 --- a/util/Logger.h +++ b/util/Logger.h @@ -1,25 +1,16 @@ #ifndef _Logger_h_ #define _Logger_h_ -#include -#include -#include -#include -#include -#include -#include - -#ifdef FREEORION_WIN32 -// Note: The is a workaround for Visual C++ non-conformant pre-processor -// handling of empty macro arguments. -// https://msdn.microsoft.com/en-us/library/hh567368.aspx -// https://blogs.msdn.microsoft.com/vcblog/2017/03/07/c-standards-conformance-from-microsoft/ -#include -#include -#endif #include #include +#include +#include +#include +#include +#include +#include +#include #include "Export.h" @@ -164,26 +155,6 @@ std::basic_ostream& operator<<( std::unordered_map ValidNameToLogLevel(); -// Prefix \p name to create a global logger name less likely to collide. -#ifndef FREEORION_WIN32 - -#define FO_GLOBAL_LOGGER_NAME(name) fo_logger_global_##name - -#else - -// Note: The is a workaround for Visual C++ non-conformant pre-processor -// handling of empty macro arguments. -// https://msdn.microsoft.com/en-us/library/hh567368.aspx -// https://blogs.msdn.microsoft.com/vcblog/2017/03/07/c-standards-conformance-from-microsoft/ -#define FO_GLOBAL_LOGGER_NAME_NO_ARG() fo_logger_global_ -#define FO_GLOBAL_LOGGER_NAME_ONE_ARG(name) fo_logger_global_##name -#define FO_GLOBAL_LOGGER_NAME(...) \ - BOOST_PP_IF(BOOST_PP_IS_EMPTY(__VA_ARGS__), \ - FO_GLOBAL_LOGGER_NAME_NO_ARG(), \ - FO_GLOBAL_LOGGER_NAME_ONE_ARG(__VA_ARGS__)) - -#endif - /** Initializes the logging system. Log to the \p log_file. If \p log_file already exists it will * be deleted. \p unnamed_logger_identifier is the name used in the log file to identify logs from * the singular unnamed logger for this executable. Logs from the named loggers are identified by @@ -220,18 +191,6 @@ using NamedThreadedLogger = boost::log::sources::severity_channel_logger_mt< // Setup file sink, formatting, and \p name channel filter for \p logger. FO_COMMON_API void ConfigureLogger(NamedThreadedLogger& logger, const std::string& name); -// Place in source file to create the previously defined global logger \p name -#define DeclareThreadSafeLogger(name) \ - BOOST_LOG_INLINE_GLOBAL_LOGGER_INIT( \ - FO_GLOBAL_LOGGER_NAME(name), NamedThreadedLogger) \ - { \ - auto lg = NamedThreadedLogger( \ - (boost::log::keywords::severity = LogLevel::debug), \ - (boost::log::keywords::channel = #name)); \ - ConfigureLogger(lg, #name); \ - return lg; \ - } - // Signal that logger \p name has been created using LoggerCreatedSignalType = boost::signals2::signal; FO_COMMON_API extern LoggerCreatedSignalType LoggerCreatedSignal; @@ -240,95 +199,75 @@ FO_COMMON_API extern LoggerCreatedSignalType LoggerCreatedSignal; // loggers intialized during static initialization. FO_COMMON_API std::vector CreatedLoggersNames(); -// Create the default logger -#ifndef FREEORION_WIN32 - -DeclareThreadSafeLogger(); - -#else // Note: The is a workaround for Visual C++ non-conformant pre-processor // handling of empty macro arguments. // https://msdn.microsoft.com/en-us/library/hh567368.aspx // https://blogs.msdn.microsoft.com/vcblog/2017/03/07/c-standards-conformance-from-microsoft/ -BOOST_LOG_INLINE_GLOBAL_LOGGER_INIT( \ - fo_logger_global_, NamedThreadedLogger) \ -{ \ - auto lg = NamedThreadedLogger( \ - (boost::log::keywords::severity = LogLevel::debug), \ - (boost::log::keywords::channel = "")); \ - ConfigureLogger(lg, ""); \ - return lg; \ -} - +#if defined(FREEORION_WIN32) +# define FO_LOGGER_WIN32_WORKAROUND 1 +#else +# define FO_LOGGER_WIN32_WORKAROUND 0 #endif -BOOST_LOG_ATTRIBUTE_KEYWORD(log_severity, "Severity", LogLevel); -BOOST_LOG_ATTRIBUTE_KEYWORD(log_channel, "Channel", std::string) -BOOST_LOG_ATTRIBUTE_KEYWORD(log_src_filename, "SrcFilename", std::string); -BOOST_LOG_ATTRIBUTE_KEYWORD(log_src_linenum, "SrcLinenum", int); - -#define __BASE_FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +// Prefix \p name to create a global logger name less likely to collide. +#define FO_GLOBAL_LOGGER_NAME(...) \ + fo_logger_global_ ## __VA_ARGS__ -#ifndef FREEORION_WIN32 +// Place in source file to create the previously defined global logger \p name +#define DeclareThreadSafeLogger(...) \ + BOOST_LOG_INLINE_GLOBAL_LOGGER_INIT( \ + FO_GLOBAL_LOGGER_NAME(__VA_ARGS__), NamedThreadedLogger) \ + { \ + constexpr auto channel = BOOST_PP_IF( \ + BOOST_PP_AND( \ + FO_LOGGER_WIN32_WORKAROUND, \ + BOOST_PP_IS_EMPTY(__VA_ARGS__)), \ + "", \ + #__VA_ARGS__); \ + auto lg = NamedThreadedLogger( \ + (boost::log::keywords::severity = LogLevel::debug), \ + (boost::log::keywords::channel = channel)); \ + ConfigureLogger(lg, channel); \ + return lg; \ + } -#define FO_LOGGER(name, lvl) \ - BOOST_LOG_STREAM_WITH_PARAMS( \ - FO_GLOBAL_LOGGER_NAME(name)::get(), \ - (boost::log::keywords::severity = lvl)) \ - << boost::log::add_value("SrcFilename", __BASE_FILENAME__) \ - << boost::log::add_value("SrcLinenum", __LINE__) -#define TraceLogger(name) FO_LOGGER(name, LogLevel::trace) +// Create the default logger +DeclareThreadSafeLogger(); -#define DebugLogger(name) FO_LOGGER(name, LogLevel::debug) -#define InfoLogger(name) FO_LOGGER(name, LogLevel::info) +#define __BASE_FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) -#define WarnLogger(name) FO_LOGGER(name, LogLevel::warn) -#define ErrorLogger(name) FO_LOGGER(name, LogLevel::error) +#define FO_LOGGER(lvl, ...) \ + BOOST_LOG_STREAM_WITH_PARAMS( \ + FO_GLOBAL_LOGGER_NAME(__VA_ARGS__)::get(), \ + (boost::log::keywords::severity = lvl)) \ -#else +#define TraceLogger(...) FO_LOGGER(LogLevel::trace, __VA_ARGS__) \ + << boost::log::add_value("SrcFilename", __BASE_FILENAME__) \ + << boost::log::add_value("SrcLinenum", __LINE__) +#define DebugLogger(...) FO_LOGGER(LogLevel::debug, __VA_ARGS__) \ + << boost::log::add_value("SrcFilename", __BASE_FILENAME__) \ + << boost::log::add_value("SrcLinenum", __LINE__) -// Note: The is a workaround for Visual C++ non-conformant pre-processor -// handling of empty macro arguments. -// https://msdn.microsoft.com/en-us/library/hh567368.aspx -// https://blogs.msdn.microsoft.com/vcblog/2017/03/07/c-standards-conformance-from-microsoft/ -#define FO_LOGGER_PRESTITCHED(lvl, logger) \ - BOOST_LOG_STREAM_WITH_PARAMS( \ - logger::get(), \ - (boost::log::keywords::severity = lvl)) \ - << boost::log::add_value("SrcFilename", __BASE_FILENAME__) \ - << boost::log::add_value("SrcLinenum", __LINE__) +#define InfoLogger(...) FO_LOGGER(LogLevel::info, __VA_ARGS__) \ + << boost::log::add_value("SrcFilename", __BASE_FILENAME__) \ + << boost::log::add_value("SrcLinenum", __LINE__) -#define TraceLogger(...) FO_LOGGER_PRESTITCHED(LogLevel::trace, fo_logger_global_##__VA_ARGS__) -#define DebugLogger(...) FO_LOGGER_PRESTITCHED(LogLevel::debug, fo_logger_global_##__VA_ARGS__) -#define InfoLogger(...) FO_LOGGER_PRESTITCHED(LogLevel::info, fo_logger_global_##__VA_ARGS__) -#define WarnLogger(...) FO_LOGGER_PRESTITCHED(LogLevel::warn, fo_logger_global_##__VA_ARGS__) -#define ErrorLogger(...) FO_LOGGER_PRESTITCHED(LogLevel::error, fo_logger_global_##__VA_ARGS__) +#define WarnLogger(...) FO_LOGGER(LogLevel::warn, __VA_ARGS__) \ + << boost::log::add_value("SrcFilename", __BASE_FILENAME__) \ + << boost::log::add_value("SrcLinenum", __LINE__) +#define ErrorLogger(...) FO_LOGGER(LogLevel::error, __VA_ARGS__) \ + << boost::log::add_value("SrcFilename", __BASE_FILENAME__) \ + << boost::log::add_value("SrcLinenum", __LINE__) -#endif /** Sets the \p threshold of \p source. \p source == "" is the default logger.*/ FO_COMMON_API void SetLoggerThreshold(const std::string& source, LogLevel threshold); -/** Apply \p configure_front_end to a new FileSinkFrontEnd for \p channel_name. During static - initialization if the backend does not yet exist, then \p configure_front_end will be - stored until the backend is created.*/ -using LoggerTextFileSinkFrontend = boost::log::sinks::synchronous_sink; -using LoggerFileSinkFrontEndConfigurer = std::function; -FO_COMMON_API void ApplyConfigurationToFileSinkFrontEnd( - const std::string& channel_name, - const LoggerFileSinkFrontEndConfigurer& configure_front_end); - -extern int g_indent; - -/** A function that returns the correct amount of spacing for the current - * indentation level during a dump. */ -std::string DumpIndent(); - #endif // _Logger_h_ diff --git a/util/LoggerWithOptionsDB.h b/util/LoggerWithOptionsDB.h index e66e74ae848..d46b8d7c064 100644 --- a/util/LoggerWithOptionsDB.h +++ b/util/LoggerWithOptionsDB.h @@ -60,7 +60,8 @@ FO_COMMON_API void ChangeLoggerThresholdInOptionsDB(const std::string& option_na enum class LoggerTypes { exec = 1, ///< the unnamed logger for a particular executable named = 2, ///< a normal named source - both = exec | named}; + both = exec | named +}; /** Return the option names, labels and levels for logger oy \p type from OptionsDB. */ FO_COMMON_API std::set> diff --git a/util/ModeratorAction.cpp b/util/ModeratorAction.cpp index 4e4ef02ebea..683fe4ffbd4 100644 --- a/util/ModeratorAction.cpp +++ b/util/ModeratorAction.cpp @@ -5,6 +5,7 @@ #include "../universe/System.h" #include "../universe/Planet.h" #include "../universe/Enums.h" +#include "AppInterface.h" #include "Directories.h" #include "Logger.h" #include "i18n.h" @@ -50,7 +51,7 @@ Moderator::SetOwner::SetOwner(int object_id, int new_owner_empire_id) : {} void Moderator::SetOwner::Execute() const { - std::shared_ptr obj = GetUniverseObject(m_object_id); + auto obj = Objects().get(m_object_id); if (!obj) { ErrorLogger() << "Moderator::SetOwner::Execute couldn't get object with id: " << m_object_id; return; @@ -80,12 +81,12 @@ Moderator::AddStarlane::AddStarlane(int system_1_id, int system_2_id) : {} void Moderator::AddStarlane::Execute() const { - std::shared_ptr sys1 = GetSystem(m_id_1); + auto sys1 = Objects().get(m_id_1); if (!sys1) { ErrorLogger() << "Moderator::AddStarlane::Execute couldn't get system with id: " << m_id_1; return; } - std::shared_ptr sys2 = GetSystem(m_id_2); + auto sys2 = Objects().get(m_id_2); if (!sys2) { ErrorLogger() << "Moderator::AddStarlane::Execute couldn't get system with id: " << m_id_2; return; @@ -116,12 +117,12 @@ Moderator::RemoveStarlane::RemoveStarlane(int system_1_id, int system_2_id) : {} void Moderator::RemoveStarlane::Execute() const { - std::shared_ptr sys1 = GetSystem(m_id_1); + auto sys1 = Objects().get(m_id_1); if (!sys1) { ErrorLogger() << "Moderator::RemoveStarlane::Execute couldn't get system with id: " << m_id_1; return; } - std::shared_ptr sys2 = GetSystem(m_id_2); + auto sys2 = Objects().get(m_id_2); if (!sys2) { ErrorLogger() << "Moderator::RemoveStarlane::Execute couldn't get system with id: " << m_id_2; return; @@ -157,14 +158,11 @@ namespace { std::string GenerateSystemName() { static std::vector star_names = UserStringList("STAR_NAMES"); - const ObjectMap& objects = Objects(); - std::vector> systems = objects.FindObjects(); - // pick a name for the system for (const std::string& star_name : star_names) { // does an existing system have this name? bool dupe = false; - for (std::shared_ptr system : systems) { + for (auto& system : Objects().all()) { if (system->Name() == star_name) { dupe = true; break; // another system has this name. skip to next potential name. @@ -211,7 +209,7 @@ Moderator::CreatePlanet::CreatePlanet(int system_id, PlanetType planet_type, Pla {} void Moderator::CreatePlanet::Execute() const { - std::shared_ptr location = GetSystem(m_system_id); + auto location = Objects().get(m_system_id); if (!location) { ErrorLogger() << "CreatePlanet::Execute couldn't get a System object at which to create the planet"; return; diff --git a/util/ModeratorAction.h b/util/ModeratorAction.h index 84458080388..2c3dca44120 100644 --- a/util/ModeratorAction.h +++ b/util/ModeratorAction.h @@ -34,7 +34,7 @@ class ModeratorAction { private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -44,9 +44,6 @@ class FO_COMMON_API DestroyUniverseObject : public ModeratorAction { explicit DestroyUniverseObject(int object_id); - virtual ~DestroyUniverseObject() - {} - void Execute() const override; std::string Dump() const override; @@ -55,7 +52,7 @@ class FO_COMMON_API DestroyUniverseObject : public ModeratorAction { int m_object_id; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -65,9 +62,6 @@ class FO_COMMON_API SetOwner : public ModeratorAction { SetOwner(int object_id, int new_owner_empire_id); - virtual ~SetOwner() - {} - void Execute() const override; std::string Dump() const override; @@ -77,7 +71,7 @@ class FO_COMMON_API SetOwner : public ModeratorAction { int m_new_owner_empire_id; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -87,9 +81,6 @@ class FO_COMMON_API AddStarlane : public ModeratorAction { AddStarlane(int system_1_id, int system_2_id); - virtual ~AddStarlane() - {} - void Execute() const override; std::string Dump() const override; @@ -99,7 +90,7 @@ class FO_COMMON_API AddStarlane : public ModeratorAction { int m_id_2; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -109,9 +100,6 @@ class FO_COMMON_API RemoveStarlane : public ModeratorAction { RemoveStarlane(int system_1_id, int system_2_id); - virtual ~RemoveStarlane() - {} - void Execute() const override; std::string Dump() const override; @@ -121,7 +109,7 @@ class FO_COMMON_API RemoveStarlane : public ModeratorAction { int m_id_2; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -131,9 +119,6 @@ class FO_COMMON_API CreateSystem : public ModeratorAction { CreateSystem(double x, double y, StarType star_type); - virtual ~CreateSystem() - {} - void Execute() const override; std::string Dump() const override; @@ -144,7 +129,7 @@ class FO_COMMON_API CreateSystem : public ModeratorAction { StarType m_star_type; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -154,9 +139,6 @@ class FO_COMMON_API CreatePlanet : public ModeratorAction { CreatePlanet(int system_id, PlanetType planet_type, PlanetSize planet_size); - virtual ~CreatePlanet() - {} - void Execute() const override; std::string Dump() const override; @@ -167,7 +149,7 @@ class FO_COMMON_API CreatePlanet : public ModeratorAction { PlanetSize m_planet_size; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/util/MultiplayerCommon.cpp b/util/MultiplayerCommon.cpp index 91e92c3e79e..d49a687210a 100644 --- a/util/MultiplayerCommon.cpp +++ b/util/MultiplayerCommon.cpp @@ -1,16 +1,15 @@ #include "MultiplayerCommon.h" #include "Directories.h" +#include "GameRules.h" #include "i18n.h" #include "LoggerWithOptionsDB.h" #include "OptionsDB.h" #include "Random.h" -#include "../parse/Parse.h" -#include "../universe/Fleet.h" -#include "../universe/Planet.h" -#include "../universe/System.h" +#include "AppInterface.h" #include "../universe/Enums.h" + #if defined(_MSC_VER) && defined(int64_t) #undef int64_t #endif @@ -23,168 +22,65 @@ const std::string SP_SAVE_FILE_EXTENSION = ".sav"; namespace { // command-line options void AddOptions(OptionsDB& db) { - db.Add("resource-dir", UserStringNop("OPTIONS_DB_RESOURCE_DIR"), PathString(GetRootDataDir() / "default")); - db.Add('S', "save-dir", UserStringNop("OPTIONS_DB_SAVE_DIR"), PathString(GetUserDataDir() / "save")); - db.Add("log-level", UserStringNop("OPTIONS_DB_LOG_LEVEL"), "", - OrValidator(LogLevelValidator(), DiscreteValidator("")), false); - db.Add("stringtable-filename", UserStringNop("OPTIONS_DB_STRINGTABLE_FILENAME"), PathString(GetRootDataDir() / "default" / "stringtables" / "en.txt")); - db.Add("binary-serialization", UserStringNop("OPTIONS_DB_BINARY_SERIALIZATION"), false); - db.Add("xml-zlib-serialization", UserStringNop("OPTIONS_DB_XML_ZLIB_SERIALIZATION"),true); - - // AI Testing options-- the following options are to facilitate AI testing and do not currently have an options page widget; + db.Add("resource.path", UserStringNop("OPTIONS_DB_RESOURCE_DIR"), PathToString(GetRootDataDir() / "default")); + db.Add('S', "save.path", UserStringNop("OPTIONS_DB_SAVE_DIR"), PathToString(GetUserDataDir() / "save")); + db.Add("save.server.path", UserStringNop("OPTIONS_DB_SERVER_SAVE_DIR"), PathToString(GetUserDataDir() / "save")); + db.Add("log-level", UserStringNop("OPTIONS_DB_LOG_LEVEL"), "", + OrValidator(LogLevelValidator(), DiscreteValidator("")), false); + db.Add("log-file", UserStringNop("OPTIONS_DB_LOG_FILE"), "", + Validator(), false); + // Default stringtable filename is deferred to i18n.cpp::InitStringtableFileName + db.Add("resource.stringtable.path", UserStringNop("OPTIONS_DB_STRINGTABLE_FILENAME"), ""); + db.Add("save.format.binary.enabled", UserStringNop("OPTIONS_DB_BINARY_SERIALIZATION"), false); + db.Add("save.format.xml.zlib.enabled", UserStringNop("OPTIONS_DB_XML_ZLIB_SERIALIZATION"), true); + db.Add("save.auto.hostless.enabled", UserStringNop("OPTIONS_DB_AUTOSAVE_HOSTLESS"), true); + db.Add("save.auto.interval", UserStringNop("OPTIONS_DB_AUTOSAVE_INTERVAL"), 0); + db.Add("load", UserStringNop("OPTIONS_DB_LOAD"), "", Validator(), false); + db.Add("save.auto.exit.enabled", UserStringNop("OPTIONS_DB_AUTOSAVE_GAME_CLOSE"), true); + db.AddFlag('q', "quickstart", UserStringNop("OPTIONS_DB_QUICKSTART"), false); + + // Common galaxy settings + db.Add("setup.seed", UserStringNop("OPTIONS_DB_GAMESETUP_SEED"), std::string("0"), Validator()); + db.Add("setup.star.count", UserStringNop("OPTIONS_DB_GAMESETUP_STARS"), 150, RangedValidator(10, 5000)); + db.Add("setup.galaxy.shape", UserStringNop("OPTIONS_DB_GAMESETUP_GALAXY_SHAPE"), DISC, RangedValidator(SPIRAL_2, RANDOM)); + db.Add("setup.galaxy.age", UserStringNop("OPTIONS_DB_GAMESETUP_GALAXY_AGE"), GALAXY_SETUP_MEDIUM, RangedValidator(GALAXY_SETUP_LOW, GALAXY_SETUP_RANDOM)); + db.Add("setup.planet.density", UserStringNop("OPTIONS_DB_GAMESETUP_PLANET_DENSITY"), GALAXY_SETUP_MEDIUM, RangedValidator(GALAXY_SETUP_LOW, GALAXY_SETUP_RANDOM)); + db.Add("setup.starlane.frequency", UserStringNop("OPTIONS_DB_GAMESETUP_STARLANE_FREQUENCY"), GALAXY_SETUP_MEDIUM, RangedValidator(GALAXY_SETUP_LOW, GALAXY_SETUP_RANDOM)); + db.Add("setup.specials.frequency", UserStringNop("OPTIONS_DB_GAMESETUP_SPECIALS_FREQUENCY"), GALAXY_SETUP_MEDIUM, RangedValidator(GALAXY_SETUP_NONE, GALAXY_SETUP_RANDOM)); + db.Add("setup.monster.frequency", UserStringNop("OPTIONS_DB_GAMESETUP_MONSTER_FREQUENCY"), GALAXY_SETUP_MEDIUM, RangedValidator(GALAXY_SETUP_NONE, GALAXY_SETUP_RANDOM)); + db.Add("setup.native.frequency", UserStringNop("OPTIONS_DB_GAMESETUP_NATIVE_FREQUENCY"), GALAXY_SETUP_MEDIUM, RangedValidator(GALAXY_SETUP_NONE, GALAXY_SETUP_RANDOM)); + db.Add("setup.ai.player.count", UserStringNop("OPTIONS_DB_GAMESETUP_NUM_AI_PLAYERS"), 6, RangedValidator(0, IApp::MAX_AI_PLAYERS())); + db.Add("setup.ai.aggression", UserStringNop("OPTIONS_DB_GAMESETUP_AI_MAX_AGGRESSION"), MANIACAL, RangedValidator(BEGINNER, MANIACAL)); + + + // AI Testing options-- the following options are to facilitate AI testing and do not currently have an options page widget; // they are intended to be changed via the command line and are not currently storable in the configuration file. - db.Add("ai-path", UserStringNop("OPTIONS_DB_AI_FOLDER_PATH"), "python/AI", Validator(), false); - db.Add("ai-config", UserStringNop("OPTIONS_DB_AI_CONFIG"), "", Validator(), false); + db.Add("ai-path", UserStringNop("OPTIONS_DB_AI_FOLDER_PATH"), "python/AI", + Validator(), false); + db.Add("ai-config", UserStringNop("OPTIONS_DB_AI_CONFIG"), "", + Validator(), false); } bool temp_bool = RegisterOptions(&AddOptions); - std::vector& GameRulesRegistry() { - static std::vector game_rules_registry; - return game_rules_registry; - } -} - -///////////////////////////////////////////// -// Free Functions -///////////////////////////////////////////// -bool RegisterGameRules(GameRulesFn function) { - GameRulesRegistry().push_back(function); - return true; -} + void AddRules(GameRules& rules) { + rules.Add("RULE_THRESHOLD_HUMAN_PLAYER_WIN", "RULE_THRESHOLD_HUMAN_PLAYER_WIN_DESC", + "MULTIPLAYER", 0, true, RangedValidator(0, 999)); -GameRules& GetGameRules() { - static GameRules game_rules; - bool do_parse = game_rules.Empty(); + rules.Add("RULE_ONLY_ALLIANCE_WIN", "RULE_ONLY_ALLIANCE_WIN_DESC", + "MULTIPLAYER", true, true); - if (!GameRulesRegistry().empty()) { - for (GameRulesFn fn : GameRulesRegistry()) - fn(game_rules); - GameRulesRegistry().clear(); - } + rules.Add("RULE_ALLOW_CONCEDE", "RULE_ALLOW_CONCEDE_DESC", + "MULTIPLAYER", false, true); - if (do_parse) { - try { - parse::game_rules(game_rules); - } catch (const std::exception& e) { - ErrorLogger() << "Failed parsing game rules: error: " << e.what(); - throw e; - } + rules.Add("RULE_CONCEDE_COLONIES_THRESHOLD", "RULE_CONCEDE_COLONIES_THRESHOLD_DESC", + "MULTIPLAYER", 1, true, RangedValidator(0, 9999)); - DebugLogger() << "Registered and Parsed Game Rules:"; - for (const auto& entry : game_rules.GetRulesAsStrings()) - DebugLogger() << " ... " << entry.first << " : " << entry.second; + rules.Add("RULE_SHOW_DETAILED_EMPIRES_DATA", "RULE_SHOW_DETAILED_EMPIRES_DATA_DESC", + "MULTIPLAYER", true, true); } - return game_rules; + bool temp_bool2 = RegisterGameRules(&AddRules); } - -///////////////////////////////////////////////////// -// GameRules -///////////////////////////////////////////////////// -GameRules::Rule::Rule() : - OptionsDB::Option() -{} - -GameRules::Rule::Rule(RuleType rule_type_, const std::string& name_, const boost::any& value_, - const boost::any& default_value_, const std::string& description_, - const ValidatorBase *validator_, bool engine_internal_, - const std::string& category_) : - OptionsDB::Option(static_cast(0), name_, value_, default_value_, - description_, validator_, engine_internal_, false, true), - rule_type(rule_type_), - category(category_) -{} - -GameRules::GameRules() -{} - -bool GameRules::RuleExists(const std::string& name, RuleType rule_type) const { - if (rule_type == INVALID_RULE_TYPE) - return false; - auto rule_it = m_game_rules.find(name); - if (rule_it == m_game_rules.end()) - return false; - return rule_it->second.rule_type == rule_type; -} - -GameRules::RuleType GameRules::GetRuleType(const std::string& name) const { - auto rule_it = m_game_rules.find(name); - if (rule_it == m_game_rules.end()) - return INVALID_RULE_TYPE; - return rule_it->second.rule_type; -} - -bool GameRules::RuleIsInternal(const std::string& name) const { - auto rule_it = m_game_rules.find(name); - if (rule_it == m_game_rules.end()) - return false; - return rule_it->second.IsInternal(); -} - -const std::string& GameRules::GetDescription(const std::string& rule_name) const { - auto it = m_game_rules.find(rule_name); - if (it == m_game_rules.end()) - throw std::runtime_error(("GameRules::GetDescription(): No option called \"" + rule_name + "\" could be found.").c_str()); - return it->second.description; -} - -std::shared_ptr GameRules::GetValidator(const std::string& rule_name) const { - auto it = m_game_rules.find(rule_name); - if (it == m_game_rules.end()) - throw std::runtime_error(("GameRules::GetValidator(): No option called \"" + rule_name + "\" could be found.").c_str()); - return it->second.validator; -} - -void GameRules::ClearExternalRules() { - auto it = m_game_rules.begin(); - while (it != m_game_rules.end()) { - bool engine_internal = it->second.storable; // OptionsDB::Option member used to store if this option is engine-internal - if (!engine_internal) - m_game_rules.erase((it++)->first); // note postfix operator++ - else - ++it; - } -} - -void GameRules::ResetToDefaults() { - for (auto& it : m_game_rules) - it.second.SetToDefault(); -} - -std::vector> GameRules::GetRulesAsStrings() const { - std::vector> retval; - for (const auto& rule : m_game_rules) - retval.push_back(std::make_pair(rule.first, rule.second.ValueToString())); - return retval; -} - -void GameRules::SetFromStrings(const std::vector>& names_values) { - DebugLogger() << "Setting Rules from Strings:"; - for (const auto& entry : names_values) - DebugLogger() << " " << entry.first << " : " << entry.second; - - ResetToDefaults(); - for (auto& entry : names_values) { - auto rule_it = m_game_rules.find(entry.first); - if (rule_it == m_game_rules.end()) { - InfoLogger() << "GameRules::serialize received unrecognized rule: " << entry.first; - continue; - } - try { - rule_it->second.SetFromString(entry.second); - } catch (const boost::bad_lexical_cast& e) { - ErrorLogger() << "Unable to set rule: " << entry.first << " to value: " << entry.second << " - couldn't cast string to allowed value for this option"; - } catch (...) { - ErrorLogger() << "Unable to set rule: " << entry.first << " to value: " << entry.second; - } - } - - DebugLogger() << "After Setting Rules:"; - for (const auto& entry : m_game_rules) - DebugLogger() << " " << entry.first << " : " << entry.second.ValueToString(); -} - - ///////////////////////////////////////////////////// // GalaxySetupData ///////////////////////////////////////////////////// @@ -207,10 +103,11 @@ namespace { << " from 0 to " << static_cast(enum_vals_count) - 1; return hash_value % static_cast(enum_vals_count); } + + static char alphanum[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; } GalaxySetupData::GalaxySetupData() : - m_seed(), m_size(100), m_shape(SPIRAL_2), m_age(GALAXY_SETUP_MEDIUM), @@ -219,9 +116,26 @@ GalaxySetupData::GalaxySetupData() : m_specials_freq(GALAXY_SETUP_MEDIUM), m_monster_freq(GALAXY_SETUP_MEDIUM), m_native_freq(GALAXY_SETUP_MEDIUM), - m_ai_aggr(MANIACAL) + m_ai_aggr(MANIACAL), + m_encoding_empire(ALL_EMPIRES) {} +GalaxySetupData::GalaxySetupData(GalaxySetupData&& base) : + m_seed(std::move(base.m_seed)), + m_size(base.m_size), + m_shape(base.m_shape), + m_age(base.m_age), + m_starlane_freq(base.m_starlane_freq), + m_planet_density(base.m_planet_density), + m_specials_freq(base.m_specials_freq), + m_monster_freq(base.m_monster_freq), + m_native_freq(base.m_native_freq), + m_ai_aggr(base.m_ai_aggr), + m_game_rules(std::move(base.m_game_rules)), + m_game_uid(std::move(base.m_game_uid)), + m_encoding_empire(base.m_encoding_empire) +{ SetSeed(m_seed); } + const std::string& GalaxySetupData::GetSeed() const { return m_seed; } @@ -274,9 +188,27 @@ GalaxySetupOption GalaxySetupData::GetNativeFreq() const { Aggression GalaxySetupData::GetAggression() const { return m_ai_aggr; } -const std::vector>& GalaxySetupData::GetGameRules() const +const std::map& GalaxySetupData::GetGameRules() const { return m_game_rules; } +const std::string& GalaxySetupData::GetGameUID() const +{ return m_game_uid; } + +void GalaxySetupData::SetSeed(const std::string& seed) { + std::string new_seed = seed; + if (new_seed.empty() || new_seed == "RANDOM") { + ClockSeed(); + new_seed.clear(); + for (int i = 0; i < 8; ++i) + new_seed += alphanum[RandSmallInt(0, (sizeof(alphanum) - 2))]; + DebugLogger() << "Set empty or requested random seed to " << new_seed; + } + m_seed = std::move(new_seed); +} + +void GalaxySetupData::SetGameUID(const std::string& game_uid) +{ m_game_uid = game_uid; } + ///////////////////////////////////////////////////// // PlayerSetupData ///////////////////////////////////////////////////// @@ -287,7 +219,8 @@ bool operator==(const PlayerSetupData& lhs, const PlayerSetupData& rhs) { lhs.m_player_name == rhs.m_player_name && lhs.m_save_game_empire_id == rhs.m_save_game_empire_id && lhs.m_starting_species_name == rhs.m_starting_species_name && - lhs.m_player_ready == rhs.m_player_ready; + lhs.m_player_ready == rhs.m_player_ready && + lhs.m_starting_team == rhs.m_starting_team; } bool operator!=(const PlayerSetupData& lhs, const PlayerSetupData& rhs) diff --git a/util/MultiplayerCommon.h b/util/MultiplayerCommon.h index e3e567b843b..0b3293495bd 100644 --- a/util/MultiplayerCommon.h +++ b/util/MultiplayerCommon.h @@ -3,8 +3,9 @@ #include "../universe/EnumsFwd.h" #include "../network/Networking.h" -#include "OptionsDB.h" #include "Export.h" +#include "OptionsDB.h" +#include "Pending.h" #include "Serialize.h" #include @@ -14,143 +15,21 @@ #include #include #include +#include FO_COMMON_API extern const std::string MP_SAVE_FILE_EXTENSION; FO_COMMON_API extern const std::string SP_SAVE_FILE_EXTENSION; - FO_COMMON_API extern const int ALL_EMPIRES; +FO_COMMON_API extern const int INVALID_GAME_TURN; -class GameRules; - -///////////////////////////////////////////// -// Free Functions -///////////////////////////////////////////// -typedef void (*GameRulesFn)(GameRules&); ///< the function signature for functions that add Rules to the GameRules (void (GameRules&)) - -/** adds \a function to a vector of pointers to functions that add Rules to - * the GameRules. This function returns a boolean so that it can be used to - * declare a dummy static variable that causes \a function to be registered as - * a side effect (e.g. at file scope: - * "bool unused_bool = RegisterGameRules(&foo)"). */ -FO_COMMON_API bool RegisterGameRules(GameRulesFn function); - -/** returns the single instance of the GameRules class */ -FO_COMMON_API GameRules& GetGameRules(); - -/** Database of values that control how the game mechanics function. */ -class FO_COMMON_API GameRules { -public: - enum RuleType : int { - INVALID_RULE_TYPE = -1, - TOGGLE, - INT, - DOUBLE, - STRING - }; - - RuleType RuleTypeForType(bool dummy) - { return TOGGLE; } - RuleType RuleTypeForType(int dummy) - { return INT; } - RuleType RuleTypeForType(double dummy) - { return DOUBLE; } - RuleType RuleTypeForType(std::string dummy) - { return STRING; } - - struct FO_COMMON_API Rule : public OptionsDB::Option { - Rule(); - Rule(RuleType rule_type_, const std::string& name_, const boost::any& value_, - const boost::any& default_value_, const std::string& description_, - const ValidatorBase *validator_, bool engine_internal_, - const std::string& category_ = ""); - bool IsInternal() const { return this->storable; } - - RuleType rule_type = INVALID_RULE_TYPE; - std::string category = ""; - }; - - /** \name Accessors */ //@{ - bool Empty() const - { return m_game_rules.empty(); } - std::unordered_map::const_iterator begin() const - { return m_game_rules.begin(); } - std::unordered_map::const_iterator end() const - { return m_game_rules.end(); } - - bool RuleExists(const std::string& name) const - { return m_game_rules.find(name) != m_game_rules.end(); } - bool RuleExists(const std::string& name, RuleType rule_type) const; - RuleType GetRuleType(const std::string& name) const; - bool RuleIsInternal(const std::string& name) const; - - /** returns the description string for rule \a rule_name, or throws - * std::runtime_error if no such rule exists. */ - const std::string& GetDescription(const std::string& rule_name) const; - - /** returns the validator for rule \a rule_name, or throws - * std::runtime_error if no such rule exists. */ - std::shared_ptr GetValidator(const std::string& rule_name) const; - - /** returns all contained rules as name and value string pairs. */ - std::vector> GetRulesAsStrings() const; - - template - T Get(const std::string& name) const - { - auto it = m_game_rules.find(name); - if (it == m_game_rules.end()) - throw std::runtime_error("GameRules::Get<>() : Attempted to get nonexistent rule \"" + name + "\"."); - return boost::any_cast(it->second.value); - } - //@} - - /** \name Mutators */ //@{ - /** adds a rule, optionally with a custom validator */ - template - void Add(const std::string& name, const std::string& description, - const std::string& category, T default_value, - bool engine_interal, const ValidatorBase& validator = Validator()) - { - auto it = m_game_rules.find(name); - if (it != m_game_rules.end()) - throw std::runtime_error("GameRules::Add<>() : Rule " + name + " was added twice."); - m_game_rules[name] = Rule(RuleTypeForType(T()), name, default_value, default_value, - description, validator.Clone(), engine_interal, category); - DebugLogger() << "Added game rule named " << name << " with default value " << default_value; - } - - template - void Set(const std::string& name, const T& value) - { - auto it = m_game_rules.find(name); - if (it == m_game_rules.end()) - throw std::runtime_error("GameRules::Set<>() : Attempted to set nonexistent rule \"" + name + "\"."); - it->second.SetFromValue(value); - } - - void SetFromStrings(const std::vector>& names_values); - - /** Removes game rules that were added without being specified as - engine internal. */ - void ClearExternalRules(); - - /** Resets all rules to default values. */ - void ResetToDefaults(); - //@} - -private: - GameRules(); - - std::unordered_map m_game_rules; - - friend FO_COMMON_API GameRules& GetGameRules(); -}; /** The data that represent the galaxy setup for a new game. */ struct FO_COMMON_API GalaxySetupData { /** \name Structors */ //@{ GalaxySetupData(); + GalaxySetupData(const GalaxySetupData&) = default; + GalaxySetupData(GalaxySetupData&& base); //@} /** \name Accessors */ //@{ @@ -164,10 +43,18 @@ struct FO_COMMON_API GalaxySetupData { GalaxySetupOption GetMonsterFreq() const; GalaxySetupOption GetNativeFreq() const; Aggression GetAggression() const; - const std::vector>& + const std::map& GetGameRules() const; + const std::string& GetGameUID() const; + //@} + + /** \name Mutators */ //@{ + void SetSeed(const std::string& seed); + void SetGameUID(const std::string& game_uid); //@} + GalaxySetupData& operator=(const GalaxySetupData&) = default; + std::string m_seed; int m_size; Shape m_shape; @@ -178,21 +65,25 @@ struct FO_COMMON_API GalaxySetupData { GalaxySetupOption m_monster_freq; GalaxySetupOption m_native_freq; Aggression m_ai_aggr; - std::vector> + std::map m_game_rules; + std::string m_game_uid; + + /** HACK! This must be set to the encoding empire's id when serializing a + * GalaxySetupData, so that only the relevant parts of the galaxy data are + * serialized. The use of this local field is done just so I don't + * have to rewrite any custom boost::serialization classes that implement + * empire-dependent visibility. */ + int m_encoding_empire; ///< used during serialization to globally set what empire knowledge to use private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -BOOST_CLASS_VERSION(GalaxySetupData, 1); +BOOST_CLASS_VERSION(GalaxySetupData, 3); -extern template FO_COMMON_API void GalaxySetupData::serialize(freeorion_bin_oarchive&, const unsigned int); -extern template FO_COMMON_API void GalaxySetupData::serialize(freeorion_bin_iarchive&, const unsigned int); -extern template FO_COMMON_API void GalaxySetupData::serialize(freeorion_xml_oarchive&, const unsigned int); -extern template FO_COMMON_API void GalaxySetupData::serialize(freeorion_xml_iarchive&, const unsigned int); /** Contains the UI data that must be saved in save game files in order to * restore games to the users' last views. */ @@ -202,18 +93,20 @@ struct FO_COMMON_API SaveGameUIData { double map_zoom_steps_in; std::set fleets_exploring; - std::vector> ordered_ship_design_ids_and_obsolete; + // See DesignWnd.cpp for the usage of the following variables. + int obsolete_ui_event_count; + std::vector>>> ordered_ship_design_ids_and_obsolete; + std::vector>> ordered_ship_hull_and_obsolete; + std::unordered_map obsolete_ship_parts; private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -extern template FO_COMMON_API void SaveGameUIData::serialize(freeorion_bin_oarchive&, const unsigned int); -extern template FO_COMMON_API void SaveGameUIData::serialize(freeorion_bin_iarchive&, const unsigned int); -extern template FO_COMMON_API void SaveGameUIData::serialize(freeorion_xml_oarchive&, const unsigned int); -extern template FO_COMMON_API void SaveGameUIData::serialize(freeorion_xml_iarchive&, const unsigned int); +BOOST_CLASS_VERSION(SaveGameUIData, 4); + /** The data for one empire necessary for game-setup during multiplayer loading. */ struct FO_COMMON_API SaveGameEmpireData { @@ -222,14 +115,21 @@ struct FO_COMMON_API SaveGameEmpireData { m_empire_id(ALL_EMPIRES), m_empire_name(), m_player_name(), - m_color() + m_color(), + m_authenticated(false), + m_eliminated(false), + m_won(false) {} SaveGameEmpireData(int empire_id, const std::string& empire_name, - const std::string& player_name, const GG::Clr& colour) : + const std::string& player_name, const GG::Clr& colour, + bool authenticated, bool eliminated, bool won) : m_empire_id(empire_id), m_empire_name(empire_name), m_player_name(player_name), - m_color(colour) + m_color(colour), + m_authenticated(authenticated), + m_eliminated(eliminated), + m_won(won) {} //@} @@ -237,17 +137,93 @@ struct FO_COMMON_API SaveGameEmpireData { std::string m_empire_name; std::string m_player_name; GG::Clr m_color; + bool m_authenticated; + bool m_eliminated; + bool m_won; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +BOOST_CLASS_VERSION(SaveGameEmpireData, 2); + +/** Contains basic data about a player in a game. */ +struct FO_COMMON_API PlayerSaveHeaderData { + PlayerSaveHeaderData() : + m_name(), + m_empire_id(ALL_EMPIRES), + m_client_type(Networking::INVALID_CLIENT_TYPE) + {} + + PlayerSaveHeaderData(const std::string& name, int empire_id, + Networking::ClientType client_type) : + m_name(name), + m_empire_id(empire_id), + m_client_type(client_type) + {} + + std::string m_name; + int m_empire_id; + Networking::ClientType m_client_type; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +/** Contains data that must be saved for a single player. */ +struct FO_COMMON_API PlayerSaveGameData : public PlayerSaveHeaderData { + PlayerSaveGameData() : + PlayerSaveHeaderData(), + m_orders(), + m_ui_data(), + m_save_state_string() + {} + + PlayerSaveGameData(const std::string& name, int empire_id, + const std::shared_ptr& orders, + const std::shared_ptr& ui_data, + const std::string& save_state_string, + Networking::ClientType client_type) : + PlayerSaveHeaderData(name, empire_id, client_type), + m_orders(orders), + m_ui_data(ui_data), + m_save_state_string(save_state_string) + {} + + std::shared_ptr m_orders; + std::shared_ptr m_ui_data; + std::string m_save_state_string; private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -extern template FO_COMMON_API void SaveGameEmpireData::serialize(freeorion_bin_oarchive&, const unsigned int); -extern template FO_COMMON_API void SaveGameEmpireData::serialize(freeorion_bin_iarchive&, const unsigned int); -extern template FO_COMMON_API void SaveGameEmpireData::serialize(freeorion_xml_oarchive&, const unsigned int); -extern template FO_COMMON_API void SaveGameEmpireData::serialize(freeorion_xml_iarchive&, const unsigned int); +BOOST_CLASS_VERSION(PlayerSaveGameData, 2); + +/** Data that must be retained by the server when saving and loading a + * game that isn't player data or the universe */ +struct FO_COMMON_API ServerSaveGameData { + ServerSaveGameData() : + m_current_turn(INVALID_GAME_TURN) + {} + + ServerSaveGameData(int current_turn) : + m_current_turn(current_turn) + {} + + int m_current_turn; + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; /** The data structure used to represent a single player's setup options for a * multiplayer game (in the multiplayer lobby screen). */ @@ -261,30 +237,33 @@ struct PlayerSetupData { m_starting_species_name(), m_save_game_empire_id(ALL_EMPIRES), m_client_type(Networking::INVALID_CLIENT_TYPE), - m_player_ready(false) + m_player_ready(false), + m_authenticated(false), + m_starting_team(Networking::NO_TEAM_ID) {} //@} std::string m_player_name; ///< the player's name int m_player_id; ///< player id - std::string m_empire_name; ///< the name of the player's empire when starting a new game GG::Clr m_empire_color; ///< the color used to represent this player's empire when starting a new game std::string m_starting_species_name;///< name of the species with which the player starts when starting a new game - int m_save_game_empire_id; ///< when loading a game, the ID of the empire that this player will control - Networking::ClientType m_client_type; ///< is this player an AI, human player or...? bool m_player_ready; ///< if player ready to play. + bool m_authenticated; ///< if player was authenticated + int m_starting_team; ///< team id or -1 if no team. private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; bool FO_COMMON_API operator==(const PlayerSetupData& lhs, const PlayerSetupData& rhs); bool operator!=(const PlayerSetupData& lhs, const PlayerSetupData& rhs); +BOOST_CLASS_VERSION(PlayerSetupData, 2); + /** The data needed to establish a new single player game. If \a m_new_game * is true, a new game is to be started, using the remaining members besides * \a m_filename. Otherwise, the saved game \a m_filename will be loaded @@ -298,13 +277,13 @@ struct SinglePlayerSetupData : public GalaxySetupData { {} //@} - bool m_new_game; - std::string m_filename; - std::vector m_players; + bool m_new_game; + std::string m_filename; + std::vector m_players; private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -312,29 +291,85 @@ struct SinglePlayerSetupData : public GalaxySetupData { struct FO_COMMON_API MultiplayerLobbyData : public GalaxySetupData { /** \name Structors */ //@{ MultiplayerLobbyData() : + m_any_can_edit(false), + m_new_game(true), + m_start_locked(false), + m_players(), + m_save_game(), + m_save_game_empire_data(), + m_save_game_current_turn(0), + m_in_game(false) + {} + + MultiplayerLobbyData(const GalaxySetupData& base) : + GalaxySetupData(base), + m_any_can_edit(false), + m_new_game(true), + m_start_locked(false), + m_players(), + m_save_game(), + m_save_game_empire_data(), + m_save_game_current_turn(0), + m_in_game(false) + {} + + MultiplayerLobbyData(GalaxySetupData&& base) : + GalaxySetupData(std::move(base)), + m_any_can_edit(false), m_new_game(true), + m_start_locked(false), m_players(), m_save_game(), - m_save_game_empire_data() + m_save_game_empire_data(), + m_save_game_current_turn(0), + m_in_game(false) {} //@} std::string Dump() const; + bool m_any_can_edit; bool m_new_game; + bool m_start_locked; // TODO: Change from a list<(player_id, PlayerSetupData)> where // PlayerSetupData contain player_id to a vector of PlayerSetupData std::list> m_players; // std::string m_save_game; //< File name of a save file std::map m_save_game_empire_data;// indexed by empire_id + int m_save_game_current_turn; + + std::string m_start_lock_cause; + bool m_in_game; ///< In-game lobby + +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version); +}; + +BOOST_CLASS_VERSION(MultiplayerLobbyData, 2); + +/** The data structure stores information about latest chat massages. */ +struct FO_COMMON_API ChatHistoryEntity { + /** \name Structors */ //@{ + ChatHistoryEntity() + {} + //@} + + boost::posix_time::ptime m_timestamp; + std::string m_player_name; + std::string m_text; + GG::Clr m_text_color; private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; +BOOST_CLASS_VERSION(ChatHistoryEntity, 1); + /** Information about one player that other players are informed of. Assembled by server and sent to players. */ struct PlayerInfo { PlayerInfo() : @@ -357,7 +392,7 @@ struct PlayerInfo { bool host; ///< true iff this is the host player friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/util/OptionValidators.h b/util/OptionValidators.h index 6716d805cef..56ed6e23644 100644 --- a/util/OptionValidators.h +++ b/util/OptionValidators.h @@ -13,16 +13,20 @@ // these are needed by the StepValidator namespace details { - template inline T mod (T dividend, T divisor) + template + inline T mod (T dividend, T divisor) { return (dividend % divisor); } - template <> inline float mod(float dividend, float divisor) + template <> + inline float mod(float dividend, float divisor) { return std::fmod(dividend, divisor); } - template <> inline double mod(double dividend, double divisor) + template <> + inline double mod(double dividend, double divisor) { return std::fmod(dividend, divisor); } - template <> inline long double mod(long double dividend, long double divisor) + template <> + inline long double mod(long double dividend, long double divisor) { return std::fmod(dividend, divisor); } } @@ -43,7 +47,7 @@ struct ValidatorBase }; /** determines if a string is a valid value for an OptionsDB option */ -template +template struct Validator : public ValidatorBase { boost::any Validate(const std::string& str) const override @@ -73,7 +77,7 @@ struct Validator> : public ValidatorBase }; /** a Validator that constrains the range of valid values */ -template +template struct RangedValidator : public Validator { RangedValidator(const T& min, const T& max) : m_min(min), m_max(max) {} @@ -96,7 +100,7 @@ struct RangedValidator : public Validator (eg: 0, 25, 50, ...). The steps are assumed to begin at the validated type's default-constructed value, unless another origin is specified. */ -template +template struct StepValidator : public Validator { StepValidator(const T& step, const T& origin = T()) : m_step_size(step), m_origin(origin) {} @@ -116,7 +120,7 @@ struct StepValidator : public Validator }; /** a Validator similar to a StepValidator, but that further constrains the valid values to be within a certain range (eg: [25, 50, ..., 200]). */ -template +template struct RangedStepValidator : public Validator { public: @@ -143,7 +147,7 @@ struct RangedStepValidator : public Validator /// a Validator that specifies a finite number of valid values. /** Probably won't work well with floating point types. */ -template +template struct DiscreteValidator : public Validator { DiscreteValidator(const T& single_value) : @@ -154,7 +158,7 @@ struct DiscreteValidator : public Validator m_values(values) { } - template + template DiscreteValidator(iter start, iter finish) : m_values(start, finish) { } @@ -167,7 +171,7 @@ struct DiscreteValidator : public Validator boost::any Validate(const std::string& str) const override { T val = boost::lexical_cast(str); - if (m_values.find(val) == m_values.end()) + if (!m_values.count(val)) throw boost::bad_lexical_cast(); return boost::any(val); @@ -184,7 +188,7 @@ struct DiscreteValidator : public Validator /** Stores and owns clones of the provided validators in std::unique_ptr. * Always calls m_validator_a->Validate(). Only calls m_validator_b->Validate() * if the first one throws. */ -template +template struct OrValidator : public Validator { OrValidator(const Validator& validator_a, diff --git a/util/OptionsDB.cpp b/util/OptionsDB.cpp index 5999c1ff0b6..dfa0a932459 100644 --- a/util/OptionsDB.cpp +++ b/util/OptionsDB.cpp @@ -8,6 +8,7 @@ #include "util/Directories.h" #include +#include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include @@ -73,7 +75,8 @@ OptionsDB::Option::Option() OptionsDB::Option::Option(char short_name_, const std::string& name_, const boost::any& value_, const boost::any& default_value_, const std::string& description_, - const ValidatorBase* validator_, bool storable_, bool flag_, bool recognized_) : + const ValidatorBase* validator_, bool storable_, bool flag_, bool recognized_, + const std::string& section) : name(name_), short_name(short_name_), value(value_), @@ -87,6 +90,15 @@ OptionsDB::Option::Option(char short_name_, const std::string& name_, const boos { if (short_name_) short_names[short_name_] = name; + + auto name_it = name.rfind('.'); + if (name_it != std::string::npos) + sections.emplace(name.substr(0, name_it)); + + if (!section.empty()) + sections.emplace(section); + else if (sections.empty()) + sections.emplace("misc"); } bool OptionsDB::Option::SetFromString(const std::string& str) { @@ -103,20 +115,14 @@ bool OptionsDB::Option::SetFromString(const std::string& str) { } if (changed) { - value = value_; + value = std::move(value_); (*option_changed_sig_ptr)(); } return changed; } bool OptionsDB::Option::SetToDefault() { - bool changed = false; - if (!flag) { - changed = validator->String(value) != validator->String(default_value); - } else { - changed = (boost::lexical_cast(boost::any_cast(value)) - != boost::lexical_cast(boost::any_cast(default_value))); - } + bool changed = !ValueIsDefault(); if (changed) { value = default_value; (*option_changed_sig_ptr)(); @@ -138,6 +144,21 @@ std::string OptionsDB::Option::DefaultValueToString() const { return boost::lexical_cast(boost::any_cast(default_value)); } +bool OptionsDB::Option::ValueIsDefault() const +{ return ValueToString() == DefaultValueToString(); } + + +///////////////////////////////////////////// +// OptionsDB::OptionSection +///////////////////////////////////////////// +OptionsDB::OptionSection::OptionSection() = default; + +OptionsDB::OptionSection::OptionSection(const std::string& name_, const std::string& description_, + std::function option_predicate_) : + name(name_), + description(description_), + option_predicate(option_predicate_) +{} ///////////////////////////////////////////// // OptionsDB @@ -145,28 +166,59 @@ std::string OptionsDB::Option::DefaultValueToString() const { // static(s) OptionsDB* OptionsDB::s_options_db = nullptr; -OptionsDB::OptionsDB() { +OptionsDB::OptionsDB() : m_dirty(false) { if (s_options_db) throw std::runtime_error("Attempted to create a duplicate instance of singleton class OptionsDB."); s_options_db = this; } -void OptionsDB::Commit() { - if (!m_dirty) - return; +bool OptionsDB::Commit(bool only_if_dirty, bool only_non_default) { + if (only_if_dirty && !m_dirty) + return true; boost::filesystem::ofstream ofs(GetConfigPath()); if (ofs) { XMLDoc doc; - GetOptionsDB().GetXML(doc); + GetOptionsDB().GetXML(doc, only_non_default, true); doc.WriteDoc(ofs); m_dirty = false; + return true; } else { std::cerr << UserString("UNABLE_TO_WRITE_CONFIG_XML") << std::endl; - std::cerr << PathString(GetConfigPath()) << std::endl; + std::cerr << PathToString(GetConfigPath()) << std::endl; ErrorLogger() << UserString("UNABLE_TO_WRITE_CONFIG_XML"); - ErrorLogger() << PathString(GetConfigPath()); + ErrorLogger() << PathToString(GetConfigPath()); + return false; + } +} + +bool OptionsDB::CommitPersistent() { + bool retval = false; + auto config_file = GetPersistentConfigPath(); + XMLDoc doc; + GetOptionsDB().GetXML(doc, true, false); // only output non-default options + try { + // Remove any previously existing file + boost::filesystem::remove(config_file); + + boost::filesystem::ofstream ofs(GetPersistentConfigPath()); + if (ofs) { + doc.WriteDoc(ofs); + retval = true; + } else { + std::string err_msg = UserString("UNABLE_TO_WRITE_PERSISTENT_CONFIG_XML") + " : " + config_file.string(); + ErrorLogger() << err_msg; + std::cerr << err_msg << std::endl; + } + } catch (const boost::filesystem::filesystem_error& ec) { + ErrorLogger() << "Error during file operations when creating persistent config : " << ec.what(); + } catch (...) { + std::string err_msg = "Unknown exception during persistent config creation"; + ErrorLogger() << err_msg; + std::cerr << err_msg << std::endl; } + + return retval; } void OptionsDB::Validate(const std::string& name, const std::string& value) const { @@ -208,64 +260,275 @@ std::shared_ptr OptionsDB::GetValidator(const std::string& return it->second.validator; } -void OptionsDB::GetUsage(std::ostream& os, const std::string& command_line/* = ""*/) const { - os << UserString("COMMAND_LINE_USAGE") << command_line << "\n"; +namespace { + const std::size_t TERMINAL_LINE_WIDTH = 80; + + /** Breaks and indents text over multiple lines when it exceeds width limits + * @param text String to format, tokenized by spaces, tabs, and newlines (newlines retained but potentially indented) + * @param indents amount of space prior to text. First for initial line, second for any new lines. + * @param widths width to limit the text to. First for initial line, second for any new lines. + * @returns string Formatted results of @p text + */ + std::string SplitText(const std::string& text, std::pair indents = { 0, 0 }, + std::pair widths = { TERMINAL_LINE_WIDTH, TERMINAL_LINE_WIDTH }) + { + boost::char_separator separator { " \t", "\n" }; + boost::tokenizer> tokens { text, separator }; + + std::vector lines { "" }; + for (const auto& token : tokens) { + if (token == "\n") + lines.push_back(""); + else if (widths.second < lines.back().size() + token.size() + indents.second) + lines.push_back(token + " "); + else if (!token.empty()) + lines.back().append(token + " "); + } + + std::string indent { std::string(indents.second, ' ') }; + std::stringstream retval; + auto first_line = std::move(lines.front()); + retval << std::string(indents.first, ' ') << first_line << std::endl; + for (auto line : lines) + if (!line.empty()) + retval << indent << line << std::endl; - int longest_param_name = 0; - for (auto& option : m_options) { - if (longest_param_name < static_cast(option.first.size())) - longest_param_name = option.first.size(); + return retval.str(); } - int description_column = 5; - int description_width = 80 - description_column; + bool OptionNameHasParentSection(const std::string& lhs, const std::string& rhs) { + auto it = lhs.find_last_of('.'); + if (it == std::string::npos) + return false; + return lhs.substr(0, it) == rhs; + } +} - if (description_width <= 0) - throw std::runtime_error("The longest parameter name leaves no room for a description."); +std::unordered_map> OptionsDB::OptionsBySection(bool allow_unrecognized) const { + // Determine sections after all predicate calls from known options + std::unordered_map> sections_by_option; + for (const auto& option : m_options) { + if (!allow_unrecognized && !option.second.recognized) + continue; - for (const std::map::value_type& option : m_options) { - // Ignore unrecognized options that have not been formally registered - // with Add(). - if (!option.second.recognized) + for (const auto& section : option.second.sections) + sections_by_option[option.first].emplace(section); + + for (auto& section : m_sections) + if (section.second.option_predicate && section.second.option_predicate(option.first)) + sections_by_option[option.first].emplace(section.first); + } + + // tally the total number of options under each section + std::unordered_map total_options_per_section; + for (const auto& option_section : sections_by_option) { + auto option_name = option_section.first; + auto dot_it = option_name.find_first_of("."); + // increment count of each containing parent section + while (dot_it != std::string::npos) { + total_options_per_section[option_name.substr(0, dot_it)]++; + dot_it++; + dot_it = option_name.find_first_of(".", dot_it); + } + } + + // sort options into common sections + std::unordered_map> options_by_section; + for (const auto& option : sections_by_option) { + for (const auto& section : option.second) { + auto section_name = section; + auto defined_section_it = m_sections.find(section_name); + bool has_descr = defined_section_it != m_sections.end() ? + !defined_section_it->second.description.empty() : + false; + + // move options from sparse sections to more common parent + auto section_count = total_options_per_section[section_name]; + auto section_end_it = section_name.find_last_of("."); + while (!has_descr && section_count < 4 && section_end_it != std::string::npos) { + auto new_section_name = section_name.substr(0, section_end_it); + // prevent moving into dense sections + if (total_options_per_section[new_section_name] > ( 7 - section_count )) + break; + total_options_per_section[section_name]--; + section_name = new_section_name; + section_end_it = section_name.find_last_of("."); + section_count = total_options_per_section[section_name]; + + defined_section_it = m_sections.find(section_name); + if (defined_section_it != m_sections.end()) + has_descr = !defined_section_it->second.description.empty(); + } + + options_by_section[section_name].emplace(option.first); + } + } + + // define which section are top level sections ("root"), move top level candidates with single option to misc + for (const auto& section_it : total_options_per_section) { + auto root_name = section_it.first.substr(0, section_it.first.find_first_of(".")); + // root_name with no dot element allowed to pass if an option is known, potentially moving to misc section + auto total_it = total_options_per_section.find(root_name); + if (total_it == total_options_per_section.end()) continue; - if (option.second.short_name) - os << "-" << option.second.short_name << ", --" << option.second.name << "\n"; - else - os << "--" << option.second.name << "\n"; - - os << std::string(description_column - 1, ' '); - - typedef boost::tokenizer> Tokenizer; - boost::char_separator separator(" \t"); - Tokenizer tokens(UserString(option.second.description), separator); - int curr_column = description_column; - for (const Tokenizer::value_type& token : tokens) { - if (80 < curr_column + token.size()) { - os << "\n" << std::string(description_column, ' ') << token; - curr_column = description_column + token.size(); - } else { - os << " " << token; - curr_column += token.size() + 1; + if (total_it->second > 1) { + options_by_section["root"].emplace(root_name); + } else if (section_it.first != "misc" && + section_it.first != "root" && + !m_sections.count(section_it.first)) + { + // move option to misc section + auto section_option_it = options_by_section.find(section_it.first); + if (section_option_it == options_by_section.end()) + continue; + for (auto&& option : section_option_it->second) + options_by_section["misc"].emplace(std::move(option)); + options_by_section.erase(section_it.first); + } + } + + return options_by_section; +} + +void OptionsDB::GetUsage(std::ostream& os, const std::string& command_line, bool allow_unrecognized) const { + // Prevent logger output from garbling console display for low severity messages + OverrideAllLoggersThresholds(LogLevel::warn); + + auto options_by_section = OptionsBySection(allow_unrecognized); + if (!command_line.empty() || command_line == "all" || command_line == "raw") { + // remove the root section if unneeded + if (options_by_section.count("root")) + options_by_section.erase("root"); + } + + // print description of command_line arg as section + if (command_line == "all") { + os << UserString("OPTIONS_DB_SECTION_ALL") << " "; + } else if (command_line == "raw") { + os << UserString("OPTIONS_DB_SECTION_RAW") << " "; + } else { + auto command_section_it = m_sections.find(command_line); + if (command_section_it != m_sections.end() && !command_section_it->second.description.empty()) + os << UserString(command_section_it->second.description) << " "; + } + + bool print_misc_section = command_line.empty(); + std::set section_list {}; + // print option sections + if (command_line != "all" && command_line != "raw") { + std::size_t name_col_width = 20; + if (command_line.empty()) { + auto root_it = options_by_section.find("root"); + if (root_it != options_by_section.end()) { + for (const auto& section : root_it->second) + if (section.find_first_of(".") == std::string::npos) + if (section_list.emplace(section).second && name_col_width < section.size()) + name_col_width = section.size(); } + } else { + for (const auto& it : options_by_section) + if (OptionNameHasParentSection(it.first, command_line)) + if (section_list.emplace(it.first).second && name_col_width < it.first.size()) + name_col_width = it.first.size(); } + name_col_width += 5; - if (option.second.validator) { - std::stringstream stream; - stream << UserString("COMMAND_LINE_DEFAULT") << option.second.DefaultValueToString(); - if (80 < curr_column + stream.str().size() + 3) { - os << "\n" << std::string(description_column, ' ') << stream.str() << "\n"; - } else { - os << " | " << stream.str() << "\n"; + if (!section_list.empty()) + os << UserString("COMMAND_LINE_SECTIONS") << ":" << std::endl; + + auto indents = std::make_pair(2, name_col_width + 4); + auto widths = std::make_pair(TERMINAL_LINE_WIDTH - name_col_width, TERMINAL_LINE_WIDTH); + for (const auto& section : section_list) { + if (section == "misc") { + print_misc_section = true; + continue; } + auto section_it = m_sections.find(section); + std::string descr = (section_it == m_sections.end()) ? "" : UserString(section_it->second.description); + + os << std::setw(2) << "" // indent + << std::setw(name_col_width) << std::left << section // section name + << SplitText(descr, indents, widths); // section description + } + + if (print_misc_section) { + // Add special miscellaneous section to bottom + os << std::setw(2) << "" << std::setw(name_col_width) << std::left << "misc"; + os << SplitText(UserString("OPTIONS_DB_SECTION_MISC"), indents, widths); + } + + // add empty line between groups and options + if (!section_list.empty() && !print_misc_section) + os << std::endl; + } + + + // print options + if (!command_line.empty()) { + std::set option_list; + if (command_line == "all" || command_line == "raw") { + for (const auto& option_section_it : options_by_section) + for (const auto& option : option_section_it.second) + option_list.emplace(option); } else { - os << "\n"; + auto option_section_it = options_by_section.find(command_line); + if (option_section_it != options_by_section.end()) + option_list = option_section_it->second; + // allow traversal by node when no other results are found + if (option_list.empty() && section_list.empty()) + FindOptions(option_list, command_line, allow_unrecognized); + } + + // insert command_line as option, if it exists + if (command_line != "all" && command_line != "raw" && m_options.count(command_line)) + option_list.emplace(command_line); + + if (!option_list.empty()) + os << UserString("COMMAND_LINE_OPTIONS") << ":" << std::endl; + + for (const auto& option_name : option_list) { + auto option_it = m_options.find(option_name); + if (option_it == m_options.end() || (!allow_unrecognized && !option_it->second.recognized)) + continue; + + if (command_line == "raw") { + os << option_name << ", " << option_it->second.description << "," << std::endl; + if (option_it->second.short_name) + os << option_it->second.short_name << ", " << option_it->second.description << "," << std::endl; + } else { + // option name(s) + if (option_it->second.short_name) + os << "-" << option_it->second.short_name << " | --" << option_name; + else + os << "--" << option_name; + + // option description + if (!option_it->second.description.empty()) + os << std::endl << SplitText(UserString(option_it->second.description), {5, 7}); + else + os << std::endl; + + // option default value + if (option_it->second.validator) { + auto validator_str = UserString("COMMAND_LINE_DEFAULT") + ": " + option_it->second.DefaultValueToString(); + os << SplitText(validator_str, {5, 7}, {TERMINAL_LINE_WIDTH - validator_str.size(), 77}); + } + os << std::endl; + } + } + + if (section_list.empty() && option_list.empty()) { + os << UserString("COMMAND_LINE_NOT_FOUND") << ": " << command_line << std::endl << std::endl; + os << UserString("COMMAND_LINE_USAGE") << std::endl; } - os << "\n"; } + + // reset override in case this function is later repurposed + OverrideAllLoggersThresholds(boost::none); } -void OptionsDB::GetXML(XMLDoc& doc) const { +void OptionsDB::GetXML(XMLDoc& doc, bool non_default_only, bool include_version) const { doc = XMLDoc(); std::vector elem_stack; @@ -274,13 +537,47 @@ void OptionsDB::GetXML(XMLDoc& doc) const { for (const auto& option : m_options) { if (!option.second.storable) continue; + + if (!option.second.recognized) + continue; + std::string::size_type last_dot = option.first.find_last_of('.'); std::string section_name = last_dot == std::string::npos ? "" : option.first.substr(0, last_dot); std::string name = option.first.substr(last_dot == std::string::npos ? 0 : last_dot + 1); + + // "version.gl.check.done" is automatically set to true after other logic is performed + if (option.first == "version.gl.check.done") + continue; + + // Skip unwanted config options + // BUG Some windows may be shown as a child of an other window, but not initially visible. + // The OptionDB default of "*.visible" in these cases may be false, but setting the option to false + // in a config file may prevent such windows from showing when requested. + if (name == "visible") + continue; + + // Storing "version.string" in persistent config would render all config options invalid after a new build + if (!include_version && option.first == "version.string") + continue; + + // do want to store version string if requested, regardless of whether + // it is default. for other strings, if storing non-default only, + // check if option is default and if it is, skip it. + if (non_default_only && option.first != "version.string") { + bool is_default_nonflag = !option.second.flag && IsDefaultValue(m_options.find(option.first)); + if (is_default_nonflag) + continue; + + // Default value of flag options will throw bad_any_cast, fortunately they always default to false + if (option.second.flag && !boost::any_cast(option.second.value)) + continue; + } + + while (1 < elem_stack.size()) { std::string prev_section = PreviousSectionName(elem_stack); if (prev_section == section_name) { - section_name = ""; + section_name.clear(); break; } else if (section_name.find(prev_section + '.') == 0) { section_name = section_name.substr(prev_section.size() + 1); @@ -353,12 +650,17 @@ void OptionsDB::SetFromCommandLine(const std::vector& args) { for (unsigned int i = 1; i < args.size(); ++i) { std::string current_token(args[i]); + if (current_token.find("--") == 0) { std::string option_name = current_token.substr(2); - std::map::iterator it = m_options.find(option_name); + if (option_name.empty()) + throw std::runtime_error("A \'--\' was given with no option name."); + + auto it = m_options.find(option_name); - if (it == m_options.end() || !it->second.recognized) { // unrecognized option: may be registered later on so we'll store it for now + if (it == m_options.end() || !it->second.recognized) { + // unrecognized option: may be registered later on so we'll store it for now // Check for more parameters (if this is the last one, assume that it is a flag). std::string value_str("-"); if (i + 1 < static_cast(args.size())) { @@ -369,32 +671,35 @@ void OptionsDB::SetFromCommandLine(const std::vector& args) { if (value_str.at(0) == '-') { // this is either the last parameter or the next parameter is another option, assume this one is a flag m_options[option_name] = Option(static_cast(0), option_name, true, boost::lexical_cast(false), - "", 0, false, true, false); + "", 0, false, true, false, std::string()); } else { // the next parameter is the value, store it as a string to be parsed later m_options[option_name] = Option(static_cast(0), option_name, value_str, value_str, "", new Validator(), - false, false, false); // don't attempt to store options that have only been specified on the command line + false, false, false, std::string()); // don't attempt to store options that have only been specified on the command line } WarnLogger() << "Option \"" << option_name << "\", was specified on the command line but was not recognized. It may not be registered yet or could be a typo."; + } else { + // recognized option Option& option = it->second; if (option.value.empty()) throw std::runtime_error("The value member of option \"--" + option.name + "\" is undefined."); if (!option.flag) { // non-flag try { - // ensure a parameter exists... - if (i + 1 >= static_cast(args.size())) - throw std::runtime_error("the option \"" + option.name + - "\" was specified, at the end of the list, with no parameter value."); + // check if parameter exists... + if (i + 1 >= static_cast(args.size())) { + m_dirty |= option.SetFromString(""); + continue; + } // get parameter value std::string value_str(args[++i]); StripQuotation(value_str); // ensure parameter is actually a parameter, and not the next option name (which would indicate // that the option was specified without a parameter value, as if it was a flag) - if (value_str.at(0) == '-') + if (!value_str.empty() && value_str.at(0) == '-') throw std::runtime_error("the option \"" + option.name + "\" was followed by the parameter \"" + value_str + "\", which appears to be an option flag, not a parameter value, because it begins with a \"-\" character."); @@ -412,35 +717,39 @@ void OptionsDB::SetFromCommandLine(const std::vector& args) { #ifdef FREEORION_MACOSX && current_token.find("-psn") != 0 // Mac OS X passes a process serial number to all applications using Carbon or Cocoa, it should be ignored here #endif - ) { + ) + { std::string single_char_options = current_token.substr(1); - if (single_char_options.size() == 0) { + if (single_char_options.empty()) throw std::runtime_error("A \'-\' was given with no options."); - } else { - for (unsigned int j = 0; j < single_char_options.size(); ++j) { - std::map::iterator short_name_it = Option::short_names.find(single_char_options[j]); - if (short_name_it == Option::short_names.end()) - throw std::runtime_error(std::string("Unknown option \"-") + single_char_options[j] + "\" was given."); + for (unsigned int j = 0; j < single_char_options.size(); ++j) { + auto short_name_it = Option::short_names.find(single_char_options[j]); - std::map::iterator name_it = m_options.find(short_name_it->second); + if (short_name_it == Option::short_names.end()) + throw std::runtime_error(std::string("Unknown option \"-") + single_char_options[j] + "\" was given."); - if (name_it == m_options.end()) - throw std::runtime_error("Option \"--" + short_name_it->second + "\", abbreviated as \"-" + short_name_it->first + "\", could not be found."); + auto name_it = m_options.find(short_name_it->second); - Option& option = name_it->second; - if (option.value.empty()) - throw std::runtime_error("The value member of option \"--" + option.name + "\" is undefined."); + if (name_it == m_options.end()) + throw std::runtime_error("Option \"--" + short_name_it->second + "\", abbreviated as \"-" + short_name_it->first + "\", could not be found."); - if (!option.flag) { - if (j < single_char_options.size() - 1) - throw std::runtime_error(std::string("Option \"-") + single_char_options[j] + "\" was given with no parameter."); + Option& option = name_it->second; + if (option.value.empty()) + throw std::runtime_error("The value member of option \"--" + option.name + "\" is undefined."); + + if (!option.flag) { + if (j < single_char_options.size() - 1) { + throw std::runtime_error(std::string("Option \"-") + single_char_options[j] + "\" was given with no parameter."); + } else { + if (i + 1 >= static_cast(args.size())) + m_dirty |= option.SetFromString(""); else m_dirty |= option.SetFromString(args[++i]); - } else { - option.value = true; } + } else { + option.value = true; } } } @@ -455,13 +764,12 @@ void OptionsDB::SetFromFile(const boost::filesystem::path& file_path, boost::filesystem::ifstream ifs(file_path); if (ifs) { doc.ReadDoc(ifs); - if (version.empty() || (doc.root_node.ContainsChild("version-string") && - doc.root_node.Child("version-string").Text() == version)) - { - GetOptionsDB().SetFromXML(doc); - } + if (version.empty() || (doc.root_node.ContainsChild("version") && + doc.root_node.Child("version").ContainsChild("string") && + version == doc.root_node.Child("version").Child("string").Text())) + { GetOptionsDB().SetFromXML(doc); } } - } catch (const std::exception&) { + } catch (...) { std::cerr << UserString("UNABLE_TO_READ_CONFIG_XML") << ": " << file_path << std::endl; } @@ -473,47 +781,81 @@ void OptionsDB::SetFromXML(const XMLDoc& doc) { } void OptionsDB::SetFromXMLRecursive(const XMLElement& elem, const std::string& section_name) { - std::string option_name = section_name + (section_name == "" ? "" : ".") + elem.Tag(); + std::string option_name = section_name + (section_name.empty() ? "" : ".") + elem.Tag(); + if (option_name == "version.string") + return; if (!elem.children.empty()) { for (const XMLElement& child : elem.children) SetFromXMLRecursive(child, option_name); + } - } else { - auto it = m_options.find(option_name); + auto it = m_options.find(option_name); - if (it == m_options.end() || !it->second.recognized) { + if (it == m_options.end() || !it->second.recognized) { + if (elem.Text().length() == 0) { + // do not retain empty XML options + return; + } else { // Store unrecognized option to be parsed later if this options is added. - if (elem.Text().length() == 0) { // empty string: may be a flag - m_options[option_name] = Option(static_cast(0), option_name, true, - boost::lexical_cast(false), - "", 0, true, true, false); - } else { // otherwise just store the string to be parsed later - m_options[option_name] = Option(static_cast(0), option_name, - elem.Text(), elem.Text(), "", - new Validator(), - true, false, false); - } + m_options[option_name] = Option(static_cast(0), option_name, + elem.Text(), elem.Text(), + "", new Validator(), + true, false, false, section_name); + } - TraceLogger() << "Option \"" << option_name << "\", was in config.xml but was not recognized. It may not be registered yet or you may need to delete your config.xml if it is out of date."; - m_dirty = true; - return; + TraceLogger() << "Option \"" << option_name << "\", was in config.xml but was not recognized. It may not be registered yet or you may need to delete your config.xml if it is out of date."; + m_dirty = true; + return; + } + + Option& option = it->second; + //if (!option.flag && option.value.empty()) { + // ErrorLogger() << "The value member of option \"" << option.name << "\" in config.xml is undefined."; + // return; + //} + + if (option.flag) { + static auto lexical_true_str = boost::lexical_cast(true); + option.value = static_cast(elem.Text() == lexical_true_str); + } else { + try { + m_dirty |= option.SetFromString(elem.Text()); + } catch (const std::exception& e) { + ErrorLogger() << "OptionsDB::SetFromXMLRecursive() : while processing config.xml the following exception was caught when attempting to set option \"" + << option_name << "\" to \"" << elem.Text() << "\": " << e.what(); } + } +} - Option& option = it->second; - //if (!option.flag && option.value.empty()) { - // ErrorLogger() << "The value member of option \"" << option.name << "\" in config.xml is undefined."; - // return; - //} +void OptionsDB::AddSection(const std::string& name, const std::string& description, + std::function option_predicate) +{ + auto insert_result = m_sections.emplace(name, OptionSection(name, description, option_predicate)); + // if previously existing section, update description/predicate if empty/null + if (!insert_result.second) { + if (!description.empty() && insert_result.first->second.description.empty()) + insert_result.first->second.description = description; + if (option_predicate != nullptr && insert_result.first->second.option_predicate == nullptr) + insert_result.first->second.option_predicate = option_predicate; + } +} - if (option.flag) { - option.value = true; - } else { - try { - m_dirty |= option.SetFromString(elem.Text()); - } catch (const std::exception& e) { - ErrorLogger() << "OptionsDB::SetFromXMLRecursive() : while processing config.xml the following exception was caught when attempting to set option \"" << option_name << "\": " << e.what(); - } +template <> +std::vector OptionsDB::Get>(const std::string& name) const +{ + auto it = m_options.find(name); + if (!OptionExists(it)) + throw std::runtime_error("OptionsDB::Get>() : Attempted to get nonexistent option \"" + name + "\"."); + try { + return boost::any_cast>(it->second.value); + } catch (const boost::bad_any_cast& e) { + ErrorLogger() << "bad any cast converting value option named: " << name << ". Returning default value instead"; + try { + return boost::any_cast>(it->second.default_value); + } catch (const boost::bad_any_cast& e) { + ErrorLogger() << "bad any cast converting default value of std::vector option named: " << name << ". Returning empty vector instead"; + return std::vector(); } } } @@ -540,4 +882,3 @@ std::vector StringToList(const std::string& input_string) { retval.push_back(token); return retval; } - diff --git a/util/OptionsDB.h b/util/OptionsDB.h index 563659b5704..ae62583b700 100644 --- a/util/OptionsDB.h +++ b/util/OptionsDB.h @@ -9,7 +9,9 @@ #include #include +#include #include +#include class OptionsDB; @@ -19,7 +21,9 @@ class XMLElement; ///////////////////////////////////////////// // Free Functions ///////////////////////////////////////////// -typedef void (*OptionsDBFn)(OptionsDB&); ///< the function signature for functions that add Options to the OptionsDB (void (OptionsDB&)) + +//! The function signature for functions that add Options to the OptionsDB (void (OptionsDB&)) +typedef std::function OptionsDBFn; /** adds \a function to a vector of pointers to functions that add Options to * the OptionsDB. This function returns a boolean so that it can be used to @@ -82,8 +86,8 @@ FO_COMMON_API OptionsDB& GetOptionsDB(); * options. All flag command-line options (specified with AddFlag()) are * assumed to have false as their default value. This means that their mere * presence on the command line means that they indicate a value of true; - * they need no argument. For example, specifying "--help" on the command - * line sets the option "help" in the DB to true, and leaving it out sets the + * they need no argument. For example, specifying "--version" on the command + * line sets the option "version" in the DB to true, and leaving it out sets the * option to false. *

Long-form names should be preceded with "--", and the * single-character version should be preceded with "-". An exception to this @@ -111,43 +115,63 @@ class FO_COMMON_API OptionsDB { /** indicates whether an option with name \a name has been added to this OptionsDB. */ - bool OptionExists(const std::string& name) const - { return m_options.find(name) != m_options.end() && m_options.at(name).recognized; } + bool OptionExists(const std::string& name) const + { return m_options.count(name) && m_options.at(name).recognized; } - /** write back the optionDB's state to the XML config file - if it has changed since it was last saved. */ - void Commit(); + /** write the optionDB's non-default state to the XML config file. */ + bool Commit(bool only_if_dirty = true, bool only_non_default = true); + + /** Write any options that are not at default value to persistent config, replacing any existing file + * + * @returns bool If file was successfully written + */ + bool CommitPersistent(); /** validates a value for an option. throws std::runtime_error if no option * \a name exists. throws bad_lexical_cast if \a value cannot be * converted to the type of the option \a name. */ - void Validate(const std::string& name, const std::string& value) const; + void Validate(const std::string& name, const std::string& value) const; /** returns the value of option \a name. Note that the exact type of item * stored in the option \a name must be known in advance. This means that * Get() must be called as Get("foo"), etc. */ - template - T Get(const std::string& name) const + template + T Get(const std::string& name) const { auto it = m_options.find(name); if (!OptionExists(it)) throw std::runtime_error("OptionsDB::Get<>() : Attempted to get nonexistent option \"" + name + "\"."); - return boost::any_cast(it->second.value); + try { + return boost::any_cast(it->second.value); + } catch (const boost::bad_any_cast&) { + ErrorLogger() << "bad any cast converting value option named: " << name << ". Returning default value instead"; + try { + return boost::any_cast(it->second.default_value); + } catch (const boost::bad_any_cast&) { + ErrorLogger() << "bad any cast converting default value of option named: " << name << ". Returning data-type default value instead: " << T(); + return T(); + } + } } /** returns the default value of option \a name. Note that the exact type * of item stored in the option \a name must be known in advance. This * means that GetDefault() must be called as Get("foo"), etc. */ - template - T GetDefault(const std::string& name) const + template + T GetDefault(const std::string& name) const { auto it = m_options.find(name); if (!OptionExists(it)) throw std::runtime_error("OptionsDB::GetDefault<>() : Attempted to get nonexistent option \"" + name + "\"."); - return boost::any_cast(it->second.default_value); + try { + return boost::any_cast(it->second.default_value); + } catch (const boost::bad_any_cast&) { + ErrorLogger() << "bad any cast converting default value of option named: " << name << " returning type default value instead"; + return T(); + } } - bool IsDefaultValue(const std::string& name) const { + bool IsDefaultValue(const std::string& name) const { auto it = m_options.find(name); if (!OptionExists(it)) throw std::runtime_error("OptionsDB::IsDefaultValue<>() : Attempted to get nonexistent option \"" + name + "\"."); @@ -170,18 +194,20 @@ class FO_COMMON_API OptionsDB { std::shared_ptr GetValidator(const std::string& option_name) const; /** writes a usage message to \a os */ - void GetUsage(std::ostream& os, const std::string& command_line = "") const; + void GetUsage(std::ostream& os, const std::string& command_line = "", bool allow_unrecognized = false) const; /** @brief Saves the contents of the options DB to the @p doc XMLDoc. * * @param[in,out] doc The document this OptionsDB should be written to. * This resets the given @p doc. + * @param[in] non_default_only Do not include options which are set to their + * default value, is unrecognized, or is "version.string" */ - void GetXML(XMLDoc& doc) const; + void GetXML(XMLDoc& doc, bool non_default_only = false, bool include_version = true) const; /** find all registered Options that begin with \a prefix and store them in * \a ret. If \p allow_unrecognized then include unrecognized options. */ - void FindOptions(std::set& ret, const std::string& prefix, bool allow_unrecognized = false) const; + void FindOptions(std::set& ret, const std::string& prefix, bool allow_unrecognized = false) const; /** the option changed signal object for the given option */ OptionChangedSignalType& OptionChangedSignal(const std::string& option); @@ -190,9 +216,10 @@ class FO_COMMON_API OptionsDB { mutable OptionRemovedSignalType OptionRemovedSignal; ///< the change removed signal object for this DB /** adds an Option, optionally with a custom validator */ - template - void Add(const std::string& name, const std::string& description, T default_value, - const ValidatorBase& validator = Validator(), bool storable = true) + template + void Add(const std::string& name, const std::string& description, T default_value, + const ValidatorBase& validator = Validator(), bool storable = true, + const std::string& section = std::string()) { auto it = m_options.find(name); boost::any value = default_value; @@ -212,16 +239,17 @@ class FO_COMMON_API OptionsDB { } } m_options[name] = Option(static_cast(0), name, value, default_value, - description, validator.Clone(), storable, false, true); + description, validator.Clone(), storable, false, true, section); m_dirty = true; OptionAddedSignal(name); } /** adds an Option with an alternative one-character shortened name, * optionally with a custom validator */ - template - void Add(char short_name, const std::string& name, const std::string& description, T default_value, - const ValidatorBase& validator = Validator(), bool storable = true) + template + void Add(char short_name, const std::string& name, const std::string& description, T default_value, + const ValidatorBase& validator = Validator(), bool storable = true, + const std::string& section = std::string()) { auto it = m_options.find(name); boost::any value = default_value; @@ -236,12 +264,12 @@ class FO_COMMON_API OptionsDB { // This option was previously specified externally but was not recognized at the time, attempt to parse the value found there value = validator.Validate(it->second.ValueToString()); } catch (boost::bad_lexical_cast&) { - ErrorLogger() << "OptionsDB::Add<>() : Option " << name << " was given the value \"" << it->second.ValueToString() << "\" from the command line or a config file but that value couldn't be converted to the correct type, using default value instead."; + ErrorLogger() << "OptionsDB::Add<>() : Option " << name << " was given the value from the command line or a config file that cannot be converted to the correct type. Using default value instead."; } } } m_options[name] = Option(short_name, name, value, default_value, description, - validator.Clone(), storable, false, true); + validator.Clone(), storable, false, true, section); m_dirty = true; OptionAddedSignal(name); } @@ -249,8 +277,8 @@ class FO_COMMON_API OptionsDB { /** adds a flag Option, which is treated as a boolean value with a default * of false. Using the flag on the command line at all indicates that its * value it set to true. */ - void AddFlag(const std::string& name, const std::string& description, - bool storable = true) + void AddFlag(const std::string& name, const std::string& description, + bool storable = true, const std::string& section = std::string()) { auto it = m_options.find(name); bool value = false; @@ -264,7 +292,7 @@ class FO_COMMON_API OptionsDB { } m_options[name] = Option(static_cast(0), name, value, boost::lexical_cast(false), - description, nullptr, storable, true, true); + description, nullptr, storable, true, true, section); m_dirty = true; OptionAddedSignal(name); } @@ -272,8 +300,9 @@ class FO_COMMON_API OptionsDB { /** adds an Option with an alternative one-character shortened name, which * is treated as a boolean value with a default of false. Using the flag * on the command line at all indicates that its value it set to true. */ - void AddFlag(char short_name, const std::string& name, - const std::string& description, bool storable = true) + void AddFlag(char short_name, const std::string& name, + const std::string& description, bool storable = true, + const std::string& section = std::string()) { auto it = m_options.find(name); bool value = false; @@ -287,21 +316,21 @@ class FO_COMMON_API OptionsDB { } m_options[name] = Option(short_name, name, value, boost::lexical_cast(false), - description, nullptr, storable, true, true); + description, nullptr, storable, true, true, section); m_dirty = true; OptionAddedSignal(name); } /** removes an Option */ - void Remove(const std::string& name); + void Remove(const std::string& name); /** removes all unrecognized Options that begin with \a prefix. A blank * string will remove all unrecognized Options. */ - void RemoveUnrecognized(const std::string& prefix = ""); + void RemoveUnrecognized(const std::string& prefix = ""); /** sets the value of option \a name to \a value */ - template - void Set(const std::string& name, const T& value) + template + void Set(const std::string& name, const T& value) { auto it = m_options.find(name); if (!OptionExists(it)) @@ -309,25 +338,37 @@ class FO_COMMON_API OptionsDB { m_dirty |= it->second.SetFromValue(value); } + /** Set the default value of option @p name to @p value */ + template + void SetDefault(const std::string& name, const T& value) { + std::map::iterator it = m_options.find(name); + if (!OptionExists(it)) + throw std::runtime_error("Attempted to set default value of nonexistent option \"" + name + "\"."); + if (it->second.default_value.type() != typeid(T)) + throw boost::bad_any_cast(); + it->second.default_value = value; + } + /** if an xml file exists at \a file_path and has the same version tag as \a version, fill the * DB options contained in that file (read the file using XMLDoc, then fill the DB using SetFromXML) * if the \a version string is empty, bypass that check */ - void SetFromFile(const boost::filesystem::path& file_path, - const std::string& version = ""); + void SetFromFile(const boost::filesystem::path& file_path, + const std::string& version = ""); /** fills some or all of the options of the DB from values passed in from * the command line */ - void SetFromCommandLine(const std::vector& args); + void SetFromCommandLine(const std::vector& args); /** fills some or all of the options of the DB from values stored in * XMLDoc \a doc */ - void SetFromXML(const XMLDoc& doc); + void SetFromXML(const XMLDoc& doc); struct FO_COMMON_API Option { Option(); Option(char short_name_, const std::string& name_, const boost::any& value_, const boost::any& default_value_, const std::string& description_, - const ValidatorBase *validator_, bool storable_, bool flag_, bool recognized_); + const ValidatorBase *validator_, bool storable_, bool flag_, bool recognized_, + const std::string& section = std::string()); // SetFromValue returns true if this->value is successfully changed template @@ -338,12 +379,14 @@ class FO_COMMON_API OptionsDB { bool SetToDefault(); std::string ValueToString() const; std::string DefaultValueToString() const; + bool ValueIsDefault() const; std::string name; ///< the name of the option char short_name; ///< the one character abbreviation of the option boost::any value; ///< the value of the option boost::any default_value; ///< the default value of the option std::string description; ///< a desription of the option + std::unordered_set sections; ///< sections this option should display under /** A validator for the option. Flags have no validators; lexical_cast boolean conversions are done for them. */ @@ -358,23 +401,46 @@ class FO_COMMON_API OptionsDB { static std::map short_names; ///< the master list of abbreviated option names, and their corresponding long-form names }; + struct FO_COMMON_API OptionSection { + OptionSection(); + OptionSection(const std::string& name_, const std::string& description_, + std::function option_predicate_); + + std::string name; + std::string description; + std::function option_predicate = nullptr; + }; + + /** Defines an option section with a description and optionally a option predicate. + * @param name Name of section, typically in the form of a left side subset of an option name. + * @param description Stringtable key used for local description + * @param option_predicate Functor accepting a option name in the form of a std::string const ref and + * returning a bool. Options which return true are displayed in the section for @p name */ + void AddSection(const std::string& name, const std::string& description, + std::function option_predicate = nullptr); + private: /** indicates whether the option referenced by \a it has been added to this OptionsDB. Overloaded for convenient use within other OptionsDB functions */ - bool OptionExists(std::map::const_iterator it) const + bool OptionExists(std::map::const_iterator it) const { return it != m_options.end() && it->second.recognized; } /** indicates whether the current value of the option references by \a is the default value for that option */ - bool IsDefaultValue(std::map::const_iterator it) const + bool IsDefaultValue(std::map::const_iterator it) const { return it != m_options.end() && it->second.ValueToString() == it->second.DefaultValueToString(); } OptionsDB(); - void SetFromXMLRecursive(const XMLElement& elem, const std::string& section_name); + void SetFromXMLRecursive(const XMLElement& elem, const std::string& section_name); + + /** Determine known option sections and which options each contains + * A special "root" section is added for determined top-level sections */ + std::unordered_map> OptionsBySection(bool allow_unrecognized = false) const; std::map m_options; + std::unordered_map m_sections; static OptionsDB* s_options_db; bool m_dirty; //< has OptionsDB changed since last Commit() @@ -384,15 +450,20 @@ class FO_COMMON_API OptionsDB { template bool OptionsDB::Option::SetFromValue(const T& value_) { if (value.type() != typeid(T)) - throw boost::bad_any_cast(); + ErrorLogger() << "OptionsDB::Option::SetFromValue expected type " << value.type().name() << " but got value of type " << typeid(T).name(); bool changed = false; - if (!flag) { - changed = validator->String(value) != validator->String(value_); - } else { - changed = (boost::lexical_cast(boost::any_cast(value)) - != boost::lexical_cast(boost::any_cast(value_))); + try { + if (!flag) { + changed = validator->String(value) != validator->String(value_); + } else { + changed = (boost::lexical_cast(boost::any_cast(value)) + != boost::lexical_cast(boost::any_cast(value_))); + } + } catch (...) { + ErrorLogger() << "Exception thrown when setting option value, probably due to the previous value being invalid?"; + changed = true; } if (changed) { @@ -402,6 +473,9 @@ bool OptionsDB::Option::SetFromValue(const T& value_) { return changed; } +// needed because std::vector is not streamable +template <> +FO_COMMON_API std::vector OptionsDB::Get>(const std::string& name) const; #endif // _OptionsDB_h_ diff --git a/util/Order.cpp b/util/Order.cpp index 4b64ebee0cc..20e718c27cd 100644 --- a/util/Order.cpp +++ b/util/Order.cpp @@ -2,6 +2,7 @@ #include "Logger.h" #include "OrderSet.h" +#include "AppInterface.h" #include "../universe/Fleet.h" #include "../universe/Predicates.h" #include "../universe/Species.h" @@ -18,6 +19,8 @@ #include "../Empire/Empire.h" #include +#include +#include #include #include @@ -57,203 +60,189 @@ bool Order::UndoImpl() const //////////////////////////////////////////////// // RenameOrder //////////////////////////////////////////////// -RenameOrder::RenameOrder() : - m_object(INVALID_OBJECT_ID) -{} - RenameOrder::RenameOrder(int empire, int object, const std::string& name) : Order(empire), m_object(object), m_name(name) { - std::shared_ptr obj = GetUniverseObject(object); - if (!obj) { - ErrorLogger() << "RenameOrder::RenameOrder() : Attempted to rename nonexistant object with id " << object; - return; - } - - if (m_name.empty()) { - ErrorLogger() << "RenameOrder::RenameOrder() : Attempted to name an object \"\"."; - // make order do nothing + if (!Check(empire, object, name)) { m_object = INVALID_OBJECT_ID; return; } } -void RenameOrder::ExecuteImpl() const { - GetValidatedEmpire(); +bool RenameOrder::Check(int empire, int object, const std::string& new_name) { + // disallow the name "", since that denotes an unknown object + if (new_name.empty()) { + ErrorLogger() << "RenameOrder::Check() : passed an empty new_name."; + return false; + } - std::shared_ptr obj = GetUniverseObject(m_object); + auto obj = Objects().get(object); if (!obj) { - ErrorLogger() << "Attempted to rename nonexistant object with id " << m_object; - return; + ErrorLogger() << "RenameOrder::Check() : passed an invalid object."; + return false; } // verify that empire specified in order owns specified object - if (!obj->OwnedBy(EmpireID())) { - ErrorLogger() << "Empire (" << EmpireID() - << ") specified in rename order does not own specified object which is owned by " - << obj->Owner() << "."; - return; + if (!obj->OwnedBy(empire)) { + ErrorLogger() << "RenameOrder::Check() : Object " << object << " is" + << " not owned by empire " << empire << "."; + return false; } - // disallow the name "", since that denotes an unknown object - if (m_name == "") { - ErrorLogger() << "Name \"\" specified in rename order is invalid."; - return; + if (obj->Name() == new_name) { + ErrorLogger() << "RenameOrder::Check() : Object " << object + << " should renamed to the same name."; + return false; } + return true; +} + +void RenameOrder::ExecuteImpl() const { + if (!Check(EmpireID(), m_object, m_name)) + return; + + GetValidatedEmpire(); + + auto obj = Objects().get(m_object); + obj->Rename(m_name); } //////////////////////////////////////////////// // CreateFleetOrder //////////////////////////////////////////////// -NewFleetOrder::NewFleetOrder() : - m_system_id(INVALID_OBJECT_ID) -{} - NewFleetOrder::NewFleetOrder(int empire, const std::string& fleet_name, - int system_id, const std::vector& ship_ids, + const std::vector& ship_ids, bool aggressive) : - NewFleetOrder(empire, std::vector(1, fleet_name), - system_id, std::vector>(1, ship_ids), - std::vector(1, aggressive) ) -{} - -NewFleetOrder::NewFleetOrder(int empire, const std::vector& fleet_names, - int system_id, - const std::vector>& ship_id_groups, - const std::vector& aggressives) : Order(empire), - m_fleet_names(fleet_names), - m_system_id(system_id), - m_fleet_ids(std::vector(m_fleet_names.size(), INVALID_OBJECT_ID)), - m_ship_id_groups(ship_id_groups), - m_aggressives(aggressives) -{} + m_fleet_name(fleet_name), + m_fleet_id(INVALID_OBJECT_ID), + m_ship_ids(ship_ids), + m_aggressive(aggressive) +{ + if (!Check(empire, fleet_name, ship_ids, aggressive)) + return; +} -void NewFleetOrder::ExecuteImpl() const { - GetValidatedEmpire(); +bool NewFleetOrder::Check(int empire, const std::string& fleet_name, const std::vector& ship_ids, bool aggressive) { + if (ship_ids.empty()) { + ErrorLogger() << "Empire attempted to create a new fleet without ships"; + return false; + } + + int system_id = INVALID_OBJECT_ID; + + for (const auto& ship : Objects().find(ship_ids)) { + // verify that empire is not trying to take ships from somebody else's fleet + if (!ship) { + ErrorLogger() << "Empire attempted to create a new fleet with an invalid ship"; + return false; + } + if (!ship->OwnedBy(empire)) { + ErrorLogger() << "Empire attempted to create a new fleet with ships from another's fleet."; + return false; + } + if (ship->SystemID() == INVALID_OBJECT_ID) { + ErrorLogger() << "Empire to create a new fleet with traveling ships."; + return false; + } + + if (system_id == INVALID_OBJECT_ID) + system_id = ship->SystemID(); + + if (ship->SystemID() != system_id) { + ErrorLogger() << "Empire attempted to make a new fleet from ship in the wrong system"; + return false; + } + } - if (m_system_id == INVALID_OBJECT_ID) { + if (system_id == INVALID_OBJECT_ID) { ErrorLogger() << "Empire attempted to create a new fleet outside a system"; - return; + return false; } - std::shared_ptr system = GetSystem(m_system_id); + auto system = Objects().get(system_id); if (!system) { ErrorLogger() << "Empire attempted to create a new fleet in a nonexistant system"; - return; + return false; } - if (m_fleet_names.empty()) - return; - if (m_fleet_names.size() != m_fleet_ids.size() - || m_fleet_names.size() != m_ship_id_groups.size() - || m_fleet_names.size() != m_aggressives.size()) - { - ErrorLogger() << "NewFleetOrder has inconsistent data container sizes..."; + return true; +} + +void NewFleetOrder::ExecuteImpl() const { + GetValidatedEmpire(); + + if (!Check(EmpireID(), m_fleet_name, m_ship_ids, m_aggressive)) return; - } GetUniverse().InhibitUniverseObjectSignals(true); - std::vector> created_fleets; - created_fleets.reserve(m_ship_id_groups.size()); - std::unordered_set> modified_fleets; + // validate specified ships + auto validated_ships = Objects().find(m_ship_ids); - // create fleet for each group of ships - for (int i = 0; i < static_cast(m_ship_id_groups.size()); ++i) { - const std::string& fleet_name = m_fleet_names[i]; - int fleet_id = m_fleet_ids[i]; - const std::vector& ship_ids = m_ship_id_groups[i]; - bool aggressive = m_aggressives[i]; - - if (ship_ids.empty()) - continue; // nothing to do... - - // validate specified ships - std::vector> validated_ships; - std::vector validated_ships_ids; - for (int ship_id : ship_ids) { - // verify that empire is not trying to take ships from somebody else's fleet - std::shared_ptr ship = GetShip(ship_id); - if (!ship) { - ErrorLogger() << "Empire attempted to create a new fleet with an invalid ship"; - continue; - } - if (!ship->OwnedBy(EmpireID())) { - ErrorLogger() << "Empire attempted to create a new fleet with ships from another's fleet."; - continue; - } - if (ship->SystemID() != m_system_id) { - ErrorLogger() << "Empire attempted to make a new fleet from ship in the wrong system"; - continue; - } - validated_ships.push_back(ship); - validated_ships_ids.push_back(ship->ID()); - } - if (validated_ships.empty()) - continue; - - std::shared_ptr fleet; - if (fleet_id == INVALID_OBJECT_ID) { - // create fleet - fleet = GetUniverse().InsertNew(fleet_name, system->X(), system->Y(), EmpireID()); - m_fleet_ids[i] = fleet->ID(); - } else { - fleet = GetUniverse().InsertByEmpireWithID( - EmpireID(), fleet_id, fleet_name, system->X(), system->Y(), EmpireID()); - } - - if (!fleet) { - ErrorLogger() << "Unable to create fleet."; - return; - } + int system_id = validated_ships[0]->SystemID(); + auto system = Objects().get(system_id); - fleet->GetMeter(METER_STEALTH)->SetCurrent(Meter::LARGE_VALUE); - fleet->SetAggressive(aggressive); + std::shared_ptr fleet; + if (m_fleet_id == INVALID_OBJECT_ID) { + // create fleet + fleet = GetUniverse().InsertNew(m_fleet_name, system->X(), system->Y(), EmpireID()); + m_fleet_id = fleet->ID(); + } else { + fleet = GetUniverse().InsertByEmpireWithID( + EmpireID(), m_fleet_id, m_fleet_name, system->X(), system->Y(), EmpireID()); + } - // an ID is provided to ensure consistancy between server and client universes - GetUniverse().SetEmpireObjectVisibility(EmpireID(), fleet->ID(), VIS_FULL_VISIBILITY); + if (!fleet) { + ErrorLogger() << "Unable to create fleet."; + return; + } - system->Insert(fleet); + fleet->GetMeter(METER_STEALTH)->SetCurrent(Meter::LARGE_VALUE); + fleet->SetAggressive(m_aggressive); - // new fleet will get same m_arrival_starlane as fleet of the first ship in the list. - std::shared_ptr firstShip = validated_ships[0]; - std::shared_ptr firstFleet = GetFleet(firstShip->FleetID()); - if (firstFleet) - fleet->SetArrivalStarlane(firstFleet->ArrivalStarlane()); + // an ID is provided to ensure consistancy between server and client universes + GetUniverse().SetEmpireObjectVisibility(EmpireID(), fleet->ID(), VIS_FULL_VISIBILITY); - // remove ships from old fleet(s) and add to new - for (std::shared_ptr ship : validated_ships) { - if (std::shared_ptr old_fleet = GetFleet(ship->FleetID())) { - modified_fleets.insert(old_fleet); - old_fleet->RemoveShip(ship->ID()); - } - ship->SetFleetID(fleet->ID()); - } - fleet->AddShips(validated_ships_ids); + system->Insert(fleet); - if (fleet_name.empty()) - fleet->Rename(fleet->GenerateFleetName()); + // new fleet will get same m_arrival_starlane as fleet of the first ship in the list. + auto first_ship = validated_ships[0]; + auto first_fleet = Objects().get(first_ship->FleetID()); + if (first_fleet) + fleet->SetArrivalStarlane(first_fleet->ArrivalStarlane()); - created_fleets.push_back(fleet); + std::unordered_set> modified_fleets; + // remove ships from old fleet(s) and add to new + for (auto& ship : validated_ships) { + if (auto old_fleet = Objects().get(ship->FleetID())) { + modified_fleets.insert(old_fleet); + old_fleet->RemoveShips({ship->ID()}); + } + ship->SetFleetID(fleet->ID()); } + fleet->AddShips(m_ship_ids); + + if (m_fleet_name.empty()) + fleet->Rename(fleet->GenerateFleetName()); GetUniverse().InhibitUniverseObjectSignals(false); + std::vector> created_fleets{fleet}; system->FleetsInsertedSignal(created_fleets); system->StateChangedSignal(); // Signal changed state of modified fleets and remove any empty fleets. - for (std::shared_ptr modified_fleet : modified_fleets) { + for (auto& modified_fleet : modified_fleets) { if (!modified_fleet->Empty()) modified_fleet->StateChangedSignal(); else { - if (std::shared_ptr system = GetSystem(modified_fleet->SystemID())) - system->Remove(modified_fleet->ID()); + if (auto modified_fleet_system = Objects().get(modified_fleet->SystemID())) + modified_fleet_system->Remove(modified_fleet->ID()); GetUniverse().Destroy(modified_fleet->ID()); } @@ -264,207 +253,218 @@ void NewFleetOrder::ExecuteImpl() const { //////////////////////////////////////////////// // FleetMoveOrder //////////////////////////////////////////////// -FleetMoveOrder::FleetMoveOrder() : - Order(), - m_fleet(INVALID_OBJECT_ID), - m_start_system(INVALID_OBJECT_ID), - m_dest_system(INVALID_OBJECT_ID), - m_append(false) -{} - -FleetMoveOrder::FleetMoveOrder(int empire, int fleet_id, int start_system_id, int dest_system_id, bool append) : - Order(empire), +FleetMoveOrder::FleetMoveOrder(int empire_id, int fleet_id, int dest_system_id, + bool append) : + Order(empire_id), m_fleet(fleet_id), - m_start_system(start_system_id), m_dest_system(dest_system_id), m_append(append) { - // perform sanity checks - std::shared_ptr fleet = GetFleet(FleetID()); - if (!fleet) { - ErrorLogger() << "Empire with id " << EmpireID() << " ordered fleet with id " << FleetID() << " to move, but no such fleet exists"; + if (!Check(empire_id, fleet_id, dest_system_id)) return; - } - std::shared_ptr destination_system = GetSystem(DestinationSystemID()); - if (!destination_system) { - ErrorLogger() << "Empire with id " << EmpireID() << " ordered fleet to move to system with id " << DestinationSystemID() << " but no such system exists / is known to exist"; - return; - } + auto fleet = Objects().get(FleetID()); - // verify that empire specified in order owns specified fleet - if (!fleet->OwnedBy(EmpireID()) ) { - ErrorLogger() << "Empire with id " << EmpireID() << " order to move but does not own fleet with id " << FleetID(); + int start_system = fleet->SystemID(); + if (start_system == INVALID_OBJECT_ID) + start_system = fleet->NextSystemID(); + if (append && !fleet->TravelRoute().empty()) + start_system = fleet->TravelRoute().back(); + + auto short_path = GetPathfinder()->ShortestPath(start_system, m_dest_system, EmpireID()); + if (short_path.first.empty()) { + ErrorLogger() << "FleetMoveOrder generated empty shortest path between system " << start_system + << " and " << m_dest_system << " for empire " << EmpireID() << " with fleet " << fleet_id; return; } - std::pair, double> short_path = GetPathfinder()->ShortestPath(m_start_system, m_dest_system, empire); + // if in a system now, don't include it in the route + if (short_path.first.front() == fleet->SystemID()) { + DebugLogger() << "FleetMoveOrder removing fleet " << fleet_id + << " current system location " << fleet->SystemID() + << " from shortest path to system " << m_dest_system; + short_path.first.pop_front(); + } - m_route.clear(); std::copy(short_path.first.begin(), short_path.first.end(), std::back_inserter(m_route)); // ensure a zero-length (invalid) route is not requested / sent to a fleet if (m_route.empty()) - m_route.push_back(m_start_system); + m_route.push_back(start_system); } -void FleetMoveOrder::ExecuteImpl() const { - GetValidatedEmpire(); - - std::shared_ptr fleet = GetFleet(FleetID()); +bool FleetMoveOrder::Check(int empire_id, int fleet_id, int dest_system_id, bool append) { + auto fleet = Objects().get(fleet_id); if (!fleet) { - ErrorLogger() << "Empire with id " << EmpireID() << " ordered fleet with id " << FleetID() << " to move, but no such fleet exists"; - return; - } - - std::shared_ptr destination_system = GetEmpireKnownSystem(DestinationSystemID(), EmpireID()); - if (!destination_system) { - ErrorLogger() << "Empire with id " << EmpireID() << " ordered fleet to move to system with id " << DestinationSystemID() << " but no such system is known to that empire"; - return; + ErrorLogger() << "Empire with id " << empire_id << " ordered fleet with id " << fleet_id << " to move, but no such fleet exists"; + return false; } - // reject empty routes - if (m_route.empty()) { - ErrorLogger() << "Empire with id " << EmpireID() << " ordered fleet to move on empty route"; - return; + if (!fleet->OwnedBy(empire_id) ) { + ErrorLogger() << "Empire with id " << empire_id << " order to move but does not own fleet with id " << fleet_id; + return false; } - // verify that empire specified in order owns specified fleet - if (!fleet->OwnedBy(EmpireID()) ) { - ErrorLogger() << "Empire with id " << EmpireID() << " order to move but does not own fleet with id " << FleetID(); - return; + auto dest_system = EmpireKnownObjects(empire_id).get(dest_system_id); + if (!dest_system) { + ErrorLogger() << "Empire with id " << empire_id << " ordered fleet to move to system with id " << dest_system_id << " but no such system is known to that empire"; + return false; } + return true; +} - // verify fleet route first system - int fleet_sys_id = fleet->SystemID(); - if (!m_append || fleet->TravelRoute().empty()) { - if (fleet_sys_id != INVALID_OBJECT_ID) { - // fleet is in a system. Its move path should also start from that system. - if (fleet_sys_id != m_start_system) { - ErrorLogger() << "Empire with id " << EmpireID() - << " ordered a fleet to move from a system with id " << m_start_system - << " that it is not at. Fleet is located at system with id " << fleet_sys_id; - return; - } - } else { - // fleet is not in a system. Its move path should start from the next system it is moving to. - int next_system = fleet->NextSystemID(); - if (next_system != m_start_system) { - ErrorLogger() << "Empire with id " << EmpireID() - << " ordered a fleet to move starting from a system with id " << m_start_system - << ", but the fleet's next destination is system with id " << next_system; - return; - } - } - } else { - // We should append and there is something to append to - int last_system = fleet->TravelRoute().back(); - if (last_system != m_start_system) { - ErrorLogger() << "Empire with id " << EmpireID() - << " ordered a fleet to continue from system with id " << m_start_system - << ", but the fleet's current route won't lead there, it leads to system " << last_system; - return; - } - } +void FleetMoveOrder::ExecuteImpl() const { + GetValidatedEmpire(); + if (!Check(EmpireID(), m_fleet, m_dest_system)) + return; // convert list of ids to list of System std::list route_list; - if(m_append && !fleet->TravelRoute().empty()){ - route_list = fleet->TravelRoute(); - route_list.erase(--route_list.end());// Remove the last one since it is the first one of the other + auto fleet = Objects().get(FleetID()); + + if (m_append && !fleet->TravelRoute().empty()) { + route_list = fleet->TravelRoute(); // copy existing route + + DebugLogger() << "FleetMoveOrder::ExecuteImpl appending initial" << [&]() { + std::stringstream ss; + for (int waypoint : route_list) + ss << " " << waypoint; + return ss.str(); + }() << " with" << [&]() { + std::stringstream ss; + for (int waypoint : m_route) + ss << " " << waypoint; + return ss.str(); + }(); + + route_list.erase(--route_list.end()); // remove last item as it should be the first in the appended route } std::copy(m_route.begin(), m_route.end(), std::back_inserter(route_list)); + DebugLogger() << [fleet, route_list]() { + std::stringstream ss; + ss << "FleetMoveOrder::ExecuteImpl Setting route of fleet " << fleet->ID() << " at system " << fleet->SystemID() << " to: "; + if (route_list.empty()) + return std::string("[empty route]"); + for (int waypoint : route_list) + ss << " " << std::to_string(waypoint); + return ss.str(); + }(); - - // validate route. Only allow travel between systems connected in series by starlanes known to this fleet's owner. + if (route_list.front() == fleet->SystemID()) { + DebugLogger() << "FleetMoveOrder::ExecuteImpl given route that starts with fleet " << fleet->ID() << "'s current system (" << route_list.front() << "); removing it"; + route_list.pop_front(); + } // check destination validity: disallow movement that's out of range - std::pair eta = fleet->ETA(fleet->MovePath(route_list)); + auto eta = fleet->ETA(fleet->MovePath(route_list)); if (eta.first == Fleet::ETA_NEVER || eta.first == Fleet::ETA_OUT_OF_RANGE) { DebugLogger() << "FleetMoveOrder::ExecuteImpl rejected out of range move order"; return; } - std::string waypoints; - for (int waypoint : route_list) { - waypoints += std::string(" ") + std::to_string(waypoint); + try { + fleet->SetRoute(route_list); + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception setting fleet route while executing fleet move order: " << e.what(); } - DebugLogger() << "FleetMoveOrder::ExecuteImpl Setting route of fleet " << fleet->ID() << " to " << waypoints; - - fleet->SetRoute(route_list); } //////////////////////////////////////////////// // FleetTransferOrder //////////////////////////////////////////////// -FleetTransferOrder::FleetTransferOrder() : - m_dest_fleet(INVALID_OBJECT_ID) -{} - -FleetTransferOrder::FleetTransferOrder(int empire, int dest_fleet, const std::vector& ships) : +FleetTransferOrder::FleetTransferOrder(int empire, int dest_fleet, + const std::vector& ships) : Order(empire), m_dest_fleet(dest_fleet), m_add_ships(ships) -{} - -void FleetTransferOrder::ExecuteImpl() const { - GetValidatedEmpire(); +{ + if (!Check(empire, dest_fleet, ships)) + return; +} - // look up the destination fleet - std::shared_ptr target_fleet = GetFleet(DestinationFleet()); - if (!target_fleet) { +bool FleetTransferOrder::Check(int empire_id, int dest_fleet_id, const std::vector& ship_ids) { + auto fleet = Objects().get(dest_fleet_id); + if (!fleet) { ErrorLogger() << "Empire attempted to move ships to a nonexistant fleet"; - return; + return false; } // check that destination fleet is owned by empire - if (!target_fleet->OwnedBy(EmpireID())) { - ErrorLogger() << "Empire attempted to move ships to a fleet it does not own"; - return; + if (!fleet->OwnedBy(empire_id)) { + ErrorLogger() << "IssueFleetTransferOrder : passed fleet_id "<< dest_fleet_id << " of fleet not owned by player"; + return false; } - // verify that fleet is in a system - if (target_fleet->SystemID() == INVALID_OBJECT_ID) { - ErrorLogger() << "Empire attempted to transfer ships to/from fleet(s) not in a system"; - return; + + if (fleet->SystemID() == INVALID_OBJECT_ID) { + ErrorLogger() << "IssueFleetTransferOrder : new fleet is not in a system"; + return false; } - // check that all ships are in the same system - std::vector> ships = Objects().FindObjects(m_add_ships); + bool invalid_ships {false}; - std::vector> validated_ships; - validated_ships.reserve(m_add_ships.size()); - std::vector validated_ship_ids; - validated_ship_ids.reserve(m_add_ships.size()); + for (auto ship : Objects().find(ship_ids)) { + if (!ship) { + ErrorLogger() << "IssueFleetTransferOrder : passed an invalid ship_id"; + invalid_ships = true; + break; + } - for (std::shared_ptr ship : ships) { - if (!ship->OwnedBy(EmpireID())) - continue; - if (ship->SystemID() != target_fleet->SystemID()) - continue; - if (ship->FleetID() == target_fleet->ID()) - continue; - validated_ships.push_back(ship); - validated_ship_ids.push_back(ship->ID()); + if (!ship->OwnedBy(empire_id)) { + ErrorLogger() << "IssueFleetTransferOrder : passed ship_id of ship not owned by player"; + invalid_ships = true; + break; + } + + if (ship->SystemID() == INVALID_OBJECT_ID) { + ErrorLogger() << "IssueFleetTransferOrder : ship is not in a system"; + invalid_ships = true; + break; + } + + if (ship->SystemID() != fleet->SystemID()) { + ErrorLogger() << "IssueFleetTransferOrder : passed ship is not in the same system as the target fleet"; + invalid_ships = true; + break; + } } - if (validated_ships.empty()) + + return !invalid_ships; +} + +void FleetTransferOrder::ExecuteImpl() const { + GetValidatedEmpire(); + + if (!Check(EmpireID(), DestinationFleet(), m_add_ships)) return; + // look up the destination fleet + auto target_fleet = Objects().get(DestinationFleet()); + + // check that all ships are in the same system + auto ships = Objects().find(m_add_ships); + GetUniverse().InhibitUniverseObjectSignals(true); // remove from old fleet(s) std::set> modified_fleets; - for (std::shared_ptr ship : validated_ships) { - if (std::shared_ptr source_fleet = GetFleet(ship->FleetID())) { - source_fleet->RemoveShip(ship->ID()); + for (auto& ship : ships) { + if (auto source_fleet = Objects().get(ship->FleetID())) { + source_fleet->RemoveShips({ship->ID()}); modified_fleets.insert(source_fleet); } ship->SetFleetID(target_fleet->ID()); } // add to new fleet + std::vector validated_ship_ids; + validated_ship_ids.reserve(m_add_ships.size()); + + for (auto& ship : ships) + validated_ship_ids.push_back(ship->ID()); + target_fleet->AddShips(validated_ship_ids); GetUniverse().InhibitUniverseObjectSignals(false); @@ -472,11 +472,11 @@ void FleetTransferOrder::ExecuteImpl() const { // signal change to fleet states modified_fleets.insert(target_fleet); - for (std::shared_ptr modified_fleet : modified_fleets) { + for (auto& modified_fleet : modified_fleets) { if (!modified_fleet->Empty()) modified_fleet->StateChangedSignal(); else { - if (std::shared_ptr system = GetSystem(modified_fleet->SystemID())) + if (auto system = Objects().get(modified_fleet->SystemID())) system->Remove(modified_fleet->ID()); GetUniverse().Destroy(modified_fleet->ID()); @@ -487,87 +487,106 @@ void FleetTransferOrder::ExecuteImpl() const { //////////////////////////////////////////////// // ColonizeOrder //////////////////////////////////////////////// -ColonizeOrder::ColonizeOrder() : - m_ship(INVALID_OBJECT_ID), - m_planet(INVALID_OBJECT_ID) -{} - ColonizeOrder::ColonizeOrder(int empire, int ship, int planet) : Order(empire), m_ship(ship), m_planet(planet) -{} - -void ColonizeOrder::ExecuteImpl() const { - GetValidatedEmpire(); - int empire_id = EmpireID(); +{ + if (!Check(empire, ship, planet)) + return; +} - std::shared_ptr ship = GetShip(m_ship); +bool ColonizeOrder::Check(int empire_id, int ship_id, int planet_id) { + auto ship = Objects().get(ship_id); if (!ship) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl couldn't get ship with id " << m_ship; - return; + ErrorLogger() << "ColonizeOrder::Check() : passed an invalid ship_id " << ship_id; + return false; } - if (!ship->CanColonize()) { // verifies that species exists and can colonize and that ship can colonize - ErrorLogger() << "ColonizeOrder::ExecuteImpl got ship that can't colonize"; - return; + auto fleet = Objects().get(ship->FleetID()); + if (!fleet) { + ErrorLogger() << "ColonizeOrder::Check() : ship with passed ship_id has invalid fleet_id"; + return false; + } + + if (!fleet->OwnedBy(empire_id)) { + ErrorLogger() << "ColonizeOrder::Check() : empire does not own fleet of passed ship"; + return 0; } if (!ship->OwnedBy(empire_id)) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl got ship that isn't owned by the order-issuing empire"; - return; + ErrorLogger() << "ColonizeOrder::Check() : got ship that isn't owned by the order-issuing empire"; + return false; } - float colonist_capacity = ship->ColonyCapacity(); + if (!ship->CanColonize()) { // verifies that species exists and can colonize and that ship can colonize + ErrorLogger() << "ColonizeOrder::Check() : got ship that can't colonize"; + return false; + } - std::shared_ptr planet = GetPlanet(m_planet); + auto planet = Objects().get(planet_id); + float colonist_capacity = ship->ColonyCapacity(); if (!planet) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl couldn't get planet with id " << m_planet; - return; + ErrorLogger() << "ColonizeOrder::Check() : couldn't get planet with id " << planet_id; + return false; } - if (planet->CurrentMeterValue(METER_POPULATION) > 0.0f) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl given planet that already has population"; - return; + if (planet->InitialMeterValue(METER_POPULATION) > 0.0f) { + ErrorLogger() << "ColonizeOrder::Check() : given planet that already has population"; + return false; } if (!planet->Unowned() && planet->Owner() != empire_id) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl given planet that owned by another empire"; - return; + ErrorLogger() << "ColonizeOrder::Check() : given planet that owned by another empire"; + return false; } if (planet->OwnedBy(empire_id) && colonist_capacity == 0.0f) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl given planet that is already owned by empire and colony ship with zero capcity"; - return; + ErrorLogger() << "ColonizeOrder::Check() : given planet that is already owned by empire and colony ship with zero capcity"; + return false; } - if (GetUniverse().GetObjectVisibilityByEmpire(m_planet, empire_id) < VIS_PARTIAL_VISIBILITY) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl given planet that empire has insufficient visibility of"; - return; + if (GetUniverse().GetObjectVisibilityByEmpire(planet_id, empire_id) < VIS_PARTIAL_VISIBILITY) { + ErrorLogger() << "ColonizeOrder::Check() : given planet that empire has insufficient visibility of"; + return false; } if (colonist_capacity > 0.0f && planet->EnvironmentForSpecies(ship->SpeciesName()) < PE_HOSTILE) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl nonzero colonist capacity and planet that ship's species can't colonize"; - return; + ErrorLogger() << "ColonizeOrder::Check() : nonzero colonist capacity, " << colonist_capacity + << ", and planet " << planet->Name() << " of type, " << planet->Type() << ", that ship's species, " + << ship->SpeciesName() << ", can't colonize"; + return false; } int ship_system_id = ship->SystemID(); if (ship_system_id == INVALID_OBJECT_ID) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl given id of ship not in a system"; - return; + ErrorLogger() << "ColonizeOrder::Check() : given id of ship not in a system"; + return false; } int planet_system_id = planet->SystemID(); if (ship_system_id != planet_system_id) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl given ids of ship and planet not in the same system"; - return; + ErrorLogger() << "ColonizeOrder::Check() : given ids of ship and planet not in the same system"; + return false; } if (planet->IsAboutToBeColonized()) { - ErrorLogger() << "ColonizeOrder::ExecuteImpl given id planet that is already being colonized"; - return; + ErrorLogger() << "ColonizeOrder::Check() : given id planet that is already being colonized"; + return false; } + return true; +} + +void ColonizeOrder::ExecuteImpl() const { + GetValidatedEmpire(); + + if (!Check(EmpireID(), m_ship, m_planet)) + return; + + auto ship = Objects().get(m_ship); + auto planet = Objects().get(m_planet); + planet->SetIsAboutToBeColonized(true); ship->SetColonizePlanet(m_planet); - if (std::shared_ptr fleet = GetFleet(ship->FleetID())) + if (auto fleet = Objects().get(ship->FleetID())) fleet->StateChangedSignal(); } bool ColonizeOrder::UndoImpl() const { - std::shared_ptr planet = GetPlanet(m_planet); + auto planet = Objects().get(m_planet); if (!planet) { ErrorLogger() << "ColonizeOrder::UndoImpl couldn't get planet with id " << m_planet; return false; @@ -577,7 +596,7 @@ bool ColonizeOrder::UndoImpl() const { return false; } - std::shared_ptr ship = GetShip(m_ship); + auto ship = Objects().get(m_ship); if (!ship) { ErrorLogger() << "ColonizeOrder::UndoImpl couldn't get ship with id " << m_ship; return false; @@ -590,7 +609,7 @@ bool ColonizeOrder::UndoImpl() const { planet->SetIsAboutToBeColonized(false); ship->ClearColonizePlanet(); - if (std::shared_ptr fleet = GetFleet(ship->FleetID())) + if (auto fleet = Objects().get(ship->FleetID())) fleet->StateChangedSignal(); return true; @@ -599,71 +618,92 @@ bool ColonizeOrder::UndoImpl() const { //////////////////////////////////////////////// // InvadeOrder //////////////////////////////////////////////// -InvadeOrder::InvadeOrder() : - m_ship(INVALID_OBJECT_ID), - m_planet(INVALID_OBJECT_ID) -{} - InvadeOrder::InvadeOrder(int empire, int ship, int planet) : Order(empire), m_ship(ship), m_planet(planet) -{} - -void InvadeOrder::ExecuteImpl() const { - GetValidatedEmpire(); - int empire_id = EmpireID(); +{ + if(!Check(empire, ship, planet)) + return; +} - std::shared_ptr ship = GetShip(m_ship); +bool InvadeOrder::Check(int empire_id, int ship_id, int planet_id) { + // make sure ship_id is a ship... + auto ship = Objects().get(ship_id); if (!ship) { - ErrorLogger() << "InvadeOrder::ExecuteImpl couldn't get ship with id " << m_ship; - return; + ErrorLogger() << "IssueInvadeOrder : passed an invalid ship_id"; + return false; + } + + if (!ship->OwnedBy(empire_id)) { + ErrorLogger() << "IssueInvadeOrder : empire does not own passed ship"; + return false; } if (!ship->HasTroops()) { ErrorLogger() << "InvadeOrder::ExecuteImpl got ship that can't invade"; - return; + return false; } - if (!ship->OwnedBy(empire_id)) { - ErrorLogger() << "InvadeOrder::ExecuteImpl got ship that isn't owned by the order-issuing empire"; - return; + + // get fleet of ship + auto fleet = Objects().get(ship->FleetID()); + if (!fleet) { + ErrorLogger() << "IssueInvadeOrder : ship with passed ship_id has invalid fleet_id"; + return false; } - std::shared_ptr planet = GetPlanet(m_planet); + // make sure player owns ship and its fleet + if (!fleet->OwnedBy(empire_id)) { + ErrorLogger() << "IssueInvadeOrder : empire does not own fleet of passed ship"; + return false; + } + + auto planet = Objects().get(planet_id); if (!planet) { - ErrorLogger() << "InvadeOrder::ExecuteImpl couldn't get planet with id " << m_planet; - return; + ErrorLogger() << "InvadeOrder::ExecuteImpl couldn't get planet with id " << planet_id; + return false; } - if (planet->Unowned() && planet->CurrentMeterValue(METER_POPULATION) == 0.0) { - ErrorLogger() << "InvadeOrder::ExecuteImpl given unpopulated planet"; - return; + + if (ship->SystemID() != planet->SystemID()) { + ErrorLogger() << "InvadeOrder::ExecuteImpl given ids of ship and planet not in the same system"; + return false; } - if (planet->CurrentMeterValue(METER_SHIELD) > 0.0) { - ErrorLogger() << "InvadeOrder::ExecuteImpl given planet with shield > 0"; - return; + + if (GetUniverse().GetObjectVisibilityByEmpire(planet_id, empire_id) < VIS_BASIC_VISIBILITY) { + ErrorLogger() << "InvadeOrder::ExecuteImpl given planet that empire reportedly has insufficient visibility of, but will be allowed to proceed pending investigation"; + return false; } + if (planet->OwnedBy(empire_id)) { ErrorLogger() << "InvadeOrder::ExecuteImpl given planet that is already owned by the order-issuing empire"; - return; + return false; } - if (!planet->Unowned() && Empires().GetDiplomaticStatus(planet->Owner(), empire_id) != DIPLO_WAR) { - ErrorLogger() << "InvadeOrder::ExecuteImpl given planet owned by an empire not at war with order-issuing empire"; - return; + + if (planet->Unowned() && planet->InitialMeterValue(METER_POPULATION) == 0.0) { + ErrorLogger() << "InvadeOrder::ExecuteImpl given unpopulated planet"; + return false; } - if (GetUniverse().GetObjectVisibilityByEmpire(m_planet, empire_id) < VIS_BASIC_VISIBILITY) { - ErrorLogger() << "InvadeOrder::ExecuteImpl given planet that empire reportedly has insufficient visibility of, but will be allowed to proceed pending investigation"; - //return; + + if (planet->InitialMeterValue(METER_SHIELD) > 0.0) { + ErrorLogger() << "InvadeOrder::ExecuteImpl given planet with shield > 0"; + return false; } - int ship_system_id = ship->SystemID(); - if (ship_system_id == INVALID_OBJECT_ID) { - ErrorLogger() << "InvadeOrder::ExecuteImpl given id of ship not in a system"; - return; + if (!planet->Unowned() && Empires().GetDiplomaticStatus(planet->Owner(), empire_id) != DIPLO_WAR) { + ErrorLogger() << "InvadeOrder::ExecuteImpl given planet owned by an empire not at war with order-issuing empire"; + return false; } - int planet_system_id = planet->SystemID(); - if (ship_system_id != planet_system_id) { - ErrorLogger() << "InvadeOrder::ExecuteImpl given ids of ship and planet not in the same system"; + + return true; +} + +void InvadeOrder::ExecuteImpl() const { + GetValidatedEmpire(); + + if(!Check(EmpireID(), m_ship, m_planet)) return; - } + + auto ship = Objects().get(m_ship); + auto planet = Objects().get(m_planet); // note: multiple ships, from same or different empires, can invade the same planet on the same turn DebugLogger() << "InvadeOrder::ExecuteImpl set for ship " << m_ship << " " @@ -671,18 +711,18 @@ void InvadeOrder::ExecuteImpl() const { planet->SetIsAboutToBeInvaded(true); ship->SetInvadePlanet(m_planet); - if (std::shared_ptr fleet = GetFleet(ship->FleetID())) + if (auto fleet = Objects().get(ship->FleetID())) fleet->StateChangedSignal(); } bool InvadeOrder::UndoImpl() const { - std::shared_ptr planet = GetPlanet(m_planet); + auto planet = Objects().get(m_planet); if (!planet) { ErrorLogger() << "InvadeOrder::UndoImpl couldn't get planet with id " << m_planet; return false; } - std::shared_ptr ship = GetShip(m_ship); + auto ship = Objects().get(m_ship); if (!ship) { ErrorLogger() << "InvadeOrder::UndoImpl couldn't get ship with id " << m_ship; return false; @@ -695,7 +735,7 @@ bool InvadeOrder::UndoImpl() const { planet->SetIsAboutToBeInvaded(false); ship->ClearInvadePlanet(); - if (std::shared_ptr fleet = GetFleet(ship->FleetID())) + if (auto fleet = Objects().get(ship->FleetID())) fleet->StateChangedSignal(); return true; @@ -704,82 +744,93 @@ bool InvadeOrder::UndoImpl() const { //////////////////////////////////////////////// // BombardOrder //////////////////////////////////////////////// -BombardOrder::BombardOrder() : - m_ship(INVALID_OBJECT_ID), - m_planet(INVALID_OBJECT_ID) -{} - BombardOrder::BombardOrder(int empire, int ship, int planet) : Order(empire), m_ship(ship), m_planet(planet) -{} - -void BombardOrder::ExecuteImpl() const { - GetValidatedEmpire(); - int empire_id = EmpireID(); +{ + if(!Check(empire, ship, planet)) + return; +} - std::shared_ptr ship = GetShip(m_ship); +bool BombardOrder::Check(int empire_id, int ship_id, int planet_id) { + auto ship = Objects().get(ship_id); if (!ship) { - ErrorLogger() << "BombardOrder::ExecuteImpl couldn't get ship with id " << m_ship; - return; + ErrorLogger() << "BombardOrder::ExecuteImpl couldn't get ship with id " << ship_id; + return false; } if (!ship->CanBombard()) { ErrorLogger() << "BombardOrder::ExecuteImpl got ship that can't bombard"; - return; + return false; } if (!ship->OwnedBy(empire_id)) { ErrorLogger() << "BombardOrder::ExecuteImpl got ship that isn't owned by the order-issuing empire"; - return; + return false; + } + if (ship->TotalWeaponsDamage() <= 0.0f) { // this will test the current meter values. potential issue if some local change sets these to zero even though they will be nonzero on server when bombard is processed before effects application / meter update + ErrorLogger() << "IssueBombardOrder : ship can't attack / bombard"; + return false; } - std::shared_ptr planet = GetPlanet(m_planet); + auto planet = Objects().get(planet_id); if (!planet) { - ErrorLogger() << "BombardOrder::ExecuteImpl couldn't get planet with id " << m_planet; - return; + ErrorLogger() << "BombardOrder::ExecuteImpl couldn't get planet with id " << planet_id; + return false; } if (planet->OwnedBy(empire_id)) { ErrorLogger() << "BombardOrder::ExecuteImpl given planet that is already owned by the order-issuing empire"; - return; + return false; } if (!planet->Unowned() && Empires().GetDiplomaticStatus(planet->Owner(), empire_id) != DIPLO_WAR) { ErrorLogger() << "BombardOrder::ExecuteImpl given planet owned by an empire not at war with order-issuing empire"; - return; + return false; } - if (GetUniverse().GetObjectVisibilityByEmpire(m_planet, empire_id) < VIS_BASIC_VISIBILITY) { + if (GetUniverse().GetObjectVisibilityByEmpire(planet_id, empire_id) < VIS_BASIC_VISIBILITY) { ErrorLogger() << "BombardOrder::ExecuteImpl given planet that empire reportedly has insufficient visibility of, but will be allowed to proceed pending investigation"; - //return; } int ship_system_id = ship->SystemID(); if (ship_system_id == INVALID_OBJECT_ID) { ErrorLogger() << "BombardOrder::ExecuteImpl given id of ship not in a system"; - return; + return false; } int planet_system_id = planet->SystemID(); if (ship_system_id != planet_system_id) { ErrorLogger() << "BombardOrder::ExecuteImpl given ids of ship and planet not in the same system"; - return; + return false; } - // note: multiple ships, from same or different empires, can invade the same planet on the same turn + return true; +} + +void BombardOrder::ExecuteImpl() const { + GetValidatedEmpire(); + + if(!Check(EmpireID(), m_ship, m_planet)) + return; + + auto ship = Objects().get(m_ship); + auto planet = Objects().get(m_planet); + + // note: multiple ships, from same or different empires, can bombard the same planet on the same turn DebugLogger() << "BombardOrder::ExecuteImpl set for ship " << m_ship << " " - << ship->Name() << " to bombard planet " << m_planet << " " << planet->Name(); + << ship->Name() << " to bombard planet " << m_planet << " " + << planet->Name(); planet->SetIsAboutToBeBombarded(true); ship->SetBombardPlanet(m_planet); - if (std::shared_ptr fleet = GetFleet(ship->FleetID())) + if (auto fleet = Objects().get(ship->FleetID())) fleet->StateChangedSignal(); } bool BombardOrder::UndoImpl() const { - std::shared_ptr planet = GetPlanet(m_planet); + auto planet = Objects().get(m_planet); if (!planet) { ErrorLogger() << "BombardOrder::UndoImpl couldn't get planet with id " << m_planet; return false; } - std::shared_ptr ship = GetShip(m_ship); + auto ship = Objects().get(m_ship); if (!ship) { ErrorLogger() << "BombardOrder::UndoImpl couldn't get ship with id " << m_ship; return false; @@ -792,7 +843,7 @@ bool BombardOrder::UndoImpl() const { planet->SetIsAboutToBeBombarded(false); ship->ClearBombardPlanet(); - if (std::shared_ptr fleet = GetFleet(ship->FleetID())) + if (auto fleet = Objects().get(ship->FleetID())) fleet->StateChangedSignal(); return true; @@ -801,40 +852,50 @@ bool BombardOrder::UndoImpl() const { //////////////////////////////////////////////// // ChangeFocusOrder //////////////////////////////////////////////// -ChangeFocusOrder::ChangeFocusOrder() : - m_planet(INVALID_OBJECT_ID) -{} - ChangeFocusOrder::ChangeFocusOrder(int empire, int planet, const std::string& focus) : Order(empire), m_planet(planet), m_focus(focus) -{} - -void ChangeFocusOrder::ExecuteImpl() const { - GetValidatedEmpire(); +{ + if (!Check(empire, planet, focus)) + return; +} - std::shared_ptr planet = GetPlanet(PlanetID()); +bool ChangeFocusOrder::Check(int empire_id, int planet_id, const std::string& focus) { + auto planet = Objects().get(planet_id); if (!planet) { ErrorLogger() << "Illegal planet id specified in change planet focus order."; - return; + return false; } - if (!planet->OwnedBy(EmpireID())) { + if (!planet->OwnedBy(empire_id)) { ErrorLogger() << "Empire attempted to issue change planet focus to another's planet."; - return; + return false; } + if (false) { // todo: verify that focus is valid for specified planet + ErrorLogger() << "IssueChangeFocusOrder : invalid focus specified"; + return false; + } + + return true; +} + +void ChangeFocusOrder::ExecuteImpl() const { + GetValidatedEmpire(); + + if (!Check(EmpireID(), m_planet, m_focus)) + return; + + auto planet = Objects().get(m_planet); + planet->SetFocus(m_focus); } //////////////////////////////////////////////// // ResearchQueueOrder //////////////////////////////////////////////// -ResearchQueueOrder::ResearchQueueOrder() -{} - ResearchQueueOrder::ResearchQueueOrder(int empire, const std::string& tech_name) : Order(empire), m_tech_name(tech_name), @@ -876,113 +937,184 @@ void ResearchQueueOrder::ExecuteImpl() const { //////////////////////////////////////////////// // ProductionQueueOrder //////////////////////////////////////////////// -ProductionQueueOrder::ProductionQueueOrder() -{} - -ProductionQueueOrder::ProductionQueueOrder(int empire, const ProductionQueue::ProductionItem& item, +ProductionQueueOrder::ProductionQueueOrder(ProdQueueOrderAction action, int empire, + const ProductionQueue::ProductionItem& item, int number, int location, int pos) : Order(empire), m_item(item), - m_number(number), m_location(location), - m_new_index(pos) -{} - -ProductionQueueOrder::ProductionQueueOrder(int empire, int index, int new_quantity, int new_blocksize) : - Order(empire), - m_index(index), - m_new_quantity(new_quantity), - m_new_blocksize(new_blocksize) -{} - -ProductionQueueOrder::ProductionQueueOrder(int empire, int index, int new_quantity, bool dummy) : - Order(empire), - m_index(index), - m_new_quantity(new_quantity) -{} - -ProductionQueueOrder::ProductionQueueOrder(int empire, int index, int rally_point_id, bool dummy1, bool dummy2) : - Order(empire), - m_index(index), - m_rally_point_id(rally_point_id) -{} - -ProductionQueueOrder::ProductionQueueOrder(int empire, int index, int new_index) : - Order(empire), - m_index(index), - m_new_index(new_index) -{} - -ProductionQueueOrder::ProductionQueueOrder(int empire, int index) : - Order(empire), - m_index(index) -{} - -ProductionQueueOrder::ProductionQueueOrder(int empire, int index, bool pause, float dummy) : - Order(empire), - m_index(index), - m_pause(pause ? PAUSE : RESUME) -{} - -ProductionQueueOrder::ProductionQueueOrder(int empire, int index, float dummy1) : - Order(empire), - m_index(index), - m_split_incomplete(index) -{} + m_new_quantity(number), + m_new_index(pos), + m_uuid(boost::uuids::random_generator()()), + m_uuid2(boost::uuids::nil_generator()()), + m_action(action) +{ + if (action != PLACE_IN_QUEUE) + ErrorLogger() << "ProductionQueueOrder called with parameters for placing in queue but with another action"; +} -ProductionQueueOrder::ProductionQueueOrder(int empire, int index, float dummy1, float dummy2) : +ProductionQueueOrder::ProductionQueueOrder(ProdQueueOrderAction action, int empire, + boost::uuids::uuid uuid, int num1, int num2) : Order(empire), - m_index(index), - m_dupe(index) -{} + m_uuid(uuid), + m_uuid2(boost::uuids::nil_generator()()), + m_action(action) +{ + switch(m_action) { + case REMOVE_FROM_QUEUE: + break; + case SPLIT_INCOMPLETE: + case DUPLICATE_ITEM: + m_uuid2 = boost::uuids::random_generator()(); + break; + case SET_QUANTITY_AND_BLOCK_SIZE: + m_new_quantity = num1; + m_new_blocksize = num2; + break; + case SET_QUANTITY: + m_new_quantity = num1; + break; + case MOVE_ITEM_TO_INDEX: + m_new_index = num1; + case SET_RALLY_POINT: + m_rally_point_id = num1; + break; + case PAUSE_PRODUCTION: + case RESUME_PRODUCTION: + case ALLOW_STOCKPILE_USE: + case DISALLOW_STOCKPILE_USE: + break; + default: + ErrorLogger() << "ProductionQueueOrder given unrecognized action!"; + } +} void ProductionQueueOrder::ExecuteImpl() const { - auto empire = GetValidatedEmpire(); - try { - if (m_item.build_type == BT_BUILDING || m_item.build_type == BT_SHIP) { - DebugLogger() << "ProductionQueueOrder place " << m_item.Dump(); - empire->PlaceProductionOnQueue(m_item, m_number, 1, m_location, m_new_index); - - } else if (m_split_incomplete != INVALID_SPLIT_INCOMPLETE) { - DebugLogger() << "ProductionQueueOrder splitting incomplete from item"; - empire->SplitIncompleteProductionItem(m_index); - - } else if (m_dupe != INVALID_SPLIT_INCOMPLETE) { - DebugLogger() << "ProductionQueueOrder duplicating item"; - empire->DuplicateProductionItem(m_index); - - } else if (m_new_blocksize != INVALID_QUANTITY) { - DebugLogger() << "ProductionQueueOrder quantity " << m_new_quantity << " Blocksize " << m_new_blocksize; - empire->SetProductionQuantityAndBlocksize(m_index, m_new_quantity, m_new_blocksize); - - } else if (m_new_quantity != INVALID_QUANTITY) { - DebugLogger() << "ProductionQueueOrder quantity " << m_new_quantity; - empire->SetProductionQuantity(m_index, m_new_quantity); - - } else if (m_new_index != INVALID_INDEX) { - DebugLogger() << "ProductionQueueOrder moving item in queue"; - empire->MoveProductionWithinQueue(m_index, m_new_index); - - } else if (m_rally_point_id != INVALID_OBJECT_ID) { - DebugLogger() << "ProductionQueueOrder setting rally point to id: " << m_rally_point_id; - empire->SetProductionRallyPoint(m_index, m_rally_point_id); - - } else if (m_index != INVALID_INDEX) { - if (m_pause == PAUSE) { - DebugLogger() << "ProductionQueueOrder: pausing production"; - empire->PauseProduction(m_index); - - } else if (m_pause == RESUME) { - DebugLogger() << "ProductionQueueOrder: unpausing production"; - empire->ResumeProduction(m_index); - - } else /*if (m_pause == INVALID_PAUSE_RESUME)*/ { - DebugLogger() << "ProductionQueueOrder: removing item from index " << m_index; - empire->RemoveProductionFromQueue(m_index); + auto empire = GetValidatedEmpire(); + + switch(m_action) { + case PLACE_IN_QUEUE: { + if (m_item.build_type == BT_BUILDING || m_item.build_type == BT_SHIP || m_item.build_type == BT_STOCKPILE) { + DebugLogger() << "ProductionQueueOrder place in queue: " << m_item.Dump() << " at index: " << m_new_index; + empire->PlaceProductionOnQueue(m_item, m_uuid, m_new_quantity, 1, m_location, m_new_index); + } else { + ErrorLogger() << "ProductionQueueOrder tried to place invalid build type in queue!"; } - } else { - ErrorLogger() << "ProductionQueueOrder: Malformed"; + break; + } + case REMOVE_FROM_QUEUE: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to remove invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder removing item"; + empire->RemoveProductionFromQueue(idx); + } + break; + } + case SPLIT_INCOMPLETE: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to split invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder splitting incomplete from item"; + empire->SplitIncompleteProductionItem(idx, m_uuid2); + } + break; + } + case DUPLICATE_ITEM: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to duplicate invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder duplicating item"; + empire->DuplicateProductionItem(idx, m_uuid2); + } + break; + } + case SET_QUANTITY_AND_BLOCK_SIZE: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to set quantity and blocksize of invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder setting quantity and block size"; + empire->SetProductionQuantityAndBlocksize(idx, m_new_quantity, m_new_blocksize); + } + break; + } + case SET_QUANTITY: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to set quantity of invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder setting quantity " << m_new_quantity; + empire->SetProductionQuantity(idx, m_new_quantity); + } + break; + } + case MOVE_ITEM_TO_INDEX: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to move invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder moving to index " << m_new_index; + empire->MoveProductionWithinQueue(idx, m_new_index); + } + break; + } + case SET_RALLY_POINT: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to set rally point of invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder setting rally point to " << m_rally_point_id; + empire->SetProductionRallyPoint(idx, m_rally_point_id); + } + break; + } + case PAUSE_PRODUCTION: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to pause invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder pausing"; + empire->PauseProduction(idx); + } + break; + } + case RESUME_PRODUCTION: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to resume invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder resuming"; + empire->ResumeProduction(idx); + } + break; + } + case ALLOW_STOCKPILE_USE: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to allow stockpiling on invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder allowing stockpile"; + empire->AllowUseImperialPP(idx, true); + } + break; + } + case DISALLOW_STOCKPILE_USE: { + auto idx = empire->GetProductionQueue().IndexOfUUID(m_uuid); + if (idx == -1) { + ErrorLogger() << "ProductionQueueOrder asked to disallow stockpiling on invalid UUID: " << boost::uuids::to_string(m_uuid); + } else { + DebugLogger() << "ProductionQueueOrder disallowing stockpile"; + empire->AllowUseImperialPP(idx, false); + } + break; + } + default: + ErrorLogger() << "ProductionQueueOrder::ExecuteImpl got invalid action"; } } catch (const std::exception& e) { ErrorLogger() << "Build order execution threw exception: " << e.what(); @@ -1072,9 +1204,13 @@ void ShipDesignOrder::ExecuteImpl() const { // On the client create a new design id universe.InsertShipDesign(new_ship_design); m_design_id = new_ship_design->ID(); + DebugLogger() << "ShipDesignOrder::ExecuteImpl Create new ship design ID " << m_design_id; } else { // On the server use the design id passed from the client - universe.InsertShipDesignID(new_ship_design, EmpireID(), m_design_id); + if (!universe.InsertShipDesignID(new_ship_design, EmpireID(), m_design_id)) { + ErrorLogger() << "Couldn't insert ship design by ID " << m_design_id; + return; + } } universe.SetEmpireKnowledgeOfShipDesign(m_design_id, EmpireID()); @@ -1083,7 +1219,7 @@ void ShipDesignOrder::ExecuteImpl() const { } else if (m_update_name_or_description) { // player is ordering empire to rename a design const std::set& empire_known_design_ids = universe.EmpireKnownShipDesignIDs(EmpireID()); - std::set::iterator design_it = empire_known_design_ids.find(m_design_id); + auto design_it = empire_known_design_ids.find(m_design_id); if (design_it == empire_known_design_ids.end()) { ErrorLogger() << "Empire, " << EmpireID() << ", tried to rename/redescribe a ShipDesign id = " << m_design_id << " that this empire hasn't seen"; @@ -1119,7 +1255,7 @@ void ShipDesignOrder::ExecuteImpl() const { // check if the empire can see any objects that have this design (thus enabling it to be copied) const std::set& empire_known_design_ids = universe.EmpireKnownShipDesignIDs(EmpireID()); - if (empire_known_design_ids.find(m_design_id) != empire_known_design_ids.end()) { + if (empire_known_design_ids.count(m_design_id)) { empire->AddShipDesign(m_design_id); } else { ErrorLogger() << "Empire, " << EmpireID() << ", tried to remember a ShipDesign id = " << m_design_id @@ -1133,32 +1269,50 @@ void ShipDesignOrder::ExecuteImpl() const { //////////////////////////////////////////////// // ScrapOrder //////////////////////////////////////////////// -ScrapOrder::ScrapOrder() : - m_object_id(INVALID_OBJECT_ID) -{} - ScrapOrder::ScrapOrder(int empire, int object_id) : Order(empire), m_object_id(object_id) -{} +{ + if (!Check(empire, object_id)) + return; +} + +bool ScrapOrder::Check(int empire_id, int object_id) { + auto obj = Objects().get(object_id); + + if (!obj) { + ErrorLogger() << "IssueScrapOrder : passed an invalid object_id"; + return false; + } + + if (!obj->OwnedBy(empire_id)) { + ErrorLogger() << "IssueScrapOrder : passed object_id of object not owned by player"; + return false; + } + + if (obj->ObjectType() != OBJ_SHIP && obj->ObjectType() != OBJ_BUILDING) { + ErrorLogger() << "ScrapOrder::Check : passed object that is not a ship or building"; + return false; + } + + auto ship = Objects().get(object_id); + if (ship && ship->SystemID() == INVALID_OBJECT_ID) { + ErrorLogger() << "ScrapOrder::Check : can scrap a traveling ship"; + } + + return true; +} void ScrapOrder::ExecuteImpl() const { GetValidatedEmpire(); - int empire_id = EmpireID(); - if (std::shared_ptr ship = GetShip(m_object_id)) { - if (ship->SystemID() != INVALID_OBJECT_ID && ship->OwnedBy(empire_id)) { - ship->SetOrderedScrapped(true); - //DebugLogger() << "ScrapOrder::ExecuteImpl empire: " << empire_id - // << " on ship: " << ship->ID() << " at system: " << ship->SystemID() - // << " ... ordered scrapped?: " << ship->OrderedScrapped(); - } - } else if (std::shared_ptr building = GetBuilding(m_object_id)) { - int planet_id = building->PlanetID(); - if (std::shared_ptr planet = GetPlanet(planet_id)) { - if (building->OwnedBy(empire_id) && planet->OwnedBy(empire_id)) - building->SetOrderedScrapped(true); - } + if (!Check(EmpireID(), m_object_id)) + return; + + if (auto ship = Objects().get(m_object_id)) { + ship->SetOrderedScrapped(true); + } else if (auto building = Objects().get(m_object_id)) { + building->SetOrderedScrapped(true); } } @@ -1166,10 +1320,10 @@ bool ScrapOrder::UndoImpl() const { GetValidatedEmpire(); int empire_id = EmpireID(); - if (std::shared_ptr ship = GetShip(m_object_id)) { + if (auto ship = Objects().get(m_object_id)) { if (ship->OwnedBy(empire_id)) ship->SetOrderedScrapped(false); - } else if (std::shared_ptr building = GetBuilding(m_object_id)) { + } else if (auto building = Objects().get(m_object_id)) { if (building->OwnedBy(empire_id)) building->SetOrderedScrapped(false); } else { @@ -1181,64 +1335,120 @@ bool ScrapOrder::UndoImpl() const { //////////////////////////////////////////////// // AggressiveOrder //////////////////////////////////////////////// -AggressiveOrder::AggressiveOrder() : - m_object_id(INVALID_OBJECT_ID), - m_aggression(false) -{} - AggressiveOrder::AggressiveOrder(int empire, int object_id, bool aggression/* = true*/) : Order(empire), m_object_id(object_id), m_aggression(aggression) -{} +{ + if (!Check(empire, object_id, aggression)) + return; +} + +bool AggressiveOrder::Check(int empire_id, int object_id, bool aggression) { + auto fleet = Objects().get(object_id); + if (!fleet) { + ErrorLogger() << "IssueAggressionOrder : no fleet with passed id"; + return false; + } + + if (!fleet->OwnedBy(empire_id)) { + ErrorLogger() << "IssueAggressionOrder : passed object_id of object not owned by player"; + return false; + } + + return true; +} void AggressiveOrder::ExecuteImpl() const { GetValidatedEmpire(); - int empire_id = EmpireID(); - if (std::shared_ptr fleet = GetFleet(m_object_id)) { - if (fleet->OwnedBy(empire_id)) - fleet->SetAggressive(m_aggression); - } + + if (!Check(EmpireID(), m_object_id, m_aggression)) + return; + + auto fleet = Objects().get(m_object_id); + + fleet->SetAggressive(m_aggression); } ///////////////////////////////////////////////////// // GiveObjectToEmpireOrder ///////////////////////////////////////////////////// -GiveObjectToEmpireOrder::GiveObjectToEmpireOrder() : - m_object_id(INVALID_OBJECT_ID), - m_recipient_empire_id(ALL_EMPIRES) -{} - GiveObjectToEmpireOrder::GiveObjectToEmpireOrder(int empire, int object_id, int recipient) : Order(empire), m_object_id(object_id), m_recipient_empire_id(recipient) -{} +{ + if (!Check(empire, object_id, recipient)) + return; +} + +bool GiveObjectToEmpireOrder::Check(int empire_id, int object_id, int recipient_empire_id) { + if (GetEmpire(recipient_empire_id) == 0) { + ErrorLogger() << "IssueGiveObjectToEmpireOrder : given invalid recipient empire id"; + return false; + } + + auto dip = Empires().GetDiplomaticStatus(empire_id, recipient_empire_id); + if (dip < DIPLO_PEACE) { + ErrorLogger() << "IssueGiveObjectToEmpireOrder : attempting to give to empire not at peace"; + return false; + } + + auto obj = Objects().get(object_id); + if (!obj) { + ErrorLogger() << "IssueGiveObjectToEmpireOrder : passed invalid object id"; + return false; + } + + if (!obj->OwnedBy(empire_id)) { + ErrorLogger() << "IssueGiveObjectToEmpireOrder : passed object not owned by player"; + return false; + } + + auto system = Objects().get(obj->SystemID()); + if (!system) { + ErrorLogger() << "IssueGiveObjectToEmpireOrder : couldn't get system of object"; + return false; + } + + if (obj->ObjectType() != OBJ_FLEET && obj->ObjectType() != OBJ_PLANET) { + ErrorLogger() << "IssueGiveObjectToEmpireOrder : passed object that is not a fleet or planet"; + return false; + } + + auto system_objects = Objects().find(system->ObjectIDs()); + if (!std::any_of(system_objects.begin(), system_objects.end(), + [recipient_empire_id](const std::shared_ptr o){ return o->Owner() == recipient_empire_id; })) + { + ErrorLogger() << "IssueGiveObjectToEmpireOrder : recipient empire has nothing in system"; + return false; + } + + return true; +} void GiveObjectToEmpireOrder::ExecuteImpl() const { GetValidatedEmpire(); - int empire_id = EmpireID(); - if (std::shared_ptr fleet = GetFleet(m_object_id)) { - if (fleet->OwnedBy(empire_id)) - fleet->SetGiveToEmpire(m_recipient_empire_id); + if (!Check(EmpireID(), m_object_id, m_recipient_empire_id)) + return; - } else if (std::shared_ptr planet = GetPlanet(m_object_id)) { - if (planet->OwnedBy(empire_id)) - planet->SetGiveToEmpire(m_recipient_empire_id); - } + if (auto fleet = Objects().get(m_object_id)) + fleet->SetGiveToEmpire(m_recipient_empire_id); + else if (auto planet = Objects().get(m_object_id)) + planet->SetGiveToEmpire(m_recipient_empire_id); } bool GiveObjectToEmpireOrder::UndoImpl() const { GetValidatedEmpire(); int empire_id = EmpireID(); - if (std::shared_ptr fleet = GetFleet(m_object_id)) { + if (auto fleet = Objects().get(m_object_id)) { if (fleet->OwnedBy(empire_id)) { fleet->ClearGiveToEmpire(); return true; } - } else if (std::shared_ptr planet = GetPlanet(m_object_id)) { + } else if (auto planet = Objects().get(m_object_id)) { if (planet->OwnedBy(empire_id)) { planet->ClearGiveToEmpire(); return true; @@ -1250,10 +1460,6 @@ bool GiveObjectToEmpireOrder::UndoImpl() const { //////////////////////////////////////////////// // ForgetOrder //////////////////////////////////////////////// -ForgetOrder::ForgetOrder() : - m_object_id(INVALID_OBJECT_ID) -{} - ForgetOrder::ForgetOrder(int empire, int object_id) : Order(empire), m_object_id(object_id) diff --git a/util/Order.h b/util/Order.h index 283dc1fdc9b..899c7a882b7 100644 --- a/util/Order.h +++ b/util/Order.h @@ -43,8 +43,11 @@ class FO_COMMON_API Order { /** \name Accessors */ //@{ /** Returns the ID of the Empire issuing the order. */ - int EmpireID() const - { return m_empire; } + int EmpireID() const { return m_empire; } + + /** Returns true iff this order has been executed (a second execution + * indicates server-side execution). */ + bool Executed() const; //@} /** Executes the order on the Universe and Empires. @@ -71,15 +74,10 @@ class FO_COMMON_API Order { /** Verifies that the empire ID in this order is valid and return the Empire pointer. * Throws an std::runtime_error if not valid. */ Empire* GetValidatedEmpire() const; - - /** Returns true iff this order has been executed (a second execution - * indicates server-side execution). */ - bool Executed() const; //@} private: virtual void ExecuteImpl() const = 0; - virtual bool UndoImpl() const; int m_empire = ALL_EMPIRES; @@ -88,7 +86,7 @@ class FO_COMMON_API Order { mutable bool m_executed = false; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -100,8 +98,6 @@ class FO_COMMON_API Order { class FO_COMMON_API RenameOrder : public Order { public: /** \name Structors */ //@{ - RenameOrder(); - RenameOrder(int empire, int object, const std::string& name); //@} @@ -115,7 +111,12 @@ class FO_COMMON_API RenameOrder : public Order { { return m_name; } //@} + //! Returns true when the Order parameters are valid. + static bool Check(int empire, int object, const std::string& new_name); + private: + RenameOrder() = default; + /** * Preconditions of execute: * - the designated planet must exist, be owned by the issuing empire @@ -126,11 +127,11 @@ class FO_COMMON_API RenameOrder : public Order { */ void ExecuteImpl() const override; - int m_object; + int m_object = INVALID_OBJECT_ID; std::string m_name; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -143,35 +144,29 @@ class FO_COMMON_API RenameOrder : public Order { class FO_COMMON_API NewFleetOrder : public Order { public: /** \name Structors */ //@{ - NewFleetOrder(); - NewFleetOrder(int empire, const std::string& fleet_name, - int system_id, const std::vector& ship_ids, - bool aggressive = false); - - NewFleetOrder(int empire, const std::vector& fleet_names, - int system_id, const std::vector>& ship_id_groups, - const std::vector& aggressives); + const std::vector& ship_ids, + bool aggressive); //@} /** \name Accessors */ //@{ - int SystemID() const - { return m_system_id; } - - const std::vector& FleetNames() const - { return m_fleet_names; } + const std::string& FleetName() const + { return m_fleet_name; } - const std::vector& FleetIDs() const - { return m_fleet_ids; } + const int& FleetID() const + { return m_fleet_id; } - const std::vector>& ShipIDGroups() const - { return m_ship_id_groups; } + const std::vector& ShipIDs() const + { return m_ship_ids; } - const std::vector& Aggressive() const - { return m_aggressives; } + bool Aggressive() const + { return m_aggressive; } //@} + static bool Check(int empire, const std::string& fleet_name, const std::vector& ship_ids, bool aggressive); private: + NewFleetOrder() = default; + /** * Preconditions of execute: * None. @@ -182,15 +177,14 @@ class FO_COMMON_API NewFleetOrder : public Order { */ void ExecuteImpl() const override; - std::vector m_fleet_names; - int m_system_id; - /** m_fleet_ids is mutable because ExecuteImpl generates the fleet ids. */ - mutable std::vector m_fleet_ids; - std::vector> m_ship_id_groups; - std::vector m_aggressives; + std::string m_fleet_name; + /** m_fleet_id is mutable because ExecuteImpl generates the fleet id. */ + mutable int m_fleet_id; + std::vector m_ship_ids; + bool m_aggressive; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -203,20 +197,14 @@ class FO_COMMON_API NewFleetOrder : public Order { class FO_COMMON_API FleetMoveOrder : public Order { public: /** \name Structors */ //@{ - FleetMoveOrder(); - - FleetMoveOrder(int empire, int fleet_id, int start_system_id, int dest_system_id, bool append = false); + FleetMoveOrder(int empire_id, int fleet_id, int dest_system_id, + bool append = false); //@} /** \name Accessors */ //@{ /** Returns ID of fleet selected in this order. */ int FleetID() const - {return m_fleet;} - - /** Returns ID of system set as the start system for this order (the system - the route starts from). */ - int StartSystemID() const - { return m_start_system; } + { return m_fleet; } /* Returns ID of system set as destination for this order. */ int DestinationSystemID() const @@ -227,7 +215,10 @@ class FO_COMMON_API FleetMoveOrder : public Order { { return m_route; } //@} + static bool Check(int empire_id, int fleet_id, int dest_fleet_id, bool append = false); private: + FleetMoveOrder() = default; + /** * Preconditions of execute: * - m_fleet is a valid id of a fleet owned by the order-giving empire @@ -241,14 +232,13 @@ class FO_COMMON_API FleetMoveOrder : public Order { */ void ExecuteImpl() const override; - int m_fleet; - int m_start_system; - int m_dest_system; + int m_fleet = INVALID_OBJECT_ID; + int m_dest_system = INVALID_OBJECT_ID; std::vector m_route; - bool m_append; + bool m_append = false; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -262,8 +252,6 @@ class FO_COMMON_API FleetMoveOrder : public Order { class FO_COMMON_API FleetTransferOrder : public Order { public: /** \name Structors */ //@{ - FleetTransferOrder(); - FleetTransferOrder(int empire, int dest_fleet, const std::vector& ships); //@} @@ -277,7 +265,11 @@ class FO_COMMON_API FleetTransferOrder : public Order { { return m_add_ships; } //@} + static bool Check(int empire_id, int dest_fleet_id, const std::vector& ship_ids); + private: + FleetTransferOrder() = default; + /** * FleetTransferOrder's preconditions are: * - m_into_fleet must be the ID of a fleet owned by the issuing empire @@ -289,11 +281,11 @@ class FO_COMMON_API FleetTransferOrder : public Order { */ void ExecuteImpl() const override; - int m_dest_fleet; + int m_dest_fleet = INVALID_OBJECT_ID; std::vector m_add_ships; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -305,8 +297,6 @@ class FO_COMMON_API FleetTransferOrder : public Order { class FO_COMMON_API ColonizeOrder : public Order { public: /** \name Structors */ //@{ - ColonizeOrder(); - ColonizeOrder(int empire, int ship, int planet); //@} @@ -320,7 +310,11 @@ class FO_COMMON_API ColonizeOrder : public Order { { return m_ship; } //@} + static bool Check(int empire_id, int ship_id, int planet_id); + private: + ColonizeOrder() = default; + /** * Preconditions: * - m_planet must be the ID of an un-owned planet. @@ -336,11 +330,11 @@ class FO_COMMON_API ColonizeOrder : public Order { bool UndoImpl() const override; - int m_ship; - int m_planet; + int m_ship = INVALID_OBJECT_ID; + int m_planet = INVALID_OBJECT_ID; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -352,8 +346,6 @@ class FO_COMMON_API ColonizeOrder : public Order { class FO_COMMON_API InvadeOrder : public Order { public: /** \name Structors */ //@{ - InvadeOrder(); - InvadeOrder(int empire, int ship, int planet); //@} @@ -367,7 +359,11 @@ class FO_COMMON_API InvadeOrder : public Order { { return m_ship; } //@} + static bool Check(int empire_id, int ship_id, int planet_id); + private: + InvadeOrder() = default; + /** * Preconditions: * - m_planet must be the ID of a populated planet not owned by the issuing empire @@ -383,11 +379,11 @@ class FO_COMMON_API InvadeOrder : public Order { bool UndoImpl() const override; - int m_ship; - int m_planet; + int m_ship = INVALID_OBJECT_ID; + int m_planet = INVALID_OBJECT_ID; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -399,8 +395,6 @@ class FO_COMMON_API InvadeOrder : public Order { class FO_COMMON_API BombardOrder : public Order { public: /** \name Structors */ //@{ - BombardOrder(); - BombardOrder(int empire, int ship, int planet); //@} @@ -414,7 +408,11 @@ class FO_COMMON_API BombardOrder : public Order { { return m_ship; } //@} + static bool Check(int empire_id, int ship_id, int planet_id); + private: + BombardOrder() = default; + /** * Preconditions: * - m_planet must be the ID of a planet @@ -428,11 +426,11 @@ class FO_COMMON_API BombardOrder : public Order { bool UndoImpl() const override; - int m_ship; - int m_planet; + int m_ship = INVALID_OBJECT_ID; + int m_planet = INVALID_OBJECT_ID; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -444,8 +442,6 @@ class FO_COMMON_API BombardOrder : public Order { class FO_COMMON_API ChangeFocusOrder : public Order { public: /** \name Structors */ //@{ - ChangeFocusOrder(); - ChangeFocusOrder(int empire, int planet, const std::string& focus); //@} @@ -455,7 +451,11 @@ class FO_COMMON_API ChangeFocusOrder : public Order { { return m_planet; } //@} + static bool Check(int empire_id, int planet_id, const std::string& focus); + private: + ChangeFocusOrder() = default; + /** * Preconditions of execute: * - the designated planet must exist, be owned by the issuing empire @@ -465,11 +465,11 @@ class FO_COMMON_API ChangeFocusOrder : public Order { */ void ExecuteImpl() const override; - int m_planet; + int m_planet = INVALID_OBJECT_ID; std::string m_focus; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -483,16 +483,14 @@ class FO_COMMON_API ChangeFocusOrder : public Order { class FO_COMMON_API ResearchQueueOrder : public Order { public: /** \name Structors */ //@{ - ResearchQueueOrder(); - ResearchQueueOrder(int empire, const std::string& tech_name); - ResearchQueueOrder(int empire, const std::string& tech_name, int position); - ResearchQueueOrder(int empire, const std::string& tech_name, bool pause, float dummy); //@} private: + ResearchQueueOrder() = default; + void ExecuteImpl() const override; std::string m_tech_name; @@ -506,7 +504,7 @@ class FO_COMMON_API ResearchQueueOrder : public Order { static const int INVALID_PAUSE_RESUME = -1; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -521,52 +519,51 @@ class FO_COMMON_API ResearchQueueOrder : public Order { * \a empire's queue. */ class FO_COMMON_API ProductionQueueOrder : public Order { public: - /** \name Structors */ //@{ - ProductionQueueOrder(); - - ProductionQueueOrder(int empire, const ProductionQueue::ProductionItem& item, int number, int location, int pos = -1); - - ProductionQueueOrder(int empire, int index, int new_quantity, bool dummy); - - ProductionQueueOrder(int empire, int index, int rally_point_id, bool dummy1, bool dummy2); - - ProductionQueueOrder(int empire, int index, int new_quantity, int new_blocksize); - - ProductionQueueOrder(int empire, int index, int new_index); - - ProductionQueueOrder(int empire, int index); + enum ProdQueueOrderAction : int { + INVALID_PROD_QUEUE_ACTION = -1, + PLACE_IN_QUEUE, + REMOVE_FROM_QUEUE, + SPLIT_INCOMPLETE, + DUPLICATE_ITEM, + SET_QUANTITY_AND_BLOCK_SIZE, + SET_QUANTITY, + MOVE_ITEM_TO_INDEX, + SET_RALLY_POINT, + PAUSE_PRODUCTION, + RESUME_PRODUCTION, + ALLOW_STOCKPILE_USE, + DISALLOW_STOCKPILE_USE, + NUM_PROD_QUEUE_ACTIONS + }; - ProductionQueueOrder(int empire, int index, bool pause, float dummy); - - ProductionQueueOrder(int empire, int index, float dummy1); - - ProductionQueueOrder(int empire, int index, float dummy1, float dummy2); + /** \name Structors */ //@{ + ProductionQueueOrder(ProdQueueOrderAction action, int empire, + const ProductionQueue::ProductionItem& item, + int number, int location, int pos = -1); + ProductionQueueOrder(ProdQueueOrderAction action, int empire, + boost::uuids::uuid uuid, + int num1 = -1, int num2 = -1); //@} private: + ProductionQueueOrder() = default; + void ExecuteImpl() const override; ProductionQueue::ProductionItem m_item; - int m_number = 0; - int m_location = INVALID_OBJECT_ID; - int m_index = INVALID_INDEX; - int m_new_quantity = INVALID_QUANTITY; - int m_new_blocksize = INVALID_QUANTITY; - int m_new_index = INVALID_INDEX; - int m_rally_point_id = INVALID_OBJECT_ID; - int m_pause = INVALID_PAUSE_RESUME; - int m_split_incomplete = INVALID_SPLIT_INCOMPLETE; - int m_dupe = INVALID_SPLIT_INCOMPLETE; + int m_location = INVALID_OBJECT_ID; + int m_new_quantity = INVALID_QUANTITY; + int m_new_blocksize = INVALID_QUANTITY; + int m_new_index = INVALID_INDEX; + int m_rally_point_id = INVALID_OBJECT_ID; + boost::uuids::uuid m_uuid, m_uuid2; + ProdQueueOrderAction m_action = INVALID_PROD_QUEUE_ACTION; static const int INVALID_INDEX = -500; static const int INVALID_QUANTITY = -1000; - static const int PAUSE = 1; - static const int RESUME = 2; - static const int INVALID_PAUSE_RESUME = -1; - static const int INVALID_SPLIT_INCOMPLETE = -1; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -588,21 +585,19 @@ class FO_COMMON_API ProductionQueueOrder : public Order { class FO_COMMON_API ShipDesignOrder : public Order { public: /** \name Structors */ //@{ - ShipDesignOrder(); - ShipDesignOrder(int empire, int existing_design_id_to_remember); - ShipDesignOrder(int empire, int design_id_to_erase, bool dummy); - ShipDesignOrder(int empire, const ShipDesign& ship_design); - - ShipDesignOrder(int empire, int existing_design_id, const std::string& new_name, const std::string& new_description = ""); + ShipDesignOrder(int empire, int existing_design_id, const std::string& new_name, + const std::string& new_description = ""); //@} int DesignID() const { return m_design_id; } private: + ShipDesignOrder(); + /** * Preconditions of execute: * - For creating a new design, the passed design is a valid reference @@ -643,7 +638,7 @@ class FO_COMMON_API ShipDesignOrder : public Order { // end details of design to create friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -656,8 +651,6 @@ class FO_COMMON_API ShipDesignOrder : public Order { class FO_COMMON_API ScrapOrder : public Order { public: /** \name Structors */ //@{ - ScrapOrder(); - ScrapOrder(int empire, int object_id); //@} @@ -667,7 +660,10 @@ class FO_COMMON_API ScrapOrder : public Order { { return m_object_id; } //@} + static bool Check(int empire_id, int object_id); private: + ScrapOrder() = default; + /** * Preconditions: * - m_object_id must be the ID of an object owned by issuing empire @@ -680,10 +676,10 @@ class FO_COMMON_API ScrapOrder : public Order { bool UndoImpl() const override; - int m_object_id; + int m_object_id = INVALID_OBJECT_ID; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -696,8 +692,6 @@ class FO_COMMON_API ScrapOrder : public Order { class FO_COMMON_API AggressiveOrder : public Order { public: /** \name Structors */ //@{ - AggressiveOrder(); - AggressiveOrder(int empire, int object_id, bool aggression = true); //@} @@ -711,7 +705,11 @@ class FO_COMMON_API AggressiveOrder : public Order { { return m_aggression; } //@} + static bool Check(int empire_id, int object_id, bool aggression); + private: + AggressiveOrder() = default; + /** * Preconditions: * - m_object_id must be the ID of an object owned by issuing empire @@ -722,11 +720,11 @@ class FO_COMMON_API AggressiveOrder : public Order { */ void ExecuteImpl() const override; - int m_object_id; - bool m_aggression; + int m_object_id = INVALID_OBJECT_ID; + bool m_aggression = false; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -739,8 +737,6 @@ class FO_COMMON_API AggressiveOrder : public Order { class FO_COMMON_API GiveObjectToEmpireOrder : public Order { public: /** \name Structors */ //@{ - GiveObjectToEmpireOrder(); - GiveObjectToEmpireOrder(int empire, int object_id, int recipient); //@} @@ -754,7 +750,10 @@ class FO_COMMON_API GiveObjectToEmpireOrder : public Order { { return m_recipient_empire_id; } //@} + static bool Check(int empire_id, int object_id, int recipient_empire_id); private: + GiveObjectToEmpireOrder() = default; + /** * Preconditions: * - m_object_id must be the ID of an object owned by issuing empire @@ -767,11 +766,11 @@ class FO_COMMON_API GiveObjectToEmpireOrder : public Order { bool UndoImpl() const override; - int m_object_id; - int m_recipient_empire_id; + int m_object_id = INVALID_OBJECT_ID; + int m_recipient_empire_id = ALL_EMPIRES; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -782,8 +781,6 @@ class FO_COMMON_API GiveObjectToEmpireOrder : public Order { class FO_COMMON_API ForgetOrder : public Order { public: /** \name Structors */ //@{ - ForgetOrder(); - ForgetOrder(int empire, int object_id); //@} @@ -794,6 +791,8 @@ class FO_COMMON_API ForgetOrder : public Order { //@} private: + ForgetOrder() = default; + /** * Preconditions: * - m_object_id must be the ID of an object not owned by issuing empire @@ -803,10 +802,10 @@ class FO_COMMON_API ForgetOrder : public Order { */ void ExecuteImpl() const override; - int m_object_id; + int m_object_id = INVALID_OBJECT_ID; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/util/OrderSet.cpp b/util/OrderSet.cpp index 6b1944986b6..67b6717ef08 100644 --- a/util/OrderSet.cpp +++ b/util/OrderSet.cpp @@ -4,15 +4,15 @@ #include "Order.h" -OrderSet::OrderSet() -{} - -const OrderPtr OrderSet::ExamineOrder(int order) const { - OrderPtr retval; - OrderMap::const_iterator it = m_orders.find(order); - if (it != m_orders.end()) - retval = it->second; - return retval; +namespace { + OrderPtr EMPTY_ORDER_PTR; +} + +OrderPtr& OrderSet::operator[](std::size_t i) { + auto it = m_orders.find(i); + if (it == m_orders.end()) + return EMPTY_ORDER_PTR; + return it->second; } int OrderSet::IssueOrder(const OrderPtr& order) @@ -22,29 +22,56 @@ int OrderSet::IssueOrder(OrderPtr&& order) { int retval = ((m_orders.rbegin() != m_orders.rend()) ? m_orders.rbegin()->first + 1 : 0); // Insert the order into the m_orders map. forward the rvalue to use the move constructor. - auto inserted = m_orders.insert(std::make_pair(retval, std::forward(order))); + auto inserted = m_orders.insert({retval, std::forward(order)}); + m_last_added_orders.insert(retval); inserted.first->second->Execute(); + TraceLogger() << "OrderSetIssueOrder m_orders size: " << m_orders.size(); + return retval; } void OrderSet::ApplyOrders() { DebugLogger() << "OrderSet::ApplyOrders() executing " << m_orders.size() << " orders"; - for (OrderMap::value_type& order : m_orders) - order.second->Execute(); + try { + for (auto& order : m_orders) + order.second->Execute(); + } catch (const std::exception& e) { + ErrorLogger() << "Caught exception executing orders: " << e.what(); + } } bool OrderSet::RescindOrder(int order) { bool retval = false; - OrderMap::iterator it = m_orders.find(order); + auto it = m_orders.find(order); if (it != m_orders.end()) { if (it->second->Undo()) { m_orders.erase(it); + m_last_deleted_orders.insert(it->first); retval = true; } } return retval; } -void OrderSet::Reset() -{ m_orders.clear(); } +void OrderSet::Reset() { + m_orders.clear(); + m_last_added_orders.clear(); + m_last_deleted_orders.clear(); +} + +std::pair> OrderSet::ExtractChanges() { + OrderSet added_orders; + for(int added : m_last_added_orders) { + auto it = m_orders.find(added); + if (it != m_orders.end()) { + added_orders.m_orders.insert(*it); + } else { + m_last_deleted_orders.insert(added); + } + } + m_last_added_orders.clear(); + std::set deleted_orders = std::move(m_last_deleted_orders); + m_last_deleted_orders.clear(); + return {added_orders, deleted_orders}; +} diff --git a/util/OrderSet.h b/util/OrderSet.h index 3ca2efcdec6..1e85229a2a5 100644 --- a/util/OrderSet.h +++ b/util/OrderSet.h @@ -9,8 +9,7 @@ #include #include -#include - +#include class Order; @@ -39,64 +38,66 @@ class FO_COMMON_API OrderSet { typedef std::map OrderMap; public: - typedef OrderMap::const_iterator const_iterator; ///< defines a public const_iterator type for OrderSet - typedef std::vector OrderVec; ///< the return type of FindOrders() - - /** \name Structors */ //@{ - OrderSet(); - //@} + typedef OrderMap::const_iterator const_iterator; + typedef OrderMap::iterator iterator; + typedef OrderMap::value_type value_type; + typedef OrderMap::size_type size_type; + typedef OrderMap::key_type key_type; + typedef OrderMap::difference_type difference_type; + typedef OrderMap::key_compare key_compare; /** \name Accessors */ //@{ - const OrderPtr ExamineOrder(int order) const; ///< returns a pointer to any order, so that it can be examined through its accessors; returns 0 if no such order exists - - /** returns all the orders that match \a pred. Predicates used with this function must take a single OrderPtr - parameter and must return bool or a type for which there is a conversion to bool.*/ - template - OrderVec FindOrders(Pred pred) const - { - OrderVec retval; - for (const OrderMap::value_type& order : m_orders) { - auto &o = order.second; - if (pred(o)) - retval.push_back(o); - } - return retval; - } - - const_iterator begin() const {return m_orders.begin();} ///< returns the begin const_iterator for the OrderSet - const_iterator end() const {return m_orders.end();} ///< returns the end const_iterator for the OrderSet + const_iterator begin() const { return m_orders.begin(); }///< returns the begin const_iterator for the OrderSet + const_iterator end() const { return m_orders.end(); } ///< returns the end const_iterator for the OrderSet + iterator begin() { return m_orders.begin(); }///< returns the begin const_iterator for the OrderSet + iterator end() { return m_orders.end(); } ///< returns the end const_iterator for the OrderSet + std::size_t size() const { return m_orders.size(); } + bool empty() const { return m_orders.empty(); } + iterator find(const key_type& k) { return m_orders.find(k); } + std::pair insert (const value_type& val) { return m_orders.insert(val); } ///< direct insert without saving changes + void erase(const key_type& k){ m_orders.erase(k); } ///< direct erase without saving changes + OrderPtr& operator[](std::size_t i); + key_compare key_comp() const { return m_orders.key_comp(); } //@} /** \name Mutators */ //@{ /** Execute the \p order immediately on the client. Store the \p order in the OrderSet to be executed later on the server. Return an index that can be used to reference the order. */ - int IssueOrder(const OrderPtr& order); - int IssueOrder(OrderPtr&& order); + int IssueOrder(const OrderPtr& order); + int IssueOrder(OrderPtr&& order); /** Applies all Orders in the OrderSet. As of this writing, this is needed only after deserializing an OrderSet client-side during game loading. */ - void ApplyOrders(); + void ApplyOrders(); /** Try to Undo() \p order and if it succeeds remove the order from the set. Return true if \p order exists and was successfully removed. */ - bool RescindOrder(int order); - void Reset(); ///< clears all orders; should be called at the beginning of a new turn + bool RescindOrder(int order); + void Reset(); ///< clears all orders; should be called at the beginning of a new turn + + std::pair> ExtractChanges(); ///< extract and clear changed orders //@} private: - OrderMap m_orders; + OrderMap m_orders; + std::set m_last_added_orders; ///< set of ids added/updated orders + std::set m_last_deleted_orders; ///< set of ids deleted orders friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; // Template Implementations -template +template void OrderSet::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_orders); + if (Archive::is_loading::value) { + m_last_added_orders.clear(); + m_last_deleted_orders.clear(); + } } #endif // _OrderSet_h_ diff --git a/util/Pending.h b/util/Pending.h new file mode 100644 index 00000000000..56ce2e2dc17 --- /dev/null +++ b/util/Pending.h @@ -0,0 +1,110 @@ +#ifndef _Pending_h_ +#define _Pending_h_ + +#include "Export.h" +#include "Logger.h" + +#include +#include + +#include +#include + +/** namespace Pending collection classes and functions used for + asynchronously parsing universe types, statistics etc.*/ +namespace Pending { + /** Pending holds the std::future results of a T.*/ + template + struct FO_COMMON_API Pending { + Pending( + boost::optional>&& pending_, + const std::string& name_ + ) : + pending(std::move(pending_)), + filename(name_) + {} + + Pending(Pending&& other) : + pending(std::move(other.pending)), + filename(std::move(other.filename)) + {} + + Pending& operator=(Pending&& other) { + pending = std::move(other.pending); + filename = std::move(other.filename); + return *this; + } + + boost::optional> pending = boost::none; + std::string filename; + }; + + /** Wait for the \p pending parse to complete. Set pending to boost::none + and return the parsed T. Return boost::none on errors.*/ + template + boost::optional WaitForPending(boost::optional>& pending) { + if (!pending) + return boost::none; + + std::future_status status; + do { + status = pending->pending->wait_for(std::chrono::seconds(1)); + if (status == std::future_status::timeout) + DebugLogger() << "Waiting for parse of \"" << pending->filename << "\" to complete."; + + if (status == std::future_status::deferred) { + ErrorLogger() << "Pending parse is unable to handle deferred future."; + throw "deferred future not handled"; + } + + } while (status != std::future_status::ready); + + try { + auto x = std::move(pending->pending->get()); + pending = boost::none; + return std::move(x); + } catch (const std::exception& e) { + ErrorLogger() << "Parsing of \"" << pending->filename << "\" failed with error: " << e.what(); + pending = boost::none; + } + + return boost::none; + } + + /** If there is a pending parse, wait for it and swap it with the stored + value. Return the stored value.*/ + template + T& SwapPending(boost::optional>& pending, T& stored) { + if (auto tt = WaitForPending(pending)) + std::swap(*tt, stored); + return stored; + } + + /** If there is a pending parse, wait for it and swap it with the stored + value. Return the stored value. + + TODO: remove this function once all of the raw pointer containers are removed. + */ + template + T& SwapPendingRawPointers(boost::optional>& pending, T& stored) { + if (auto parsed = WaitForPending(pending)) { + std::swap(*parsed, stored); + + // Don't leak old types + for (auto& entry : *parsed) + delete entry.second; + } + return stored; + } + + /** Return a Pending constructed with \p parser and \p path*/ + template + auto StartParsing(const Func& parser, const boost::filesystem::path& path) + -> Pending + { + return Pending( + std::async(std::launch::async, parser, path), path.filename().string()); + } +} + +#endif // _Pending_h_ diff --git a/util/Process.cpp b/util/Process.cpp index 13f0e302ed8..73912735fb5 100644 --- a/util/Process.cpp +++ b/util/Process.cpp @@ -23,16 +23,17 @@ class Process::Impl { ~Impl(); bool SetLowPriority(bool low); + bool Terminate(); void Kill(); void Free(); private: - bool m_free; + bool m_free = false; #if defined(FREEORION_WIN32) - STARTUPINFOW m_startup_info; - PROCESS_INFORMATION m_process_info; + STARTUPINFOW m_startup_info; + PROCESS_INFORMATION m_process_info; #elif defined(FREEORION_LINUX) || defined(FREEORION_MACOSX) - pid_t m_process_id; + pid_t m_process_id; #endif }; @@ -76,6 +77,24 @@ void Process::Kill() { RequestTermination(); } +bool Process::Terminate() { + // Early exit if already killed. + if (!m_impl && m_empty && !m_low_priority) + return true; + + bool result = true; + DebugLogger() << "Process::Terminate"; + if (m_impl) { + DebugLogger() << "Process::Terminate calling m_impl->Terminate()"; + result = m_impl->Terminate(); + } else { + DebugLogger() << "Process::Terminate found no m_impl"; + } + DebugLogger() << "Process::Terminate calling RequestTermination()"; + RequestTermination(); + return result; +} + void Process::RequestTermination() { m_impl.reset(); m_empty = true; @@ -90,9 +109,7 @@ void Process::Free() { #if defined(FREEORION_WIN32) -Process::Impl::Impl(const std::string& cmd, const std::vector& argv) : - m_free(false) -{ +Process::Impl::Impl(const std::string& cmd, const std::vector& argv) { std::wstring wcmd; std::wstring wargs; @@ -132,6 +149,12 @@ bool Process::Impl::SetLowPriority(bool low) { return (SetPriorityClass(m_process_info.hProcess, NORMAL_PRIORITY_CLASS) != 0); } +bool Process::Impl::Terminate() { + // ToDo: Use actual WinAPI termination. + Kill(); + return true; +} + void Process::Impl::Kill() { if (m_process_info.hProcess && !TerminateProcess(m_process_info.hProcess, 0)) { std::string err_str; @@ -187,9 +210,7 @@ void Process::Impl::Kill() { #include -Process::Impl::Impl(const std::string& cmd, const std::vector& argv) : - m_free(false) -{ +Process::Impl::Impl(const std::string& cmd, const std::vector& argv) { std::vector args; for (unsigned int i = 0; i < argv.size(); ++i) { args.push_back(const_cast(&(const_cast(argv[i])[0]))); @@ -223,6 +244,24 @@ bool Process::Impl::SetLowPriority(bool low) { return (setpriority(PRIO_PROCESS, m_process_id, 0) == 0); } +bool Process::Impl::Terminate() { + if (m_free) { + DebugLogger() << "Process::Impl::Terminate called but m_free is true so returning with no action"; + return true; + } + int status = -1; + DebugLogger() << "Process::Impl::Terminate calling kill(m_process_id, SIGINT)"; + kill(m_process_id, SIGINT); + DebugLogger() << "Process::Impl::Terminate calling waitpid(m_process_id, &status, 0)"; + waitpid(m_process_id, &status, 0); + DebugLogger() << "Process::Impl::Terminate done"; + if (status != 0) { + WarnLogger() << "Process::Impl::Terminate got failure status " << status; + return false; + } + return true; +} + void Process::Impl::Kill() { if (m_free) { DebugLogger() << "Process::Impl::Kill called but m_free is true so returning with no action"; @@ -230,7 +269,7 @@ void Process::Impl::Kill() { } int status; DebugLogger() << "Process::Impl::Kill calling kill(m_process_id, SIGKILL)"; - kill(m_process_id, SIGKILL); + kill(m_process_id, SIGKILL); DebugLogger() << "Process::Impl::Kill calling waitpid(m_process_id, &status, 0)"; waitpid(m_process_id, &status, 0); DebugLogger() << "Process::Impl::Kill done"; diff --git a/util/Process.h b/util/Process.h index 92cd35789f9..4afe5305057 100644 --- a/util/Process.h +++ b/util/Process.h @@ -10,9 +10,9 @@ /** Encapsulates a spawned child process in a platform-independent manner. A Process object holds a shared_ptr to the - data on the process it creates; therefore Process objects can be freely copied, with the same copy semantics as - a shared_ptr. In addition, the created process is automatically killed when its owning Process object is - destroyed, unless it is explicitly Free()d. Note that whether or not the process is explicitly Free()d, it may be + data on the process it creates; therefore Process objects can be freely copied, with the same copy semantics as + a shared_ptr. In addition, the created process is automatically killed when its owning Process object is + destroyed, unless it is explicitly Free()d. Note that whether or not the process is explicitly Free()d, it may be explicitly Kill()ed at any time.
Currently, creating processes is supported on these operating systems: @@ -54,11 +54,15 @@ class FO_COMMON_API Process { /** \name Mutators */ //@{ /** sets process priority */ - bool SetLowPriority(bool low); + bool SetLowPriority(bool low); /** kills the controlled process immediately. */ void Kill(); + /** terminate the controlled process and wait for finish. + * Return true if exit status was 0. */ + bool Terminate(); + /** kills the controlled process iff it has not been freed. */ void RequestTermination(); @@ -69,9 +73,9 @@ class FO_COMMON_API Process { private: class Impl; - std::shared_ptr m_impl; - bool m_empty; ///< true iff this is a default-constructed Process (no associated process exists) - bool m_low_priority = false; ///< true if this process is set to low priority + std::shared_ptr m_impl; + bool m_empty = false; ///< true iff this is a default-constructed Process (no associated process exists) + bool m_low_priority = false; ///< true if this process is set to low priority }; #endif // _Process_h_ diff --git a/util/RunQueue.h b/util/RunQueue.h deleted file mode 100644 index 09a6f0bce5a..00000000000 --- a/util/RunQueue.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef _Run_Queue_h_ -#define _Run_Queue_h_ - -#include -#include -#include -#include -#include -#include - -template -class RunQueue; - -template -struct ThreadQueue : public boost::noncopyable { - RunQueue* global_queue; - volatile unsigned running_queue_size; - volatile unsigned schedule_queue_size; - std::vector* running_queue; - std::vector* schedule_queue; - std::vector work_queue_1; - std::vector work_queue_2; - boost::thread thread; - - ThreadQueue(RunQueue* the_global_queue); - void operator ()(); -}; - -template -class RunQueue : public boost::noncopyable { -public: - RunQueue(unsigned n_threads); - ~RunQueue(); - void AddWork(WorkItem* item); - void Wait(boost::unique_lock& lock); - -private: - volatile bool m_terminate; - boost::shared_mutex m_schedule_mutex; - boost::condition_variable_any m_work_available; - boost::condition_variable_any m_work_done; - std::vector>> m_thread_queues; - std::vector m_transfer_queue; - volatile unsigned m_transfer_queue_size; - - friend struct ThreadQueue; - void GetTotalWorkload(unsigned& total_workload, unsigned& scheduleable_workload); - bool Schedule(ThreadQueue& requested_by); -}; - -#include "RunQueue.tcc" - -#endif // _Run_Queue_h_ diff --git a/util/RunQueue.tcc b/util/RunQueue.tcc deleted file mode 100644 index 28b978f0656..00000000000 --- a/util/RunQueue.tcc +++ /dev/null @@ -1,206 +0,0 @@ - -#include "RunQueue.h" -#include -#include -#include - -template -ThreadQueue::ThreadQueue(RunQueue* the_global_queue) : work_queue_1(), work_queue_2() { - global_queue = the_global_queue; - running_queue_size = 0U; - schedule_queue_size = 0U; - running_queue = &work_queue_1; - schedule_queue = &work_queue_2; - thread = boost::thread(boost::ref(*this)); -} - -template -void ThreadQueue::operator ()() { - while (true) { - while (running_queue_size) { - WorkItem* current_item = (*running_queue)[running_queue_size - 1]; - (*current_item)(); - delete current_item; - --running_queue_size; - } - { - boost::shared_lock schedule_lock(global_queue->m_schedule_mutex); - - while (schedule_queue_size == 0U) { - schedule_lock.unlock(); - if (global_queue->Schedule(*this)) return; - schedule_lock.lock(); - } - - unsigned const n_fetched_jobs = (std::min)(1000U, ((unsigned) schedule_queue_size + 1U)/2U); - typename std::vector::iterator const schedule_queue_end = schedule_queue->begin() + schedule_queue_size; - - if (n_fetched_jobs > running_queue->size()) - running_queue->resize(n_fetched_jobs); - - std::copy(schedule_queue_end - n_fetched_jobs, schedule_queue_end, running_queue->begin()); - schedule_queue_size -= n_fetched_jobs; - running_queue_size = n_fetched_jobs; - } - } -} - -template -RunQueue::RunQueue(unsigned n_threads) : - m_terminate (false), - m_schedule_mutex(), - m_work_available(), - m_work_done(), - m_thread_queues(), - m_transfer_queue(), - m_transfer_queue_size(0U) -{ - boost::unique_lock schedule_lock(m_schedule_mutex); - - for (unsigned i = 0U; i < n_threads; ++i) { - m_thread_queues.push_back(std::make_shared>(this)); - } -} - -template -RunQueue::~RunQueue() { - { - boost::shared_lock schedule_lock(m_schedule_mutex); - m_terminate = true; - } - m_work_available.notify_all(); - for (std::shared_ptr> thread_queue : m_thread_queues) - thread_queue->thread.join(); -} - -template -void RunQueue::AddWork(WorkItem* item) { - boost::shared_lock schedule_lock(m_schedule_mutex); - const unsigned old_transfer_queue_size = m_transfer_queue_size++; - - if (m_transfer_queue.size() < m_transfer_queue_size) - m_transfer_queue.resize(m_transfer_queue_size); - m_transfer_queue[old_transfer_queue_size] = item; - m_work_available.notify_one(); -} - -namespace { - template class scoped_unlock : public boost::noncopyable { - private: - Lockable& m_lockable; - public: - scoped_unlock(Lockable& lockable) : m_lockable(lockable) { m_lockable.unlock(); } - ~scoped_unlock() { m_lockable.lock(); } - }; -} - -template -void RunQueue::Wait(boost::unique_lock& lock) { - scoped_unlock< boost::unique_lock > wait_unlock(lock); // create before schedule_lock, destroy after schedule_lock - boost::unique_lock schedule_lock(m_schedule_mutex); // create after wait_unlock, destroy before wait_unlock - - while (true) { - unsigned total_workload, scheduleable_workload; - - GetTotalWorkload(total_workload, scheduleable_workload); - if (total_workload == 0) break; - - if (scheduleable_workload > 0) m_work_available.notify_one(); - m_work_done.wait(schedule_lock); // m_schedule_mutex is unlocked by wait() while the thread waits - } -} - -template -bool RunQueue::Schedule(ThreadQueue& requested_by) { - boost::unique_lock schedule_lock(m_schedule_mutex); - unsigned total_workload; - - while (true) { - bool new_work_scheduled; - - while (true) { - unsigned scheduleable_workload; - - // did we get work while waiting for the lock? -> early out - if (requested_by.schedule_queue_size > 0U) - return m_terminate; - - GetTotalWorkload(total_workload, scheduleable_workload); - // no point scheduling when there isn't at least one scheduleable work item - if (scheduleable_workload > 0) - break; - if (total_workload == 0) - m_work_done.notify_all(); - - // all done here, but never wait for work when we shall terminate - if (m_terminate) - return true; - - m_work_available.wait(schedule_lock); // m_schedule_mutex is unlocked by wait() while the thread waits - } - - new_work_scheduled = false; - for (unsigned balancing_threshold = total_workload; true; balancing_threshold += m_transfer_queue_size) { - unsigned threads_to_be_scheduled = m_thread_queues.size(); - - for (std::shared_ptr> thread_queue : m_thread_queues) { - const unsigned old_schedule_queue_size = thread_queue->schedule_queue_size; - const unsigned running_queue_size = thread_queue->running_queue_size; // running_queue_size might be accessed concurrently - const unsigned old_transfer_queue_size = m_transfer_queue_size; - const unsigned target_thread_workload = (balancing_threshold + threads_to_be_scheduled - 1) / threads_to_be_scheduled; // round up in order to avoid infinite loop when the last thread cant take more work - const unsigned new_schedule_queue_size = std::min(std::max(running_queue_size, target_thread_workload) - running_queue_size, old_schedule_queue_size + old_transfer_queue_size); - std::vector& schedule_queue = *(thread_queue->schedule_queue); - typename std::vector::iterator schedule_queue_begin = schedule_queue.begin(); - typename std::vector::iterator transfer_queue_begin = m_transfer_queue.begin(); - - if (new_schedule_queue_size < old_schedule_queue_size) { - m_transfer_queue_size += (old_schedule_queue_size - new_schedule_queue_size); - if (m_transfer_queue_size > m_transfer_queue.size()) - m_transfer_queue.resize(m_transfer_queue_size); - schedule_queue_begin = schedule_queue.begin(); - transfer_queue_begin = m_transfer_queue.begin(); - - std::copy(schedule_queue_begin + new_schedule_queue_size, schedule_queue_begin + old_schedule_queue_size, transfer_queue_begin + old_transfer_queue_size); - } else if (new_schedule_queue_size > old_schedule_queue_size) { - new_work_scheduled = true; - - m_transfer_queue_size -= (new_schedule_queue_size - old_schedule_queue_size); - if (new_schedule_queue_size > schedule_queue.size()) - schedule_queue.resize(new_schedule_queue_size); - schedule_queue_begin = schedule_queue.begin(); - transfer_queue_begin = m_transfer_queue.begin(); - - std::copy(transfer_queue_begin + m_transfer_queue_size, transfer_queue_begin + old_transfer_queue_size, schedule_queue_begin + old_schedule_queue_size); - } - thread_queue->schedule_queue_size = new_schedule_queue_size; - --threads_to_be_scheduled; - } - if (m_transfer_queue_size == 0U) break; - } - // did anay thread receive new work? -> notify it by notifying all - if (new_work_scheduled) - m_work_available.notify_all(); - - // did we manage to schedule work for ourselves? -> done here - if (requested_by.schedule_queue_size > 0U) - return m_terminate; - - // nothing more we can do here, but never wait for work when we shall terminate - if (m_terminate) - return true; - - m_work_available.wait(schedule_lock); // m_schedule_mutex is unlocked by wait() while the thread waits - } // new work -> start over and try if we got or can get some of it - - return false; // should be unreachable -} - -template -void RunQueue::GetTotalWorkload(unsigned& total_workload, unsigned& scheduleable_workload) { - total_workload = scheduleable_workload = m_transfer_queue_size; - - for (std::shared_ptr> thread_queue : m_thread_queues) { - scheduleable_workload += thread_queue->schedule_queue_size; - total_workload += thread_queue->schedule_queue_size + thread_queue->running_queue_size; // running_queue_size might be accessed concurrently - } -} diff --git a/util/SaveGamePreviewUtils.cpp b/util/SaveGamePreviewUtils.cpp index 1344b5e18dd..0f1f1338d37 100644 --- a/util/SaveGamePreviewUtils.cpp +++ b/util/SaveGamePreviewUtils.cpp @@ -4,7 +4,6 @@ #include "i18n.h" #include "Directories.h" #include "Logger.h" -#include "MultiplayerCommon.h" #include "EnumText.h" #include "Serialize.h" #include "Serialize.ipp" @@ -32,10 +31,12 @@ namespace { const std::string XML_SAVE_FILE_DESCRIPTION("This is an XML archive FreeOrion saved game. Initial header information is uncompressed. The main gamestate information follows, possibly stored as zlib-comprssed XML archive in the last entry in the main archive."); const std::string BIN_SAVE_FILE_DESCRIPTION("This is binary archive FreeOrion saved game."); + const std::string XML_COMPRESSED_MARKER("zlib-xml"); + /// Splits time and date on separate lines for an ISO datetime string std::string split_time(const std::string& time) { std::string result = time; - std::string::size_type pos = result.find("T"); + std::string::size_type pos = result.find('T'); if (pos != std::string::npos) { result.replace(pos, 1, "\n"); } @@ -52,7 +53,7 @@ namespace { fs::ifstream ifs(path, std::ios_base::binary); - full.filename = PathString(path.filename()); + full.filename = PathToString(path.filename()); if (!ifs) throw std::runtime_error(UNABLE_TO_OPEN_FILE); @@ -64,24 +65,27 @@ namespace { DebugLogger() << "LoadSaveGamePreviewData: Loading preview from: " << path.string(); try { - try { - ScopedTimer timer("LoadSaveGamePreviewData (binary): " + path.string(), true); - + // read the first five letters of the stream and check if it is opening an xml file + std::string xxx5(5, ' '); + ifs.read(&xxx5[0], 5); + const std::string xml5{"> BOOST_SERIALIZATION_NVP(save_preview_data); ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data); - } catch (...) { - // if binary deserialization failed, try more-portable XML deserialization - - // reset to start of stream (attempted binary serialization will have consumed some input...) - boost::iostreams::seek(ifs, 0, std::ios_base::beg); - - DebugLogger() << "Deserializing XML data"; + } else { freeorion_xml_iarchive ia(ifs); ia >> BOOST_SERIALIZATION_NVP(save_preview_data); + + if (BOOST_VERSION >= 106600 && save_preview_data.save_format_marker == XML_COMPRESSED_MARKER) + throw std::invalid_argument("Save Format Not Compatible with Boost Version " BOOST_LIB_VERSION); + ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data); } @@ -121,7 +125,7 @@ bool SaveGamePreviewData::Valid() const void SaveGamePreviewData::SetBinary(bool bin) { description = bin ? BIN_SAVE_FILE_DESCRIPTION : XML_SAVE_FILE_DESCRIPTION; } -template +template void SaveGamePreviewData::serialize(Archive& ar, unsigned int version) { if (version >= 2) { @@ -155,7 +159,8 @@ template void SaveGamePreviewData::serialize(freeorion_b template void SaveGamePreviewData::serialize(freeorion_xml_oarchive&, unsigned int); template void SaveGamePreviewData::serialize(freeorion_xml_iarchive&, unsigned int); -template + +template void FullPreview::serialize(Archive& ar, unsigned int version) { ar & BOOST_SERIALIZATION_NVP(filename) @@ -182,6 +187,63 @@ template void PreviewInformation::serialize(freeorion_bi template void PreviewInformation::serialize(freeorion_xml_oarchive&, const unsigned int); template void PreviewInformation::serialize(freeorion_xml_iarchive&, const unsigned int); + +bool SaveFileWithValidHeader(const boost::filesystem::path& path) { + if (!fs::exists(path)) + return false; + + fs::ifstream ifs(path, std::ios_base::binary); + if (!ifs) + return false; + + // dummy holders for deserialized data + SaveGamePreviewData ignored_save_preview_data; + GalaxySetupData ignored_galaxy_setup_data; + ServerSaveGameData ignored_server_save_game_data; + std::vector ignored_player_save_header_data; + std::map ignored_empire_save_game_data; + + DebugLogger() << "SaveFileWithValidHeader: Loading headers from: " << path.string(); + try { + // read the first five letters of the stream and check if it is opening an xml file + std::string xxx5(5, ' '); + ifs.read(&xxx5[0], 5); + const std::string xml5{"> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); + ia >> BOOST_SERIALIZATION_NVP(ignored_galaxy_setup_data); + ia >> BOOST_SERIALIZATION_NVP(ignored_server_save_game_data); + ia >> BOOST_SERIALIZATION_NVP(ignored_player_save_header_data); + ia >> BOOST_SERIALIZATION_NVP(ignored_empire_save_game_data); + } else { + DebugLogger() << "Deserializing XML data"; + freeorion_xml_iarchive ia(ifs); + + ia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); + + if (BOOST_VERSION >= 106600 && ignored_save_preview_data.save_format_marker == XML_COMPRESSED_MARKER) + throw std::invalid_argument("Save Format Not Compatible with Boost Version " BOOST_LIB_VERSION); + + ia >> BOOST_SERIALIZATION_NVP(ignored_galaxy_setup_data); + ia >> BOOST_SERIALIZATION_NVP(ignored_server_save_game_data); + ia >> BOOST_SERIALIZATION_NVP(ignored_player_save_header_data); + ia >> BOOST_SERIALIZATION_NVP(ignored_empire_save_game_data); + } + + } catch (const std::exception& e) { + ErrorLogger() << "SaveFileWithValidHeader: Failed to read headers of " << path.string() << " because: " << e.what(); + return false; + } + return true; +} + std::string ColumnInPreview(const FullPreview& full, const std::string& name, bool thin) { if (name == "player") { return full.preview.main_player_name; @@ -234,7 +296,8 @@ void LoadSaveGamePreviews(const fs::path& orig_path, const std::string& extensio fs::path path = orig_path; // Relative path relative to the save directory if (path.is_relative()) { - path = GetSaveDir() / path; + ErrorLogger() << "LoadSaveGamePreviews: supplied path must not be relative, \"" << path << "\" "; + return; } if (!fs::exists(path)) { @@ -248,7 +311,6 @@ void LoadSaveGamePreviews(const fs::path& orig_path, const std::string& extensio for (fs::directory_iterator it(path); it != end_it; ++it) { try { - std::string filename = PathString(it->path().filename()); if ((it->path().filename().extension() == extension) && !fs::is_directory(it->path())) { if (LoadSaveGamePreviewData(*it, data)) { // Add preview entry to list @@ -260,21 +322,3 @@ void LoadSaveGamePreviews(const fs::path& orig_path, const std::string& extensio } } } - -bool IsInside(const fs::path& path, const fs::path& directory) { - const fs::path target = fs::canonical(directory); - - if (!path.has_parent_path()) { - return false; - } - - fs::path cur = path.parent_path(); - while (cur.has_parent_path()) { - if (cur == target) { - return true; - } else { - cur = cur.parent_path(); - } - } - return false; -} diff --git a/util/SaveGamePreviewUtils.h b/util/SaveGamePreviewUtils.h index ca97a6aa83c..ee3f9dc8985 100644 --- a/util/SaveGamePreviewUtils.h +++ b/util/SaveGamePreviewUtils.h @@ -43,7 +43,7 @@ struct FO_COMMON_API SaveGamePreviewData { unsigned int uncompressed_text_size = 0; /// How many bytes capacity does the uncompressed save text take up? (ie. the part that was / will be compressed with zlib for compressed xml format saves) unsigned int compressed_text_size = 0; /// How many bytes capacity does the compressed save text take up? - template + template void serialize(Archive& ar, unsigned int version); }; @@ -61,7 +61,7 @@ struct FO_COMMON_API FullPreview { GalaxySetupData galaxy; private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; @@ -72,10 +72,15 @@ struct FO_COMMON_API PreviewInformation { std::vector previews; /// The previews of the saves in this folder private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; +/// Attempts to load headers of a save file. +/// Returns true on success, false if header data could not be loaded +/// @param path Filename to load headers of +FO_COMMON_API bool SaveFileWithValidHeader(const boost::filesystem::path& path); + /// Get the value of column name in this preview /// @param full FullPreview to match for column @p name /// \param name The name of the column @@ -88,8 +93,4 @@ FO_COMMON_API std::string ColumnInPreview(const FullPreview& full, const std::st /// \param [out] previews The previews will be put here FO_COMMON_API void LoadSaveGamePreviews(const boost::filesystem::path& path, const std::string& extension, std::vector& previews); - -/// If path is inside directory, returns true -FO_COMMON_API bool IsInside(const boost::filesystem::path& path, const boost::filesystem::path& directory); - #endif // SAVEGAMEPREVIEW_H diff --git a/util/ScopedTimer.cpp b/util/ScopedTimer.cpp index cad22353bfe..f5383b01dd9 100644 --- a/util/ScopedTimer.cpp +++ b/util/ScopedTimer.cpp @@ -2,15 +2,18 @@ #include "Logger.h" -#include +#include #include #include +namespace { + DeclareThreadSafeLogger(timer); +} + class ScopedTimer::Impl { public: - Impl(const std::string& timed_name, bool enable_output, - std::chrono::microseconds threshold) : + Impl(const std::string& timed_name, bool enable_output, std::chrono::microseconds threshold) : m_start(std::chrono::high_resolution_clock::now()), m_name(timed_name), m_enable_output(enable_output), @@ -18,33 +21,81 @@ class ScopedTimer::Impl { {} ~Impl() { - std::chrono::nanoseconds duration = std::chrono::high_resolution_clock::now() - m_start; - if (!ShouldOutput(duration)) + if (!m_enable_output) + return; + + std::chrono::nanoseconds duration{std::chrono::high_resolution_clock::now() - m_start}; + + if (duration < m_threshold) return; std::stringstream ss; ss << m_name << " time: "; FormatDuration(ss, duration); - DebugLogger() << ss.str(); + DebugLogger(timer) << ss.str(); } - bool ShouldOutput(const std::chrono::nanoseconds & duration) { - return ((duration >= m_threshold) && m_enable_output ); + void Restart() + { m_start = std::chrono::high_resolution_clock::now(); } + + template + double Duration() const { + using ns = std::chrono::nanoseconds; + ns duration{std::chrono::high_resolution_clock::now() - m_start}; + return static_cast(duration.count()) / ns::period::den * ns::period::num / UNITS::period::num * UNITS::period::den; + } + + std::string DurationString() const { + std::stringstream ss; + std::chrono::nanoseconds duration{std::chrono::high_resolution_clock::now() - m_start}; + FormatDuration(ss, duration); + return ss.str(); } - void FormatDuration(std::stringstream& ss, const std::chrono::nanoseconds & duration) { + template + static void FormatDurationFixedUnits(std::stringstream& ss, const std::chrono::nanoseconds& duration) { + ss << std::setw(8) << std::right << std::chrono::duration_cast(duration).count(); + if (std::is_same()) + ss << " s"; + else if (std::is_same()) + ss << " ms"; + else if (std::is_same()) + ss << " µs"; + else if (std::is_same()) + ss << " ns"; + } + + static void FormatDuration(std::stringstream& ss, const std::chrono::nanoseconds& duration) { ss << std::setw(8) << std::right; - if (duration >= std::chrono::milliseconds(10)) + if (duration >= std::chrono::seconds(10)) { + ss << std::chrono::duration_cast(duration).count() << " s"; + + } else if (duration >= std::chrono::seconds(10)) { + auto ms{std::chrono::duration_cast(duration).count()}; + ss << (ms / 100) / 10.0 << " s"; // round to 10ths of seconds + + } else if (duration >= std::chrono::milliseconds(100)) { ss << std::chrono::duration_cast(duration).count() << " ms"; - else if (duration >= std::chrono::microseconds(10)) + + } else if (duration >= std::chrono::milliseconds(10)) { + auto ms{std::chrono::duration_cast(duration).count()}; + ss << (ms / 100) / 10.0 << " ms"; // round to 10ths of milliseconds + + } else if (duration >= std::chrono::microseconds(100)) { ss << std::chrono::duration_cast(duration).count() << " µs"; - else + + } else if (duration >= std::chrono::microseconds(10)) { + auto ns{std::chrono::duration_cast(duration).count()}; + ss << (ns / 100) / 10.0 << " µs"; // round to 10ths of microseconds + + } else { ss << std::chrono::duration_cast(duration).count() << " ns"; + } } std::chrono::high_resolution_clock::time_point m_start; - std::string m_name; - bool m_enable_output; + std::string m_name; + bool m_enable_output; std::chrono::microseconds m_threshold; }; @@ -53,32 +104,45 @@ ScopedTimer::ScopedTimer(const std::string& timed_name, bool enable_output, m_impl(new Impl(timed_name, enable_output, threshold)) {} -ScopedTimer::ScopedTimer(const std::string& timed_name, std::chrono::microseconds threshold) : +ScopedTimer::ScopedTimer(const std::string& timed_name, + std::chrono::microseconds threshold) : m_impl(new Impl(timed_name, true, threshold)) {} -// ~ScopedTimer is required because Impl is defined here. +//! @note +//! ~ScopedTimer is required because Impl is defined here. ScopedTimer::~ScopedTimer() {} +void ScopedTimer::restart() +{ m_impl->Restart(); } +double ScopedTimer::duration() const +{ return m_impl->Duration(); } + +std::string ScopedTimer::DurationString() const +{ return m_impl->DurationString(); } -class SectionedScopedTimer::Impl : public ScopedTimer::Impl { + +class SectionedScopedTimer::Impl : public ScopedTimer::Impl { /** Sections store a time and a duration for each section of the elapsed time report.*/ struct Sections { - Sections(const std::chrono::high_resolution_clock::time_point &now, + Sections(const std::chrono::high_resolution_clock::time_point& now, const std::chrono::nanoseconds& time_from_start) : - m_table(), m_section_start(now), m_curr(), m_section_names() + m_table(), + m_section_start(now), + m_curr(), + m_section_names() { // Create a dummy "" section so that m_curr is always a valid iterator. - std::pair curr = m_table.insert(std::make_pair("", time_from_start)); + auto curr = m_table.insert({"", time_from_start}); m_curr = curr.first; } /** Add time to the current section and then setup the new section. */ - void Accumulate(const std::chrono::high_resolution_clock::time_point &now, - const std::string & section_name) + void Accumulate(const std::chrono::high_resolution_clock::time_point& now, + const std::string& section_name) { if (m_curr->first == section_name) return; @@ -88,9 +152,8 @@ class SectionedScopedTimer::Impl : public ScopedTimer::Impl { m_section_start = now; // Create a new section if needed and update m_curr. - std::pair maybe_new = m_table.insert( - std::make_pair(section_name, - std::chrono::high_resolution_clock::duration::zero())); + auto maybe_new = m_table.insert( + {section_name, std::chrono::high_resolution_clock::duration::zero()}); m_curr = maybe_new.first; // Insert succeed, so grab the new section name. @@ -100,7 +163,7 @@ class SectionedScopedTimer::Impl : public ScopedTimer::Impl { } //Table of section durations - typedef boost::unordered_map SectionTable; + typedef std::unordered_map SectionTable; SectionTable m_table; // Currently running section start time and iterator @@ -112,60 +175,72 @@ class SectionedScopedTimer::Impl : public ScopedTimer::Impl { // Names of the sections in order or creation. std::vector m_section_names; - }; public: - Impl(const std::string& timed_name, bool enable_output, - std::chrono::microseconds threshold) : - ScopedTimer::Impl(timed_name, enable_output, threshold) + Impl(const std::string& timed_name, std::chrono::microseconds threshold, + bool enable_output, bool unify_section_duration_units) : + ScopedTimer::Impl(timed_name, enable_output, threshold), + m_unify_units(unify_section_duration_units) {} /** The destructor will print the table of accumulated times. */ ~Impl() { + if (!m_enable_output || !m_sections) + return; + std::chrono::nanoseconds duration = std::chrono::high_resolution_clock::now() - m_start; - if (!ShouldOutput(duration)) + if (duration < m_threshold) return; - // No table so use basic ScopedTimer output. - if (!m_sections) { - std::stringstream ss; - ss << m_name << " time: "; - ScopedTimer::Impl::FormatDuration(ss, duration); - DebugLogger() << ss.str(); - return; - } + EnterSection(""); // Stop the final section + + if (m_sections->m_section_names.size() == 1 && *m_sections->m_section_names.begin() == "") + return; // Don't print the table if the only section is the default section - //Stop the final section. - EnterSection(""); //Print the section times followed by the total time elapsed. - // Find the longest name to right align the times. + // Find the longest name to right align the times and longest time to align the units size_t longest_section_name(0); - for (const std::string& section_name : m_sections->m_section_names) { - longest_section_name = std::max(longest_section_name, section_name.size()); + std::chrono::nanoseconds longest_section_duration(0); + for (const auto& section : m_sections->m_table) { + longest_section_name = std::max(longest_section_name, section.first.size()); + longest_section_duration = std::max(longest_section_duration, section.second); } + // Output section names and times in order they were created for (const std::string& section_name : m_sections->m_section_names) { - Sections::SectionTable::const_iterator jt = m_sections->m_table.find(section_name); + auto jt = m_sections->m_table.find(section_name); if (jt == m_sections->m_table.end()) { - ErrorLogger() << "Missing section " << section_name << " in section table."; + ErrorLogger(timer) << "Missing section " << section_name << " in section table."; continue; } - if (!ShouldOutput(jt->second)) + // is duration yet long enough to output? + if (jt->second < m_threshold) continue; // Create a header with padding, so all times align. std::stringstream header, tail; - FormatDuration(tail, jt->second); + if (m_unify_units) { + if (longest_section_duration < std::chrono::microseconds(10)) + FormatDurationFixedUnits(tail, jt->second); + else if (longest_section_duration < std::chrono::milliseconds(10)) + FormatDurationFixedUnits(tail, jt->second); + else if (longest_section_duration < std::chrono::seconds(10)) + FormatDurationFixedUnits(tail, jt->second); + else + FormatDurationFixedUnits(tail, jt->second); + } else { + FormatDuration(tail, jt->second); + } header << m_name << " - " << std::setw(longest_section_name) << std::left << section_name << std::right << " time: " << tail.str(); - DebugLogger() << header.str(); + DebugLogger(timer) << header.str(); } // Create a header with padding, so all times align. @@ -175,14 +250,14 @@ class SectionedScopedTimer::Impl : public ScopedTimer::Impl { << std::setw(longest_section_name + 3 + 7) << std::right << " time: " << tail.str(); - DebugLogger() << header.str(); + DebugLogger(timer) << header.str(); // Prevent the base class from outputting a duplicate total time. m_enable_output = false; } void EnterSection(const std::string& section_name) { - std::chrono::high_resolution_clock::time_point now(std::chrono::high_resolution_clock::now()); + auto now(std::chrono::high_resolution_clock::now()); // One time initialization. if (!m_sections) @@ -191,8 +266,9 @@ class SectionedScopedTimer::Impl : public ScopedTimer::Impl { m_sections->Accumulate(now, section_name); } +private: /** CreateSections allow m_sections to only be initialized if it is used.*/ - Sections* CreateSections(const std::chrono::high_resolution_clock::time_point &now) { + Sections* CreateSections(const std::chrono::high_resolution_clock::time_point& now) { m_sections.reset(new Sections(now, now - m_start)); return m_sections.get(); } @@ -202,15 +278,14 @@ class SectionedScopedTimer::Impl : public ScopedTimer::Impl { // section-less timer. std::unique_ptr m_sections; + bool m_unify_units = false; }; -SectionedScopedTimer::SectionedScopedTimer(const std::string& timed_name, bool enable_output, - std::chrono::microseconds threshold) : - m_impl(new Impl(timed_name, enable_output, threshold)) -{} - -SectionedScopedTimer::SectionedScopedTimer(const std::string& timed_name, std::chrono::microseconds threshold) : - m_impl(new Impl(timed_name, true, threshold)) +SectionedScopedTimer::SectionedScopedTimer(const std::string& timed_name, + std::chrono::microseconds threshold, + bool enable_output, + bool unify_section_duration_units) : + m_impl(new Impl(timed_name, threshold, enable_output, unify_section_duration_units)) {} // ~SectionedScopedTimer is required because Impl is defined here. diff --git a/util/ScopedTimer.h b/util/ScopedTimer.h index 1d63fe9f48f..120619214cb 100644 --- a/util/ScopedTimer.h +++ b/util/ScopedTimer.h @@ -8,93 +8,108 @@ #include -/** Outputs time during which this object existed. - Created in the scope of a function, and passed the appropriate - name, it will output to DebugLogger() the time elapsed while - the function was executing. - - If \p enable_output is true and duration is greater than threshold then - print output. -*/ +//! Logs period during which this object existed. +//! +//! Created in the scope of a function, and passed the appropriate name, it will +//! output to DebugLogger() the time elapsed while the function was executing. +//! +//! If @p enable_output is true and duration is greater than threshold then +//! print output. class FO_COMMON_API ScopedTimer { public: - ScopedTimer(const std::string& timed_name, bool enable_output = false, - std::chrono::microseconds threshold = std::chrono::milliseconds(1)); + explicit ScopedTimer(const std::string& timed_name = "", bool enable_output = false, + std::chrono::microseconds threshold = std::chrono::milliseconds(1)); ScopedTimer(const std::string& timed_name, std::chrono::microseconds threshold); ~ScopedTimer(); + void restart(); + + double duration() const; + std::string DurationString() const; + class Impl; private: std::unique_ptr const m_impl; }; -/** Similar to ScopedTimer SectionedScopedTimer times the duration of its own existence. It also - allows the creation of sub section timers. Timers are created by calling EnterSection(name). - - Only one sub-section timer is active at any time. - - Timers can be re-entered, which is effective to profile looping structures. - - Any name is valid except an empty string. Calling EnterSection("") will stop the currently - running sub section timer. The total time will still keep running. - - When SectionedScopedTimer goes out of scope it will print a table of its elapsed time and the - sub sections. - - It only prints times if the time is greater than the threshold. Each section is only printed - if its time is greater than the threshold. The whole table is only printed if the total time - is greater than the threshold. - - The tables of times will look like the following: - - Title - Section time: xxxx ms - Title - Section2 time: xxxx ms - Title time: xxxx ms - - The following is a usage example. It shows how to create timer with a section before a loop, - two sections in a loop, a section after a loop and a section that will not be timed. - - Usage: - void function_to_profile () { - - SectionedScopedTimer timer("Title", std::chrono::milliseconds(1)); - timer.EnterSection("initial section"); - - profiled_code(); - - for() { - timer.EnterSection("for loop 1 section"); - - more_code(); - - timer.EnterSection("for loop 2 section"); - - even_more_code(); - } - - timer.EnterSection(""); // don't time this section - - untimed_code(); - - timer.EnterSection("final section"); - - final_timed_code() - } - -*/ +//! Logs period during which a this object or named sub sections existed. +//! +//! Similar to ScopedTimer SectionedScopedTimer times the duration of its own +//! existence. It also allows the creation of sub section timers. Timers are +//! created by calling EnterSection(). +//! +//! Only one sub-section timer is active at any time. +//! +//! Timers can be re-entered, which is effective to profile looping structures. +//! +//! Any name is valid except an empty string. Calling EnterSection("") will +//! stop the currently running sub section timer. The total time will still +//! keep running. +//! +//! When SectionedScopedTimer goes out of scope it will print a table of its +//! elapsed time and the sub sections. +//! +//! It only prints times if the time is greater than the threshold. Each +//! section is only printed if its time is greater than the threshold. The +//! whole table is only printed if the total time is greater than the threshold. +//! +//! The following is a usage example. It shows how to create timer with a +//! section before a loop, two sections in a loop, a section after a loop and a +//! section that will not be timed. +//! +//! ```{.cpp} +//! void function_to_profile () { +//! +//! SectionedScopedTimer timer("Title"); +//! timer.EnterSection("initial section"); +//! +//! profiled_code(); +//! +//! for () { +//! timer.EnterSection("for loop 1 section"); +//! +//! more_code(); +//! +//! timer.EnterSection("for loop 2 section"); +//! +//! even_more_code(); +//! } +//! +//! timer.EnterSection(""); // don't time this section +//! +//! untimed_code(); +//! +//! timer.EnterSection("final section"); +//! +//! final_timed_code() +//! } +//! ``` +//! +//! The tables of times will look like the following: +//! +//! ```{.txt} +//! Title - initial section time: xxxx ms +//! Title - for loop 1 section time: xxxx ms +//! Title - for loop 2 section time: xxxx ms +//! Title - final section time: xxxx ms +//! Title time: xxxx ms +//! ``` class FO_COMMON_API SectionedScopedTimer { public: - SectionedScopedTimer(const std::string& timed_name, bool enable_output = false, - std::chrono::microseconds threshold = std::chrono::milliseconds(1)); - SectionedScopedTimer(const std::string& timed_name, std::chrono::microseconds threshold); + explicit SectionedScopedTimer(const std::string& timed_name, + std::chrono::microseconds threshold = std::chrono::milliseconds(1), + bool enable_output = true, + bool unify_section_duration_units = true); ~SectionedScopedTimer(); - /** Start recording times for \p section_name. - This can be called multiple times to add more time to a previously created section. Use this - feature to profile separate parts of loop strucures. */ + //! Start recording times for @p section_name. + //! + //! This can be called multiple times to add more time to a previously + //! created section. Use this feature to profile separate parts of loop + //! strucures. void EnterSection(const std::string& section_name); private: @@ -103,4 +118,4 @@ class FO_COMMON_API SectionedScopedTimer { std::unique_ptr const m_impl; }; -#endif // _MultiplayerCommon_h_ +#endif // _ScopedTimer_h_ diff --git a/util/Serialize.h b/util/Serialize.h index f30a5ad97b2..5e55d66da02 100644 --- a/util/Serialize.h +++ b/util/Serialize.h @@ -11,7 +11,6 @@ #include "Export.h" class OrderSet; -class PathingEngine; class Universe; class UniverseObject; @@ -20,39 +19,35 @@ typedef boost::archive::binary_oarchive freeorion_bin_oarchive; typedef boost::archive::xml_iarchive freeorion_xml_iarchive; typedef boost::archive::xml_oarchive freeorion_xml_oarchive; -// NB: Do not try to serialize types that contain longs, since longs are different sizes on 32- and 64-bit -// architectures. Replace your longs with long longs for portability. See longer note in Serialize.cpp for more info. +//! @warning +//! Do not try to serialize types that contain longs, since longs are +//! different sizes on 32- and 64-bit architectures. Replace your longs +//! with long longs for portability. It would seem that short of writing +//! some Boost.Serialization archive that handles longs portably, we cannot +//! transmit longs across machines with different bit-size architectures. -/** Serializes \a universe to output archive \a oa. */ -template +//! Serialize @p universe to output archive @p oa. +template FO_COMMON_API void Serialize(Archive& oa, const Universe& universe); -/** Serializes \a object_map to output archive \a oa. */ -template +//! Serialize @p object_map to output archive @p oa. +template void Serialize(Archive& oa, const std::map>& objects); -/** Serializes \a order_set to output archive \a oa. */ -template +//! Serialize @p order_set to output archive @p oa. +template void Serialize(Archive& oa, const OrderSet& order_set); -/** Serializes \a pathing_engine to output archive \a oa. */ -template -void Serialize(Archive& oa, const PathingEngine& pathing_engine); - -/** Deserializes \a universe from input archive \a ia. */ -template +//! Deserialize @p universe from input archive @p ia. +template FO_COMMON_API void Deserialize(Archive& ia, Universe& universe); -/** Serializes \a object_map from input archive \a ia. */ -template +//! Deserialize @p object_map from input archive @p ia. +template void Deserialize(Archive& ia, std::map>& objects); -/** Deserializes \a order_set from input archive \a ia. */ -template +//! Deserialize @p order_set from input archive @p ia. +template void Deserialize(Archive& ia, OrderSet& order_set); -/** Deserializes \a pathing_engine from input archive \a ia. */ -template -void Deserialize(Archive& ia, PathingEngine& pathing_engine); - #endif // _Serialize_h_ diff --git a/util/Serialize.ipp b/util/Serialize.ipp index 75f75d19dcb..fd73345efcc 100644 --- a/util/Serialize.ipp +++ b/util/Serialize.ipp @@ -2,19 +2,10 @@ #undef int64_t #endif -#include -#include #include -#if BOOST_VERSION == 105600 -// HACK: The following two includes work around a bug in boost 1.56, -#include // This -#include //This -#endif - #if BOOST_VERSION == 105800 // HACK: The following two includes work around a bug in boost 1.58 -#include #include #endif @@ -22,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -29,40 +21,13 @@ #include #include #include - -// disabling these tests as they reportedly cause problems on some systems -// and binary serialization portability is apparently broken regardless of -// whether these tests pass, as far as I'm aware. -#if 0 -// some endianness and size checks to ensure portability of binary save files; -// of one or more of these fails, it means that FreeOrion is not supported on -// your platform/compiler pair, and must be modified to provide data of the -// appropriate size(s). -#ifndef BOOST_LITTLE_ENDIAN -# error "Incompatible endianness for binary serialization." -#endif -BOOST_STATIC_ASSERT(sizeof(char) == 1); -BOOST_STATIC_ASSERT(sizeof(short) == 2); -BOOST_STATIC_ASSERT(sizeof(int) == 4); -BOOST_STATIC_ASSERT(sizeof(long long) == 8); -BOOST_STATIC_ASSERT(sizeof(float) == 4); -BOOST_STATIC_ASSERT(sizeof(double) == 8); - -// This is commented out, but left here by way of explanation. This assert is -// the only one that seems to fail on 64-bit systems. It would seem that -// short of writing some Boost.Serialization archive that handles longs -// portably, we cannot transmit longs across machines with different bit-size -// architectures. So, don't use longs -- use long longs instead if you need -// something bigger than an int for some reason. -//BOOST_STATIC_ASSERT(sizeof(long) == 4); -#endif - +#include #include namespace boost { namespace serialization { - template + template void serialize(Archive& ar, GG::Clr& clr, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(clr.r) diff --git a/util/SerializeEmpire.cpp b/util/SerializeEmpire.cpp index 356cc50c691..9b359705bfc 100644 --- a/util/SerializeEmpire.cpp +++ b/util/SerializeEmpire.cpp @@ -8,10 +8,14 @@ #include "../Empire/Diplomacy.h" #include "../universe/Universe.h" +#include "GameRules.h" + #include "Serialize.ipp" +#include +#include -template +template void ResearchQueue::Element::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(name) @@ -26,7 +30,7 @@ template void ResearchQueue::Element::serialize(freeorio template void ResearchQueue::Element::serialize(freeorion_xml_oarchive&, const unsigned int); template void ResearchQueue::Element::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void ResearchQueue::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_queue) @@ -40,7 +44,7 @@ template void ResearchQueue::serialize(freeorion_bin_iar template void ResearchQueue::serialize(freeorion_xml_oarchive&, const unsigned int); template void ResearchQueue::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void ProductionQueue::ProductionItem::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(build_type) @@ -53,7 +57,7 @@ template void ProductionQueue::ProductionItem::serialize template void ProductionQueue::ProductionItem::serialize(freeorion_xml_oarchive&, const unsigned int); template void ProductionQueue::ProductionItem::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void ProductionQueue::Element::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(item) @@ -69,20 +73,48 @@ void ProductionQueue::Element::serialize(Archive& ar, const unsigned int version & BOOST_SERIALIZATION_NVP(turns_left_to_next_item) & BOOST_SERIALIZATION_NVP(turns_left_to_completion) & BOOST_SERIALIZATION_NVP(rally_point_id) - & BOOST_SERIALIZATION_NVP(paused); + & BOOST_SERIALIZATION_NVP(paused) + & BOOST_SERIALIZATION_NVP(allowed_imperial_stockpile_use); + + if (Archive::is_saving::value) { + // Serialization of uuid as a primitive doesn't work as expected from + // the documentation. This workaround instead serializes a string + // representation. + auto string_uuid = boost::uuids::to_string(uuid); + ar & BOOST_SERIALIZATION_NVP(string_uuid); + + } else if (Archive::is_loading::value && version < 2) { + // assign a random ID to this element so that future-issued orders can refer to it + uuid = boost::uuids::random_generator()(); + + } else { + // convert string back into UUID + std::string string_uuid; + ar & BOOST_SERIALIZATION_NVP(string_uuid); + + try { + uuid = boost::lexical_cast(string_uuid); + } catch (const boost::bad_lexical_cast&) { + uuid = boost::uuids::random_generator()(); + } + } } +BOOST_CLASS_VERSION(ProductionQueue::Element, 2) + template void ProductionQueue::Element::serialize(freeorion_bin_oarchive&, const unsigned int); template void ProductionQueue::Element::serialize(freeorion_bin_iarchive&, const unsigned int); template void ProductionQueue::Element::serialize(freeorion_xml_oarchive&, const unsigned int); template void ProductionQueue::Element::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void ProductionQueue::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_queue) & BOOST_SERIALIZATION_NVP(m_projects_in_progress) & BOOST_SERIALIZATION_NVP(m_object_group_allocated_pp) + & BOOST_SERIALIZATION_NVP(m_object_group_allocated_stockpile_pp) + & BOOST_SERIALIZATION_NVP(m_expected_new_stockpile_amount) & BOOST_SERIALIZATION_NVP(m_empire_id); } @@ -91,7 +123,7 @@ template void ProductionQueue::serialize(freeorion_bin_i template void ProductionQueue::serialize(freeorion_xml_oarchive&, const unsigned int); template void ProductionQueue::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void Empire::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_id) @@ -101,25 +133,70 @@ void Empire::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_capital_id) & BOOST_SERIALIZATION_NVP(m_source_id) & BOOST_SERIALIZATION_NVP(m_eliminated) - & BOOST_SERIALIZATION_NVP(m_victories) - & BOOST_SERIALIZATION_NVP(m_techs) - & BOOST_SERIALIZATION_NVP(m_meters) - & BOOST_SERIALIZATION_NVP(m_research_queue) - & BOOST_SERIALIZATION_NVP(m_research_progress) - & BOOST_SERIALIZATION_NVP(m_production_queue) - & BOOST_SERIALIZATION_NVP(m_available_building_types) - & BOOST_SERIALIZATION_NVP(m_available_part_types) - & BOOST_SERIALIZATION_NVP(m_available_hull_types) - & BOOST_SERIALIZATION_NVP(m_supply_system_ranges) - & BOOST_SERIALIZATION_NVP(m_supply_unobstructed_systems) - & BOOST_SERIALIZATION_NVP(m_available_system_exit_lanes); + & BOOST_SERIALIZATION_NVP(m_victories); - if (GetUniverse().AllObjectsVisible() || + bool visible = GetUniverse().AllObjectsVisible() || GetUniverse().EncodingEmpire() == ALL_EMPIRES || - m_id == GetUniverse().EncodingEmpire()) - { - ar & BOOST_SERIALIZATION_NVP(m_ship_designs) - & BOOST_SERIALIZATION_NVP(m_sitrep_entries) + m_id == GetUniverse().EncodingEmpire(); + + if (Archive::is_loading::value && version < 1) { + // adapt set to map + std::set temp_stringset; + ar & boost::serialization::make_nvp("m_techs", temp_stringset); + m_techs.clear(); + for (auto& entry : temp_stringset) + m_techs[entry] = BEFORE_FIRST_TURN; + } else if (Archive::is_saving::value && !visible && !GetGameRules().Get("RULE_SHOW_DETAILED_EMPIRES_DATA")) { + std::map dummy_string_int_map; + // show other empire tech only if the current empire already knowns this tech without disclosure turn + // this allows to see effects if the empire learned appropriate tech + const Empire* encoding_empire = Empires().GetEmpire(GetUniverse().EncodingEmpire()); + if (encoding_empire) { + for (const auto& tech : encoding_empire->m_techs) { + const auto it = m_techs.find(tech.first); + if (it != m_techs.end()) { + dummy_string_int_map.emplace(tech.first, BEFORE_FIRST_TURN); + } + } + } + ar & boost::serialization::make_nvp("m_techs", dummy_string_int_map); + } else { + ar & BOOST_SERIALIZATION_NVP(m_techs); + } + + ar & BOOST_SERIALIZATION_NVP(m_meters); + if (Archive::is_saving::value && !visible && !GetGameRules().Get("RULE_SHOW_DETAILED_EMPIRES_DATA")) { + // don't send what other empires building and researching + // and which building and ship parts are available to them + ResearchQueue empty_research_queue(m_id); + std::map empty_research_progress; + ProductionQueue empty_production_queue(m_id); + std::set empty_string_set; + ar & boost::serialization::make_nvp("m_research_queue", empty_research_queue) + & boost::serialization::make_nvp("m_research_progress", empty_research_progress) + & boost::serialization::make_nvp("m_production_queue", empty_production_queue) + & boost::serialization::make_nvp("m_available_building_types", empty_string_set) + & boost::serialization::make_nvp("m_available_part_types", empty_string_set) + & boost::serialization::make_nvp("m_available_hull_types", empty_string_set); + } else { + // processing all data on deserialization, saving to savegame, + // sending data to the current empire itself, + // or rule allows to see all data + ar & BOOST_SERIALIZATION_NVP(m_research_queue) + & BOOST_SERIALIZATION_NVP(m_research_progress) + & BOOST_SERIALIZATION_NVP(m_production_queue) + & BOOST_SERIALIZATION_NVP(m_available_building_types) + & boost::serialization::make_nvp("m_available_part_types", m_available_ship_parts) + & boost::serialization::make_nvp("m_available_hull_types", m_available_ship_hulls); + } + + ar & BOOST_SERIALIZATION_NVP(m_supply_system_ranges) + & BOOST_SERIALIZATION_NVP(m_supply_unobstructed_systems) + & BOOST_SERIALIZATION_NVP(m_preserved_system_exit_lanes); + + if (visible) { + ar & boost::serialization::make_nvp("m_ship_designs", m_known_ship_designs); + ar & BOOST_SERIALIZATION_NVP(m_sitrep_entries) & BOOST_SERIALIZATION_NVP(m_resource_pools) & BOOST_SERIALIZATION_NVP(m_population_pool) @@ -128,7 +205,7 @@ void Empire::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_species_ships_owned) & BOOST_SERIALIZATION_NVP(m_ship_designs_owned) - & BOOST_SERIALIZATION_NVP(m_ship_part_types_owned) + & boost::serialization::make_nvp("m_ship_part_types_owned", m_ship_parts_owned) & BOOST_SERIALIZATION_NVP(m_ship_part_class_owned) & BOOST_SERIALIZATION_NVP(m_species_colonies_owned) & BOOST_SERIALIZATION_NVP(m_outposts_owned) @@ -139,6 +216,7 @@ void Empire::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_species_ships_destroyed) & BOOST_SERIALIZATION_NVP(m_species_planets_invaded) + & BOOST_SERIALIZATION_NVP(m_ship_designs_in_production) & BOOST_SERIALIZATION_NVP(m_species_ships_produced) & BOOST_SERIALIZATION_NVP(m_ship_designs_produced) & BOOST_SERIALIZATION_NVP(m_species_ships_lost) @@ -152,14 +230,33 @@ void Empire::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_building_types_produced) & BOOST_SERIALIZATION_NVP(m_building_types_scrapped); } + + if (Archive::is_loading::value && version < 3) { + m_authenticated = false; + } else { + ar & BOOST_SERIALIZATION_NVP(m_authenticated); + } + + if (Archive::is_loading::value && version < 4) { + m_ready = false; + } else { + ar & BOOST_SERIALIZATION_NVP(m_ready); + } } +BOOST_CLASS_VERSION(Empire, 4) + template void Empire::serialize(freeorion_bin_oarchive&, const unsigned int); template void Empire::serialize(freeorion_bin_iarchive&, const unsigned int); template void Empire::serialize(freeorion_xml_oarchive&, const unsigned int); template void Empire::serialize(freeorion_xml_iarchive&, const unsigned int); -template +namespace { + std::pair DiploKey(int id1, int ind2) + { return std::make_pair(std::max(id1, ind2), std::min(id1, ind2)); } +} + +template void EmpireManager::serialize(Archive& ar, const unsigned int version) { if (Archive::is_loading::value) { @@ -174,8 +271,36 @@ void EmpireManager::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_empire_diplomatic_statuses) & BOOST_SERIALIZATION_NVP(messages); - if (Archive::is_loading::value) - m_diplomatic_messages = messages; + if (Archive::is_loading::value) { + m_diplomatic_messages = std::move(messages); + + // erase invalid empire diplomatic statuses + std::vector> to_erase; + for (auto r : m_empire_diplomatic_statuses) { + auto e1 = r.first.first; + auto e2 = r.first.second; + if (m_empire_map.count(e1) < 1 || m_empire_map.count(e2) < 1) { + to_erase.push_back({e1, e2}); + ErrorLogger() << "Erased invalid diplomatic status between empires " << e1 << " and " << e2; + } + } + for (auto p : to_erase) + m_empire_diplomatic_statuses.erase(p); + + // add missing empire diplomatic statuses + for (auto e1 : m_empire_map) { + for (auto e2 : m_empire_map) { + if (e1.first >= e2.first) + continue; + auto dk = DiploKey(e1.first, e2.first); + if (m_empire_diplomatic_statuses.count(dk) < 1) + { + m_empire_diplomatic_statuses[dk] = DIPLO_WAR; + ErrorLogger() << "Added missing diplomatic status (default WAR) between empires " << e1.first << " and " << e2.first; + } + } + } + } } template void EmpireManager::serialize(freeorion_bin_oarchive&, const unsigned int); @@ -183,7 +308,7 @@ template void EmpireManager::serialize(freeorion_bin_iar template void EmpireManager::serialize(freeorion_xml_oarchive&, const unsigned int); template void EmpireManager::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void DiplomaticMessage::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_sender_empire) @@ -196,7 +321,7 @@ template void DiplomaticMessage::serialize(freeorion_bin template void DiplomaticMessage::serialize(freeorion_xml_oarchive&, const unsigned int); template void DiplomaticMessage::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void SupplyManager::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_supply_starlane_traversals) diff --git a/util/SerializeModeratorAction.cpp b/util/SerializeModeratorAction.cpp index bd15a39952e..0351e05b172 100644 --- a/util/SerializeModeratorAction.cpp +++ b/util/SerializeModeratorAction.cpp @@ -4,7 +4,7 @@ #include "Serialize.ipp" -// exports for boost serialization of polymorphic ModeratorAction hierarchy + BOOST_CLASS_EXPORT(Moderator::DestroyUniverseObject) BOOST_CLASS_EXPORT(Moderator::SetOwner) BOOST_CLASS_EXPORT(Moderator::AddStarlane) @@ -13,7 +13,7 @@ BOOST_CLASS_EXPORT(Moderator::CreateSystem) BOOST_CLASS_EXPORT(Moderator::CreatePlanet) -template +template void Moderator::ModeratorAction::serialize(Archive& ar, const unsigned int version) {} @@ -22,7 +22,7 @@ template void Moderator::ModeratorAction::serialize(free template void Moderator::ModeratorAction::serialize(freeorion_xml_oarchive&, const unsigned int); template void Moderator::ModeratorAction::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void Moderator::DestroyUniverseObject::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ModeratorAction) @@ -34,7 +34,7 @@ template void Moderator::DestroyUniverseObject::serialize(freeorion_xml_oarchive&, const unsigned int); template void Moderator::DestroyUniverseObject::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void Moderator::SetOwner::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ModeratorAction) @@ -42,16 +42,12 @@ void Moderator::SetOwner::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_new_owner_empire_id); } -/// @cond SERIALIZE_MOD_ACTION // Conditional api documentation - template void Moderator::SetOwner::serialize(freeorion_bin_oarchive&, const unsigned int); template void Moderator::SetOwner::serialize(freeorion_bin_iarchive&, const unsigned int); template void Moderator::SetOwner::serialize(freeorion_xml_oarchive&, const unsigned int); template void Moderator::SetOwner::serialize(freeorion_xml_iarchive&, const unsigned int); -/// @endcond - -template +template void Moderator::AddStarlane::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ModeratorAction) @@ -64,7 +60,7 @@ template void Moderator::AddStarlane::serialize(freeorio template void Moderator::AddStarlane::serialize(freeorion_xml_oarchive&, const unsigned int); template void Moderator::AddStarlane::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void Moderator::RemoveStarlane::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ModeratorAction) @@ -77,7 +73,7 @@ template void Moderator::RemoveStarlane::serialize(freeo template void Moderator::RemoveStarlane::serialize(freeorion_xml_oarchive&, const unsigned int); template void Moderator::RemoveStarlane::serialize(freeorion_xml_iarchive&, const unsigned int); -template +template void Moderator::CreateSystem::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ModeratorAction) @@ -86,16 +82,12 @@ void Moderator::CreateSystem::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_star_type); } -/// @cond SERIALIZE_MOD_ACTION // Conditional api documentation - template void Moderator::CreateSystem::serialize(freeorion_bin_oarchive&, const unsigned int); template void Moderator::CreateSystem::serialize(freeorion_bin_iarchive&, const unsigned int); template void Moderator::CreateSystem::serialize(freeorion_xml_oarchive&, const unsigned int); template void Moderator::CreateSystem::serialize(freeorion_xml_iarchive&, const unsigned int); -/// @endcond - -template +template void Moderator::CreatePlanet::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ModeratorAction) @@ -104,11 +96,7 @@ void Moderator::CreatePlanet::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_planet_size); } -/// @cond SERIALIZE_MOD_ACTION // Conditional api documentation - template void Moderator::CreatePlanet::serialize(freeorion_bin_oarchive&, const unsigned int); template void Moderator::CreatePlanet::serialize(freeorion_bin_iarchive&, const unsigned int); template void Moderator::CreatePlanet::serialize(freeorion_xml_oarchive&, const unsigned int); template void Moderator::CreatePlanet::serialize(freeorion_xml_iarchive&, const unsigned int); - -/// @endcond diff --git a/util/SerializeMultiplayerCommon.cpp b/util/SerializeMultiplayerCommon.cpp index ef8c106a94c..160e7503066 100644 --- a/util/SerializeMultiplayerCommon.cpp +++ b/util/SerializeMultiplayerCommon.cpp @@ -1,15 +1,29 @@ #include "Serialize.h" #include "MultiplayerCommon.h" +#include "OptionsDB.h" +#include "OrderSet.h" +#include "Order.h" #include "../universe/System.h" #include "Serialize.ipp" -template +#include +#include +#include + + +template void GalaxySetupData::serialize(Archive& ar, const unsigned int version) { - ar & BOOST_SERIALIZATION_NVP(m_seed) - & BOOST_SERIALIZATION_NVP(m_size) + if (Archive::is_saving::value && m_encoding_empire != ALL_EMPIRES && (!GetOptionsDB().Get("network.server.publish-seed"))) { + std::string dummy = ""; + ar & boost::serialization::make_nvp("m_seed", dummy); + } else { + ar & BOOST_SERIALIZATION_NVP(m_seed); + } + + ar & BOOST_SERIALIZATION_NVP(m_size) & BOOST_SERIALIZATION_NVP(m_shape) & BOOST_SERIALIZATION_NVP(m_age) & BOOST_SERIALIZATION_NVP(m_starlane_freq) @@ -22,6 +36,14 @@ void GalaxySetupData::serialize(Archive& ar, const unsigned int version) if (version >= 1) { ar & BOOST_SERIALIZATION_NVP(m_game_rules); } + + if (version >= 2) { + ar & BOOST_SERIALIZATION_NVP(m_game_uid); + } else { + if (Archive::is_loading::value) { + m_game_uid = boost::uuids::to_string(boost::uuids::random_generator()()); + } + } } template void GalaxySetupData::serialize(freeorion_bin_oarchive&, const unsigned int); @@ -29,7 +51,8 @@ template void GalaxySetupData::serialize(freeorion_bin_i template void GalaxySetupData::serialize(freeorion_xml_oarchive&, const unsigned int); template void GalaxySetupData::serialize(freeorion_xml_iarchive&, const unsigned int); -template + +template void SinglePlayerSetupData::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(GalaxySetupData) @@ -43,15 +66,91 @@ template void SinglePlayerSetupData::serialize(freeorion template void SinglePlayerSetupData::serialize(freeorion_xml_oarchive&, const unsigned int); template void SinglePlayerSetupData::serialize(freeorion_xml_iarchive&, const unsigned int); -template + +template void SaveGameUIData::serialize(Archive& ar, const unsigned int version) { + TraceLogger() << "SaveGameUIData::serialize " << (Archive::is_saving::value ? "saving" : "loading") + << " version " << version; ar & BOOST_SERIALIZATION_NVP(map_top) & BOOST_SERIALIZATION_NVP(map_left) & BOOST_SERIALIZATION_NVP(map_zoom_steps_in) - & BOOST_SERIALIZATION_NVP(fleets_exploring); + & BOOST_SERIALIZATION_NVP(fleets_exploring) + & BOOST_SERIALIZATION_NVP(obsolete_ui_event_count); + TraceLogger() << "SaveGameUIData::serialize processed obsolete UI event count"; + if (Archive::is_saving::value || version >= 3) { + // serializing / deserializing boost::optional can cause problem, so + // store instead in separate containers + + // std::vector>>> ordered_ship_design_ids_and_obsolete; + std::vector ordered_ship_design_ids; + std::map> ids_obsolete; + + if (Archive::is_saving::value) { + // populate temp containers + for (auto id_pair_pair : ordered_ship_design_ids_and_obsolete) { + ordered_ship_design_ids.push_back(id_pair_pair.first); + if (id_pair_pair.second) + ids_obsolete[id_pair_pair.first] = id_pair_pair.second.get(); + } + // serialize into archive + TraceLogger() << "SaveGameUIData::serialize design data into archive"; + ar & BOOST_SERIALIZATION_NVP(ordered_ship_design_ids) + & BOOST_SERIALIZATION_NVP(ids_obsolete); + TraceLogger() << "SaveGameUIData::serialize design data into archive completed"; + } else { // is_loading with version >= 3 + // deserialize into temp containers + TraceLogger() << "SaveGameUIData::serialize design data from archive"; + ar & BOOST_SERIALIZATION_NVP(ordered_ship_design_ids) + & BOOST_SERIALIZATION_NVP(ids_obsolete); + TraceLogger() << "SaveGameUIData::serialize design data from archive completed"; + + // extract from temp containers into member storage with boost::optional + ordered_ship_design_ids_and_obsolete.clear(); + for (int id : ordered_ship_design_ids) { + auto it = ids_obsolete.find(id); + auto opt_p_i = it == ids_obsolete.end() ? + boost::optional>() : + boost::optional>(it->second); + ordered_ship_design_ids_and_obsolete.push_back(std::make_pair(id, opt_p_i)); + } + TraceLogger() << "SaveGameUIData::serialize design data extracted"; + } + } else { // is_loading with version < 3 + // (attempt to) directly deserialize / load design ordering and obsolescence + try { + ar & BOOST_SERIALIZATION_NVP(ordered_ship_design_ids_and_obsolete); + } catch (...) { + ErrorLogger() << "Deserializing ship design ids and obsoletes failed. Skipping hull order and obsoletion, and obsolete ship parts."; + return; + } + } + ar & BOOST_SERIALIZATION_NVP(ordered_ship_hull_and_obsolete); + TraceLogger() << "SaveGameUIData::serialize ship hull processed"; + if (Archive::is_saving::value || version >= 4) { + std::map ordered_obsolete_ship_parts; + + if (Archive::is_saving::value) { + // populate temp container + ordered_obsolete_ship_parts = std::move(std::map(obsolete_ship_parts.begin(), obsolete_ship_parts.end())); + // serialize into archive + ar & BOOST_SERIALIZATION_NVP(ordered_obsolete_ship_parts); + } else { + // deserialize into temp container + ar & BOOST_SERIALIZATION_NVP(ordered_obsolete_ship_parts); - ar & BOOST_SERIALIZATION_NVP(ordered_ship_design_ids_and_obsolete); + // extract from temp container + obsolete_ship_parts = std::move(std::unordered_map(ordered_obsolete_ship_parts.begin(), ordered_obsolete_ship_parts.end())); + } + } else { // is_loading with version < 4 + try { + ar & BOOST_SERIALIZATION_NVP(obsolete_ship_parts); + } catch (...) { + ErrorLogger() << "Deserializing obsolete ship parts failed."; + } + } + TraceLogger() << "SaveGameUIData::serialize obsoleted ship parts processed " << obsolete_ship_parts.size() + << " items. Bucket count " << obsolete_ship_parts.bucket_count(); } template void SaveGameUIData::serialize(freeorion_bin_oarchive&, const unsigned int); @@ -59,13 +158,21 @@ template void SaveGameUIData::serialize(freeorion_bin_ia template void SaveGameUIData::serialize(freeorion_xml_oarchive&, const unsigned int); template void SaveGameUIData::serialize(freeorion_xml_iarchive&, const unsigned int); -template + +template void SaveGameEmpireData::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_empire_id) & BOOST_SERIALIZATION_NVP(m_empire_name) & BOOST_SERIALIZATION_NVP(m_player_name) & BOOST_SERIALIZATION_NVP(m_color); + if (version >= 1) { + ar & BOOST_SERIALIZATION_NVP(m_authenticated); + } + if (version >= 2) { + ar & BOOST_SERIALIZATION_NVP(m_eliminated); + ar & BOOST_SERIALIZATION_NVP(m_won); + } } template void SaveGameEmpireData::serialize(freeorion_bin_oarchive&, const unsigned int); @@ -73,7 +180,55 @@ template void SaveGameEmpireData::serialize(freeorion_bi template void SaveGameEmpireData::serialize(freeorion_xml_oarchive&, const unsigned int); template void SaveGameEmpireData::serialize(freeorion_xml_iarchive&, const unsigned int); -template + +template +void PlayerSaveHeaderData::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_empire_id) + & BOOST_SERIALIZATION_NVP(m_client_type); +} + +template void PlayerSaveHeaderData::serialize(freeorion_bin_oarchive&, const unsigned int); +template void PlayerSaveHeaderData::serialize(freeorion_bin_iarchive&, const unsigned int); +template void PlayerSaveHeaderData::serialize(freeorion_xml_oarchive&, const unsigned int); +template void PlayerSaveHeaderData::serialize(freeorion_xml_iarchive&, const unsigned int); + + +template +void PlayerSaveGameData::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_NVP(m_name) + & BOOST_SERIALIZATION_NVP(m_empire_id) + & BOOST_SERIALIZATION_NVP(m_orders) + & BOOST_SERIALIZATION_NVP(m_ui_data) + & BOOST_SERIALIZATION_NVP(m_save_state_string) + & BOOST_SERIALIZATION_NVP(m_client_type); + if (version == 1) { + bool ready{false}; + ar & boost::serialization::make_nvp("m_ready", ready); + } +} + +template void PlayerSaveGameData::serialize(freeorion_bin_oarchive&, const unsigned int); +template void PlayerSaveGameData::serialize(freeorion_bin_iarchive&, const unsigned int); +template void PlayerSaveGameData::serialize(freeorion_xml_oarchive&, const unsigned int); +template void PlayerSaveGameData::serialize(freeorion_xml_iarchive&, const unsigned int); + + +template +void ServerSaveGameData::serialize(Archive& ar, const unsigned int version) +{ + ar & BOOST_SERIALIZATION_NVP(m_current_turn); +} + +template void ServerSaveGameData::serialize(freeorion_bin_oarchive&, const unsigned int); +template void ServerSaveGameData::serialize(freeorion_bin_iarchive&, const unsigned int); +template void ServerSaveGameData::serialize(freeorion_xml_oarchive&, const unsigned int); +template void ServerSaveGameData::serialize(freeorion_xml_iarchive&, const unsigned int); + + +template void PlayerSetupData::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_player_name) @@ -84,6 +239,12 @@ void PlayerSetupData::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_save_game_empire_id) & BOOST_SERIALIZATION_NVP(m_client_type) & BOOST_SERIALIZATION_NVP(m_player_ready); + if (version >= 1) { + ar & BOOST_SERIALIZATION_NVP(m_authenticated); + } + if (version >= 2) { + ar & BOOST_SERIALIZATION_NVP(m_starting_team); + } } template void PlayerSetupData::serialize(freeorion_bin_oarchive&, const unsigned int); @@ -91,14 +252,24 @@ template void PlayerSetupData::serialize(freeorion_bin_i template void PlayerSetupData::serialize(freeorion_xml_oarchive&, const unsigned int); template void PlayerSetupData::serialize(freeorion_xml_iarchive&, const unsigned int); -template + +template void MultiplayerLobbyData::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(GalaxySetupData) & BOOST_SERIALIZATION_NVP(m_new_game) & BOOST_SERIALIZATION_NVP(m_players) & BOOST_SERIALIZATION_NVP(m_save_game) - & BOOST_SERIALIZATION_NVP(m_save_game_empire_data); + & BOOST_SERIALIZATION_NVP(m_save_game_empire_data) + & BOOST_SERIALIZATION_NVP(m_any_can_edit) + & BOOST_SERIALIZATION_NVP(m_start_locked) + & BOOST_SERIALIZATION_NVP(m_start_lock_cause); + if (version >= 1) { + ar & BOOST_SERIALIZATION_NVP(m_save_game_current_turn); + } + if (version >= 2) { + ar & BOOST_SERIALIZATION_NVP(m_in_game); + } } template void MultiplayerLobbyData::serialize(freeorion_bin_oarchive&, const unsigned int); @@ -106,7 +277,29 @@ template void MultiplayerLobbyData::serialize(freeorion_ template void MultiplayerLobbyData::serialize(freeorion_xml_oarchive&, const unsigned int); template void MultiplayerLobbyData::serialize(freeorion_xml_iarchive&, const unsigned int); -template + +template +void ChatHistoryEntity::serialize(Archive& ar, const unsigned int version) +{ + if (version < 1) { + ar & BOOST_SERIALIZATION_NVP(m_timestamp) + & BOOST_SERIALIZATION_NVP(m_player_name) + & BOOST_SERIALIZATION_NVP(m_text); + } else { + ar & BOOST_SERIALIZATION_NVP(m_text) + & BOOST_SERIALIZATION_NVP(m_player_name) + & BOOST_SERIALIZATION_NVP(m_text_color) + & BOOST_SERIALIZATION_NVP(m_timestamp); + } +} + +template void ChatHistoryEntity::serialize(freeorion_bin_oarchive&, const unsigned int); +template void ChatHistoryEntity::serialize(freeorion_bin_iarchive&, const unsigned int); +template void ChatHistoryEntity::serialize(freeorion_xml_oarchive&, const unsigned int); +template void ChatHistoryEntity::serialize(freeorion_xml_iarchive&, const unsigned int); + + +template void PlayerInfo::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(name) @@ -119,4 +312,3 @@ template void PlayerInfo::serialize(freeorion_bin_oarchi template void PlayerInfo::serialize(freeorion_bin_iarchive&, const unsigned int); template void PlayerInfo::serialize(freeorion_xml_oarchive&, const unsigned int); template void PlayerInfo::serialize(freeorion_xml_iarchive&, const unsigned int); - diff --git a/util/SerializeOrderSet.cpp b/util/SerializeOrderSet.cpp index 9cea5a425b8..db3439a53fc 100644 --- a/util/SerializeOrderSet.cpp +++ b/util/SerializeOrderSet.cpp @@ -9,16 +9,13 @@ #include -//////////////////////////////////////////////////////////// -// Galaxy Map orders -//////////////////////////////////////////////////////////// - -// exports for boost serialization of polymorphic Order hierarchy BOOST_CLASS_EXPORT(Order) BOOST_CLASS_VERSION(Order, 1) BOOST_CLASS_EXPORT(RenameOrder) BOOST_CLASS_EXPORT(NewFleetOrder) +BOOST_CLASS_VERSION(NewFleetOrder, 1) BOOST_CLASS_EXPORT(FleetMoveOrder) +BOOST_CLASS_VERSION(FleetMoveOrder, 2) BOOST_CLASS_EXPORT(FleetTransferOrder) BOOST_CLASS_EXPORT(ColonizeOrder) BOOST_CLASS_EXPORT(InvadeOrder) @@ -26,28 +23,28 @@ BOOST_CLASS_EXPORT(BombardOrder) BOOST_CLASS_EXPORT(ChangeFocusOrder) BOOST_CLASS_EXPORT(ResearchQueueOrder) BOOST_CLASS_EXPORT(ProductionQueueOrder) +BOOST_CLASS_VERSION(ProductionQueueOrder, 2) BOOST_CLASS_EXPORT(ShipDesignOrder) +BOOST_CLASS_VERSION(ShipDesignOrder, 1) BOOST_CLASS_EXPORT(ScrapOrder) BOOST_CLASS_EXPORT(AggressiveOrder) BOOST_CLASS_EXPORT(GiveObjectToEmpireOrder) BOOST_CLASS_EXPORT(ForgetOrder) -template +template void Order::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_empire); - /** m_executed is intentionally not serialized so that orders always deserialize with m_execute - = false. See class comment for OrderSet. - ar & BOOST_SERIALIZATION_NVP(m_executed); */ + // m_executed is intentionally not serialized so that orders always + // deserialize with m_execute = false. See class comment for OrderSet. if (Archive::is_loading::value && version < 1) { bool dummy_executed; ar & boost::serialization::make_nvp("m_executed", dummy_executed); } - } -template +template void RenameOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) @@ -55,23 +52,21 @@ void RenameOrder::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_name); } -template +template void NewFleetOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) - & BOOST_SERIALIZATION_NVP(m_fleet_names) - & BOOST_SERIALIZATION_NVP(m_system_id) - & BOOST_SERIALIZATION_NVP(m_fleet_ids) - & BOOST_SERIALIZATION_NVP(m_ship_id_groups) - & BOOST_SERIALIZATION_NVP(m_aggressives); + & BOOST_SERIALIZATION_NVP(m_fleet_name) + & BOOST_SERIALIZATION_NVP(m_fleet_id) + & BOOST_SERIALIZATION_NVP(m_ship_ids) + & BOOST_SERIALIZATION_NVP(m_aggressive); } -template +template void FleetMoveOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) & BOOST_SERIALIZATION_NVP(m_fleet) - & BOOST_SERIALIZATION_NVP(m_start_system) & BOOST_SERIALIZATION_NVP(m_dest_system) & BOOST_SERIALIZATION_NVP(m_route); if (version > 0) { @@ -81,9 +76,7 @@ void FleetMoveOrder::serialize(Archive& ar, const unsigned int version) } } -BOOST_CLASS_VERSION(FleetMoveOrder, 1); - -template +template void FleetTransferOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) @@ -91,7 +84,7 @@ void FleetTransferOrder::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_add_ships); } -template +template void ColonizeOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) @@ -99,7 +92,7 @@ void ColonizeOrder::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_planet); } -template +template void InvadeOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) @@ -107,7 +100,7 @@ void InvadeOrder::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_planet); } -template +template void BombardOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) @@ -115,7 +108,7 @@ void BombardOrder::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_planet); } -template +template void ChangeFocusOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) @@ -123,7 +116,7 @@ void ChangeFocusOrder::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_focus); } -template +template void ResearchQueueOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) @@ -133,33 +126,77 @@ void ResearchQueueOrder::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_pause); } -template +template void ProductionQueueOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) - & BOOST_SERIALIZATION_NVP(m_item) - & BOOST_SERIALIZATION_NVP(m_number) - & BOOST_SERIALIZATION_NVP(m_location) - & BOOST_SERIALIZATION_NVP(m_index) - & BOOST_SERIALIZATION_NVP(m_new_quantity) + & BOOST_SERIALIZATION_NVP(m_item); + + int m_number, m_index, m_pause, m_split_incomplete, m_dupe, m_use_imperial_pp; + if (version < 2) + ar & BOOST_SERIALIZATION_NVP(m_number); + + ar & BOOST_SERIALIZATION_NVP(m_location); + + if (version < 2) + ar & BOOST_SERIALIZATION_NVP(m_index); + + ar & BOOST_SERIALIZATION_NVP(m_new_quantity) & BOOST_SERIALIZATION_NVP(m_new_blocksize) & BOOST_SERIALIZATION_NVP(m_new_index) - & BOOST_SERIALIZATION_NVP(m_rally_point_id) - & BOOST_SERIALIZATION_NVP(m_pause) - & BOOST_SERIALIZATION_NVP(m_split_incomplete) - & BOOST_SERIALIZATION_NVP(m_dupe); + & BOOST_SERIALIZATION_NVP(m_rally_point_id); + + if (version < 2) { + ar & BOOST_SERIALIZATION_NVP(m_pause) + & BOOST_SERIALIZATION_NVP(m_split_incomplete) + & BOOST_SERIALIZATION_NVP(m_dupe) + & BOOST_SERIALIZATION_NVP(m_use_imperial_pp); + } else { + ar & BOOST_SERIALIZATION_NVP(m_action); + } + + if (version < 2 && Archive::is_loading::value) { + // would need to generate action and UUID from deserialized values. instead generate an invalid order. will break partial-turn saves. + m_uuid = boost::uuids::nil_generator()(); + m_uuid2 = m_uuid; + m_action = INVALID_PROD_QUEUE_ACTION; + + } else { + // Serialization of m_uuid as a primitive doesn't work as expected from + // the documentation. This workaround instead serializes a string + // representation. + if (Archive::is_saving::value) { + auto string_uuid = boost::uuids::to_string(m_uuid); + ar & BOOST_SERIALIZATION_NVP(string_uuid); + auto string_uuid2 = boost::uuids::to_string(m_uuid2); + ar & BOOST_SERIALIZATION_NVP(string_uuid2); + + } else { + std::string string_uuid; + ar & BOOST_SERIALIZATION_NVP(string_uuid); + std::string string_uuid2; + ar & BOOST_SERIALIZATION_NVP(string_uuid2); + try { + m_uuid = boost::lexical_cast(string_uuid); + m_uuid2 = boost::lexical_cast(string_uuid2); + } catch (const boost::bad_lexical_cast&) { + m_uuid = boost::uuids::nil_generator()(); + m_uuid2 = m_uuid; + } + } + } } -template +template void ShipDesignOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order); ar & BOOST_SERIALIZATION_NVP(m_design_id); if (version >= 1) { - // UUID serialization as a primitive doesn't work as expected from the documentation - // ar & BOOST_SERIALIZATION_NVP(m_uuid); - // This workaround instead serializes a string representation. + // Serialization of m_uuid as a primitive doesn't work as expected from + // the documentation. This workaround instead serializes a string + // representation. if (Archive::is_saving::value) { auto string_uuid = boost::uuids::to_string(m_uuid); ar & BOOST_SERIALIZATION_NVP(string_uuid); @@ -190,16 +227,14 @@ void ShipDesignOrder::serialize(Archive& ar, const unsigned int version) ar & BOOST_SERIALIZATION_NVP(m_name_desc_in_stringtable); } -BOOST_CLASS_VERSION(ShipDesignOrder, 1) - -template +template void ScrapOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) & BOOST_SERIALIZATION_NVP(m_object_id); } -template +template void AggressiveOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) @@ -207,7 +242,7 @@ void AggressiveOrder::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_aggression); } -template +template void GiveObjectToEmpireOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) @@ -215,20 +250,20 @@ void GiveObjectToEmpireOrder::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_recipient_empire_id); } -template +template void ForgetOrder::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Order) & BOOST_SERIALIZATION_NVP(m_object_id); } -template +template void Serialize(Archive& oa, const OrderSet& order_set) { oa << BOOST_SERIALIZATION_NVP(order_set); } template void Serialize(freeorion_bin_oarchive& oa, const OrderSet& order_set); template void Serialize(freeorion_xml_oarchive& oa, const OrderSet& order_set); -template +template void Deserialize(Archive& ia, OrderSet& order_set) { ia >> BOOST_SERIALIZATION_NVP(order_set); } template void Deserialize(freeorion_bin_iarchive& ia, OrderSet& order_set); diff --git a/util/SerializeUniverse.cpp b/util/SerializeUniverse.cpp index 27784d71676..33167c89cd5 100644 --- a/util/SerializeUniverse.cpp +++ b/util/SerializeUniverse.cpp @@ -13,6 +13,9 @@ #include "../universe/System.h" #include "../universe/Field.h" #include "../universe/Universe.h" +#include "ScopedTimer.h" +#include "AppInterface.h" +#include "OptionsDB.h" #include #include @@ -20,27 +23,28 @@ BOOST_CLASS_EXPORT(System) BOOST_CLASS_EXPORT(Field) BOOST_CLASS_EXPORT(Planet) +BOOST_CLASS_VERSION(Planet, 2) BOOST_CLASS_EXPORT(Building) BOOST_CLASS_EXPORT(Fleet) +BOOST_CLASS_VERSION(Fleet, 3) BOOST_CLASS_EXPORT(Ship) -BOOST_CLASS_VERSION(Ship, 1) +BOOST_CLASS_VERSION(Ship, 2) BOOST_CLASS_EXPORT(ShipDesign) -BOOST_CLASS_VERSION(ShipDesign, 1) +BOOST_CLASS_VERSION(ShipDesign, 2) BOOST_CLASS_EXPORT(Universe) BOOST_CLASS_VERSION(Universe, 1) -template +template void ObjectMap::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_objects); // If loading from the archive, propagate the changes to the specialized maps. - if (Archive::is_loading::value) { + if (Archive::is_loading::value) CopyObjectsToSpecializedMaps(); - } } -template +template void Universe::serialize(Archive& ar, const unsigned int version) { ObjectMap objects; @@ -56,8 +60,11 @@ void Universe::serialize(Archive& ar, const unsigned int version) std::string serializing_label = (Archive::is_loading::value ? "deserializing" : "serializing"); + SectionedScopedTimer timer("Universe " + serializing_label); + if (Archive::is_saving::value) { DebugLogger() << "Universe::serialize : Getting gamestate data"; + timer.EnterSection("collecting data"); GetObjectsToSerialize( objects, m_encoding_empire); GetDestroyedObjectsToSerialize( destroyed_object_ids, m_encoding_empire); GetEmpireKnownObjectsToSerialize( empire_latest_known_objects, m_encoding_empire); @@ -66,15 +73,19 @@ void Universe::serialize(Archive& ar, const unsigned int version) GetEmpireKnownDestroyedObjects( empire_known_destroyed_object_ids, m_encoding_empire); GetEmpireStaleKnowledgeObjects( empire_stale_knowledge_object_ids, m_encoding_empire); GetShipDesignsToSerialize( ship_designs, m_encoding_empire); + timer.EnterSection(""); } if (Archive::is_loading::value) { - Clear(); // clean up any existing dynamically allocated contents before replacing containers with deserialized data + // clean up any existing dynamically allocated contents before replacing + // containers with deserialized data. + Clear(); } ar & BOOST_SERIALIZATION_NVP(m_universe_width); DebugLogger() << "Universe::serialize : " << serializing_label << " universe width: " << m_universe_width; + timer.EnterSection("designs"); ar & BOOST_SERIALIZATION_NVP(ship_designs); if (Archive::is_loading::value) m_ship_designs.swap(ship_designs); @@ -82,28 +93,37 @@ void Universe::serialize(Archive& ar, const unsigned int version) ar & BOOST_SERIALIZATION_NVP(m_empire_known_ship_design_ids); + timer.EnterSection("vis / known"); ar & BOOST_SERIALIZATION_NVP(empire_object_visibility); ar & BOOST_SERIALIZATION_NVP(empire_object_visibility_turns); + //ar & BOOST_SERIALIZATION_NVP(effect_specified_visibilities); ar & BOOST_SERIALIZATION_NVP(empire_known_destroyed_object_ids); ar & BOOST_SERIALIZATION_NVP(empire_stale_knowledge_object_ids); DebugLogger() << "Universe::serialize : " << serializing_label << " empire object visibility for " << empire_object_visibility.size() << ", " << empire_object_visibility_turns.size() << ", " + //<< effect_specified_visibilities.size() << ", " << empire_known_destroyed_object_ids.size() << ", " << empire_stale_knowledge_object_ids.size() << " empires"; + timer.EnterSection(""); if (Archive::is_loading::value) { + timer.EnterSection("load swap"); m_empire_object_visibility.swap(empire_object_visibility); m_empire_object_visibility_turns.swap(empire_object_visibility_turns); + //m_effect_specified_empire_object_visibilities.swap(effect_specified_visibilities); m_empire_known_destroyed_object_ids.swap(empire_known_destroyed_object_ids); m_empire_stale_knowledge_object_ids.swap(empire_stale_knowledge_object_ids); + timer.EnterSection(""); } + timer.EnterSection("objects"); ar & BOOST_SERIALIZATION_NVP(objects); - DebugLogger() << "Universe::serialize : " << serializing_label << " " << objects.NumObjects() << " objects"; + DebugLogger() << "Universe::serialize : " << serializing_label << " " << objects.size() << " objects"; if (Archive::is_loading::value) { m_objects.swap(objects); } + timer.EnterSection("destroyed ids"); ar & BOOST_SERIALIZATION_NVP(destroyed_object_ids); DebugLogger() << "Universe::serialize : " << serializing_label << " " << destroyed_object_ids.size() << " destroyed object ids"; if (Archive::is_loading::value) { @@ -111,16 +131,18 @@ void Universe::serialize(Archive& ar, const unsigned int version) m_objects.UpdateCurrentDestroyedObjects(m_destroyed_object_ids); } + timer.EnterSection("latest known objects"); ar & BOOST_SERIALIZATION_NVP(empire_latest_known_objects); DebugLogger() << "Universe::serialize : " << serializing_label << " empire known objects for " << empire_latest_known_objects.size() << " empires"; if (Archive::is_loading::value) { m_empire_latest_known_objects.swap(empire_latest_known_objects); } + timer.EnterSection("id allocator"); if (version >= 1) { DebugLogger() << "Universe::serialize : " << serializing_label << " id allocator version = " << version; - m_object_id_allocator->SerializeForEmpire(ar, 0 ,m_encoding_empire); - m_design_id_allocator->SerializeForEmpire(ar, 0 ,m_encoding_empire); + m_object_id_allocator->SerializeForEmpire(ar, version, m_encoding_empire); + m_design_id_allocator->SerializeForEmpire(ar, version, m_encoding_empire); } else { if (Archive::is_loading::value) { int dummy_last_allocated_object_id; @@ -142,33 +164,38 @@ void Universe::serialize(Archive& ar, const unsigned int version) } } - ar & BOOST_SERIALIZATION_NVP(m_stat_records); - DebugLogger() << "Universe::serialize : " << serializing_label << " " << m_stat_records.size() << " types of statistic"; - - DebugLogger() << "Universe::serialize : " << serializing_label << " done"; + timer.EnterSection("stats"); + if (Archive::is_saving::value && m_encoding_empire != ALL_EMPIRES && (!GetOptionsDB().Get("network.server.publish-statistics"))) { + std::map>> dummy_stat_records; + ar & boost::serialization::make_nvp("m_stat_records", dummy_stat_records); + } else { + ar & BOOST_SERIALIZATION_NVP(m_stat_records); + DebugLogger() << "Universe::serialize : " << serializing_label << " " << m_stat_records.size() << " types of statistic"; + } + timer.EnterSection(""); if (Archive::is_saving::value) { DebugLogger() << "Universe::serialize : Cleaning up temporary data"; - // clean up temporary objects in temporary ObjectMaps - objects.Clear(); - for (std::map::value_type& elko : empire_latest_known_objects) - { elko.second.Clear(); } + // clean up temporary objects in temporary ObjectMaps. + objects.clear(); + for (auto& elko : empire_latest_known_objects) + { elko.second.clear(); } } if (Archive::is_loading::value) { DebugLogger() << "Universe::serialize : updating empires' latest known object destruction states"; - // update known destroyed objects state in each empire's latest known objects - for (std::map::value_type& elko : m_empire_latest_known_objects) { - std::map>::iterator destroyed_ids_it = - m_empire_known_destroyed_object_ids.find(elko.first); + // update known destroyed objects state in each empire's latest known + // objects. + for (auto& elko : m_empire_latest_known_objects) { + auto destroyed_ids_it = m_empire_known_destroyed_object_ids.find(elko.first); if (destroyed_ids_it != m_empire_known_destroyed_object_ids.end()) elko.second.UpdateCurrentDestroyedObjects(destroyed_ids_it->second); } } - DebugLogger() << "Universe::serialize done"; + DebugLogger() << "Universe " << serializing_label << " done"; } -template +template void UniverseObject::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_id) @@ -182,7 +209,7 @@ void UniverseObject::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_created_on_turn); } -template +template void System::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(UniverseObject) @@ -198,17 +225,17 @@ void System::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_last_turn_battle_here); } -template +template void Field::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(UniverseObject) & BOOST_SERIALIZATION_NVP(m_type_name); } -template +template void Planet::serialize(Archive& ar, const unsigned int version) { - ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(UniverseObject) + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(UniverseObject) & BOOST_SERIALIZATION_BASE_OBJECT_NVP(PopCenter) & BOOST_SERIALIZATION_BASE_OBJECT_NVP(ResourceCenter) & BOOST_SERIALIZATION_NVP(m_type) @@ -218,16 +245,29 @@ void Planet::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_initial_orbital_position) & BOOST_SERIALIZATION_NVP(m_rotational_period) & BOOST_SERIALIZATION_NVP(m_axial_tilt) - & BOOST_SERIALIZATION_NVP(m_buildings) - & BOOST_SERIALIZATION_NVP(m_just_conquered) - & BOOST_SERIALIZATION_NVP(m_is_about_to_be_colonized) + & BOOST_SERIALIZATION_NVP(m_buildings); + if (version < 2) { + // if deserializing an old save, default to standard default never-colonized turn + m_turn_last_colonized = INVALID_GAME_TURN; + if (!SpeciesName().empty()) // but if a planet has a species, it must have been colonized, so default to the previous turn + m_turn_last_colonized = CurrentTurn() - 1; + } else { + ar & BOOST_SERIALIZATION_NVP(m_turn_last_colonized); + } + if (version < 1) { + bool dummy = false; + ar & boost::serialization::make_nvp("m_just_conquered", dummy); + } else { + ar & BOOST_SERIALIZATION_NVP(m_turn_last_conquered); + } + ar & BOOST_SERIALIZATION_NVP(m_is_about_to_be_colonized) & BOOST_SERIALIZATION_NVP(m_is_about_to_be_invaded) & BOOST_SERIALIZATION_NVP(m_is_about_to_be_bombarded) & BOOST_SERIALIZATION_NVP(m_ordered_given_to_empire_id) & BOOST_SERIALIZATION_NVP(m_last_turn_attacked_by_ship); } -template +template void Building::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(UniverseObject) @@ -237,7 +277,7 @@ void Building::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_produced_by_empire_id); } -template +template void Fleet::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(UniverseObject) @@ -246,13 +286,16 @@ void Fleet::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_next_system) & BOOST_SERIALIZATION_NVP(m_aggressive) & BOOST_SERIALIZATION_NVP(m_ordered_given_to_empire_id) - & BOOST_SERIALIZATION_NVP(m_travel_route) - & BOOST_SERIALIZATION_NVP(m_travel_distance) - & BOOST_SERIALIZATION_NVP(m_arrived_this_turn) + & BOOST_SERIALIZATION_NVP(m_travel_route); + if (version < 3) { + double dummy_travel_distance; + ar & boost::serialization::make_nvp("m_travel_distance", dummy_travel_distance); + } + ar & BOOST_SERIALIZATION_NVP(m_arrived_this_turn) & BOOST_SERIALIZATION_NVP(m_arrival_starlane); } -template +template void Ship::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(UniverseObject) @@ -268,10 +311,13 @@ void Ship::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(m_arrived_on_turn); if (version >= 1) { ar & BOOST_SERIALIZATION_NVP(m_last_turn_active_in_combat); + if (version >= 2) { + ar & BOOST_SERIALIZATION_NVP(m_last_resupplied_on_turn); + } } } -template +template void ShipDesign::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_id) @@ -280,9 +326,9 @@ void ShipDesign::serialize(Archive& ar, const unsigned int version) TraceLogger() << "ship design serialize version: " << version << " : " << (Archive::is_saving::value ? "saving" : "loading"); if (version >= 1) { - // UUID serialization as a primitive doesn't work as expected from the documentation - // ar & BOOST_SERIALIZATION_NVP(m_uuid); - // This workaround instead serializes a string representation. + // Serialization of m_uuid as a primitive doesn't work as expected from + // the documentation. This workaround instead serializes a string + // representation. if (Archive::is_saving::value) { auto string_uuid = boost::uuids::to_string(m_uuid); ar & BOOST_SERIALIZATION_NVP(string_uuid); @@ -300,8 +346,10 @@ void ShipDesign::serialize(Archive& ar, const unsigned int version) } ar & BOOST_SERIALIZATION_NVP(m_description) - & BOOST_SERIALIZATION_NVP(m_designed_on_turn) - & BOOST_SERIALIZATION_NVP(m_hull) + & BOOST_SERIALIZATION_NVP(m_designed_on_turn); + if (version >= 2) + ar & BOOST_SERIALIZATION_NVP(m_designed_by_empire); + ar & BOOST_SERIALIZATION_NVP(m_hull) & BOOST_SERIALIZATION_NVP(m_parts) & BOOST_SERIALIZATION_NVP(m_is_monster) & BOOST_SERIALIZATION_NVP(m_icon) @@ -322,7 +370,7 @@ void SpeciesManager::serialize(freeorion_bin_iarchive& a template void SpeciesManager::serialize(freeorion_xml_iarchive& ar, const unsigned int version); -template +template void SpeciesManager::serialize(Archive& ar, const unsigned int version) { // Don't need to send all the data about species, as this is derived from @@ -349,15 +397,14 @@ void SpeciesManager::serialize(Archive& ar, const unsigned int version) & BOOST_SERIALIZATION_NVP(empire_opinions) & BOOST_SERIALIZATION_NVP(other_species_opinions) & BOOST_SERIALIZATION_NVP(species_object_populations) - & BOOST_SERIALIZATION_NVP(species_ships_destroyed) - ; + & BOOST_SERIALIZATION_NVP(species_ships_destroyed); if (Archive::is_loading::value) { SetSpeciesHomeworlds(species_homeworlds); SetSpeciesEmpireOpinions(empire_opinions); SetSpeciesSpeciesOpinions(other_species_opinions); - m_species_object_populations = species_object_populations; - m_species_species_ships_destroyed = species_ships_destroyed; + m_species_object_populations = std::move(species_object_populations); + m_species_species_ships_destroyed = std::move(species_ships_destroyed); } } @@ -373,25 +420,25 @@ void System::serialize(freeorion_bin_iarchive& ar, const template void System::serialize(freeorion_xml_iarchive& ar, const unsigned int version); -template +template void Serialize(Archive& oa, const Universe& universe) { oa << BOOST_SERIALIZATION_NVP(universe); } template FO_COMMON_API void Serialize(freeorion_bin_oarchive& oa, const Universe& universe); template FO_COMMON_API void Serialize(freeorion_xml_oarchive& oa, const Universe& universe); -template +template void Serialize(Archive& oa, const std::map>& objects) { oa << BOOST_SERIALIZATION_NVP(objects); } template void Serialize(freeorion_bin_oarchive& oa, const std::map>& objects); template void Serialize(freeorion_xml_oarchive& oa, const std::map>& objects); -template +template void Deserialize(Archive& ia, Universe& universe) { ia >> BOOST_SERIALIZATION_NVP(universe); } template FO_COMMON_API void Deserialize(freeorion_bin_iarchive& ia, Universe& universe); template FO_COMMON_API void Deserialize(freeorion_xml_iarchive& ia, Universe& universe); -template +template void Deserialize(Archive& ia, std::map>& objects) { ia >> BOOST_SERIALIZATION_NVP(objects); } template void Deserialize(freeorion_bin_iarchive& ia, std::map>& objects); diff --git a/util/SitRepEntry.cpp b/util/SitRepEntry.cpp index 28a493155a0..8458f1ab2fa 100644 --- a/util/SitRepEntry.cpp +++ b/util/SitRepEntry.cpp @@ -2,6 +2,7 @@ #include "i18n.h" #include "Logger.h" +#include "AppInterface.h" #include "../universe/Predicates.h" #include "../universe/Building.h" #include "../universe/Planet.h" @@ -10,13 +11,16 @@ #include "../universe/Fleet.h" #include "../universe/Universe.h" + SitRepEntry::SitRepEntry() : VarText(), m_turn(INVALID_GAME_TURN), m_icon("/icons/sitrep/generic.png") {} -SitRepEntry::SitRepEntry(const std::string& template_string, int turn, const std::string& icon, const std::string label, bool stringtable_lookup) : +SitRepEntry::SitRepEntry(const std::string& template_string, int turn, + const std::string& icon, const std::string label, + bool stringtable_lookup) : VarText(template_string, stringtable_lookup), m_turn(turn), m_icon(icon.empty() ? "/icons/sitrep/generic.png" : icon), @@ -24,7 +28,7 @@ SitRepEntry::SitRepEntry(const std::string& template_string, int turn, const std {} int SitRepEntry::GetDataIDNumber(const std::string& tag) const { - std::map::const_iterator elem = m_variables.find(tag); + auto elem = m_variables.find(tag); try { if (elem != m_variables.end()) return boost::lexical_cast(elem->second); @@ -36,7 +40,7 @@ int SitRepEntry::GetDataIDNumber(const std::string& tag) const { const std::string& SitRepEntry::GetDataString(const std::string& tag) const { static const std::string EMPTY_STRING; - std::map::const_iterator elem = m_variables.find(tag); + auto elem = m_variables.find(tag); if (elem == m_variables.end()) return EMPTY_STRING; return elem->second; @@ -44,7 +48,7 @@ const std::string& SitRepEntry::GetDataString(const std::string& tag) const { std::string SitRepEntry::Dump() const { std::string retval = "SitRep template_string = \"" + m_template_string + "\""; - for (const std::map::value_type& variable : m_variables) + for (const auto& variable : m_variables) retval += " " + variable.first + " = " + variable.second; retval += " turn = " + std::to_string(m_turn); retval += " icon = " + m_icon; @@ -55,7 +59,7 @@ std::string SitRepEntry::Dump() const { SitRepEntry CreateTechResearchedSitRep(const std::string& tech_name) { SitRepEntry sitrep( UserStringNop("SITREP_TECH_RESEARCHED"), - CurrentTurn() + 1, + CurrentTurn(), "icons/sitrep/tech_researched.png", UserStringNop("SITREP_TECH_RESEARCHED_LABEL"), true); sitrep.AddVariable(VarText::TECH_TAG, tech_name); @@ -100,7 +104,7 @@ SitRepEntry CreateBuildingBuiltSitRep(int building_id, int planet_id) { SitRepEntry CreateTechUnlockedSitRep(const std::string& tech_name) { SitRepEntry sitrep( UserStringNop("SITREP_TECH_UNLOCKED"), - CurrentTurn() + 1, + CurrentTurn(), "icons/sitrep/tech_unlocked.png", UserStringNop("SITREP_TECH_UNLOCKED_LABEL"), true); sitrep.AddVariable(VarText::TECH_TAG, tech_name); @@ -110,7 +114,7 @@ SitRepEntry CreateTechUnlockedSitRep(const std::string& tech_name) { SitRepEntry CreateBuildingTypeUnlockedSitRep(const std::string& building_type_name) { SitRepEntry sitrep( UserStringNop("SITREP_BUILDING_TYPE_UNLOCKED"), - CurrentTurn() + 1, + CurrentTurn(), "icons/sitrep/building_type_unlocked.png", UserStringNop("SITREP_BUILDING_TYPE_UNLOCKED_LABEL"), true); sitrep.AddVariable(VarText::BUILDING_TYPE_TAG, building_type_name); @@ -120,7 +124,7 @@ SitRepEntry CreateBuildingTypeUnlockedSitRep(const std::string& building_type_na SitRepEntry CreateShipHullUnlockedSitRep(const std::string& ship_hull_name) { SitRepEntry sitrep( UserStringNop("SITREP_SHIP_HULL_UNLOCKED"), - CurrentTurn() + 1, + CurrentTurn(), "icons/sitrep/ship_hull_unlocked.png", UserStringNop("SITREP_SHIP_HULL_UNLOCKED_LABEL"), true); sitrep.AddVariable(VarText::SHIP_HULL_TAG, ship_hull_name); @@ -130,7 +134,7 @@ SitRepEntry CreateShipHullUnlockedSitRep(const std::string& ship_hull_name) { SitRepEntry CreateShipPartUnlockedSitRep(const std::string& ship_part_name) { SitRepEntry sitrep( UserStringNop("SITREP_SHIP_PART_UNLOCKED"), - CurrentTurn() + 1, + CurrentTurn(), "icons/sitrep/ship_part_unlocked.png", UserStringNop("SITREP_SHIP_PART_UNLOCKED_LABEL"), true); sitrep.AddVariable(VarText::SHIP_PART_TAG, ship_part_name); @@ -202,13 +206,13 @@ namespace { } SitRepEntry CreateCombatDamagedObjectSitRep(int object_id, int combat_system_id, int empire_id) { - std::shared_ptr obj = GetUniverseObject(object_id); + auto obj = Objects().get(object_id); if (!obj) return GenericCombatDamagedObjectSitrep(combat_system_id); SitRepEntry sitrep; - if (std::shared_ptr ship = std::dynamic_pointer_cast(obj)) { + if (auto ship = std::dynamic_pointer_cast(obj)) { if (ship->Unowned()) sitrep = SitRepEntry( UserStringNop("SITREP_UNOWNED_SHIP_DAMAGED_AT_SYSTEM"), @@ -224,19 +228,19 @@ SitRepEntry CreateCombatDamagedObjectSitRep(int object_id, int combat_system_id, sitrep.AddVariable(VarText::SHIP_ID_TAG, std::to_string(object_id)); sitrep.AddVariable(VarText::DESIGN_ID_TAG, std::to_string(ship->DesignID())); - } else if (std::shared_ptr planet = std::dynamic_pointer_cast(obj)) { + } else if (auto planet = std::dynamic_pointer_cast(obj)) { if (planet->Unowned()) sitrep = SitRepEntry( - UserStringNop("SITREP_UNOWNED_PLANET_BOMBARDED_AT_SYSTEM"), + UserStringNop("SITREP_UNOWNED_PLANET_ATTACKED_AT_SYSTEM"), CurrentTurn() + 1, "icons/sitrep/colony_bombarded.png", - UserStringNop("SITREP_UNOWNED_PLANET_BOMBARDED_AT_SYSTEM_LABEL"), true); + UserStringNop("SITREP_UNOWNED_PLANET_ATTACKED_AT_SYSTEM_LABEL"), true); else sitrep = SitRepEntry( - UserStringNop("SITREP_PLANET_BOMBARDED_AT_SYSTEM"), + UserStringNop("SITREP_PLANET_ATTACKED_AT_SYSTEM"), CurrentTurn() + 1, "icons/sitrep/colony_bombarded.png", - UserStringNop("SITREP_PLANET_BOMBARDED_AT_SYSTEM_LABEL"), true); + UserStringNop("SITREP_PLANET_ATTACKED_AT_SYSTEM_LABEL"), true); sitrep.AddVariable(VarText::PLANET_ID_TAG, std::to_string(object_id)); } else { @@ -250,7 +254,7 @@ SitRepEntry CreateCombatDamagedObjectSitRep(int object_id, int combat_system_id, } SitRepEntry CreateCombatDestroyedObjectSitRep(int object_id, int combat_system_id, int empire_id) { - std::shared_ptr obj = GetEmpireKnownObject(object_id, empire_id); + auto obj = EmpireKnownObjects(empire_id).get(object_id); if (!obj) { DebugLogger() << "Object " << object_id << " does not exist!!!"; return GenericCombatDestroyedObjectSitrep(combat_system_id); @@ -258,7 +262,7 @@ SitRepEntry CreateCombatDestroyedObjectSitRep(int object_id, int combat_system_i SitRepEntry sitrep; - if (std::shared_ptr ship = std::dynamic_pointer_cast(obj)) { + if (auto ship = std::dynamic_pointer_cast(obj)) { if (ship->Unowned()) sitrep = SitRepEntry( UserStringNop("SITREP_UNOWNED_SHIP_DESTROYED_AT_SYSTEM"), @@ -280,7 +284,7 @@ SitRepEntry CreateCombatDestroyedObjectSitRep(int object_id, int combat_system_i sitrep.AddVariable(VarText::SHIP_ID_TAG, std::to_string(object_id)); sitrep.AddVariable(VarText::DESIGN_ID_TAG, std::to_string(ship->DesignID())); - } else if (std::shared_ptr fleet = std::dynamic_pointer_cast(obj)) { + } else if (auto fleet = std::dynamic_pointer_cast(obj)) { if (fleet->Unowned()) sitrep = SitRepEntry( UserStringNop("SITREP_UNOWNED_FLEET_DESTROYED_AT_SYSTEM"), @@ -295,7 +299,7 @@ SitRepEntry CreateCombatDestroyedObjectSitRep(int object_id, int combat_system_i UserStringNop("SITREP_FLEET_DESTROYED_AT_SYSTEM_LABEL"), true); sitrep.AddVariable(VarText::FLEET_ID_TAG, std::to_string(object_id)); - } else if (std::shared_ptr planet = std::dynamic_pointer_cast(obj)) { + } else if (auto planet = std::dynamic_pointer_cast(obj)) { if (planet->Unowned()) sitrep = SitRepEntry( UserStringNop("SITREP_UNOWNED_PLANET_DESTROYED_AT_SYSTEM"), @@ -310,7 +314,7 @@ SitRepEntry CreateCombatDestroyedObjectSitRep(int object_id, int combat_system_i UserStringNop("SITREP_PLANET_DESTROYED_AT_SYSTEM_LABEL"), true); sitrep.AddVariable(VarText::PLANET_ID_TAG, std::to_string(object_id)); - } else if (std::shared_ptr building = std::dynamic_pointer_cast(obj)) { + } else if (auto building = std::dynamic_pointer_cast(obj)) { if (building->Unowned()) sitrep = SitRepEntry( UserStringNop("SITREP_UNOWNED_BUILDING_DESTROYED_ON_PLANET_AT_SYSTEM"), @@ -366,13 +370,34 @@ SitRepEntry CreatePlanetOutpostedSitRep(int planet_id) { return sitrep; } +SitRepEntry CreatePlanetGiftedSitRep(int planet_id, int empire_id) { + SitRepEntry sitrep( + UserStringNop("SITREP_PLANET_GIFTED"), + CurrentTurn() + 1, + "icons/sitrep/gift.png", + UserStringNop("SITREP_PLANET_GIFTED_LABEL"), true); + sitrep.AddVariable(VarText::PLANET_ID_TAG, std::to_string(planet_id)); + sitrep.AddVariable(VarText::EMPIRE_ID_TAG, std::to_string(empire_id)); + return sitrep; +} + +SitRepEntry CreateFleetGiftedSitRep(int fleet_id, int empire_id) { + SitRepEntry sitrep( + UserStringNop("SITREP_FLEET_GIFTED"), + CurrentTurn() + 1, + "icons/sitrep/gift.png", + UserStringNop("SITREP_FLEET_GIFTED_LABEL"), true); + sitrep.AddVariable(VarText::FLEET_ID_TAG, std::to_string(fleet_id)); + sitrep.AddVariable(VarText::EMPIRE_ID_TAG, std::to_string(empire_id)); + return sitrep; +} + SitRepEntry CreateFleetArrivedAtDestinationSitRep(int system_id, int fleet_id, int recipient_empire_id) { - std::shared_ptr fleet = GetFleet(fleet_id); + auto fleet = Objects().get(fleet_id); //bool system_contains_recipient_empire_planets = false; - //if (const System* system = GetSystem(system_id)) { - // for (int planet_id : system->FindObjectIDs()) { - // const Planet* planet = GetPlanet(planet_id); + //if (const System* system = Objects().get(system_id)) { + // for (const auto& planet : system->all()) { // if (!planet || planet->Unowned()) // continue; // if (planet->OwnedBy(recipient_empire_id)) { @@ -406,7 +431,7 @@ SitRepEntry CreateFleetArrivedAtDestinationSitRep(int system_id, int fleet_id, i sitrep.AddVariable(VarText::FLEET_ID_TAG, std::to_string(fleet_id)); int ship_id = *fleet->ShipIDs().begin(); sitrep.AddVariable(VarText::SHIP_ID_TAG, std::to_string(ship_id)); - if (std::shared_ptr ship = GetShip(ship_id)) + if (auto ship = Objects().get(ship_id)) sitrep.AddVariable(VarText::DESIGN_ID_TAG, std::to_string(ship->DesignID())); return sitrep; } else { @@ -442,7 +467,7 @@ SitRepEntry CreateFleetArrivedAtDestinationSitRep(int system_id, int fleet_id, i sitrep.AddVariable(VarText::EMPIRE_ID_TAG, std::to_string(fleet->Owner())); int ship_id = *fleet->ShipIDs().begin(); sitrep.AddVariable(VarText::SHIP_ID_TAG, std::to_string(ship_id)); - if (std::shared_ptr ship = GetShip(ship_id)) + if (auto ship = Objects().get(ship_id)) sitrep.AddVariable(VarText::DESIGN_ID_TAG, std::to_string(ship->DesignID())); return sitrep; } else { @@ -469,7 +494,7 @@ SitRepEntry CreateFleetArrivedAtDestinationSitRep(int system_id, int fleet_id, i sitrep.AddVariable(VarText::EMPIRE_ID_TAG, std::to_string(fleet->Owner())); int ship_id = *fleet->ShipIDs().begin(); sitrep.AddVariable(VarText::SHIP_ID_TAG, std::to_string(ship_id)); - if (std::shared_ptr ship = GetShip(ship_id)) + if (auto ship = Objects().get(ship_id)) sitrep.AddVariable(VarText::DESIGN_ID_TAG, std::to_string(ship->DesignID())); return sitrep; } else { diff --git a/util/SitRepEntry.h b/util/SitRepEntry.h index a4f281f5950..22768482360 100644 --- a/util/SitRepEntry.h +++ b/util/SitRepEntry.h @@ -1,6 +1,9 @@ #ifndef _SitRepEntry_h_ #define _SitRepEntry_h_ +//! @file +//! Declares the SitRepEntry calls and related factory functions. + #include "VarText.h" #include @@ -8,23 +11,19 @@ #include "Export.h" -/** Situation report entry, to be displayed in the SitRep screen. */ +//! Represents a situation report entry for a significant game event. class FO_COMMON_API SitRepEntry : public VarText { public: - /** \name Structors */ //@{ SitRepEntry(); SitRepEntry(const std::string& template_string, int turn, const std::string& icon, const std::string label, bool stringtable_lookup); - //@} - /** Accessors */ //@{ int GetDataIDNumber(const std::string& tag) const; const std::string& GetDataString(const std::string& tag) const; int GetTurn() const { return m_turn; } const std::string& GetIcon() const { return m_icon; } const std::string& GetLabelString() const { return m_label; } std::string Dump() const; - //@} private: int m_turn; @@ -32,11 +31,16 @@ class FO_COMMON_API SitRepEntry : public VarText { std::string m_label; friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -/** Sitrep constructors for each SitRep type */ +//! @name SitRepEntry factories +//! +//! Factory functions to create SitRepEntry(s) for specific situation report +//! events. +//! +//! @{ SitRepEntry CreateTechResearchedSitRep(const std::string& tech_name); SitRepEntry CreateShipBuiltSitRep(int ship_id, int system_id, int shipdesign_id); SitRepEntry CreateShipBlockBuiltSitRep(int system_id, int shipdesign_id, int number); @@ -56,14 +60,17 @@ SitRepEntry CreatePlanetDepopulatedSitRep(int planet_id); FO_COMMON_API SitRepEntry CreatePlanetColonizedSitRep(int planet_id, const std::string& species); FO_COMMON_API SitRepEntry CreatePlanetOutpostedSitRep(int planet_id); +FO_COMMON_API SitRepEntry CreatePlanetGiftedSitRep(int planet_id, int empire_id); +FO_COMMON_API SitRepEntry CreateFleetGiftedSitRep(int fleet_id, int empire_id); + FO_COMMON_API SitRepEntry CreateFleetArrivedAtDestinationSitRep(int system_id, int fleet_id, int recipient_empire_id); SitRepEntry CreateEmpireEliminatedSitRep(int empire_id); SitRepEntry CreateVictorySitRep(const std::string& reason_string, int empire_id); FO_COMMON_API SitRepEntry CreateSitRep(const std::string& template_string, int turn, const std::string& icon, const std::vector>& parameters, const std::string label = "", bool stringtable_lookup = true); +//! @} -// template implementations -template +template void SitRepEntry::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(VarText) diff --git a/util/StringTable.cpp b/util/StringTable.cpp index 4d08529decd..727639e8ad8 100644 --- a/util/StringTable.cpp +++ b/util/StringTable.cpp @@ -11,41 +11,56 @@ #include -// static(s) -const std::string StringTable_::S_DEFAULT_FILENAME = "en.txt"; -const std::string StringTable_::S_ERROR_STRING = "ERROR: "; +namespace { + const std::string DEFAULT_FILENAME = "en.txt"; + const std::string ERROR_STRING = "ERROR: "; +} + -// StringTable -StringTable_::StringTable_(): - m_filename(S_DEFAULT_FILENAME) +StringTable::StringTable(): + m_filename(DEFAULT_FILENAME) { Load(); } -StringTable_::StringTable_(const std::string& filename, const StringTable_* lookups_fallback_table /* = 0 */): +StringTable::StringTable(const std::string& filename, std::shared_ptr fallback): m_filename(filename) -{ Load(lookups_fallback_table); } +{ Load(fallback); } -StringTable_::~StringTable_() +StringTable::~StringTable() {} -bool StringTable_::StringExists(const std::string& index) const -{ return m_strings.find(index) != m_strings.end(); } +bool StringTable::StringExists(const std::string& key) const { + std::lock_guard lock(m_mutex); + return m_strings.count(key); +} -bool StringTable_::Empty() const -{ return m_strings.empty(); } +const std::string& StringTable::operator[] (const std::string& key) const { + std::lock_guard lock(m_mutex); -const std::string& StringTable_::operator[] (const std::string& index) const { - static std::string error_retval; - std::map::const_iterator it = m_strings.find(index); - return it == m_strings.end() ? error_retval = S_ERROR_STRING + index : it->second; + auto it = m_strings.find(key); + if (it != m_strings.end()) + return it->second; + + auto error = m_error_strings.insert(ERROR_STRING + key); + return *(error.first); } -void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { - boost::filesystem::path path = FilenameToPath(m_filename); +void StringTable::Load(std::shared_ptr fallback) { + std::lock_guard lock(m_mutex); + + if (fallback && !fallback->m_initialized) { + // this prevents deadlock if two stringtables were to be loaded + // simultaneously with eachother as fallback tables + ErrorLogger() << "StringTable::Load given uninitialized stringtable as fallback. Ignoring."; + fallback = nullptr; + } + + auto path = FilenameToPath(m_filename); std::string file_contents; bool read_success = parse::read_file(path, file_contents); if (!read_success) { - ErrorLogger() << "StringTable_::Load failed to read file at path: " << path.string(); + ErrorLogger() << "StringTable::Load failed to read file at path: " << path.string(); + //m_initialized intentionally left false return; } // add newline at end to avoid errors when one is left out, but is expected by parsers @@ -55,9 +70,10 @@ void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { std::map fallback_lookup_strings; std::string fallback_table_file; - if (lookups_fallback_table) { - fallback_table_file = lookups_fallback_table->Filename(); - fallback_lookup_strings.insert(lookups_fallback_table->GetStrings().begin(), lookups_fallback_table->GetStrings().end()); + if (fallback) { + std::lock_guard fallback_lock(fallback->m_mutex); + fallback_table_file = fallback->Filename(); + fallback_lookup_strings.insert(fallback->m_strings.begin(), fallback->m_strings.end()); } using namespace boost::xpressive; @@ -83,8 +99,8 @@ void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { keep("[[" >> (s1 = IDENTIFIER) >> "]]"); // parse input text stream - std::string::iterator it = file_contents.begin(); - std::string::iterator end = file_contents.end(); + auto it = file_contents.begin(); + auto end = file_contents.end(); smatch matches; bool well_formed = false; @@ -102,7 +118,7 @@ void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { it = end - matches.suffix().length(); if (well_formed) { - for (smatch::nested_results_type::const_iterator match_it = matches.nested_results().begin(); + for (auto match_it = matches.nested_results().begin(); match_it != matches.nested_results().end(); ++match_it) { if (match_it->regex_id() == KEY.regex_id()) { @@ -111,7 +127,7 @@ void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { match_it->regex_id() == MULTI_LINE_VALUE.regex_id()) { assert(key != ""); - if (m_strings.find(key) == m_strings.end()) { + if (!m_strings.count(key)) { m_strings[key] = match_it->str(); boost::algorithm::replace_all(m_strings[key], "\\n", "\n"); } else { @@ -135,12 +151,13 @@ void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { ErrorLogger() << "Last and prior keys matched: " << key << ", " << prev_key; std::cerr << "Exception caught regex parsing Stringtable: " << e.what() << std::endl; std::cerr << "Last and prior keys matched: " << key << ", " << prev_key << std::endl; + m_initialized = true; return; } if (well_formed) { // recursively expand keys -- replace [[KEY]] by the text resulting from expanding everything in the definition for KEY - for (std::map::value_type& entry : m_strings) { + for (auto& entry : m_strings) { //DebugLogger() << "Checking key expansion for: " << entry.first; std::size_t position = 0; // position in the definition string, past the already processed part smatch match; @@ -148,18 +165,20 @@ void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { cyclic_reference_check[entry.first] = entry.second.length(); std::string rawtext = entry.second; std::string cumulative_subsititions; + while (regex_search(entry.second.begin() + position, entry.second.end(), match, KEYEXPANSION)) { position += match.position(); //DebugLogger() << "checking next internal keyword match: " << match[1] << " with matchlen " << match.length(); if (match[1].length() != match.length() - 4) - ErrorLogger() << "Positional error in key expansion: " << match[1] << " with length: " << match[1].length() << "and matchlen: " << match.length(); + ErrorLogger() << "Positional error in key expansion: " << match[1] << " with length: " + << match[1].length() << "and matchlen: " << match.length(); // clear out any keywords that have been fully processed - for (std::map< std::string, std::size_t >::iterator ref_check_it = cyclic_reference_check.begin(); + for (auto ref_check_it = cyclic_reference_check.begin(); ref_check_it != cyclic_reference_check.end(); ) { if (ref_check_it->second <= position) { //DebugLogger() << "Popping from cyclic ref check: " << ref_check_it->first; - cyclic_reference_check.erase(ref_check_it++); + ref_check_it = cyclic_reference_check.erase(ref_check_it); } else if (ref_check_it->second < position + match.length()) { ErrorLogger() << "Expansion error in key expansion: [[" << ref_check_it->first << "]] having end " << ref_check_it->second; ErrorLogger() << " currently at expansion text position " << position << " with match length: " << match.length(); @@ -171,14 +190,14 @@ void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { } else ++ref_check_it; } - if (cyclic_reference_check.find(match[1]) == cyclic_reference_check.end()) { + if (!cyclic_reference_check.count(match[1])) { //DebugLogger() << "Pushing to cyclic ref check: " << match[1]; cyclic_reference_check[match[1]] = position + match.length(); - std::map::iterator map_lookup_it = m_strings.find(match[1]); + auto map_lookup_it = m_strings.find(match[1]); bool foundmatch = map_lookup_it != m_strings.end(); - if (!foundmatch && lookups_fallback_table) { - DebugLogger() << "Key expansion: " << match[1] << " not found in primary stringtable: " << m_filename - << "; checking in fallback file" << fallback_table_file; + if (!foundmatch && !fallback_lookup_strings.empty()) { + DebugLogger() << "Key expansion: " << match[1] << " not found in primary stringtable: " << m_filename + << "; checking in fallback file: " << fallback_table_file; map_lookup_it = fallback_lookup_strings.find(match[1]); foundmatch = map_lookup_it != fallback_lookup_strings.end(); } @@ -187,7 +206,7 @@ void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { cumulative_subsititions += substitution + "|**|"; entry.second.replace(position, match.length(), substitution); std::size_t added_chars = substitution.length() - match.length(); - for (std::map::value_type& ref_check : cyclic_reference_check) { + for (auto& ref_check : cyclic_reference_check) { ref_check.second += added_chars; } // replace recursively -- do not skip past substitution @@ -207,14 +226,14 @@ void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { } // nonrecursively replace references -- convert [[type REF]] to string for REF - for (std::map::value_type& entry : m_strings) { + for (auto& entry : m_strings) { std::size_t position = 0; // position in the definition string, past the already processed part smatch match; while (regex_search(entry.second.begin() + position, entry.second.end(), match, REFERENCE)) { position += match.position(); - std::map::iterator map_lookup_it = m_strings.find(match[2]); + auto map_lookup_it = m_strings.find(match[2]); bool foundmatch = map_lookup_it != m_strings.end(); - if (!foundmatch && lookups_fallback_table) { + if (!foundmatch && !fallback_lookup_strings.empty()) { DebugLogger() << "Key reference: " << match[2] << " not found in primary stringtable: " << m_filename << "; checking in fallback file" << fallback_table_file; map_lookup_it = fallback_lookup_strings.find(match[2]); @@ -234,4 +253,6 @@ void StringTable_::Load(const StringTable_* lookups_fallback_table /* = 0 */) { } else { ErrorLogger() << "StringTable file \"" << m_filename << "\" is malformed around line " << std::count(file_contents.begin(), it, '\n'); } + + m_initialized = true; } diff --git a/util/StringTable.h b/util/StringTable.h index 7db2f5d0e4d..6ba7f7d9e69 100644 --- a/util/StringTable.h +++ b/util/StringTable.h @@ -1,105 +1,197 @@ -#ifndef StringTable__h_ -#define StringTable__h_ +#ifndef StringTable_h_ +#define StringTable_h_ + +//! @file +//! Declares the StringTable class. #include #include +#include +#include +#include -// HACK! StringTable is renamed to StringTable_ because freeimage defines -// a class StringTable too. If both are named identically, static linking -// won't be possible. - -//! This is an implementation of a String Table for internationalization purposes. -//! The table is built from a file of the following format:
-//! name_of_language
-//! ID1
-//! STRING1

-//! comments are also allowed, preceded by a pound '#' sign -//! These will not be added to the table. -//! Any number of blank lines may separate strings, but an -//! identifier MUST be followed by its string on the next line. -//! Newlines sequences ("\n") within strings are converted to newlines when the string is loaded. -//! -//! An example:
-//! English
-//!
-//! WELCOME
-//! Welcome to FreeOrion
-//!
-//! EXIT
-//! Exit the program
-//!
-//! # These are comments
-//! # these are more comments
-//! TEST_ONE
-//! test one
-//! TEST TWO
-//! test two
-//!
-//! TEST THREE
-//! test three
-//! #
-//!
-//!
-//!
-//! TESTFOUR
-//! test four
-class StringTable_ { +//! Provides a key based translation string lookup with fallback. +//! +//! StringTable is a map to look up translations based on key representing a +//! translation entry. When a translation for a key is not found the +//! StringTable delegates the lookup to another fallback StringTable, which is +//! filled with entries from another language. When no fallback StringTable +//! contains the requested translation entry an error string is returned, +//! pointing out the missing translation entry. +//! +//! StringTables are loaded from text files which contain the translation +//! entries for a specific language. Those translation files are named like +//! +//! ```{.txt} +//! .txt +//! ``` +//! +//! where is the two letter language identifier for a language +//! according to ISO 639-1 (e.g English=en, Russian=ru, German=de, ...). +//! +//! The format of those translation files consists of newline separated entries +//! where: +//! +//! * The first line is the native language name. +//! * Linse starting with a hash sign `#` are considered comments and are +//! ignored when loading the file. +//! * Empty lines are ignored when loading the file. +//! * The translation entries consist of an key followed by a newline, followed +//! by the translated text followed by a newline. +//! * An identifer must be unique within an translation file. +//! * Identifiers consist of the any latin character, digits or an underscore. +//! Spaces or other characters are not allowed. +//! * The translated text is a valid UTF-8 based string, which will either be +//! terminated by a newline, trimming of any whitespace at the begining or end +//! of the translated string or a multi-line string. +//! * A multi-line string starts and ends with three single quotes `'''`. As the +//! name implies a multi-line string can span over multiple lines and any +//! whitespace inside the string will be preseved. +//! +//! A minimal example translation file for the english language `en.txt` should +//! look like: +//! +//! ```{.txt} +//! English +//! # This line is a comment, the line above is the native language name. +//! +//! # The two lines below are an translation entry consisting of an identifer +//! # and a single line of translated text. +//! A_SINGLE_LINE +//! This is the translated text associated with A_SINGLE_LINE +//! +//! # The line below is a translation entry with multiple lines of translated +//! # text. +//! A_MULTI_LINE +//! '''This text +//! spans over +//! multiple lines. +//! The whitespace before this line is preseved, same goes for the newlines +//! after this line up to the triple single quotes. +//! +//! +//! ''' +//! +//! # This translation entry doesn't preserve whitespace +//! SINGLE_LINE_WS_TRIM +//! This text is intended in the translation file, but the whitespace will be trimmed. +//! +//! # This translation entry preseves whitespace +//! SINGLE_LINE_WS_PRESERVE +//! ''' This text will keep its leading and trailing whitespace ''' +//! ``` +//! +//! StringTables implement multiple string substitution mechanism. +//! +//! The first substitution mechanism replaces references to a translation entry +//! with the text of another translation entry. The syntax for a reference +//! consist of two opening square brackets `[[`, the referenced key and two +//! closing brackets `]]`. For example: +//! +//! ```{.txt} +//! REFERENCE +//! I am the replaced text +//! +//! TRANSLATION_ENTRY +//! This translation embeds another translation right here > [[REFERENCE]] +//! ``` +//! +//! would return +//! +//! ```{.txt} +//! This translation embeds another translaion right here > I am the replaced text +//! ``` +//! +//! The second substitution mechanism replaces typed references with type links +//! and the translation text. The syntax for a typed reference consists of two +//! opening square brackets `[[`, a type key, a space, the referenced key and +//! two closing brackets `]]``. For example: +//! +//! ```{.txt} +//! REFERENCE +//! I am referenced +//! +//! TRANSLATION_ENTRY +//! This translation links to [[foo_type REFERENCE]] +//! ``` +//! +//! would return +//! +//! ```{.txt} +//! This translation links to I am referenced +//! ``` +class StringTable { public: - //! \name Structors - //!@{ - StringTable_(); //!< default construction, uses S_DEFAULT_FILENAME - - //! construct a StringTable_ from the given filename - //! @param filename A file containing the data for this StringTable_ - //! @param lookups_fallback_table A StringTable_ to be used as fallback expansions lookup - StringTable_(const std::string& filename, const StringTable_* lookups_fallback_table = nullptr); - - ~StringTable_(); //!< default destructor - //!@} - - //! \name Accessors - //!@{ - //! @param index The index of the string to lookup - //! @return The string found at index in the table, or S_ERROR_STRING if it fails - const std::string& operator[] (const std::string& index) const; //!< Looks up a string at index and returns it. - - //! @param index The index of the string to check for - //! @return true iff a string exists with that index, false otherwise - bool StringExists(const std::string& index) const; //!< Looks up a string at index and returns if the string is present. - - //! @return true iff this stringtable contain no entries - bool Empty() const; //!< Checks if this stringtable contains any entries - - //! @param index The index of the string to lookup - //! @return The string found at index in the table - inline const std::string& String(const std::string& index) const { return operator[] (index); } //!< Interface to operator() \see StringTable_::operator[] - inline const std::string& Language() const {return m_language;} //!< Returns the language of this StringTable_ - inline const std::string& Filename() const {return m_filename;} //!< accessor to the filename - //!@} - - //! \name Constants - //!@{ - static const std::string S_DEFAULT_FILENAME; //!< the default file used if none specified - static const std::string S_ERROR_STRING; //!< A string that gets returned when invalid indices are used - //!@} + //! Create a StringTable instance by loading translations from the default + //! translation file. + StringTable(); + + //! Create a StringTable from the given @p filename. + //! + //! @param filename + //! The file name of the translation file to load. + //! @param fallback + //! A StringTable that should be used look up unknown translation + //! entries. + StringTable(const std::string& filename, std::shared_ptr fallback = nullptr); + + ~StringTable(); + + //! Returns a translation for @p key. + //! + //! @param key + //! The identifying key of a translation entry. + //! + //! @return + //! The translation for @p key or S_ERROR_STRING if no translation was + //! found. + const std::string& operator[] (const std::string& key) const; + + //! Returns if a translation for @p key exists. + //! + //! @param key + //! The identifying key of a translation entry. + //! + //! @return + //! True iff a translation with that key exists, false otherwise. + bool StringExists(const std::string& key) const; + + //! Returns the native language name of this StringTable. + inline const std::string& Language() const + { return m_language; } + + //! Returns the translation file name this StringTable was loaded from. + inline const std::string& Filename() const + { return m_filename; } private: - //! \name Internal Functions - //!@{ - //! Loads the String table file from m_filename - //! @param lookups_fallback_table A StringTable_ to be used as fallback expansions lookup - void Load(const StringTable_* lookups_fallback_table = nullptr); - - const std::map& GetStrings() const {return m_strings;} //!< returns a const reference to the strings in this table - - //!@} - - //! \name Data Members - //!@{ - std::string m_filename; //!< the name of the file this StringTable_ was constructed with - std::string m_language; //!< A string containing the name of the language used - std::map m_strings; //!< The strings in the table - //!@} + //! Loads this StringTable from #m_filename + //! + //! @param fallback + //! A StringTable that should be used look up unknown translation + //! entries. + void Load(std::shared_ptr fallback = nullptr); + + //! The filename this StringTable was loaded from. + std::string m_filename; + + //! The native language name of the StringTable translations. + std::string m_language; + + //! Mapping of translation entry keys to translated strings. + std::map m_strings; + + //! Cache for missing translation keys to ensure the returned error + //! reference string is not destroyed due local scope. + mutable std::unordered_set m_error_strings; + + //! Ensure that multithreaded access to a StringTable is done in an orderly + //! fashion. + mutable std::mutex m_mutex; + + //! True if the StringTable was completely loaded and all references + //! were successfully resolved. + bool m_initialized = false; }; -#endif // StringTable__h_ +#endif // StringTable_h_ diff --git a/util/VarText.cpp b/util/VarText.cpp index 2fb8a5ad7d2..b17c9c056be 100644 --- a/util/VarText.cpp +++ b/util/VarText.cpp @@ -4,136 +4,85 @@ #include "../universe/ShipDesign.h" #include "../universe/System.h" #include "../universe/Enums.h" -#include "../Empire/EmpireManager.h" #include "../Empire/Empire.h" #include "i18n.h" #include "Logger.h" +#include "AppInterface.h" -#include -#include -#include +#include +#include #include -FO_COMMON_API extern const int INVALID_DESIGN_ID; +namespace xpr = boost::xpressive; -// Forward declarations class Tech; class BuildingType; class Special; class Species; class FieldType; +class ShipHull; +class ShipPart; const Tech* GetTech(const std::string& name); const BuildingType* GetBuildingType(const std::string& name); const Special* GetSpecial(const std::string& name); const Species* GetSpecies(const std::string& name); const FieldType* GetFieldType(const std::string& name); +const ShipHull* GetShipHull(const std::string& name); +const ShipPart* GetShipPart(const std::string& name); namespace { - const std::string START_VAR("%"); - const std::string END_VAR("%"); - const std::string LABEL_SEPARATOR(":"); - - ////////////////////////////////////////// - ///// Tag substitution generators//////// - //////////////////////////////////////// - - /// Surround content with approprite tags based on tag_of + //! Return @p content surrounded by the given @p tags. + //! + //! @param content + //! The text that should be wrapped into @p tags. + //! @param tag + //! The tags that should be wrapping the given @p content. The tag + //! shouldn't contain whitespace. + //! @param data + //! Additional data assigned to the @p tag. + //! + //! @return + //! The tagged content. std::string WithTags(const std::string& content, const std::string& tag, const std::string& data) { std::string open_tag = "<" + tag + " " + data + ">"; std::string close_tag = ""; return open_tag + content + close_tag; } - /// The signature of functions that generate substitution strings for - /// tags. - typedef std::string (*TagString)(const std::string& data, const std::string& tag, bool& valid); - - /// Get string substitute for a translated text tag - std::string TextString(const std::string& data, const std::string& tag, bool& valid) { - return UserString(data); - } - - /// Get string substitute for a raw text tag - std::string RawTextString(const std::string& data, const std::string& tag, bool& valid) { - return data; - } + //! Function signature of tag substitution functions. + //! + //! @param data + //! Data values The signature of functions that generate substitution + //! strings for tags. + typedef std::function (const std::string& data)> TagString; - ///Get string substitute for a tag that is a universe object - std::string UniverseObjectString(const std::string& data, const std::string& tag, bool& valid) { + //! Get string substitute for a tag that is a universe object + boost::optional UniverseObjectString(const std::string& data, const std::string& tag) { int object_id = INVALID_OBJECT_ID; try { object_id = boost::lexical_cast(data); - } catch (const std::exception&) { - ErrorLogger() << "UniverseObjectString couldn't cast \"" << data << "\" to int for object ID."; - valid = false; - return UserString("ERROR"); - } - std::shared_ptr obj = GetUniverseObject(object_id); - if (!obj) { - //ErrorLogger() << "UniverseObjectString couldn't get object with ID " << object_id; - valid = false; - return UserString("ERROR"); + } catch (...) { + return boost::none; } + auto obj = Objects().get(object_id); + if (!obj) + return boost::none; return WithTags(GetVisibleObjectName(obj), tag, data); } - /// combat links always just labelled "Combat"; don't need to look up details - std::string CombatLogString(const std::string& data, const std::string& tag, bool& valid) - { return WithTags(UserString("COMBAT"), tag, data); } - - /// Returns substitution string for a ship design tag - std::string ShipDesignString(const std::string& data, const std::string& tag, bool& valid) { - int design_id = INVALID_DESIGN_ID; - try { - design_id = boost::lexical_cast(data); - } catch (const std::exception&) { - ErrorLogger() << "SubstituteAndAppend couldn't cast \"" << data << "\" to int for ship design ID."; - valid = false; - return UserString("ERROR"); - } - const ShipDesign* design = GetShipDesign(design_id); - if (!design) { - ErrorLogger() << "SubstituteAndAppend couldn't get ship design with ID " << design_id; - valid = false; - return UserString("ERROR"); - } - return WithTags(design->Name(), tag, data); - } - - /// Returns substitution string for a predefined ship design tag - std::string PredefinedShipDesignString(const std::string& data, const std::string& tag, bool& valid) { + //! Returns substitution string for a predefined ship design tag + boost::optional PredefinedShipDesignString(const std::string& data) { const ShipDesign* design = GetPredefinedShipDesign(data); - if (!design) { - ErrorLogger() << "SubstituteAndAppend couldn't get predefined ship design with name " << data; - valid = false; - return UserString("ERROR"); - } - return WithTags(design->Name(), tag, data); - } + if (!design) + return boost::none; - /// Returns substitution string for an empire tag - std::string EmpireString(const std::string& data, const std::string& tag, bool& valid) { - int empire_id = ALL_EMPIRES; - try { - empire_id = boost::lexical_cast(data); - } catch (const std::exception&) { - ErrorLogger() << "SubstituteAndAppend couldn't cast \"" << data << "\" to int for empire ID."; - valid = false; - return UserString("ERROR"); - } - const Empire* empire = GetEmpire(empire_id); - if (!empire) { - ErrorLogger() << "SubstituteAndAppend couldn't get empire with ID " << empire_id; - valid = false; - return UserString("ERROR"); - } - return WithTags(empire->Name(), tag, data); + return WithTags(design->Name(), VarText::PREDEFINED_DESIGN_TAG, data); } - std::string MeterTypeString(const std::string& data, const std::string& tag, bool& valid) { - std::string retval = UserString("ERROR"); + boost::optional MeterTypeString(const std::string& data) { + boost::optional retval = boost::none; // validate data MeterType meter_type = INVALID_METER_TYPE; std::istringstream data_ss(data); @@ -141,131 +90,132 @@ namespace { if (meter_type > INVALID_METER_TYPE && meter_type < NUM_METER_TYPES) { retval = boost::lexical_cast(meter_type); - if (UserStringExists(retval)) - retval = WithTags(UserString(retval), tag, retval); + if (UserStringExists(*retval)) + retval = WithTags(UserString(*retval), VarText::METER_TYPE_TAG, *retval); } return retval; } - /// Interprets value of data as a name. - /// Returns translation of name, if Get says - /// that a thing by that name exists, otherwise ERROR. - template - std::string NameString(const std::string& data, const std::string& tag, bool& valid) { - if (!GetByName(data)) { - valid = false; - return UserString("ERROR"); + //! Returns substitution string for a ship design tag + template + boost::optional IDString(const std::string& data, const std::string& tag) { + int id{}; + try { + id = boost::lexical_cast(data); + } catch (...) { + return boost::none; } - return WithTags(UserString(data), tag, data); + T* object = GetByID(id); + if (!object) { + if (std::is_same::value) + return UserString("FW_UNKNOWN_DESIGN_NAME"); + else + return boost::none; + } + + return WithTags(object->Name(), tag, data); } - /// Returns a map that tells shich function should be used to - /// generate a substitution for which tag. - std::map CreateSubstituterMap() { - std::map subs; - subs[VarText::TEXT_TAG] = TextString; - subs[VarText::RAW_TEXT_TAG] = RawTextString; - - subs[VarText::PLANET_ID_TAG] = - subs[VarText::SYSTEM_ID_TAG] = - subs[VarText::SHIP_ID_TAG] = - subs[VarText::FLEET_ID_TAG] = - subs[VarText::BUILDING_ID_TAG] = - subs[VarText::FIELD_ID_TAG] = UniverseObjectString; - subs[VarText::COMBAT_ID_TAG] = CombatLogString; - subs[VarText::TECH_TAG] = NameString; - subs[VarText::BUILDING_TYPE_TAG] = NameString; - subs[VarText::SHIP_HULL_TAG] = NameString; - subs[VarText::SHIP_PART_TAG] = NameString; - subs[VarText::SPECIAL_TAG] = NameString; - subs[VarText::SPECIES_TAG] = NameString; - subs[VarText::FIELD_TYPE_TAG] = NameString; - subs[VarText::METER_TYPE_TAG] = MeterTypeString; - subs[VarText::DESIGN_ID_TAG] = ShipDesignString; - subs[VarText::PREDEFINED_DESIGN_TAG] = PredefinedShipDesignString; - subs[VarText::EMPIRE_ID_TAG] = EmpireString; - return subs; + //! Returns substitution string for an empire tag + //! Interprets value of data as a name. + //! Returns translation of name, if Get says + //! that a thing by that name exists, otherwise boost::none. + template + boost::optional NameString(const std::string& data, const std::string& tag) { + if (!GetByName(data)) + return boost::none; + return WithTags(UserString(data), tag, data); } - /// Global substitution map, wrapped in a function to avoid initialization order issues + //! Global substitution map, wrapped in a function to avoid initialization order issues const std::map& SubstitutionMap() { - static std::map subs = CreateSubstituterMap(); - return subs; + static std::map substitute_map{ + {VarText::TEXT_TAG, [](const std::string& data) -> boost::optional + { return UserString(data); }}, + {VarText::RAW_TEXT_TAG, [](const std::string& data) -> boost::optional + { return data; }}, + {VarText::PLANET_ID_TAG, [](const std::string& data) + { return UniverseObjectString(data, VarText::PLANET_ID_TAG); }}, + {VarText::SYSTEM_ID_TAG, [](const std::string& data) + { return UniverseObjectString(data, VarText::SYSTEM_ID_TAG); }}, + {VarText::SHIP_ID_TAG, [](const std::string& data) + { return UniverseObjectString(data, VarText::SHIP_ID_TAG); }}, + {VarText::FLEET_ID_TAG, [](const std::string& data) + { return UniverseObjectString(data, VarText::FLEET_ID_TAG); }}, + {VarText::BUILDING_ID_TAG, [](const std::string& data) + { return UniverseObjectString(data, VarText::BUILDING_ID_TAG); }}, + {VarText::FIELD_ID_TAG, [](const std::string& data) + { return UniverseObjectString(data, VarText::FIELD_ID_TAG); }}, + {VarText::COMBAT_ID_TAG, [](const std::string& data) -> boost::optional + { return WithTags(UserString("COMBAT"), VarText::COMBAT_ID_TAG, data); }}, + {VarText::TECH_TAG, [](const std::string& data) + { return NameString(data, VarText::TECH_TAG); }}, + {VarText::BUILDING_TYPE_TAG, [](const std::string& data) + { return NameString(data, VarText::BUILDING_TYPE_TAG); }}, + {VarText::SHIP_HULL_TAG, [](const std::string& data) + { return NameString(data, VarText::SHIP_HULL_TAG); }}, + {VarText::SHIP_PART_TAG, [](const std::string& data) + { return NameString(data, VarText::SHIP_PART_TAG); }}, + {VarText::SPECIAL_TAG, [](const std::string& data) + { return NameString(data, VarText::SPECIAL_TAG); }}, + {VarText::SPECIES_TAG, [](const std::string& data) + { return NameString(data, VarText::SPECIES_TAG); }}, + {VarText::FIELD_TYPE_TAG, [](const std::string& data) + { return NameString(data, VarText::FIELD_TYPE_TAG); }}, + {VarText::METER_TYPE_TAG, MeterTypeString}, + {VarText::DESIGN_ID_TAG, [](const std::string& data) + { return IDString(data, VarText::DESIGN_ID_TAG); }}, + {VarText::PREDEFINED_DESIGN_TAG, PredefinedShipDesignString}, + {VarText::EMPIRE_ID_TAG, [](const std::string& data) + { return IDString(data, VarText::EMPIRE_ID_TAG); }}, + }; + + return substitute_map; } - /** Converts (first, last) to a string, looks up its value in the Universe, - * then appends this to the end of a std::string. */ - struct SubstituteAndAppend { - SubstituteAndAppend(const std::map& variables, - std::string& str, bool& valid) : + //! Looks up the given match in the Universe and returns the Universe + //! entities value. + struct Substitute { + Substitute(const std::map& variables, + bool& valid) : m_variables(variables), - m_str(str), m_valid(valid) {} - void operator()(const char* first, const char* last) const { - std::string token(first, last); - - // special case: "%%" is interpreted to be a '%' character - if (token.empty()) { - m_str += "%"; - return; - } - - // Labelled tokens have the form %tag:label%, unlabelled are just %tag% - std::vector pieces; - boost::split(pieces, token, boost::is_any_of(LABEL_SEPARATOR)); - - std::string tag; //< The tag of the token (the type) - std::string label; //< The label of the token (the key to fetch data by) - if (pieces.size() == 1) { - // No separator. There is only a tag. The tag is the default label - tag = token; - label = token; - } else if (pieces.size() == 2) { - // Had a separator - tag = pieces[0]; - label = pieces[1]; - } + std::string operator()(xpr::smatch const& match) const { + // Labelled variables have the form %tag:label%, unlabelled are just %tag% + std::string tag = match[1]; + // Use the label value. When missing, use the tag submatch as label instead. + std::string label = match[match[2].matched ? 2 : 1]; // look up child - std::map::const_iterator elem = m_variables.find(label); + auto elem = m_variables.find(label); if (m_variables.end() == elem) { - m_str += UserString("ERROR"); + ErrorLogger() << "Substitute::operator(): No value found for label: " << label << " from token: " << match.str(); m_valid = false; - return; + return UserString("ERROR"); } - std::map::const_iterator substituter = SubstitutionMap().find(tag); + auto substituter = SubstitutionMap().find(tag); if (substituter != SubstitutionMap().end()) { - m_str += substituter->second(elem->second, tag, m_valid); - } else { - ErrorLogger() << "SubstituteAndAppend::operator(): No substitution scheme defined for tag: " << tag << " from token: " << token; - m_str += UserString("ERROR"); - m_valid = false; + if (auto substitution = substituter->second(elem->second)) { + return *substitution; + } } - } - - const std::map& m_variables; - std::string& m_str; - bool& m_valid; - }; - // sticks a sequence of characters onto the end of a std::string - struct StringAppend { - StringAppend(std::string& str) : - m_str(str) - {} + ErrorLogger() << "Substitute::operator(): No substitution executed for tag: " << tag << " from token: " << match.str(); + m_valid = false; + return UserString("ERROR"); + } - void operator()(const char* first, const char* last) const - { m_str += std::string(first, last); } - std::string& m_str; + const std::map& m_variables; + bool& m_valid; }; } -// static(s) + const std::string VarText::TEXT_TAG = "text"; const std::string VarText::RAW_TEXT_TAG = "rawtext"; @@ -292,13 +242,12 @@ const std::string VarText::FIELD_TYPE_TAG = "fieldtype"; const std::string VarText::METER_TYPE_TAG = "metertype"; -VarText::VarText() : - m_stringtable_lookup_flag(false) +VarText::VarText() {} -VarText::VarText(const std::string& template_string, bool stringtable_lookup_template/* = true*/) : +VarText::VarText(const std::string& template_string, bool stringtable_lookup/* = true*/) : m_template_string(template_string), - m_stringtable_lookup_flag(stringtable_lookup_template) + m_stringtable_lookup_flag(stringtable_lookup) {} const std::string& VarText::GetText() const { @@ -313,14 +262,14 @@ bool VarText::Validate() const { return m_validated; } -void VarText::SetTemplateString(const std::string& text, bool stringtable_lookup_template/* = true*/) { - m_text = text; - m_stringtable_lookup_flag = stringtable_lookup_template; +void VarText::SetTemplateString(const std::string& template_string, bool stringtable_lookup/* = true*/) { + m_template_string = template_string; + m_stringtable_lookup_flag = stringtable_lookup; } std::vector VarText::GetVariableTags() const { std::vector retval; - for (const std::map::value_type& variable : m_variables) + for (const auto& variable : m_variables) retval.push_back(variable.first); return retval; } @@ -340,16 +289,6 @@ void VarText::GenerateVarText() const { // get string into which to substitute variables std::string template_str = m_stringtable_lookup_flag ? UserString(m_template_string) : m_template_string; - // set up parser - using namespace boost::spirit::classic; - rule<> token = *(anychar_p - space_p - END_VAR.c_str()); - rule<> var = START_VAR.c_str() >> token[SubstituteAndAppend(m_variables, m_text, m_validated)] >> END_VAR.c_str(); - rule<> non_var = anychar_p - START_VAR.c_str(); - - // parse and substitute variables - try { - parse(template_str.c_str(), *(non_var[StringAppend(m_text)] | var)); - } catch (const std::exception&) { - ErrorLogger() << "VarText::GenerateVartText caught exception when parsing template string: " << m_template_string; - } + xpr::sregex var = '%' >> (xpr::s1 = -+xpr::_w) >> !(':' >> (xpr::s2 = -+xpr::_w)) >> '%'; + m_text = xpr::regex_replace(template_str, var, Substitute(m_variables, m_validated)); } diff --git a/util/VarText.h b/util/VarText.h index c1e3b7004c9..93b06b866ed 100644 --- a/util/VarText.h +++ b/util/VarText.h @@ -1,6 +1,9 @@ #ifndef _VarText_h_ #define _VarText_h_ +//! @file +//! Declares the VarText class. + #include #include #include @@ -10,90 +13,192 @@ #include "Export.h" -/** VarText is a string tagged with variable names which are substituded with - * actual data at runtime. The variables are stored in a XML element tree. - * - * The format for the VarText template string is as follows: - * use % as delimiters for the variable. For example: The planet %planet% has - * been conlonized will look through the VarText data for the xml element with - * the tag name of "planet" and substitute the name of the planet in the game - * universe with the appropriate ID number that is stored in that VarText - * entry. - * - * If you need to use more than one item of the same type, you must label - * the tags. For example: %planet:my_planet% crashed into %planet:other_planet%. - * That will look for the xml elements "my_planet" and "other_planet" and - * do the planet lookup for them. - * - * An example of VarText implementation are SitReps. They are created by the - * server which knows nothing about what the final string will look like. - * ClientUI.cpp ultimately generates a string from the variables. - */ +//! Provides a lazy evaluated template string with named variable tags. +//! +//! VarText is a template string tagged with variable names which are +//! substituted with actual data when when calling VarText::GetText(). +//! +//! The format for VarText template string consists of named variable tags +//! enclosed by percent signs (%). Depending on the parameter tag used the +//! value assigned to a named parameter will be evaluated with the known name of +//! the object. When using multiple named variable tags it is possible to +//! separate the tag with an double colon (:) from an identifing label. The +//! label should be limited to lower case latin characters and the underscore. +//! +//! See @ref variable_tags "Variable Tags" for a list of supported variable +//! tags. +//! +//! +//! For example: When assigning template string: +//! +//! On %planet%: the %building% has been produced. +//! +//! to a VarText instance it declares the two named variable tags @e %%planet% +//! and @e %%building%. +//! +//! By calling VarText::AddVariable() identifiers can be assigned to those named +//! variable tags. For example: +//! +//! ```{.cpp} +//! // the_capital_planet.VisibleName() == "Garmillas II" +//! var_text.AddVariable("planet", the_capital_planet.ID()); +//! // the_imperial_palace.VisibleName() == "Imperial Palace" +//! var_text.AddVariable("building", the_imperial_palace.ID()); +//! ``` +//! +//! Now when calling VarText::GetText() the references are replaced with the +//! actual object name (assuming the resolver can see all the objects): +//! +//! On Garmillas II: the Imperial Palace has been produced. +//! +//! In case there are multiple named variable tags of the same type it is +//! possible to add labels to distinguish those. For example the template +//! string: +//! +//! In %system%: "%fleet:defender%" was overrun by "%fleet:attacker%" +//! +//! where the name variable tags are set to: +//! +//! ```{.cpp} +//! // the_battleground.VisibleName() == "Balun" +//! var_text.AddVariable("system", the_battleground.ID()); +//! // the_defender_fleet.VisibleName() == "Great Garmillas Armada" +//! var_text.AddVariable("fleet:defender", the_defender_fleet.ID()); +//! // the_attacker_fleet.VisibleName() == "UNCN Special Ops fleet" +//! var_text.AddVariable("fleet:attacker", the_attacker_fleet.ID()); +//! ``` +//! +//! would resolve into: +//! +//! In Balun: "Geat Garmillas Armada" was overrun by "UNCN Special Ops fleet" class FO_COMMON_API VarText { public: - /** \name Structors */ //@{ + //! Create a VarText instance with an empty #m_template_string. VarText(); - explicit VarText(const std::string& template_string, bool stringtable_lookup_template = true); - //@} - - /** \name Accessors */ //@{ - const std::string& GetText() const; //!< Returns text generated after substituting all variables. - bool Validate() const; //!< Does text generation succeed without any errors occurring? - const std::string& GetTemplateString() const { return m_template_string; } - bool GetStringtableLookupFlag() const { return m_stringtable_lookup_flag; } - std::vector GetVariableTags() const;//!< returns a list of tags for the variables this vartext has - //@} - - /** \name Mutators */ //@{ - void SetTemplateString(const std::string& text, bool stringtable_lookup_template = true); - void AddVariable(const std::string& tag, const std::string& data); - //@} - - /** Tag strings that are recognized and replaced in VarText. */ - static const std::string TEXT_TAG; // stringtable lookup - static const std::string RAW_TEXT_TAG; // no stringtable lookup + //! Create a VarText instance from the given @p template_string. + //! + //! @param template_string @see #m_template_string. + //! @param stringtable_lookup @see #m_stringtable_lookup_flag + explicit VarText(const std::string& template_string, bool stringtable_lookup = true); + + //! Return the text generated after substituting all variables. + const std::string& GetText() const; + + //! Return if the text substitution was successful. + bool Validate() const; + + //! Return the #m_template_string + const std::string& GetTemplateString() const + { return m_template_string; } + + //! Return the #m_stringtable_lookup_flag + bool GetStringtableLookupFlag() const + { return m_stringtable_lookup_flag; } + + //! Return the variables available for substitution. + std::vector GetVariableTags() const; + + //! Set the #m_template_string to the given @p template_string. + //! + //! @param template_string @see #m_template_string. + //! @param stringtable_lookup @see #m_stringtable_lookup_flag + void SetTemplateString(const std::string& template_string, bool stringtable_lookup = true); + + //! Assign @p data to a given @p tag. + //! + //! The @p data should match @p tag as listed in + //! @ref variable_tags "Variable tags". + //! + //! @param tag + //! Tag of the #m_variables set, may be labled. + //! @param data + //! Data value of the #m_variables set. + void AddVariable(const std::string& tag, const std::string& data); + + //! @name Variable tags + //! @anchor variable_tags + //! + //! Tag strings that are recognized and replaced in VarText with the + //! corresponding reference to an specific game entity. + //! + //! @{ + + //! Variable value is a StringTable key. + static const std::string TEXT_TAG; + //! Variable value is a literal string. + static const std::string RAW_TEXT_TAG; + //! Variable value is a Planet::ID(). static const std::string PLANET_ID_TAG; + //! Variable value is a System::ID(). static const std::string SYSTEM_ID_TAG; + //! Variable value is a Ship::ID(). static const std::string SHIP_ID_TAG; + //! Variable value is a Fleet::ID(). static const std::string FLEET_ID_TAG; + //! Variable value is a Building::ID(). static const std::string BUILDING_ID_TAG; + //! Variable value is a Field::ID(). static const std::string FIELD_ID_TAG; - + //! Variable value represents a CombatLog. static const std::string COMBAT_ID_TAG; - + //! Variable value is an Empire::EmpireID(). static const std::string EMPIRE_ID_TAG; + //! Variable value is a ShipDesign::ID(). static const std::string DESIGN_ID_TAG; + //! Variable value is a ShipDesign::ID() of a predefined ShipDesign. static const std::string PREDEFINED_DESIGN_TAG; - + //! Variable value is a Tech::Name(). static const std::string TECH_TAG; + //! Variable value is a BuildingType::Name(). static const std::string BUILDING_TYPE_TAG; + //! Variable value is a Special::Name(). static const std::string SPECIAL_TAG; + //! Variable value is a ShipHull::Name(). static const std::string SHIP_HULL_TAG; + //! Variable value is a ShipPart::Name(). static const std::string SHIP_PART_TAG; + //! Variable value is a Species::Name(). static const std::string SPECIES_TAG; + //! Variable value is a FieldType::Name(). static const std::string FIELD_TYPE_TAG; + //! Variable value is a predefined MeterType string representation. static const std::string METER_TYPE_TAG; + //! @} + protected: - /** Combines the template with the variables contained in object to - * create a string with variables replaced with text. */ - void GenerateVarText() const; + //! Combines the template with the variables contained in object to + //! create a string with variables replaced with text. + void GenerateVarText() const; + + //! The template text used by this VarText, into which variables are + //! substituted to render the text as user-readable. + //! + //! @see #m_stringtable_lookup_flag + std::string m_template_string; + + //! If true the #m_template_string will be looked up in the stringtable + //! prior to substitution for variables. + bool m_stringtable_lookup_flag = false; + + //! Maps variable tags into values, which are used during text substitution. + std::map m_variables; + + //! #m_template_string with applied #m_variables substitute. + mutable std::string m_text; - std::string m_template_string; ///< the template string for this VarText, into which variables are substituted to render the text as user-readable - bool m_stringtable_lookup_flag; ///< should the template string be looked up in the stringtable prior to substitution for variables? - std::map m_variables; ///< the data about variables to be substitued into the template string to render the VarText - mutable std::string m_text; ///< the user-readable rendered text with substitutions made - mutable bool m_validated = false; ///< did vartext generation succeed without problems? + //! True if the #m_template_string stubstitution was executed without + //! errors. + mutable bool m_validated = false; private: friend class boost::serialization::access; - template + template void serialize(Archive& ar, const unsigned int version); }; -// template implementations -template +template void VarText::serialize(Archive& ar, const unsigned int version) { ar & BOOST_SERIALIZATION_NVP(m_template_string) diff --git a/util/Version.cpp.in b/util/Version.cpp.in index 106ae2ec1c0..23722817471 100644 --- a/util/Version.cpp.in +++ b/util/Version.cpp.in @@ -1,12 +1,12 @@ #include "util/Version.h" -/** @file - * @brief Implement a free function to access the application version. - * - * @warning DON'T EDIT MANUALLY Version.cpp. @e Version.cpp is generated from - * @e Version.cpp.in by the @e cmake/make_versioncpp.py utility script - * that is used or should be used by all build systems. - */ +//! @file +//! Implement a free function to access the application version. +//! +//! @warning +//! DON'T EDIT MANUALLY Version.cpp. @e Version.cpp is generated from +//! @e Version.cpp.in by the @e cmake/make_versioncpp.py utility script +//! that is used or should be used by all build systems. namespace { static const std::string retval = "v${FreeOrion_VERSION} ${FreeOrion_BRANCH}[build ${FreeOrion_BUILD_NO}] ${FreeOrion_BUILDSYS}"; diff --git a/util/Version.h b/util/Version.h index b1f0c421262..646a4b68cbe 100644 --- a/util/Version.h +++ b/util/Version.h @@ -1,47 +1,45 @@ #ifndef _Version_h_ #define _Version_h_ -/** @file - * @brief Declares free functions to access the application and dependency - * versions. - */ +//! @file +//! Declares free functions to access the application and dependency +//! versions. #include #include #include "Export.h" -/** @brief Returns the version string of FreeOrion. - * - * The version strings consists of: - * - * * the VERSION number composed from @e MAJOR.MINOR.PATCH numbers. An - * optional '+' suffix indicates an in between test releases (think - * 0.4.6 + some more). Release candidates are suffixed with rcNUMBER. - * * the BRANCH, which represents the Git branch used to create this build. - * * the DATE as ISO 8601 date format. - * * the SHORT_COMMIT_HASH, which is the shortes Git commit hash, which - * identifies the commit this build was created from. - * * the BUILD_SYSTEM, which can be one of "CMake", "MSVC" or "XCode". - * - * @return The version string with the format - * VERSION BRANCH_NAME [build DATE.SHORT_COMMIT_HASH] BUILD_SYSTEM - */ +//! Returns the version string of FreeOrion. +//! +//! The version strings consists of: +//! +//! * the VERSION number composed from @e MAJOR.MINOR.PATCH numbers. An +//! optional '+' suffix indicates an in between test releases (think +//! 0.4.6 + some more). Release candidates are suffixed with rcNUMBER. +//! * the BRANCH, which represents the Git branch used to create this build. +//! * the DATE as ISO 8601 date format. +//! * the SHORT_COMMIT_HASH, which is the shortes Git commit hash, which +//! identifies the commit this build was created from. +//! * the BUILD_SYSTEM, which can be one of "CMake" or "MSVC" +//! +//! @return +//! The version string with the format +//! VERSION BRANCH_NAME [build DATE.SHORT_COMMIT_HASH] BUILD_SYSTEM FO_COMMON_API const std::string& FreeOrionVersionString(); -/** @brief Returns a map of dependency versions. - * - * @return A map with the dependency versions. The key represents the - * dependency name, the value corresponding dependency version string. - * - * @note This function returns different dependencies, depending on which game - * executable it was called from. - */ +//! Returns a map of dependency versions. +//! +//! @return +//! A map with the dependency versions. The key represents the dependency +//! name, the value corresponding dependency version string. +//! +//! @note +//! This function returns different dependencies, depending on which game +//! executable it was called from. FO_COMMON_API std::map DependencyVersions(); -/** @brief Log the map returned by DependencyVersions() into the @e info log - * channel. - */ +//! Log the map returned by DependencyVersions() into the @e info log channel. void LogDependencyVersions(); #endif // _Version_h_ diff --git a/util/XMLDoc.cpp b/util/XMLDoc.cpp index f52cc2d34c3..828be3ee699 100644 --- a/util/XMLDoc.cpp +++ b/util/XMLDoc.cpp @@ -1,57 +1,60 @@ -/* Copyright (C) 2006 T. Zachary Laine - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public License - as published by the Free Software Foundation; either version 2.1 - of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - 02111-1307 USA - - If you do not wish to comply with the terms of the LGPL please - contact the author as other terms are available for a fee. - - Zach Laine - whatwasthataddress@hotmail.com */ - -/** This notice came from the original file from which all the XML parsing code - was taken, part of the spirit distribution. The code was modified slightly - by me, and doesn't contain all the original code. Thanks to Daniel Nuffer - for his great work. */ -/*============================================================================= - simplexml.cpp - - Spirit V1.3 - URL: http://spirit.sourceforge.net/ - - Copyright (c) 2001, Daniel C. Nuffer - - This software is provided 'as-is', without any express or implied - warranty. In no event will the copyright holder 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. -=============================================================================*/ +//! Copyright (C) 2006 T. Zachary Laine +//! +//! This library is free software; you can redistribute it and/or +//! modify it under the terms of the GNU Lesser General Public License +//! as published by the Free Software Foundation; either version 2.1 +//! of the License, or (at your option) any later version. +//! +//! This library is distributed in the hope that it will be useful, +//! but WITHOUT ANY WARRANTY; without even the implied warranty of +//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +//! Lesser General Public License for more details. +//! +//! You should have received a copy of the GNU Lesser General Public +//! License along with this library; if not, write to the Free +//! Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +//! 02111-1307 USA +//! +//! If you do not wish to comply with the terms of the LGPL please +//! contact the author as other terms are available for a fee. +//! +//! ---- +//! +//! Zach Laine +//! whatwasthataddress@hotmail.com/ +//! +//! This notice came from the original file from which all the XML parsing code +//! was taken, part of the spirit distribution. The code was modified slightly +//! by me, and doesn't contain all the original code. Thanks to Daniel Nuffer +//! for his great work. +//! +//! ---- +//! +//! simplexml.cpp +//! +//! Spirit V1.3 +//! URL: http://spirit.sourceforge.net/ +//! +//! Copyright (c) 2001, Daniel C. Nuffer +//! +//! This software is provided 'as-is', without any express or implied +//! warranty. In no event will the copyright holder 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 "XMLDoc.h" @@ -62,23 +65,19 @@ #include #include -/** @file - * @brief Implements free functions and classes to modify, read and write - * simple XML files. - */ namespace { using namespace boost::spirit::classic; typedef chset chset_t; - // XML grammar rules + //! XML grammar rules rule<> document, prolog, element, Misc, Reference, CData, doctypedecl, XMLDecl, SDDecl, VersionInfo, EncodingDecl, VersionNum, Eq, EmptyElemTag, STag, content, ETag, Attribute, AttValue, CharData, Comment, CDSect, CharRef, EntityRef, EncName, Name, Comment1, S; - // XML Character classes + //! XML Character classes chset_t Char("\x9\xA\xD\x20-\xFF"); chset_t Letter("\x41-\x5A\x61-\x7A\xC0-\xD6\xD8-\xF6\xF8-\xFF"); chset_t Digit("0-9"); @@ -87,45 +86,39 @@ namespace { chset_t Sch("\x20\x9\xD\xA"); } -//////////////////////////////////////////////// -// XMLElement -//////////////////////////////////////////////// + const std::string& XMLElement::Tag() const { return m_tag; } const std::string& XMLElement::Text() const { return m_text; } -bool XMLElement::ContainsChild(const std::string& tag) const -{ +bool XMLElement::ContainsChild(const std::string& tag) const { return children.end() != std::find_if(children.begin(), children.end(), [&tag] (const XMLElement& e) { return e.m_tag == tag; }); } -const XMLElement& XMLElement::Child(const std::string& tag) const -{ +const XMLElement& XMLElement::Child(const std::string& tag) const { auto match = std::find_if(children.begin(), children.end(), [&tag] (const XMLElement& e) { return e.m_tag == tag; }); if (match == children.end()) - throw NoSuchChild("XMLElement::Child(): The XMLElement \"" + Tag() + "\" contains no child \"" + tag + "\"."); + throw std::out_of_range("XMLElement::Child(): The XMLElement \"" + Tag() + "\" contains no child \"" + tag + "\"."); return *match; } -std::string XMLElement::WriteElement(int indent/* = 0*/, bool whitespace/* = true*/) const -{ +std::string XMLElement::WriteElement(int indent/* = 0*/, bool whitespace/* = true*/) const { std::stringstream ss; WriteElement(ss, indent, whitespace); return ss.str(); } -std::ostream& XMLElement::WriteElement(std::ostream& os, int indent/* = 0*/, bool whitespace/* = true*/) const -{ +std::ostream& XMLElement::WriteElement(std::ostream& os, int indent/* = 0*/, bool whitespace/* = true*/) const { if (whitespace) os << std::string(indent * 2, ' '); os << '<' << m_tag; - for (const std::map::value_type& attribute : attributes) + for (const auto& attribute : attributes) os << ' ' << attribute.first << "=\"" << attribute.second << "\""; if (children.empty() && m_text.empty() && !m_root) { os << "/>"; @@ -151,13 +144,12 @@ std::ostream& XMLElement::WriteElement(std::ostream& os, int indent/* = 0*/, boo return os; } -XMLElement& XMLElement::Child(const std::string& tag) -{ +XMLElement& XMLElement::Child(const std::string& tag) { auto match = std::find_if(children.begin(), children.end(), [&tag] (const XMLElement& e) { return e.m_tag == tag; }); if (match == children.end()) - throw NoSuchChild("XMLElement::Child(): The XMLElement \"" + Tag() + "\" contains no child \"" + tag + "\"."); + throw std::out_of_range("XMLElement::Child(): The XMLElement \"" + Tag() + "\" contains no child \"" + tag + "\"."); return *match; } @@ -169,10 +161,6 @@ void XMLElement::SetText(const std::string& text) { m_text = text; } -//////////////////////////////////////////////// -// XMLDoc -//////////////////////////////////////////////// -// static(s) XMLDoc* XMLDoc::s_curr_parsing_doc = nullptr; std::vector XMLDoc::s_element_stack; XMLDoc::RuleDefiner XMLDoc::s_rule_definer; @@ -187,21 +175,18 @@ XMLDoc::XMLDoc(const std::istream& is) : root_node(XMLElement()) {} -std::ostream& XMLDoc::WriteDoc(std::ostream& os, bool whitespace/* = true*/) const -{ +std::ostream& XMLDoc::WriteDoc(std::ostream& os, bool whitespace/* = true*/) const { os << ""; if (whitespace) os << "\n"; return root_node.WriteElement(os, 0, whitespace); } -void XMLDoc::ReadDoc(const std::string& s) -{ +void XMLDoc::ReadDoc(const std::string& s) { std::stringstream ss(s); ReadDoc(ss); } -std::istream& XMLDoc::ReadDoc(std::istream& is) -{ +std::istream& XMLDoc::ReadDoc(std::istream& is) { root_node = XMLElement(); // clear doc contents s_element_stack.clear(); // clear this to start a fresh read s_curr_parsing_doc = this; // indicate where to add elements @@ -225,8 +210,7 @@ void XMLDoc::SetAttributeName(const char* first, const char* last) void XMLDoc::AddAttribute(const char* first, const char* last) { s_temp_elem.attributes[s_temp_attr_name] = std::string(first, last); } -void XMLDoc::PushElem1(const char* first, const char* last) -{ +void XMLDoc::PushElem1(const char* first, const char* last) { if (XMLDoc* this_ = XMLDoc::s_curr_parsing_doc) { if (s_element_stack.empty()) { this_->root_node = s_temp_elem; @@ -238,8 +222,7 @@ void XMLDoc::PushElem1(const char* first, const char* last) } } -void XMLDoc::PushElem2(const char* first, const char* last) -{ +void XMLDoc::PushElem2(const char* first, const char* last) { if (XMLDoc* this_ = XMLDoc::s_curr_parsing_doc) { if (s_element_stack.empty()) { this_->root_node = s_temp_elem; @@ -249,14 +232,12 @@ void XMLDoc::PushElem2(const char* first, const char* last) } } -void XMLDoc::PopElem(const char*, const char*) -{ +void XMLDoc::PopElem(const char*, const char*) { if (!s_element_stack.empty()) s_element_stack.pop_back(); } -void XMLDoc::AppendToText(const char* first, const char* last) -{ +void XMLDoc::AppendToText(const char* first, const char* last) { if (!s_element_stack.empty()) { std::string text(first, last); std::string::size_type first_good_posn = (text[0] != '\"') ? 0 : 1; @@ -267,22 +248,21 @@ void XMLDoc::AppendToText(const char* first, const char* last) } } -XMLDoc::RuleDefiner::RuleDefiner() -{ +XMLDoc::RuleDefiner::RuleDefiner() { // This is the start rule for XML parsing document = prolog >> element >> *Misc ; - + S = +(Sch) ; - + Name = (Letter | '_' | ':') >> *(NameChar) ; - + AttValue = '"' >> ( diff --git a/util/XMLDoc.h b/util/XMLDoc.h index 000a1e80d5a..c49e6608e9e 100644 --- a/util/XMLDoc.h +++ b/util/XMLDoc.h @@ -1,386 +1,351 @@ #ifndef _XMLDoc_h_ #define _XMLDoc_h_ -/* Copyright (C) 2006 T. Zachary Laine - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public License - as published by the Free Software Foundation; either version 2.1 - of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - 02111-1307 USA - - If you do not wish to comply with the terms of the LGPL please - contact the author as other terms are available for a fee. - - Zach Laine - whatwasthataddress@hotmail.com */ +//! @copyright +//! Copyright (C) 2006 T. Zachary Laine +//! +//! This library is free software; you can redistribute it and/or +//! modify it under the terms of the GNU Lesser General Public License +//! as published by the Free Software Foundation; either version 2.1 +//! of the License, or (at your option) any later version. +//! +//! This library is distributed in the hope that it will be useful, +//! but WITHOUT ANY WARRANTY; without even the implied warranty of +//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +//! Lesser General Public License for more details. +//! +//! You should have received a copy of the GNU Lesser General Public +//! License along with this library; if not, write to the Free +//! Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +//! 02111-1307 USA +//! +//! If you do not wish to comply with the terms of the LGPL please +//! contact the author as other terms are available for a fee. +//! +//! Zach Laine +//! whatwasthataddress@hotmail.com + +//! @file +//! Declares XMLElement and XMLDoc class to modify, read and write simple +//! XML files. #include +#include #include #include -#include #include "Export.h" -/** @file - * @brief Declares free functions and classes to modify, read and write simple - * XML files. - */ - -/** @brief Represents a simplified XML markup element. - * - * An XMLElement represents a XML element from the opening tag * \, - * including its attributes to the corresponding closing tag \. - * This may or may not include a text data section @b OR child XML elements. - * - * Using the "burns" example: - @code{.xml} - Say Goodnight Gracie. - @endcode - * - * may not work as expected. The resulting XMLElement \ node will - * contain both the "Say " and the " Gracie." text fragment. Or represented - * differently: - * - @code{.js} - burns.Text() == "Say Gracie." - @endcode - * - * However, if used to represent data structures like the example struct foo: - * - @code{.cpp} - struct foo - { - int ref_ct; - double data; - }; - @endcode - * - * the current implementation creates a XML representation similar to: - * - @code{.xml} - - - 13 - 0.364951 - - - @endcode - * - * Further, while the "burns" example is standard XML, an XMLElement optionally - * accepts its single text string in quotes, and strips off trailing white - * space, in direct contrary to the XML standard. So "burns" from above is - * equivalent to: - @code{.xml} - "Say Gracie."Goodnight - @endcode - * or: - @code{.xml} - Say Gracie.Goodnight - @endcode - * or: - @code{.xml} - "Say Gracie." - Goodnight - - @endcode - * or: - @code{.xml} - Say Gracie. - Goodnight - - @endcode - * - * Each of these examples yields to - @code{.js} - burns.Text() == "Say Gracie." - @endcode - * - * When an XMLElement is saved, its text is saved within a CDATA section. Any - * string can be put inside one of these quoted text fields, even text that - * includes an arbitrary number of quotes. So any std::string or c-string can - * be assigned to an element. However, when hand-editing an XML file containing - * such text strings, one needs to be careful. The closing quote must be the - * last thing other than white space. Adding more than one quoted text string - * to the XML element, with each string separated by other elements, will result - * in a single concatenated string, as illustrated above. - * - * This is not the most time- or space-efficient way to organize object data, - * but it may just be one of the simplest and most easily read. - */ +//! Represents a simplified XML markup element. +//! +//! An XMLElement represents a XML element from the opening tag \, +//! including its attributes to the corresponding closing tag \. +//! This may or may not include a text data section @b OR child XML elements. +//! Using the "burns" example: +//! +//! ```{.xml} +//! Say Goodnight Gracie. +//! ``` +//! +//! may not work as expected. The resulting XMLElement \ node will +//! contain both the "Say " and the " Gracie." text fragment. Or represented +//! differently: +//! +//! ```{.js} +//! burns.Text() == "Say Gracie." +//! ``` +//! +//! However, if used to represent data structures like the example struct foo: +//! +//! ```{.cpp} +//! struct foo +//! { +//! int ref_ct; +//! double data; +//! }; +//! ``` +//! +//! the current implementation creates a XML representation similar to: +//! +//! ```{.xml} +//! +//! +//! 13 +//! 0.364951 +//! +//! +//! ``` +//! +//! Further, while the "burns" example is standard XML, an XMLElement optionally +//! accepts its single text string in quotes, and strips off trailing white +//! space, in direct contrary to the XML standard. So "burns" from above is +//! equivalent to: +//! +//! ```{.xml} +//! "Say Gracie."Goodnight +//! ``` +//! +//! or: +//! +//! ```{.xml} +//! Say Gracie.Goodnight +//! ``` +//! or: +//! +//! ```{.xml} +//! "Say Gracie." +//! Goodnight +//! +//! ``` +//! +//! or: +//! +//! ```{.xml} +//! Say Gracie. +//! Goodnight +//! +//! ``` +//! +//! Each of these examples yields to +//! +//! ```{.js} +//! burns.Text() == "Say Gracie." +//! ``` +//! +//! When an XMLElement is saved, its text is saved within a CDATA section. Any +//! string can be put inside one of these quoted text fields, even text that +//! includes an arbitrary number of quotes. So any std::string or c-string can +//! be assigned to an element. However, when hand-editing an XML file +//! containing such text strings, one needs to be careful. The closing quote +//! must be the last thing other than white space. Adding more than one quoted +//! text string to the XML element, with each string separated by other +//! elements, will result in a single concatenated string, as illustrated above. +//! +//! This is not the most time- or space-efficient way to organize object data, +//! but it may just be one of the simplest and most easily read. class FO_COMMON_API XMLElement { public: - /** \name Exceptions */ //@{ - /** @brief The base class for XMLElement based exceptions. */ - class Exception : public GG::ExceptionBase - { - public: - /** @brief Create a new exception with the given @p message. */ - Exception (const std::string& message) : - ExceptionBase(message) - {} - }; - - /** @brief Thrown when a request for a tag-name named child element cannot - * be fulfilled. - */ - class NoSuchChild : public Exception - { - public: - /** @copydoc Exception::Exception(const std::string&) */ - NoSuchChild (const std::string& message) : - Exception (message) - {} - - const char* type() const noexcept override - { return "XMLElement::NoSuchChild"; } - }; - //@} - - /** @name Structors */ //@{ - /** @brief Creates a new XMLElement with an empty tag-name assigned. - * - * Create a new XMLElement with no tag-name, text, attribute or child nodes - * set. Also the new instance isn't marked as root node. - */ + //! Creates a new XMLElement with an empty tag-name assigned. + //! + //! Create a new XMLElement with no tag-name, text, attribute or child nodes + //! set. Also the new instance isn't marked as root node. XMLElement() {} - - /** @brief Creates a new XMLElement with the given @p tag tag-name and - * @p text content. - * - * Create a new XMLElement with the given @p tag tag-name and @p text - * content, but no attribute or child nodes set. Also the new instance - * isn't marked as root node. - * - * @param[in] tag The tag name of this XML element. - * @param[in] text The text assigned to this XML element. - */ + //! Creates a new XMLElement with the given @p tag tag-name and @p text + //! content. + //! + //! Create a new XMLElement with the given @p tag tag-name and @p text + //! content, but no attribute or child nodes set. Also the new instance + //! isn't marked as root node. + //! + //! @param[in] tag + //! The tag name of this XML element. + //! @param[in] text + //! The text assigned to this XML element. explicit XMLElement(const std::string& tag, const std::string& text = "") : m_tag(tag), m_text(text) {} - //@} - /** @name Accessors */ //@{ - /** @brief Returns the the tag-name of this XMLElement. - * - * @return The tag-name of this XMLElement. Can be an empty string. - */ + //! Returns the the tag-name of this XMLElement. + //! + //! @return + //! The tag-name of this XMLElement. Can be an empty string. const std::string& Tag() const; - /** @brief Returns the text body of this XMLElement. - * - * @return The text content of this XMLElement. Can be an empty string. - */ + //! Returns the text body of this XMLElement. + //! + //! @return + //! The text content of this XMLElement. Can be an empty string. const std::string& Text() const; - /** @brief Returns if this XMLElement contains a child with @p tag as - * tag-name. - * - * @param[in] tag The tag-name of the searched child XMLElement. - * @return True if there is at least one child with a @p tag tag-name, - * false if not. - */ + //! Returns if this XMLElement contains a child with @p tag as tag-name. + //! + //! @param[in] tag + //! The tag-name of the searched child XMLElement. + //! @return + //! True if there is at least one child with a @p tag tag-name, false if + //! not. bool ContainsChild(const std::string& tag) const; - /** @brief Returns the first XMLElement child that has @p tag as tag-name. - * - * @param[in] tag The tag-name of the child XMLElement requested. - * @return A reference to the first XMLElement child which has the tag-name - * @p tag. - * @throw XMLElement::NoSuchChild When no child with a tag-name @p tag - * exists. - */ + //! Returns the first XMLElement child that has @p tag as tag-name. + //! + //! @param[in] tag + //! The tag-name of the child XMLElement requested. + //! @return + //! A reference to the first XMLElement child which has the tag-name + //! @p tag. + //! @throw std::out_of_range + //! When no child with a tag-name @p tag exists. const XMLElement& Child(const std::string& tag) const; - /** @brief Write this XMLElement XML formatted into the given output - * stream @p os with indentation level @p indent when @p whitespace - * is set. - * - * @param[in] os The output stream this document should be written to. - * @param[in] indent The indentation level this element should be - * indented. - * @param[in] whitespace If set to true the child XMLElement%s are - * indented and newline separated. - * @return The given @p os output stream. - */ + //! Write this XMLElement XML formatted into the given output stream @p os + //! with indentation level @p indent when @p whitespace is set. + //! + //! @param[in] os + //! The output stream this document should be written to. + //! @param[in] indent + //! The indentation level this element should be indented. + //! @param[in] whitespace + //! If set to true the child XMLElement%s are indented and newline + //! separated. + //! @return + //! The given @p os output stream. std::ostream& WriteElement(std::ostream& os, int indent = 0, bool whitespace = true) const; - /** @brief Return this XMLElement XML formatted as string with indentation - * level @p indent when @p whitespace is set. - * - * @param[in] indent The indentation level this element should be - * indented. - * @param[in] whitespace If set to true the child XMLElement%s are - * indented and newline separated. - * @return A string containing the XML formatted representation of this - * XMLElement. - */ + //! Return this XMLElement XML formatted as string with indentation level + //! @p indent when @p whitespace is set. + //! + //! @param[in] indent + //! The indentation level this element should be indented. + //! @param[in] whitespace + //! If set to true the child XMLElement%s are indented and newline + //! separated. + //! @return + //! A string containing the XML formatted representation of this + //! XMLElement. std::string WriteElement(int indent = 0, bool whitespace = true) const; - //@} - /** @name Mutators */ //@{ - /** @copydoc XMLElement::Child(const std::string&) const */ + //! @see XMLElement::Child(const std::string&) const XMLElement& Child(const std::string& tag); - /** @brief Sets the tag-name of this XMLElement to @p tag. - * - * @param[in] tag The new tag-name this XMLElement should have. - */ + //! Sets the tag-name of this XMLElement to @p tag. + //! + //! @param[in] tag + //! The new tag-name this XMLElement should have. void SetTag(const std::string& tag); - /** @brief Sets the text content of this XMLEement to @p text. - * - * @param[in] text The new text content this XMLElement should have. - */ + //! Sets the text content of this XMLEement to @p text. + //! + //! @param[in] text + //! The new text content this XMLElement should have. void SetText(const std::string& text); - //@} - /** @brief The attributes associated to this XMLElement by key name - * mapping. - */ + //! The attributes associated to this XMLElement by key name mapping. std::map attributes; - /** @brief Stores a list of the child XMLElement%s associated to this - * XMLElement. - * - * This list can be empty when this XMLElement has no associated child - * elements. - */ + //! Stores a list of the child XMLElement%s associated to this XMLElement. + //! + //! This list can be empty when this XMLElement has no associated child + //! elements. std::vector children; private: - /** @name Structors */ //@{ - /** @brief Creates a new XMLElement with the given @p tag tag-name and - * marked as root node, if @p root is set. - * - * @param[in] tag The tag name of this XML element. - * @param[in] root When true this XMLElement should be interpreted as root - * node in a XMLElement tree. - * - * @note Called by friend XMLDoc. - */ + //! Creates a new XMLElement with the given @p tag tag-name and marked as + //! root node, if @p root is set. + //! + //! @param[in] tag + //! The tag name of this XML element. + //! @param[in] root + //! When true this XMLElement should be interpreted as root node in + //! a XMLElement tree. + //! + //! @note + //! Called by friend XMLDoc. XMLElement(const std::string& tag, bool root) : m_tag(tag), m_root(root) {} - //@} - /** @brief Stores the tag-name associated to this XMLElement. - * - * @bug Currently this can contain an empty string but I doubt it will - * be useful as it will cause invalid XML documents serializations. - */ + //! Stores the tag-name associated to this XMLElement. + //! + //! @bug + //! Currently this can contain an empty string but I doubt it will be + //! useful as it will cause invalid XML documents serializations. std::string m_tag; - /** @brief Stores the text content associated to this XMLElement. */ + //! Stores the text content associated to this XMLElement. std::string m_text; - /** @brief Set to true if this XMLElement is the root element of an XMLDoc - * document. - */ + //! Set to true if this XMLElement is the root element of an XMLDoc + //! document. bool m_root = false; friend class XMLDoc; }; -/** @brief Represents a document formatted with XML markup. - * - * Each XMLDoc instance is assumed to represent a complete document. It - * contains a tree of nested XMLElement%s, starting from the always existing - * root XMLElement node. - */ +//! Represents a document formatted with XML markup. +//! +//! Each XMLDoc instance is assumed to represent a complete document. It +//! contains a tree of nested XMLElement%s, starting from the always existing +//! root XMLElement node. class FO_COMMON_API XMLDoc { public: - /** @name Structors */ //@{ - /** @brief Create an empty document with the given tag-name @p root_tag. - * - * @param[in] root_tag The tag-name of the created root XMLElement. - */ + //! Create an empty document with the given tag-name @p root_tag. + //! + //! @param[in] root_tag + //! The tag-name of the created root XMLElement. XMLDoc(const std::string& root_tag = "XMLDoc"); - /** @brief Construct a document from the given input stream @p is. - * - * @param[in] is An input stream that provides an XML markup document once - * read. - * - * @bug @p is isn't actually read but ignored and an empty (and maybe - * invalid) document is created. Use XMLDoc::ReadDoc(std::istream&) - * instead. - */ + //! Construct a document from the given input stream @p is. + //! + //! @param[in] is + //! An input stream that provides an XML markup document once read. + //! + //! @bug + //! @p is isn't actually read but ignored and an empty (and maybe + //! invalid) document is created. Use XMLDoc::ReadDoc(std::istream&) + //! instead. XMLDoc(const std::istream& is); - //@} - - /** @name Accessors */ //@{ - /** @brief Write the contents of the XMLDoc into the given output stream - * @p os with optional @p indent. - * - * @param[in] os The output stream this document should be written to. - * @param[in] indent If set to true the XML elements are indented and - * newline separated. - * @return The given @p os output stream. - */ + + //! Write the contents of the XMLDoc into the given output stream @p os + //! with optional @p indent. + //! + //! @param[in] os + //! The output stream this document should be written to. + //! @param[in] indent + //! If set to true the XML elements are indented and newline separated. + //! + //! @return + //! The given @p os output stream. std::ostream& WriteDoc(std::ostream& os, bool indent = true) const; - //@} - - /** @name Mutators */ //@{ - /** @brief Clears the current content of this XMLDoc instance and read a - * new document from the given input stream @p is. - * - * @param[in] is An input stream that provides an XML markup document once - * read. - * @return The given @p is input stream. - */ + + //! Clears the current content of this XMLDoc instance and read a new + //! document from the given input stream @p is. + //! + //! @param[in] is + //! An input stream that provides an XML markup document once read. + //! + //! @return + //! The given @p is input stream. std::istream& ReadDoc(std::istream& is); - /** @brief Clears the current content of this XMLDoc instance and read a - * new document from the given string @p s. - * - * @param[in] s An string containing an XML markup document. - */ + //! Clears the current content of this XMLDoc instance and read a new + //! document from the given string @p s. + //! + //! @param[in] s + //! A string containing an XML markup document. void ReadDoc(const std::string& s); - //@} - /** @brief The root element that contains the parsed document, which is - * represented by XMLElement%s. - */ + //! The root element that contains the parsed document, which is represented + //! by XMLElement%s. XMLElement root_node; private: - /** @brief Creates the XML parsing rules at static initialization time. */ + //! Creates the XML parsing rules at static initialization time. struct RuleDefiner { RuleDefiner(); }; static RuleDefiner s_rule_definer; - /** @brief Holds the XMLDoc to which the XML parser should add parsed - * elements. - * - * @todo No need to hold a static instance here, pass the document into - * the parser as additional attribute. This also avoids potential - * problems in multithreaded setups. - */ + //! Holds the XMLDoc to which the XML parser should add parsed elements. + //! + //! @todo + //! No need to hold a static instance here, pass the document into the + //! parser as additional attribute. This also avoids potential problems + //! in multithreaded setups. static XMLDoc* s_curr_parsing_doc; - /** @brief Holds the current environment for reading XMLElement%s (the - * current enclosing XMLElement%s). */ + //! Holds the current environment for reading XMLElement%s (the current + //! enclosing XMLElement%s). static std::vector s_element_stack; + //! Convert string tokens into XMLElement attributes. + //! @{ static XMLElement s_temp_elem; static std::string s_temp_attr_name; - // these are used along with the static members above during XML parsing static void SetElemName(const char* first, const char* last); static void SetAttributeName(const char* first, const char* last); static void AddAttribute(const char* first, const char* last); @@ -388,6 +353,7 @@ class FO_COMMON_API XMLDoc static void PushElem2(const char*, const char*); static void PopElem(const char*, const char*); static void AppendToText(const char* first, const char* last); + //! @} }; #endif // _XMLDoc_h_ diff --git a/util/base64_filter.h b/util/base64_filter.h new file mode 100644 index 00000000000..bdb2555d9f2 --- /dev/null +++ b/util/base64_filter.h @@ -0,0 +1,655 @@ +// base64.hpp: Base64 filter + +// Copyright Takeshi Mouri 2006, 2007. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://hamigaki.sourceforge.jp/libs/iostreams for library home page. + +#ifndef HAMIGAKI_IOSTREAMS_FILTER_BASE64_HPP +#define HAMIGAKI_IOSTREAMS_FILTER_BASE64_HPP + +#include +#include +#include +#include +#include +#include + +namespace boost { namespace iostreams { + +struct base64_traits +{ + static const char padding = '='; + + static char encode(unsigned char n) + { + const char table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + return table[n]; + } + + static unsigned char decode(char c) + { + // TODO: should support non-ASCII + const unsigned char table[] = + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0x3F, + + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, + 0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + + 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + + 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + + 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, + 0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + }; + unsigned char uc = static_cast(c); + if (static_cast(uc) >= sizeof(table)) + throw BOOST_IOSTREAMS_FAILURE("bad Base64 sequence"); + unsigned char val = table[uc]; + if (val == 0xFF) + throw BOOST_IOSTREAMS_FAILURE("bad Base64 sequence"); + return val; + } +}; + +struct urlsafe_base64_traits +{ + static const char padding = '='; + + static char encode(unsigned char n) + { + const char table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; + + return table[n]; + } + + static unsigned char decode(char c) + { + // TODO: should support non-ASCII + const unsigned char table[] = + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, + + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, + 0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + + 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, + + 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + + 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, + 0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + }; + unsigned char uc = static_cast(c); + if (static_cast(uc) >= sizeof(table)) + throw BOOST_IOSTREAMS_FAILURE("bad URL-safe Base64 sequence"); + unsigned char val = table[uc]; + if (val == 0xFF) + throw BOOST_IOSTREAMS_FAILURE("bad URL-safe Base64 sequence"); + return val; + } +}; + + +template +class arbitrary_positional_facade; + +class core_access +{ +#if defined(BOOST_NO_MEMBER_TEMPLATE_FRIENDS) +public: +#else + template + friend class arbitrary_positional_facade; + + friend struct device_operations; + + template + friend struct filter_operations; +#endif + + template + static std::streamsize read_blocks( + RepositionalSource& src, CharT* s, std::streamsize n) + { + return src.read_blocks(s, n); + } + + template + static std::streamsize read_blocks( + RepositionalInputFilter& filter, Source& src, + typename boost::iostreams::char_type_of::type* s, + std::streamsize n) + { + return filter.read_blocks(src, s, n); + } + + template + static std::streamsize write_blocks( + RepositionalSink& sink, const CharT* s, std::streamsize n) + { + return sink.write_blocks(s, n); + } + + template + static std::streamsize write_blocks( + RepositionalOutputFilter& filter, Sink& sink, + const typename boost::iostreams::char_type_of::type* s, + std::streamsize n) + { + return filter.write_blocks(sink, s, n); + } + + template + static void close_with_flush( + RepositionalSink& sink, const CharT* s, std::streamsize n) + { + return sink.close_with_flush(s, n); + } + + template + static void close_with_flush( + RepositionalOutputFilter& filter, Sink& sink, + const typename boost::iostreams::char_type_of::type* s, + std::streamsize n) + { + return filter.close_with_flush(sink, s, n); + } + + template + static std::streampos seek_blocks( + RepositionalDevice& dev, + boost::iostreams::stream_offset off, BOOST_IOS::seekdir way) + { + return dev.seek_blocks(off, way); + } + + struct device_operations + { + template + std::streamsize read_blocks( + RepositionalDevice& t, CharT* s, std::streamsize n) const + { + return core_access::read_blocks(t, s, n); + } + + template + std::streamsize write_blocks( + RepositionalDevice& t, const CharT* s, std::streamsize n) const + { + return core_access::write_blocks(t, s, n); + } + }; + + template + struct filter_operations + { + typedef typename boost::iostreams:: + char_type_of::type char_type; + + Device* dev_ptr_; + + explicit filter_operations(Device& dev) : dev_ptr_(&dev) {} + + template + std::streamsize read_blocks( + RepositionalInputFilter& t, char_type* s, std::streamsize n) const + { + return core_access::read_blocks(t, *dev_ptr_, s, n); + } + + template + std::streamsize write_blocks( + RepositionalOutputFilter& t, + const char_type* s, std::streamsize n) const + { + return core_access::write_blocks(t, *dev_ptr_, s, n); + } + }; +}; + +template +class arbitrary_positional_facade +{ +private: + typedef CharT char_type; + + Derived& derived() + { + return *static_cast(this); + } + +protected: + typedef arbitrary_positional_facade< + Derived,CharT,MaxBlockSize> arbitrary_positional_facade_; + + void block_size(std::streamsize n) + { + block_size_ = n; + } + +public: + arbitrary_positional_facade() : block_size_(MaxBlockSize), count_(0) + { + } + + explicit arbitrary_positional_facade(std::streamsize block_size) + : block_size_(block_size), count_(0) + { + BOOST_ASSERT(block_size_ <= MaxBlockSize); + } + + std::streamsize read(char_type* s, std::streamsize n) + { + return read_impl(core_access::device_operations(), s, n); + } + + template + std::streamsize read(Source& src, char_type* s, std::streamsize n) + { + return read_impl(core_access::filter_operations(src), s, n); + } + + std::streamsize write(const char_type* s, std::streamsize n) + { + return write_impl(core_access::device_operations(), s, n); + } + + template + std::streamsize write(Sink& sink, const char_type* s, std::streamsize n) + { + return write_impl(core_access::filter_operations(sink), s, n); + } + + void close() + { + BOOST_ASSERT(count_ < block_size_); + core_access::close_with_flush(derived(), buffer_, count_); + } + + template + void close(Sink& sink) + { + BOOST_ASSERT(count_ < block_size_); + core_access::close_with_flush(derived(), sink, buffer_, count_); + } + + std::streampos seek( + boost::iostreams::stream_offset off, BOOST_IOS::seekdir way) + { + if (way == BOOST_IOS::beg) + { + core_access::seek_blocks(derived(), off/block_size_, way); + + std::streamsize skip = + static_cast(off%block_size_); + if (skip == 0) + count_ = 0; + else + { + std::streamsize res = + core_access::read_blocks(derived(), buffer_, 1); + if (res != 1) + throw BOOST_IOSTREAMS_FAILURE("bad seek"); + count_ = block_size_ - skip; + } + return boost::iostreams::offset_to_position(off); + } + else if (way == BOOST_IOS::cur) + { + std::streampos pos = + core_access::seek_blocks( + derived(), (off-count_)/block_size_, way); + + std::streamsize skip = + static_cast((off-count_)%block_size_); + if (skip == 0) + { + count_ = 0; + + return boost::iostreams::offset_to_position( + boost::iostreams::position_to_offset(pos) * block_size_); + } + else + { + std::streamsize res = + core_access::read_blocks(derived(), buffer_, 1); + if (res != 1) + throw BOOST_IOSTREAMS_FAILURE("bad seek"); + count_ = block_size_ - skip; + + return boost::iostreams::offset_to_position( + boost::iostreams::position_to_offset(pos) * block_size_ + + block_size_-count_); + } + } + else + { + std::streampos pos = + core_access::seek_blocks( + derived(), (off-block_size_+1)/block_size_, way); + + count_ = + static_cast((-off)%block_size_); + if (count_ == 0) + { + return boost::iostreams::offset_to_position( + boost::iostreams::position_to_offset(pos) * block_size_); + } + else + { + std::streamsize res = + core_access::read_blocks(derived(), buffer_, 1); + if (res != 1) + throw BOOST_IOSTREAMS_FAILURE("bad seek"); + + return boost::iostreams::offset_to_position( + boost::iostreams::position_to_offset(pos) * block_size_ + + block_size_-count_); + } + } + } + +private: + char_type buffer_[MaxBlockSize]; + std::streamsize block_size_; + std::streamsize count_; + + template + std::streamsize read_impl(const Op& op, char_type* s, std::streamsize n) + { + std::streamsize total = 0; + + if (count_ != 0) + { + std::streamsize amt = (std::min)(n, count_); + char_type* start = buffer_ + (block_size_ - count_); + s = std::copy(start, start+amt, s); + n -= amt; + count_ -= amt; + total += amt; + } + + if (n >= block_size_) + { + BOOST_ASSERT(count_ == 0); + + std::streamsize request = n/block_size_; + std::streamsize res = + op.read_blocks(derived(), s, request); + + if (res != -1) + { + s += res; + n -= res; + total += res; + } + + if (res < request*block_size_) + return total != 0 ? total : -1; + } + + if (n != 0) + { + BOOST_ASSERT(n < block_size_); + BOOST_ASSERT(count_ == 0); + + std::streamsize res = + op.read_blocks(derived(), buffer_, 1); + + if (res > 0) + { + s = std::copy(buffer_, buffer_+n, s); + count_ = block_size_ - n; + total += n; + } + } + + return total != 0 ? total : -1; + } + + template + std::streamsize write_impl( + const Op& op, const char_type* s, std::streamsize n) + { + std::streamsize total = 0; + + if (count_ != 0) + { + std::streamsize amt = (std::min)(n, block_size_-count_); + std::copy(s, s+amt, buffer_+count_); + s += amt; + n -= amt; + count_ += amt; + total += amt; + + if (count_ == block_size_) + { + op.write_blocks(derived(), buffer_, 1); + count_ = 0; + } + } + + if (n >= block_size_) + { + BOOST_ASSERT(count_ == 0); + + std::streamsize request = n/block_size_; + op.write_blocks(derived(), s, request); + + std::streamsize amt = request*block_size_; + s += amt; + n -= amt; + total += amt; + } + + if (n != 0) + { + BOOST_ASSERT(n < block_size_); + BOOST_ASSERT(count_ == 0); + + std::copy(s, s+n, buffer_); + count_ = n; + total += n; + } + + return total != 0 ? total : -1; + } +}; + + +template +class basic_base64_encoder + : public arbitrary_positional_facade, char, 3> +{ + friend class core_access; + + typedef boost::uint_t<24>::fast uint24_t; + +public: + typedef char char_type; + + struct category + : public boost::iostreams::output + , public boost::iostreams::filter_tag + , public boost::iostreams::multichar_tag + , public boost::iostreams::closable_tag + {}; + +private: + static uint24_t char_to_uint24(char c) + { + return static_cast(static_cast(c) & 0xFF); + } + + static void encode(char* dst, const char* src) + { + uint24_t tmp = + (char_to_uint24(src[0]) << 16) | + (char_to_uint24(src[1]) << 8) | + (char_to_uint24(src[2]) ) ; + + dst[0] = Traits::encode((tmp >> 18) & 0x3F); + dst[1] = Traits::encode((tmp >> 12) & 0x3F); + dst[2] = Traits::encode((tmp >> 6) & 0x3F); + dst[3] = Traits::encode((tmp ) & 0x3F); + } + + template + std::streamsize write_blocks(Sink& sink, const char* s, std::streamsize n) + { + for (int i = 0; i < n; ++i) + { + char buf[4]; + encode(buf, s); + boost::iostreams::write(sink, buf, sizeof(buf)); + s += 3; + } + return n*3; + } + + template + void close_with_flush(Sink& sink, const char* s, std::streamsize n) + { + if (n != 0) + { + char src[3] = { 0, 0, 0 }; + std::copy(s, s+n, &src[0]); + + char buf[4]; + encode(buf, src); + + if (n == 1) + buf[2] = Traits::padding; + buf[3] = Traits::padding; + + boost::iostreams::write(sink, buf, sizeof(buf)); + } + } +}; + +template +class basic_base64_decoder + : public arbitrary_positional_facade, char, 3> +{ + friend class core_access; + + typedef boost::uint_t<24>::fast uint24_t; + typedef boost::uint_t<6>::least uint6_t; + +public: + typedef char char_type; + + struct category + : public boost::iostreams::input + , public boost::iostreams::filter_tag + , public boost::iostreams::multichar_tag + , public boost::iostreams::closable_tag + {}; + +private: + static char uint24_to_char(uint24_t n) + { + return static_cast(static_cast(n & 0xFF)); + } + + static uint24_t decode(char c) + { + return Traits::decode(c); + } + + static std::streamsize decode(char* dst, const char* src) + { + char buf[4]; + std::copy(src, src+4, buf); + + if (src[3] == Traits::padding) + { + buf[3] = Traits::encode(0); + if (src[2] == Traits::padding) + buf[2] = Traits::encode(0); + } + + uint24_t tmp = + (decode(buf[0]) << 18) | + (decode(buf[1]) << 12) | + (decode(buf[2]) << 6) | + (decode(buf[3]) ) ; + + std::streamsize n = 0; + dst[n++] = uint24_to_char(tmp >> 16); + if (src[2] != Traits::padding) + dst[n++] = uint24_to_char(tmp >> 8); + if (src[3] != Traits::padding) + dst[n++] = uint24_to_char(tmp ); + return n; + } + + template + std::streamsize read_blocks(Source& src, char* s, std::streamsize n) + { + std::streamsize total = 0; + for (int i = 0; i < n; ++i) + { + char buf[4]; + std::streamsize res = + boost::iostreams::read(src, buf, sizeof(buf)); + if (res == -1) + break; + std::streamsize amt = decode(s, buf); + s += amt; + total += amt; + } + return (total != 0) ? total : -1; + } + + template + void close_with_flush(Source&, const char*, std::streamsize) + { + } +}; + +typedef basic_base64_encoder base64_encoder; +typedef basic_base64_decoder base64_decoder; + +typedef basic_base64_encoder urlsafe_base64_encoder; +typedef basic_base64_decoder urlsafe_base64_decoder; + +} } // End namespaces iostreams, boost. + +#endif // HAMIGAKI_IOSTREAMS_FILTER_BASE64_HPP diff --git a/util/i18n.cpp b/util/i18n.cpp index c6f88e53f76..d4dee53cf99 100644 --- a/util/i18n.cpp +++ b/util/i18n.cpp @@ -5,75 +5,221 @@ #include "OptionsDB.h" #include "StringTable.h" +#include +#include + +#include + namespace { - std::string GetDefaultStringTableFileName() - { return PathString(GetResourceDir() / "stringtables" / "en.txt"); } + std::map> stringtables; + std::recursive_mutex stringtable_access_mutex; + bool stringtable_filename_init = false; + + // fallback stringtable to look up key in if entry is not found in currently configured stringtable + boost::filesystem::path DevDefaultEnglishStringtablePath() + { return GetResourceDir() / "stringtables/en.txt"; } + + // filename to use as default value for stringtable filename option. + // based on the system's locale. not necessarily the same as the + // "dev default" (english) stringtable filename for fallback lookup + // includes "/stringtables/" directory part of path + boost::filesystem::path GetDefaultStringTableFileName() { + std::string lang; + + // early return when unable to get locale language string + try { + lang = std::use_facet(GetLocale()).language(); + } catch(const std::bad_cast&) { + ErrorLogger() << "Bad locale cast when setting default language"; + } + + boost::algorithm::to_lower(lang); + + // handle failed locale lookup or C locale + if (lang.empty() || lang == "c" || lang == "posix") { + WarnLogger() << "Lanuage not detected from locale: \"" << lang << "\"; falling back to default en"; + lang = "en"; + } else { + DebugLogger() << "Detected locale language: " << lang; + } + + boost::filesystem::path lang_filename{ lang + ".txt" }; + boost::filesystem::path default_stringtable_path{ GetResourceDir() / "stringtables" / lang_filename }; + + // default to english if locale-derived filename not present + if (!IsExistingFile(default_stringtable_path)) { + WarnLogger() << "Detected language file not present: " << PathToString(default_stringtable_path) << " Reverting to en.txt"; + default_stringtable_path = DevDefaultEnglishStringtablePath(); + } + + if (!IsExistingFile(default_stringtable_path)) + ErrorLogger() << "Default english stringtable file also not presennt!!: " << PathToString(default_stringtable_path); + + DebugLogger() << "GetDefaultStringTableFileName returning: " << PathToString(default_stringtable_path); + return default_stringtable_path; + } + + // sets the stringtable filename option default value. + // also checks the option-set stringtable path, and if it is blank or the + // specified file doesn't exist, tries to reinterpret the option value as + // a path in the standard location, or reverts to the default stringtable + // location if other attempts fail. + void InitStringtableFileName() { + stringtable_filename_init = true; + + // set option default value based on system locale + auto default_stringtable_path = GetDefaultStringTableFileName(); + GetOptionsDB().SetDefault("resource.stringtable.path", PathToString(default_stringtable_path)); + + // get option-configured stringtable path. may be the default empty + // string (set by call to: db.Add("resource.stringtable.path" ... + // or this may have been overridden from one of the config XML files or from + // a command line argument. + std::string option_path = GetOptionsDB().Get("resource.stringtable.path"); + boost::filesystem::path stringtable_path{option_path}; + + // verify that option-derived stringtable file exists, with fallbacks + DebugLogger() << "Stringtable option path: " << option_path; + + if (option_path.empty()) { + DebugLogger() << "Stringtable option path not specified yet, using default: " << PathToString(default_stringtable_path); + stringtable_path = PathToString(default_stringtable_path); + GetOptionsDB().Set("resource.stringtable.path", PathToString(stringtable_path)); + return; + } + bool set_option = false; + + if (!IsExistingFile(stringtable_path)) { + set_option = true; + // try interpreting path as a filename located in the stringtables directory + stringtable_path = GetResourceDir() / "stringtables" / option_path; + } + if (!IsExistingFile(stringtable_path)) { + set_option = true; + // try interpreting path as directory and filename in resources directory + stringtable_path = GetResourceDir() / option_path; + } + if (!IsExistingFile(stringtable_path)) { + set_option = true; + // fall back to default option value + ErrorLogger() << "Stringtable option path file is missing: " << PathToString(stringtable_path); + DebugLogger() << "Resetting to default: " << PathToString(default_stringtable_path); + stringtable_path = default_stringtable_path; + } + + if (set_option) + GetOptionsDB().Set("resource.stringtable.path", PathToString(stringtable_path)); + } + + // get currently set stringtable filename option value, or the default value + // if the currenty value is empty std::string GetStringTableFileName() { - std::string option_filename = GetOptionsDB().Get("stringtable-filename"); - if (option_filename.empty()) - return GetDefaultStringTableFileName(); + std::lock_guard stringtable_lock(stringtable_access_mutex); + // initialize option value and default on first call + if (!stringtable_filename_init) + InitStringtableFileName(); + + std::string option_path = GetOptionsDB().Get("resource.stringtable.path"); + if (option_path.empty()) + return GetOptionsDB().GetDefault("resource.stringtable.path"); else - return option_filename; + return option_path; } - std::map stringtables; + const StringTable& GetStringTable(boost::filesystem::path stringtable_path) { + std::lock_guard stringtable_lock(stringtable_access_mutex); - const StringTable_& GetStringTable(std::string stringtable_filename = "") { - // get option-configured stringtable if no filename specified - if (stringtable_filename.empty()) - stringtable_filename = GetStringTableFileName(); + if (!stringtable_filename_init) + InitStringtableFileName(); // ensure the default stringtable is loaded first - std::map::const_iterator default_stringtable_it = - stringtables.find(GetDefaultStringTableFileName()); + auto default_stringtable_filename{GetOptionsDB().GetDefault("resource.stringtable.path")}; + auto default_stringtable_it = stringtables.find(default_stringtable_filename); if (default_stringtable_it == stringtables.end()) { - const StringTable_* table = new StringTable_(GetDefaultStringTableFileName()); - stringtables[GetDefaultStringTableFileName()] = table; - default_stringtable_it = stringtables.find(GetDefaultStringTableFileName()); + auto table = std::make_shared(default_stringtable_filename); + stringtables[default_stringtable_filename] = table; + default_stringtable_it = stringtables.find(default_stringtable_filename); } + auto stringtable_filename = PathToString(stringtable_path); + // attempt to find requested stringtable... - std::map::const_iterator it = - stringtables.find(stringtable_filename); + auto it = stringtables.find(stringtable_filename); if (it != stringtables.end()) return *(it->second); // if not already loaded, load, store, and return, // using default stringtable for fallback expansion lookups - const StringTable_* table = new StringTable_(stringtable_filename, default_stringtable_it->second); + auto table = std::make_shared(stringtable_filename, default_stringtable_it->second); stringtables[stringtable_filename] = table; return *table; } - const StringTable_& GetDefaultStringTable() - { return GetStringTable(GetDefaultStringTableFileName()); } + const StringTable& GetStringTable() + { return GetStringTable(GetStringTableFileName()); } + + const StringTable& GetDevDefaultStringTable() + { return GetStringTable(DevDefaultEnglishStringtablePath()); } } -void FlushLoadedStringTables() -{ stringtables.clear(); } +std::locale GetLocale(const std::string& name) { + static bool locale_init { false }; + // Initialize backend and generator on first use, provide a log for current enivornment locale + static auto locale_backend = boost::locale::localization_backend_manager::global(); + if (!locale_init) + locale_backend.select("std"); + static boost::locale::generator locale_gen(locale_backend); + if (!locale_init) { + locale_gen.locale_cache_enabled(true); + try { + InfoLogger() << "Global locale: " << std::use_facet(locale_gen("")).name(); + } catch (const std::runtime_error&) { + ErrorLogger() << "Global locale: set to invalid locale, setting to C locale"; + std::locale::global(std::locale::classic()); + } + locale_init = true; + } + + std::locale retval; + try { + retval = locale_gen(name); + } catch(const std::runtime_error&) { + ErrorLogger() << "Requested locale \"" << name << "\" is not a valid locale for this operating system"; + return std::locale::classic(); + } + + TraceLogger() << "Requested " << (name.empty() ? "(default)" : name) << " locale" + << " returning " << std::use_facet(retval).name(); + return retval; +} + +void FlushLoadedStringTables() { + std::lock_guard stringtable_lock(stringtable_access_mutex); + stringtables.clear(); +} const std::string& UserString(const std::string& str) { + std::lock_guard stringtable_lock(stringtable_access_mutex); if (GetStringTable().StringExists(str)) - return GetStringTable().String(str); - return GetDefaultStringTable().String(str); + return GetStringTable()[str]; + return GetDevDefaultStringTable()[str]; } std::vector UserStringList(const std::string& key) { + std::lock_guard stringtable_lock(stringtable_access_mutex); std::vector result; std::istringstream template_stream(UserString(key)); std::string item; - while (std::getline(template_stream, item)) { + while (std::getline(template_stream, item)) result.push_back(item); - } return result; } bool UserStringExists(const std::string& str) { - if (GetStringTable().StringExists(str)) - return true; - return GetDefaultStringTable().StringExists(str); + std::lock_guard stringtable_lock(stringtable_access_mutex); + return GetStringTable().StringExists(str) || GetDevDefaultStringTable().StringExists(str); } boost::format FlexibleFormat(const std::string &string_to_format) { @@ -89,8 +235,10 @@ boost::format FlexibleFormat(const std::string &string_to_format) { return retval; } -const std::string& Language() -{ return GetStringTable().Language(); } +const std::string& Language() { + std::lock_guard stringtable_lock(stringtable_access_mutex); + return GetStringTable().Language(); +} std::string RomanNumber(unsigned int n) { //letter pattern (N) and the associated values (V) @@ -127,13 +275,46 @@ namespace { const double SMALL_UI_DISPLAY_VALUE = 1.0e-6; const double LARGE_UI_DISPLAY_VALUE = 9.99999999e+9; const double UNKNOWN_UI_DISPLAY_VALUE = std::numeric_limits::infinity(); + + double RoundMagnitude(double mag, int digits) { + // power of 10 of highest valued digit in number + // = 2 (100's) for 234.4 + // = 4 (10000's) for 45324 + int pow10 = static_cast(floor(log10(mag))); + //std::cout << "magnitude power of 10: " << pow10 << std::endl; + + // round number to fit in requested number of digits + // shift number by power of 10 so that ones digit is the lowest-value digit + // that will be retained in final number + // eg. 45324 with 3 digits -> pow10 = 4, digits = 3 + // want to shift 3 to the 1's digits position, so need 4 - 3 + 1 shift = 2 + double rounding_factor = pow(10.0, static_cast(pow10 - digits + 1)); + // shift, round, shift back. this leaves 0 after the lowest retained digit + mag = mag / rounding_factor; + mag = round(mag); + mag *= rounding_factor; + //std::cout << "magnitude after initial rounding to " << digits << " digits: " << mag << std::endl; + + // rounding may have changed the power of 10 of the number + // eg. 9999 with 3 digits, shifted to 999.9, rounded to 1000, + // shifted back to 10000, now the power of 10 is 5 instead of 4. + // so, redo this calculation + pow10 = static_cast(floor(log10(mag))); + rounding_factor = pow(10.0, static_cast(pow10 - digits + 1)); + mag = mag / rounding_factor; + mag = round(mag); + mag *= rounding_factor; + //std::cout << "magnitude after second rounding to " << digits << " digits: " << mag << std::endl; + + return mag; + } } std::string DoubleToString(double val, int digits, bool always_show_sign) { - std::string text = ""; + std::string text; // = "" - // minimum digits is 2. If digits was 1, then 30 couldn't be displayed, - // as 0.1k is too much and 9 is too small and just 30 is 2 digits + // minimum digits is 2. Fewer than this and things can't be sensibly displayed. + // eg. 300 with 2 digits is 0.3k. With 1 digits, it would be unrepresentable. digits = std::max(digits, 2); // default result for sentinel value @@ -143,78 +324,88 @@ std::string DoubleToString(double val, int digits, bool always_show_sign) { double mag = std::abs(val); // early termination if magnitude is 0 - if (mag == 0.0) { - std::string format; - format += "%1." + std::to_string(digits - 1) + "f"; + if (mag == 0.0 || RoundMagnitude(mag, digits + 1) == 0.0) { + std::string format = "%1." + std::to_string(digits - 1) + "f"; text += (boost::format(format) % mag).str(); return text; } // prepend signs if neccessary - int effectiveSign = EffectiveSign(val); - if (effectiveSign == -1) { + int effective_sign = EffectiveSign(val); + if (effective_sign == -1) text += "-"; - } else { - if (always_show_sign) text += "+"; - } + else if (always_show_sign) + text += "+"; - if (mag > LARGE_UI_DISPLAY_VALUE) mag = LARGE_UI_DISPLAY_VALUE; + if (mag > LARGE_UI_DISPLAY_VALUE) + mag = LARGE_UI_DISPLAY_VALUE; // if value is effectively 0, avoid unnecessary later processing - if (effectiveSign == 0) { + if (effective_sign == 0) { text = "0.0"; for (int n = 2; n < digits; ++n) text += "0"; // fill in 0's to required number of digits return text; } - //std::cout << std::endl << "DoubleToString val: " << val << " digits: " << digits << std::endl; + const double initial_mag = mag; + + // round magnitude to appropriate precision for requested digits + mag = RoundMagnitude(initial_mag, digits); + int pow10 = static_cast(floor(log10(mag))); - // power of 10 of highest valued digit in number - int pow10 = static_cast(floor(log10(mag))); // = 2 (100's) for 234.4, = 4 (10000's) for 45324 - //std::cout << "magnitude power of 10: " << pow10 << std::endl; - // determine base unit for number: the next lower power of 10^3 from the number (inclusive) + // determine base unit for number: the next lower power of 10^3 from the + // number (inclusive) int pow10_digits_above_pow1000 = 0; if (pow10 >= 0) pow10_digits_above_pow1000 = pow10 % 3; else pow10_digits_above_pow1000 = (pow10 % 3) + 3; // +3 ensures positive result of mod int unit_pow10 = pow10 - pow10_digits_above_pow1000; + + if (digits == 2 && pow10_digits_above_pow1000 == 2) { + digits = 3; + + // rounding to 2 digits when 3 digits must be shown to display the + // number will cause apparent rounding issues. + // re-do rounding for 3 digits of precision + mag = RoundMagnitude(initial_mag, digits); + pow10 = static_cast(floor(log10(mag))); + + if (pow10 >= 0) + pow10_digits_above_pow1000 = pow10 % 3; + else + pow10_digits_above_pow1000 = (pow10 % 3) + 3; // +3 ensures positive result of mod + unit_pow10 = pow10 - pow10_digits_above_pow1000; + } + + + // special limit: currently don't use any base unit powers below 0 (1's digit) if (unit_pow10 < 0) unit_pow10 = 0; - //std::cout << "unit power of 10: " << unit_pow10 << std::endl; - - // if not enough digits to include most significant digit and next lower - // power of 10, add extra digits. this still uses less space than using the - // next higher power of 10 and adding a 0. out front. for example, 240 with - // 2 digits is better shown as "240" than "0.24k" - digits = std::max(digits, pow10_digits_above_pow1000 + 1); - //std::cout << "adjusted digits: " << digits << std::endl; int lowest_digit_pow10 = pow10 - digits + 1; - //std::cout << "lowest_digit_pow10: " << lowest_digit_pow10 << std::endl; - // fraction digits: - int fractionDigits = std::max(0, std::min(digits - 1, unit_pow10 - lowest_digit_pow10)); - //std::cout << "fractionDigits: " << fractionDigits << std::endl; + //std::cout << "unit power of 10: " << unit_pow10 + // << " pow10 digits above pow1000: " << pow10_digits_above_pow1000 + // << " lowest_digit_pow10: " << lowest_digit_pow10 + // << std::endl; + // fraction digits: + int fraction_digits = std::max(0, std::min(digits - 1, unit_pow10 - lowest_digit_pow10)); + //std::cout << "fraction_digits: " << fraction_digits << std::endl; - /* round number down at lowest digit to be displayed, to prevent lexical_cast from rounding up - in cases like 0.998k with 2 digits -> 1.00k instead of 0.99k (as it should be) */ - double roundingFactor = pow(10.0, static_cast(pow10 - digits + 1)); - mag /= roundingFactor; - mag = floor(mag); - mag *= roundingFactor; // scale number by unit power of 10 - mag /= pow(10.0, static_cast(unit_pow10)); // if mag = 45324 and unitPow = 3, get mag = 45.324 + // eg. if mag = 45324 and unit_pow10 = 3, get mag = 45.324 + mag /= pow(10.0, static_cast(unit_pow10)); std::string format; format += "%" + std::to_string(digits) + "." + - std::to_string(fractionDigits) + "f"; + std::to_string(fraction_digits) + "f"; text += (boost::format(format) % mag).str(); // append base scale SI prefix (as postfix) @@ -244,7 +435,7 @@ std::string DoubleToString(double val, int digits, bool always_show_sign) { text += "G"; // Giga break; case 12: - text += "T"; // Terra + text += "T"; // Tera break; default: break; @@ -261,8 +452,8 @@ int EffectiveSign(double val) { return 1; else return -1; - } - else + } else { return 0; + } } diff --git a/util/i18n.h b/util/i18n.h index 671aa250c65..db912042fa8 100644 --- a/util/i18n.h +++ b/util/i18n.h @@ -9,6 +9,9 @@ #include "Export.h" +/** Returns locale, which may be previously cached */ +FO_COMMON_API std::locale GetLocale(const std::string& name = std::string("")); + /** Returns a language-specific string for the key-string \a str */ FO_COMMON_API const std::string& UserString(const std::string& str); @@ -77,7 +80,7 @@ boost::format FlexibleFormatList( boost::format header_fmt = FlexibleFormat(header_template) % boost::lexical_cast(words.size()); - for (const typename HeaderContainer::value_type& word : header_words) { + for (const auto& word : header_words) { header_fmt % word; } @@ -124,7 +127,7 @@ boost::format FlexibleFormatList( boost::format fmt = FlexibleFormat(template_str) % header_fmt.str(); - for (const typename ListContainer::value_type& word : words) { + for (const auto& word : words) { fmt % word; }